ground work for graph analysis

This commit is contained in:
Branislav Stojkovic
2019-02-15 20:25:57 +01:00
parent 008ee8564b
commit 46f693addf
55 changed files with 749 additions and 132 deletions
@@ -0,0 +1,32 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC 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.
DIYLC 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 DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.common;
import java.util.Map;
import org.diylc.graph.Graph;
import org.diylc.graph.GraphKey;
public interface IGraphProcessor {
Map<GraphKey, Graph> extractGraphs();
}
@@ -53,7 +53,7 @@ import org.diylc.core.Theme;
* @see EventType
*/
public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyProcessor, IVariantProcessor,
IBlockProcessor {
IBlockProcessor, IGraphProcessor {
public static final String ANTI_ALIASING_KEY = "antiAliasing";
public static final String HI_QUALITY_RENDER_KEY = "hiQualityRender";
@@ -35,7 +35,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import org.diylc.common.HorizontalAlignment;
import org.diylc.common.VerticalAlignment;
import org.diylc.core.IDIYComponent;
@@ -279,5 +279,10 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
return Arrays.equals((Object[]) o1, (Object[]) o2);
}
return o1.equals(o2);
}
@Override
public String getControlPointNodeName(int index) {
return getName() + "." + index;
}
}
@@ -110,8 +110,18 @@ public interface IDIYComponent<T> extends Serializable {
* same component</b>. The other control point must be able to overlap too.
*/
boolean canControlPointOverlap(int index);
VisibilityPolicy getControlPointVisibilityPolicy(int index);
/**
* @param index
* @return {@link VisibilityPolicy} of the control point.
*/
VisibilityPolicy getControlPointVisibilityPolicy(int index);
/**
* @param index
* @return name of the control point node, if the control point represents a graph node, null otherwise.
*/
String getControlPointNodeName(int index);
/**
* Draws the component onto the {@link Graphics2D}.
@@ -153,5 +163,5 @@ public interface IDIYComponent<T> extends Serializable {
* @param other
* @return
*/
boolean equalsTo(IDIYComponent<?> other);
boolean equalsTo(IDIYComponent<?> other);
}
@@ -0,0 +1,51 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC 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.
DIYLC 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 DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.core;
/**
* Interface for all component that can act as switches. They must have at least one position.
*
* @author Branislav Stojkovic
*/
public interface ISwitch {
/**
* @return a positive number of positions.
*/
int getPositionCount();
/**
* @param position
* @return name of switch positions, e.g. "ON", "OFF"...
*/
String getPositionName(int position);
/**
* Tests control point connectivity.
*
* @param index1
* @param index2
* @param position
* @return true if the two points are connected in the given switch position, false otherwise.
*/
boolean arePointsConnected(int index1, int index2, int position);
}
+16 -12
View File
@@ -1,25 +1,29 @@
package org.diylc.graph;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Set;
public class Graph {
private Dictionary<String, Position> positions;
private Set<Node> nodes;
private Set<Vertex> vertices = new HashSet<Vertex>();
public Graph() {
public Graph() {
}
public Dictionary<String, Position> getPositions() {
return positions;
}
public Set<Node> getNodes() {
return nodes;
public Set<Vertex> getVertices() {
return vertices;
}
public boolean nodesMatch(Graph other) {
return this.nodes.equals(other.nodes);
public boolean verticesMatch(Graph other) {
return this.vertices.equals(other.vertices);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Vertex v : vertices) {
sb.append("\t").append(v.getNode1()).append(" - ").append(v.getNode2()).append("\n");
}
return sb.toString();
}
}
@@ -0,0 +1,54 @@
package org.diylc.graph;
import java.util.List;
public class GraphKey {
private List<Position> positions;
public GraphKey(List<Position> positions) {
super();
this.positions = positions;
}
public List<Position> getPositions() {
return positions;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((positions == null) ? 0 : positions.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GraphKey other = (GraphKey) obj;
if (positions == null) {
if (other.positions != null)
return false;
} else if (!positions.equals(other.positions))
return false;
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Position s : positions)
{
if (sb.length() > 0)
sb.append(":");
sb.append(s.toString());
}
return sb.toString();
}
}
+14 -22
View File
@@ -1,24 +1,25 @@
package org.diylc.graph;
import org.diylc.core.IDIYComponent;
public class Node {
private String type;
private String component;
private String terminal;
private IDIYComponent<?> component;
private int pointIndex;
public Node() {}
public String getType() {
return type;
public Node(IDIYComponent<?> component, int pointIndex) {
super();
this.component = component;
this.pointIndex = pointIndex;
}
public String getComponent() {
public IDIYComponent<?> getComponent() {
return component;
}
public String getTerminal() {
return terminal;
public int getPointIndex() {
return pointIndex;
}
@Override
@@ -26,8 +27,7 @@ public class Node {
final int prime = 31;
int result = 1;
result = prime * result + ((component == null) ? 0 : component.hashCode());
result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + pointIndex;
return result;
}
@@ -45,21 +45,13 @@ public class Node {
return false;
} else if (!component.equals(other.component))
return false;
if (terminal == null) {
if (other.terminal != null)
return false;
} else if (!terminal.equals(other.terminal))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
if (pointIndex != other.pointIndex)
return false;
return true;
}
@Override
public String toString() {
return type + "." + component + "." + terminal;
return component.getName() + "." + pointIndex + " @ (" + component.getControlPoint(pointIndex).getX() + ":" + component.getControlPoint(pointIndex).getY() + ")";
}
}
@@ -1,33 +1,33 @@
package org.diylc.graph;
import org.diylc.core.IDIYComponent;
import org.diylc.core.ISwitch;
public class Position {
private String ownerType;
private String owner;
private String name;
public Position() {
private ISwitch theSwitch;
private int position;
public Position(ISwitch theSwitch, int position) {
super();
this.theSwitch = theSwitch;
this.position = position;
}
public String getOwnerType() {
return ownerType;
public ISwitch getSwitch() {
return theSwitch;
}
public String getOwner() {
return owner;
}
public String getName() {
return name;
public int getPosition() {
return position;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((owner == null) ? 0 : owner.hashCode());
result = prime * result + ((ownerType == null) ? 0 : ownerType.hashCode());
result = prime * result + position;
result = prime * result + ((theSwitch == null) ? 0 : theSwitch.hashCode());
return result;
}
@@ -40,26 +40,18 @@ public class Position {
if (getClass() != obj.getClass())
return false;
Position other = (Position) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
if (position != other.position)
return false;
if (owner == null) {
if (other.owner != null)
if (theSwitch == null) {
if (other.theSwitch != null)
return false;
} else if (!owner.equals(other.owner))
return false;
if (ownerType == null) {
if (other.ownerType != null)
return false;
} else if (!ownerType.equals(other.ownerType))
} else if (!theSwitch.equals(other.theSwitch))
return false;
return true;
}
@Override
public String toString() {
return ownerType + "." + owner + "." + name;
return (theSwitch instanceof IDIYComponent<?> ? ((IDIYComponent<?>) theSwitch).getName() + "." : "") + theSwitch.getPositionName(position);
}
}
@@ -5,7 +5,9 @@ public class Vertex {
private Node node1;
private Node node2;
public Vertex() {
public Vertex(Node node1, Node node2) {
this.node1 = node1;
this.node2 = node2;
}
public Node getNode1() {
@@ -486,20 +486,33 @@ public class DrawingManager {
ConfigurationManager.getInstance().writeValue(IPlugInPort.THEME_KEY, theme);
messageDispatcher.dispatchMessage(EventType.REPAINT);
}
@SuppressWarnings("unchecked")
public void findContinuityAreaAtPoint(Project project, Point p) {
List<Area> areas = getContinuityAreas(project);
for (Area a : areas) {
if (a.contains(p)) {
continuityArea = a;
return;
}
}
continuityArea = null;
}
@SuppressWarnings("unchecked")
public List<Area> getContinuityAreas(Project project) {
// Find all individual continuity areas for all components
List<Area> preliminaryAreas = new ArrayList<Area>();
List<Boolean> checkBreakout = new ArrayList<Boolean>();
List<Connection> connections = new ArrayList<Connection>();
List<Line2D> connections = new ArrayList<Line2D>();
for (IDIYComponent<?> c : project.getComponents()) {
ComponentArea a = getComponentArea(c);
ComponentType type =
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) c.getClass());
if (type.isContinuity()) {
connections.add(new Connection(c.getControlPoint(0), c.getControlPoint(c.getControlPointCount() - 1)));
connections.add(new Line2D.Double(c.getControlPoint(0), c.getControlPoint(c.getControlPointCount() - 1)));
}
if (a == null || a.getOutlineArea() == null)
@@ -532,18 +545,18 @@ public class DrawingManager {
}
crunchAreas(areas, connections);
for (Area a : areas) {
if (a.contains(p)) {
continuityArea = a;
return;
}
}
continuityArea = null;
return areas;
}
private boolean crunchAreas(List<Area> areas, List<Connection> connections) {
/**
* Merges all areas that either overlap or are joined by connections.
*
* @param areas
* @param connections
* @return
*/
private boolean crunchAreas(List<Area> areas, List<Line2D> connections) {
boolean isChanged = false;
List<Area> newAreas = new ArrayList<Area>();
@@ -564,8 +577,8 @@ public class DrawingManager {
a1.add(a2);
consumed.set(j, true);
} else { // maybe there's a connection between them
for (Connection p : connections) {
if ((a1.contains(p.pointA) && a2.contains(p.pointB)) || (a1.contains(p.pointB) && a2.contains(p.pointA))) {
for (Line2D p : connections) {
if ((a1.contains(p.getP1()) && a2.contains(p.getP2())) || (a1.contains(p.getP2()) && a2.contains(p.getP1()))) {
a1.add(a2);
consumed.set(j, true);
break;
@@ -624,24 +637,4 @@ public class DrawingManager {
return toReturn;
}
class Connection {
private Point pointA;
private Point pointB;
public Connection(Point pointA, Point pointB) {
super();
this.pointA = pointA;
this.pointB = pointB;
}
public Point getPointA() {
return pointA;
}
public Point getPointB() {
return pointB;
}
}
}
@@ -24,6 +24,7 @@ import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DnDConstants;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedInputStream;
@@ -67,6 +68,7 @@ import org.diylc.common.PropertyWrapper;
import org.diylc.common.VariantPackage;
import org.diylc.core.ExpansionMode;
import org.diylc.core.IDIYComponent;
import org.diylc.core.ISwitch;
import org.diylc.core.IView;
import org.diylc.core.Project;
import org.diylc.core.Template;
@@ -74,6 +76,11 @@ import org.diylc.core.Theme;
import org.diylc.core.annotations.IAutoCreator;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.graph.Graph;
import org.diylc.graph.GraphKey;
import org.diylc.graph.Node;
import org.diylc.graph.Position;
import org.diylc.graph.Vertex;
import org.diylc.utils.Constants;
import com.thoughtworks.xstream.XStream;
@@ -2677,4 +2684,271 @@ public class Presenter implements IPlugInPort {
ConfigurationManager.getInstance().writeValue(TEMPLATES_KEY, newVariantMap);
}
@SuppressWarnings("unchecked")
@Override
public Map<GraphKey, Graph> extractGraphs() {
Map<GraphKey, Graph> result = new HashMap<GraphKey, Graph>();
List<Node> nodes = new ArrayList<Node>();
List<ISwitch> switches = new ArrayList<ISwitch>();
for (IDIYComponent<?> c : currentProject.getComponents()) {
ComponentType type =
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) c.getClass());
// extract nodes
if (!type.isContinuity()) {
for (int i = 0; i < c.getControlPointCount(); i++) {
String nodeName = c.getControlPointNodeName(i);
if (nodeName != null) {
nodes.add(new Node(c, i));
}
}
}
// extract switches
if (ISwitch.class.isAssignableFrom(type.getInstanceClass()))
switches.add((ISwitch)c);
}
// save us the trouble
if (nodes.isEmpty())
return null;
// if there are no switches, make one with 1 position so we get 1 result back
if (switches.isEmpty())
switches.add(new ISwitch() {
@Override
public String getPositionName(int position) {
return "Default";
}
@Override
public int getPositionCount() {
return 1;
}
@Override
public boolean arePointsConnected(int index1, int index2, int position) {
return false;
}
});
// construct all possible combinations
int[] positions = new int[switches.size()];
for (int i = 0; i < switches.size(); i++)
positions[i] = 0;
// grab continuity areas
List<Area> continuity = drawingManager.getContinuityAreas(currentProject);
int i = switches.size() - 1;
while (i >= 0) {
// process the current combination
Map<ISwitch, Integer> switchPositions = new HashMap<ISwitch, Integer>();
List<Position> posList = new ArrayList<Position>();
for (int j = 0; j < positions.length; j++) {
switchPositions.put(switches.get(j), positions[j]);
posList.add(new Position(switches.get(j), positions[j]));
}
List<Line2D> connections = getConnections(switchPositions);
Graph graph = constructGraph(nodes, connections, continuity);
result.put(new GraphKey(posList), graph);
// find the next combination if possible
if (positions[i] < switches.get(i).getPositionCount() - 1) {
positions[i]++;
} else {
while (i >= 0 && positions[i] == switches.get(i).getPositionCount() - 1)
i--;
if (i >= 0) {
positions[i]++;
for (int j = i + 1; j < positions.length; j++)
positions[j] = 0;
i = switches.size() - 1;
}
}
}
return result;
}
private Graph constructGraph(List<Node> nodes, List<Line2D> connections, List<Area> continuityAreas) {
Graph graph = new Graph();
StringBuilder sb = new StringBuilder();
sb.append("Nodes:").append("\n");
for (Node n : nodes) {
sb.append(n.toString()).append("\n");
}
sb.append("Connections:").append("\n");
for (Line2D n : connections) {
sb.append(n.getP1()).append(":").append(n.getP2()).append("\n");
}
LOG.debug(sb.toString());
double t = DrawingManager.CONTROL_POINT_SIZE;
for (int i = 0; i < nodes.size() - 1; i++)
for (int j = i + 1; j < nodes.size(); j++) {
Node node1 = nodes.get(i);
Node node2 = nodes.get(j);
Point2D point1 = node1.getComponent().getControlPoint(node1.getPointIndex());
Point2D point2 = node2.getComponent().getControlPoint(node2.getPointIndex());
// try both directions
if (point1.distance(point2) < t ||
checkGraphConnection(point1, point2, connections, continuityAreas, new boolean[connections.size()]) ||
checkGraphConnection(point2, point1, connections, continuityAreas, new boolean[connections.size()])) {
graph.getVertices().add(new Vertex(node1, node2));
}
}
return graph;
}
private boolean checkGraphConnection(Point2D point1, Point2D point2, List<Line2D> connections,
List<Area> continuityAreas, boolean[] visited) {
double t = DrawingManager.CONTROL_POINT_SIZE;
if (point1.distance(point2) < t)
return true;
for (Area a : continuityAreas) {
if (a.contains(point1) && a.contains(point2))
return true;
}
for (int i = 0; i < connections.size(); i++) {
if (visited[i])
continue;
Line2D c = connections.get(i);
if (point1.distance(c.getP1()) < t) {
visited[i] = true;
if (checkGraphConnection(c.getP2(), point2, connections, continuityAreas, visited))
return true;
}
if (point1.distance(c.getP2()) < t) {
visited[i] = true;
if (checkGraphConnection(c.getP1(), point2, connections, continuityAreas, visited))
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
private List<Line2D> getConnections(Map<ISwitch, Integer> switchPositions) {
List<Line2D> connections = new ArrayList<Line2D>();
for (IDIYComponent<?> c : currentProject.getComponents()) {
ComponentType type =
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) c.getClass());
// handle direct connections
if (type.isContinuity()) {
connections.add(new Line2D.Double(c.getControlPoint(0), c.getControlPoint(c.getControlPointCount() - 1)));
}
// handle switches
if (ISwitch.class.isAssignableFrom(type.getInstanceClass()) && switchPositions.containsKey(c)) {
int position = switchPositions.get(c);
ISwitch s = (ISwitch)c;
for (int i = 0; i < c.getControlPointCount() - 1; i++)
for (int j = i + 1; j < c.getControlPointCount(); j++)
if (s.arePointsConnected(i, j, position))
connections.add(new Line2D.Double(c.getControlPoint(i), c.getControlPoint(j)));
}
}
// // grab continuity areas
// List<Area> continuity = drawingManager.getContinuityAreas(currentProject);
//
// mergeConnections(connections, continuity);
return connections;
}
// private void mergeConnections(List<Line2D> connections, List<Area> continuity) {
// List<Line2D> merged = new ArrayList<Line2D>();
// boolean[] visited = new boolean[connections.size()];
// for (int i = 0; i < visited.length; i++)
// visited[i] = false;
//
// double t = DrawingManager.CONTROL_POINT_SIZE;
//
// for (int i = 0; i < visited.length - 1; i++) {
// if (visited[i])
// continue;
// for (int j = i + 1; j < visited.length; j++) {
// if (visited[j])
// continue;
// Line2D c1 = connections.get(i);
// Line2D c2 = connections.get(j);
// Point2D p1 = null;
// Point2D p2 = null;
// boolean connected = false;
// // test direct continuity between two connections
// if (!connected && c1.getP1().distance(c2.getP1()) < t) {
// connected = true;
// p1 = c1.getP2();
// p2 = c2.getP2();
// }
// if (!connected && c1.getP1().distance(c2.getP2()) < t) {
// connected = true;
// p1 = c1.getP2();
// p2 = c2.getP1();
// }
// if (!connected && c1.getP2().distance(c2.getP1()) < t) {
// connected = true;
// p1 = c1.getP1();
// p2 = c2.getP2();
// }
// if (!connected && c1.getP2().distance(c2.getP2()) < t) {
// connected = true;
// p1 = c1.getP1();
// p2 = c2.getP1();
// }
// // if that failed, try using continuity areas
// if (!connected) {
// for (Area a : continuity) {
// if (!connected && a.contains(c1.getP1()) && a.contains(c2.getP1())) {
// connected = true;
// p1 = c1.getP2();
// p2 = c2.getP2();
// break;
// }
// if (!connected && a.contains(c1.getP1()) && a.contains(c2.getP2())) {
// connected = true;
// p1 = c1.getP2();
// p2 = c2.getP1();
// break;
// }
// if (!connected && a.contains(c1.getP2()) && a.contains(c2.getP1())) {
// connected = true;
// p1 = c1.getP1();
// p2 = c2.getP2();
// break;
// }
// if (!connected && a.contains(c1.getP2()) && a.contains(c2.getP2())) {
// connected = true;
// p1 = c1.getP1();
// p2 = c2.getP1();
// break;
// }
// }
// }
//
// if (connected) {
// visited[i] = visited[j] = true;
// merged.add(new Line2D.Double(p1, p2));
// }
// }
// }
//
// // replace connections with merged
// if (!merged.isEmpty()) {
//// connections.clear();
// connections.addAll(merged);
// mergeConnections(connections, continuity); // attempt another round of merging if needed
// }
// }
}