/*******************************************************************************
* Copyright (c) 2009-2013 CWI
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* * Paul Klint - Paul.Klint@cwi.nl - CWI
* * Arnold Lankamp - Arnold.Lankamp@cwi.nl
*******************************************************************************/
package org.rascalmpl.eclipse.library.vis.figure.graph.layered;
import static org.rascalmpl.eclipse.library.vis.properties.Properties.FROM_ARROW;
import static org.rascalmpl.eclipse.library.vis.properties.Properties.HGAP;
import static org.rascalmpl.eclipse.library.vis.properties.Properties.LABEL;
import static org.rascalmpl.eclipse.library.vis.properties.Properties.TO_ARROW;
import static org.rascalmpl.eclipse.library.vis.properties.Properties.VGAP;
import java.util.LinkedList;
import java.util.List;
import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory;
import org.rascalmpl.eclipse.library.vis.figure.Figure;
import org.rascalmpl.eclipse.library.vis.figure.interaction.MouseOver;
import org.rascalmpl.eclipse.library.vis.graphics.GraphicsContext;
import org.rascalmpl.eclipse.library.vis.properties.PropertyManager;
import org.rascalmpl.eclipse.library.vis.swt.IFigureConstructionEnv;
import org.rascalmpl.eclipse.library.vis.swt.applet.IHasSWTElement;
import org.rascalmpl.eclipse.library.vis.util.NameResolver;
import org.rascalmpl.eclipse.library.vis.util.vector.Rectangle;
import io.usethesource.vallang.IString;
/**
* A LayeredGraphEdge is created for each "edge" constructor that occurs in a graph:
* - An optional label is a child of LayeredGraphEdge
* - Optional to and from arrows are handled outside the default Figure framework (they cannot have mouseOvers etc).
*
* @author paulk
*
*/
public class LayeredGraphEdge extends Figure {
private LayeredGraphNode from;
private LayeredGraphNode to;
private LayeredGraphNode endNode; // End node of a chain of virtual nodes that start in this node.
Figure toArrow;
Figure fromArrow;
Figure label = null;
double labelX = 0;
double labelY = 0;
boolean reversed = false;
private static boolean debug = true;
public LayeredGraphEdge(LayeredGraph G, IFigureConstructionEnv fpa, PropertyManager properties,
IString fromName, IString toName) {
super(properties);
this.from = G.getRegisteredNodeId(fromName.getValue());
if(getFrom() == null){
throw RuntimeExceptionFactory.figureException("No node with id property + \"" + fromName.getValue() + "\"",
fromName, fpa.getRascalContext().getCurrentAST(), fpa.getRascalContext().getStackTrace());
}
to = G.getRegisteredNodeId(toName.getValue());
if(to == null){
throw RuntimeExceptionFactory.figureException("No node with id property + \"" + toName.getValue() + "\"", toName, fpa.getRascalContext().getCurrentAST(), fpa.getRascalContext().getStackTrace());
}
toArrow = prop.getFig(TO_ARROW);
fromArrow = prop.getFig(FROM_ARROW);
label = prop.getFig(LABEL);
if(label != null){
this.children = new Figure[1];
this.children[0] = label;
} else
this.children = childless;
if(debug)System.err.println("edge: " + fromName.getValue() + " -> " + toName.getValue() +
", arrows (to/from): " + toArrow + " " + fromArrow + " " + label);
}
public LayeredGraphEdge(LayeredGraph G, IFigureConstructionEnv fpa, PropertyManager properties,
IString fromName, IString toName, Figure toArrow, Figure fromArrow){
super(properties);
this.from = G.getRegisteredNodeId(fromName.getValue());
if(getFrom() == null){
throw RuntimeExceptionFactory.figureException("No node with id property + \"" + fromName.getValue() + "\"",
fromName, fpa.getRascalContext().getCurrentAST(), fpa.getRascalContext().getStackTrace());
}
to = G.getRegisteredNodeId(toName.getValue());
if(to == null){
throw RuntimeExceptionFactory.figureException("No node with id property + \"" + toName.getValue() + "\"", toName, fpa.getRascalContext().getCurrentAST(), fpa.getRascalContext().getStackTrace());
}
this.toArrow = toArrow;
this.fromArrow = fromArrow;
if(label != null){
this.children = new Figure[1];
this.children[0] = label;
} else
this.children = childless;
}
public boolean initChildren(IFigureConstructionEnv env,
NameResolver resolver, MouseOver mparent, boolean swtSeen, boolean visible) {
if(fromArrow != null){
fromArrow.init(env, resolver, mparent, swtSeen, visible);
}
if(toArrow != null){
toArrow.init(env, resolver, mparent, swtSeen, visible);
}
// if(label!=null){
// label.init(env, resolver, mparent, swtSeen, visible);
// }
return false;
}
LayeredGraphNode getFrom() {
return reversed ? to : from;
}
LayeredGraphNode getFromOrg() {
return from;
}
LayeredGraphNode getTo() {
return reversed? from : to;
}
LayeredGraphNode getToOrg() {
return to;
}
public Figure getFromArrow(){
return fromArrow;
}
public Figure getToArrow(){
return toArrow;
}
void reverse(){
if(debug){
System.err.println("*** Before reverse ***");
from.print();
to.print();
}
reversed = true;
from.delOut(to);
to.delIn(from);
from.addIn(to);
to.addOut(from);
if(debug){
System.err.println("*** After reverse ***");
from.print();
to.print();
}
}
boolean isReversed(){
return reversed;
}
/*
* Primitives for drawing a multi-vertex edge
*/
double points[];
int cp;
//double x1;
//double y1;
private void beginCurve(double x, double y){
points = new double[20];
cp = 0;
addPointToCurve(x, y);
}
private void addPointToCurve(double x, double y){
if(cp == points.length){
double points1[] = new double[2*points.length];
for(int i = 0; i < cp; i++)
points1[i] = points[i];
points = points1;
}
points[cp++] = x;
points[cp++] = y;
}
private void endCurve(double x, double y){
addPointToCurve(x, y);
}
private void addLastSegment(double startImX, double startImY, LayeredGraphNode prevNode, LayeredGraphNode currentNode){
double dx = currentNode.figX() - prevNode.figX();
double dy = (currentNode.figY() - prevNode.figY());
double imScale = 0.6f;
double imX = prevNode.figX() + dx / 2;
double imY = prevNode.figY() + dy * imScale;
endNode = currentNode;
if(debug)
System.err.printf("drawLastSegment: (%f,%f) -> (%f,%f), imX=%f, imY=%f\n",
prevNode.figX(), prevNode.figY(),
currentNode.figX(), currentNode.figY(), imX, imY);
addPointToCurve(imX, imY);
endCurve(currentNode.figX(), currentNode.figY());
}
/**
* Draw a bezier curve through a list of points. Inspired by a blog post "interpolating curves" by rj, which is in turn inspired by
* Keith Peter's "Foundations of Actionscript 3.0 Animation".
*/
private void drawCurve(GraphicsContext gc) {
if (cp == 0)
return;
applyProperties(gc);
gc.noFill();
gc.beginShape();
double globalX = globalLocation.getX();
double globalY = globalLocation.getY();
double x1 = globalX + points[0];
double y1 = globalY + points[1];
double xc = 0.0f;
double yc = 0.0f;
double x2 = 0.0f;
double y2 = 0.0f;
gc.vertex(x1, y1);
for (int i = 2; i < cp - 4; i += 2) {
xc = globalX + points[i];
yc = globalY + points[i + 1];
x2 = (xc + globalX + points[i + 2]) * 0.5f;
y2 = (yc + globalY + points[i + 3]) * 0.5f;
gc.bezierVertex((x1 + 2.0f * xc) / 3.0f, (y1 + 2.0f * yc) / 3.0f,
(2.0f * xc + x2) / 3.0f, (2.0f * yc + y2) / 3.0f, x2, y2);
x1 = x2;
y1 = y2;
}
xc = globalX + points[cp - 4];
yc = globalY + points[cp - 3];
x2 = globalX + points[cp - 2];
y2 = globalY + points[cp - 1];
gc.bezierVertex((x1 + 2.0f * xc) / 3.0f, (y1 + 2.0f * yc) / 3.0f,
(2.0f * xc + x2) / 3.0f, (2.0f * yc + y2) / 3.0f, x2, y2);
gc.endShape();
}
private void drawCurveAndArrows(GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){
drawCurve(gc);
double globalX = globalLocation.getX();
double globalY = globalLocation.getY();
// Recover the intermediate points for directing the arrows
double startImX = globalX + points[2];
double startImY = globalY + points[3];
double endImX = globalX + points[cp - 4];
double endImY = globalY + points[cp - 3];
if(getFromArrow() != null){
getFrom().figure.connectArrowFrom(globalX + getFrom().figX(), globalY + getFrom().figY(),
startImX, startImY,
getFromArrow(), gc,
visibleSWTElements);
applyProperties(gc);
}
if(getToArrow() != null && endNode != null){
if(debug)System.err.println("Has a to arrow");
endNode.figure.connectArrowFrom(globalX + endNode.figX(), globalY + endNode.figY(),
endImX, endImY,
getToArrow(), gc,
visibleSWTElements);
}
applyProperties(gc);
}
private void minSizeCurve(){
double minX = Double.MAX_VALUE;
double maxX = Double.MIN_VALUE;
double minY = Double.MAX_VALUE;
double maxY = Double.MIN_VALUE;
for (int i = 0; i < cp - 2; i += 2) {
double xp = points[i];
double yp = points[i + 1];
if(xp > maxX)
maxY = xp;
if(xp < minX)
minX = xp;
if(yp > maxY)
maxY = yp;
if(yp < minY)
minY = yp;
}
if(label != null){
minX = Math.min(minX, labelX);
maxX = Math.max(maxX, labelX + label.minSize.getX());
minY = Math.min(minY, labelY - label.minSize.getY()/2);
maxY = Math.max(maxY, labelY + label.minSize.getY()/2);
}
}
@Override
public void computeMinSize() {
if(debug){
System.err.println("edge: (" + getFrom().name + ": " + getFrom().x + "," + getFrom().y + ") -> (" +
getTo().name + ": " + getTo().x + "," + getTo().y + ")");
System.err.println("label: " + label);
}
resizable.set(false,false);
if(getFrom().isVirtual()){
return;
}
if(getTo().isVirtual()){
if(debug)System.err.println("Drawing a shape, inverted=" + reversed);
LayeredGraphNode currentNode = getTo();
double dx = currentNode.figX() - getFrom().figX();
double dy = (currentNode.figY() - getFrom().figY());
double imScale = 0.4f;
double imX = getFrom().figX() + dx/2;
double imY = getFrom().figY() + dy * imScale;
if(debug)System.err.printf("(%f,%f) -> (%f,%f), midX=%f, midY=%f\n", getFrom().figX(), getFrom().figY(), currentNode.figX(), currentNode.figY(), imX, imY);
beginCurve(getFrom().figX(), getFrom().figY());
addPointToCurve(imX, imY);
LayeredGraphNode nextNode = currentNode.out.get(0);
addPointToCurve(currentNode.figX(), currentNode.figY());
LayeredGraphNode prevNode = currentNode;
currentNode = nextNode;
while(currentNode.isVirtual()){
if(debug)System.err.println("Add vertex for " + currentNode.name);
nextNode = currentNode.out.get(0);
addPointToCurve(currentNode.figX(), currentNode.figY());
prevNode = currentNode;
currentNode = nextNode;
}
addLastSegment(imX, imY, prevNode, currentNode);
minSizeCurve();
} else {
if(debug)System.err.println("Drawing a line " + getFrom().name + " -> " + getTo().name + "; inverted=" + reversed);
if(getTo() == getFrom()){ // Drawing a self edge
LayeredGraphNode node = getTo();
double h = node.figure.minSize.getY();
double w = node.figure.minSize.getX();
double hgap = getFrom().graph.prop.getReal(HGAP);
double vgap = getFrom().graph.prop.getReal(VGAP);
System.err.printf("hgap=%f, vgap=%f\n", hgap, vgap);
System.err.printf("Start self edge:\n");
beginCurve(node.figX()+w/2, node.figY()-h/4);
addPointToCurve(node.figX()+w/2+hgap/3, node.figY()-(h/2));
addPointToCurve(node.figX()+w/2+hgap/3, node.figY()-(h/2+vgap/3));
addPointToCurve(node.figX()+w/2, node.figY()-(h/2+vgap/3));
endCurve(node.figX()+w/4, node.figY()-h/2);
minSizeCurve();
return;
} else {
/*
double minX = Math.min(getTo().figX(), getFrom().figX());
double maxX = Math.max(getTo().figX() + getTo().minSize.getX(), getFrom().figX() + getFrom().minSize.getX());
double minY = Math.min(getTo().figY() - getTo().minSize.getY()/2, getFrom().figY() - getFrom().minSize.getY()/2);
double maxY = Math.max(getTo().figY() + getTo().minSize.getY()/2, getFrom().figY() + getFrom().minSize.getY()/2);
if(label != null){
minX = Math.min(minX, labelX);
maxX = Math.max(maxX, labelX + label.minSize.getX());
minY = Math.min(minY, labelY - label.minSize.getY()/2);
maxY = Math.max(maxY, labelY + label.minSize.getY()/2);
}
*/
//minSize.set(maxX - minX, maxY - minY);
}
}
}
@Override
public void resizeElement(Rectangle view) {
localLocation.set(0, 0);
/*
double minX = Math.min(getTo().figX(), getFrom().figX());
double maxX = Math.max(getTo().figX() + getTo().minSize.getX(), getFrom().figX() + getFrom().minSize.getX());
double minY = Math.min(getTo().figY() - getTo().minSize.getY()/2, getFrom().figY() - getFrom().minSize.getY()/2);
double maxY = Math.max(getTo().figY() + getTo().minSize.getY()/2, getFrom().figY() + getFrom().minSize.getY()/2);
if(label != null){
minX = Math.min(minX, labelX);
maxX = Math.max(maxX, labelX + label.minSize.getX());
minY = Math.min(minY, labelY - label.minSize.getY()/2);
maxY = Math.max(maxY, labelY + label.minSize.getY()/2);
}
//size.set(maxX - minX, maxY - minY);
*/
if(fromArrow!=null){
fromArrow.localLocation.set(0,0);
fromArrow.globalLocation.set(0,0);
fromArrow.size.set(minSize);
//fromArrow.resize(view,transform);
}
if(toArrow!=null){
toArrow.localLocation.set(0,0);
toArrow.globalLocation.set(0,0);
toArrow.size.set(minSize);
//toArrow.resize(view,transform);
}
if(label != null){
label.localLocation.setX(labelX);
label.localLocation.setY(labelY - label.minSize.getY()/2);
}
}
@Override
public void drawElement(GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){
System.err.println("Drawing edge " + getFrom().name + " -> " + getTo().name);
if(getFrom().isVirtual()){
return;
}
applyProperties(gc);
if(getTo().isVirtual()){
drawCurveAndArrows(gc, visibleSWTElements);
} else {
if(debug)System.err.println("Drawing a line " + getFrom().name + " -> " + getTo().name + "; inverted=" + reversed);
double globalX = globalLocation.getX();
double globalY = globalLocation.getY();
if(getTo() == getFrom()){ // Drawing a self edge
drawCurve(gc);
LayeredGraphNode node = getTo();
double h = node.figure.minSize.getY();
double w = node.figure.minSize.getX();
drawSelfArrow(toArrow, globalX + node.figX()+w/4, globalY + node.figY()-h/2, gc, visibleSWTElements);
drawSelfArrow(fromArrow, globalX + node.figX()+w/2, globalY + node.figY()-h/4, gc, visibleSWTElements);
return;
} else {
gc.line(globalX + getFrom().figX(), globalY + getFrom().figY(),
globalX + getTo().figX(), globalY + getTo().figY());
drawArrow(toArrow, !reversed, gc, visibleSWTElements);
drawArrow(fromArrow, reversed, gc, visibleSWTElements);
}
}
}
private void drawArrow(Figure arrow, boolean towards, GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){
if(arrow != null){
LayeredGraphNode from = towards ? getFrom() : getTo();
LayeredGraphNode to = towards ? getTo() : getFrom();
to.figure.connectArrowFrom(globalLocation.getX() + to.figX(), globalLocation.getY() + to.figY(),
globalLocation.getX() + from.figX(), globalLocation.getY() + from.figY(),
arrow, gc, visibleSWTElements);
applyProperties(gc);
}
}
private void drawSelfArrow(Figure arrow, double cx, double cy, GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){
if(arrow != null){
LayeredGraphNode to = getTo();
to.figure.connectArrowFrom(globalLocation.getX() + to.figX(), globalLocation.getY() + to.figY(),
cx, cy,
arrow, gc, visibleSWTElements);
applyProperties(gc);
}
}
/*
* Placement of labels.
*/
public void setLabelCoordinates(){
setLabelCoordinates(0.4);
}
public void setLabelCoordinates(double perc){
if(label != null){
int dirX = getFrom().x < getTo().x ? 1 : -1;
int dirY = getFrom().y < getTo().y ? 1 : -1;
labelX = 5 + getFrom().x + dirX*(Math.abs(getTo().x - getFrom().x))*perc;
labelY = getFrom().y + dirY*(Math.abs(getTo().y - getFrom().y))*perc;
System.err.println("setLabelCoordinates: " + getFrom().name + "->" + getTo().name + ": " + labelX + ", " + labelY);
}
}
public void placeLabel(double yLabel, int align){
if(label != null){
System.err.printf("%s->%s: placeLabel: %f, align=%d\n", getFrom().name, getTo().name, yLabel, align);
int dirX = getFrom().x < getTo().x ? 1 : -1;
labelY = yLabel;
double perc = (labelY- Math.min(getFrom().y, getTo().y))/(Math.abs(getTo().y - getFrom().y));
labelX = 5 + getFrom().x + dirX*(Math.abs(getTo().x - getFrom().x))*perc;
if(align < 0)
labelX -= label.minSize.getX();
label.localLocation.setX(labelX);
label.localLocation.setY(labelY - label.minSize.getY()/2);
}
}
public boolean xoverlap(LayeredGraphEdge other){
if(!yoverlap(other))
return false;
return (labelX < other.labelX) ? (labelX + label.minSize.getX() > other.labelX)
: (other.labelX + label.minSize.getX() >labelX);
}
public boolean yoverlap(LayeredGraphEdge other){
double top = labelY - label.minSize.getY()/2;
double bot = labelY + label.minSize.getY()/2;
double otop = other.labelY - other.label.minSize.getY()/2;
double obot = other.labelY + other.label.minSize.getY()/2;
return top < otop ? bot > otop : obot > top;
}
private boolean xoverlap(LinkedList<LayeredGraphEdge> layerLabels, int k){
LayeredGraphEdge other = layerLabels.get(k);
for(int i = 0; i < k; i++)
if(layerLabels.get(i).xoverlap(other)){
System.err.println("xoverlap: " + i + " and " + k);
return true;
}
return false;
}
private static void optimize(LinkedList<LayeredGraphEdge> layerLabels, int k){
if(k == 0){
LayeredGraphEdge e0 = layerLabels.get(0);
e0.labelX -= e0.label.minSize.getX() + 10;
return;
}
LayeredGraphEdge current = layerLabels.get(k);
double h = current.label.minSize.getY();
double baseY = current.labelY;
int dir = k % 2 == 0 ? 1 : -1;
double deltaY[] = {baseY, baseY + dir * h, baseY - dir * h};
int align[] = {1, -1};
for(int a : align){
for(double dy : deltaY){
current.placeLabel(dy, a);
if(!current.xoverlap(layerLabels, k))
return;
}
}
current.placeLabel(baseY, 1);
System.err.println("*** NO SOLUTION ***");
}
public static void optimizeLabels(LinkedList<LayeredGraphEdge> layerLabels){
for(int i = 0; i < layerLabels.size(); i++){
optimize(layerLabels, i);
}
}
public Figure getLabel() {
return label;
}
}