Commit d866a7a6 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

extraction of layout from gpml improved

parent d4dfed17
package lcsb.mapviewer.wikipathway.XML;
import java.awt.Color;
import java.awt.geom.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.*;
import org.apache.commons.text.StringEscapeUtils;
......@@ -9,13 +10,10 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import lcsb.mapviewer.common.Configuration;
import lcsb.mapviewer.common.Pair;
import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.common.exception.InvalidStateException;
import lcsb.mapviewer.common.geometry.PointTransformation;
import lcsb.mapviewer.common.exception.*;
import lcsb.mapviewer.converter.ConverterException;
import lcsb.mapviewer.converter.model.celldesigner.geometry.CellDesignerAliasConverter;
import lcsb.mapviewer.converter.model.celldesigner.geometry.ReactionCellDesignerConverter;
import lcsb.mapviewer.converter.model.celldesigner.geometry.helper.PolylineDataFactory;
import lcsb.mapviewer.converter.model.celldesigner.reaction.ReactionLineData;
import lcsb.mapviewer.converter.model.celldesigner.types.ModifierType;
import lcsb.mapviewer.converter.model.celldesigner.types.ModifierTypeUtils;
......@@ -33,7 +31,6 @@ import lcsb.mapviewer.model.map.species.field.*;
import lcsb.mapviewer.modelutils.map.ElementUtils;
import lcsb.mapviewer.wikipathway.model.*;
import lcsb.mapviewer.wikipathway.model.biopax.BiopaxFactory;
import lcsb.mapviewer.wikipathway.utils.Geo;
/**
* Class contains methods for GraphToModel conversion.
......@@ -47,14 +44,7 @@ public class ModelContructor {
* Default color used by complexes.
*/
private static final Color DEFAULT_COMPLEX_ALIAS_COLOR = new Color(102, 255, 255);
/**
* How much of central line is used by and operator that joins reactants.
*/
private static final int AND_OPERATOR_CENTRAL_LINE_RATIO = 10;
/**
* How much of central line is used by split operator that split products.
*/
private static final int SPLIT_OPERATOR_CENTRAL_LINE_RATIO = 10;
/**
* Epsilon used for double comparison.
*/
......@@ -92,58 +82,6 @@ public class ModelContructor {
* Parser used for extracting {@link MiriamData references} from GPML model.
*/
private BiopaxFactory biopaxFactory = new BiopaxFactory();
private PointTransformation pt = new PointTransformation();
/**
* This function splits {@link PolylineData} into two parts. It also assumes
* that last segment on the left part is equal to the length of the first
* segment on the right side.
*
* @param pd
* line to split
* @return pair of lines into which parameter was split to
*/
protected Pair<PolylineData, PolylineData> splitPolyline(PolylineData pd) {
PolylineData p1 = new PolylineData();
PolylineData p2 = new PolylineData();
p1.setColor(pd.getColor());
p2.setColor(pd.getColor());
List<Line2D> lines = pd.getLines();
double lenght = 0.0;
for (Line2D line : lines) {
lenght += Geo.lineLen(line);
}
if (lenght > Configuration.EPSILON) {
double tmp = 0.0;
boolean first = true;
for (Line2D line : lines) {
if (tmp + Geo.lineLen(line) > lenght / 2) {
if (first) {
double a = (lenght / 2 - tmp) / Geo.lineLen(line);
double x = line.getX1() + a * (line.getX2() - line.getX1());
double y = line.getY1() + a * (line.getY2() - line.getY1());
p1.addPoint((Point2D) line.getP1().clone());
p1.addPoint(new Point2D.Double(x, y));
p2.addPoint(new Point2D.Double(x, y));
first = false;
}
p2.addPoint((Point2D) line.getP2().clone());
tmp += Geo.lineLen(line);
} else {
p1.addPoint((Point2D) line.getP1().clone());
tmp += Geo.lineLen(line);
}
}
} else { // we have line with empty length
p1.addPoint((Point2D) pd.getBeginPoint().clone());
p1.addPoint((Point2D) pd.getBeginPoint().clone());
p2.addPoint((Point2D) pd.getBeginPoint().clone());
p2.addPoint((Point2D) pd.getBeginPoint().clone());
}
return new Pair<PolylineData, PolylineData>(p1, p2);
}
/**
* This function creates Species from DataNode.
......@@ -650,20 +588,19 @@ public class ModelContructor {
reaction.setZ(interaction.getzOrder());
reaction.addMiriamData(biopaxFactory.getMiriamData(graph.getBiopaxData(), interaction.getBiopaxReferences()));
PolylineData pd = interaction.getLine();
Pair<PolylineData, PolylineData> pair = splitPolyline(pd);
PolylineData pdfirstpart = pair.getLeft();
PolylineData pdsecondpart = pair.getRight();
ReactionLayoutFinder layoutFinder = new ReactionLayoutFinder();
Map<Class<?>, PolylineData> lines = layoutFinder.getNodeLines(interaction);
PolylineData reactantLine = lines.get(Reactant.class);
PolylineData productLine = lines.get(Product.class);
Reactant reactant = new Reactant();
reactant.setElement(data.id2alias.get(interaction.getStart()));
reactant.setLine(pdfirstpart);
reactant.setLine(reactantLine);
reaction.addReactant(reactant);
Product product = new Product();
product.setElement(data.id2alias.get(interaction.getEnd()));
product.setLine(pdsecondpart);
product.setLine(productLine);
reaction.addProduct(product);
for (Edge e : interaction.getReactants()) {
......@@ -676,16 +613,8 @@ public class ModelContructor {
reaction.addProduct(pro);
}
double centralLength = pdfirstpart.getPoints().get(pdfirstpart.getPoints().size() - 2)
.distance(pdfirstpart.getEndPoint()) * 2;
if (reaction.getReactants().size() > 1) {
PolylineData operatorLine = new PolylineData();
operatorLine.setColor(pdfirstpart.getColor());
operatorLine.addPoint((Point2D) pdfirstpart.getEndPoint().clone());
operatorLine.addPoint((Point2D) pdfirstpart.getEndPoint().clone());
pdfirstpart.trimEnd(centralLength / AND_OPERATOR_CENTRAL_LINE_RATIO);
operatorLine.setStartPoint((Point2D) pdfirstpart.getEndPoint().clone());
PolylineData operatorLine = lines.get(AndOperator.class);
NodeOperator andOperator;
// heterodimer association use association operator
if (reaction instanceof HeterodimerAssociationReaction) {
......@@ -698,28 +627,36 @@ public class ModelContructor {
andOperator.setLine(operatorLine);
for (int i = 1; i < reaction.getReactants().size(); i++) {
Reactant r = reaction.getReactants().get(i);
r.getLine().addPoint((Point2D) pdfirstpart.getEndPoint().clone());
r.getLine().getEndPoint().setLocation(operatorLine.getBeginPoint());
andOperator.addInput(r);
}
reaction.addNode(andOperator);
} else {
PolylineData operatorLine = lines.get(AndOperator.class);
for (int i = 1; i < operatorLine.getPoints().size(); i++) {
reactantLine.addPoint(operatorLine.getPoints().get(i));
}
reactant.setLine(PolylineDataFactory.removeCollinearPoints(reactantLine));
}
if (reaction.getProducts().size() > 1) {
PolylineData operatorLine = new PolylineData();
operatorLine.setColor(pdsecondpart.getColor());
PolylineData operatorLine = lines.get(SplitOperator.class);
operatorLine.addPoint((Point2D) pdsecondpart.getBeginPoint().clone());
pdsecondpart.trimBegin(centralLength / SPLIT_OPERATOR_CENTRAL_LINE_RATIO);
operatorLine.addPoint((Point2D) pdsecondpart.getBeginPoint().clone());
SplitOperator splitOperator = new SplitOperator();
splitOperator.addOutput(product);
splitOperator.setLine(operatorLine);
splitOperator.setLine(operatorLine.reverse());
for (int i = 1; i < reaction.getProducts().size(); i++) {
Product r = reaction.getProducts().get(i);
r.getLine().addPoint(0, (Point2D) pdsecondpart.getBeginPoint().clone());
r.getLine().getBeginPoint().setLocation(operatorLine.getEndPoint());
splitOperator.addOutput(r);
}
reaction.addNode(splitOperator);
} else {
PolylineData operatorLine = lines.get(SplitOperator.class);
for (int i = 0; i < operatorLine.getPoints().size()-1; i++) {
productLine.addPoint(i, operatorLine.getPoints().get(i));
}
product.setLine(PolylineDataFactory.removeCollinearPoints(productLine));
}
for (Edge e : interaction.getModifiers()) {
......@@ -760,7 +697,9 @@ public class ModelContructor {
}
}
createReactionCenterLine(reaction);
PolylineData modifierLine = lines.get(Modifier.class);
modifierLine.setType(rld.getLineType());
reaction.setLine(modifierLine);
ModifierTypeUtils mtu = new ModifierTypeUtils();
for (Modifier m : reaction.getModifiers()) {
......@@ -771,36 +710,6 @@ public class ModelContructor {
return reaction;
}
private void createReactionCenterLine(Reaction reaction) {
AbstractNode input = reaction.getReactants().get(0);
AbstractNode output = reaction.getProducts().get(0);
for (NodeOperator operator : reaction.getOperators()) {
if (operator.isReactantOperator()) {
input = operator;
} else if (operator.isProductOperator()) {
output = operator;
}
}
PolylineData line = new PolylineData();
double distanceToTrimm = ReactionCellDesignerConverter.RECT_SIZE;
distanceToTrimm -= input.getLine().trimEnd(distanceToTrimm / 2);
line.addPoint(pt.copyPoint(input.getLine().getEndPoint()));
if (output instanceof NodeOperator) {
distanceToTrimm -= output.getLine().trimEnd(distanceToTrimm);
line.addPoint(pt.copyPoint(output.getLine().getEndPoint()));
} else {
distanceToTrimm -= output.getLine().trimBegin(distanceToTrimm);
line.addPoint(pt.copyPoint(output.getLine().getBeginPoint()));
}
line.setType(input.getLine().getType());
line.setColor(input.getLine().getColor());
reaction.setLine(line);
}
/**
* Creates {@link Reactant} from GPML edge.
*
......
package lcsb.mapviewer.wikipathway.XML;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import lcsb.mapviewer.common.Configuration;
import lcsb.mapviewer.common.Pair;
import lcsb.mapviewer.common.comparator.DoubleComparator;
import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.common.geometry.LineTransformation;
import lcsb.mapviewer.common.geometry.PointTransformation;
import lcsb.mapviewer.converter.model.celldesigner.geometry.ReactionCellDesignerConverter;
import lcsb.mapviewer.converter.model.celldesigner.geometry.helper.PolylineDataFactory;
import lcsb.mapviewer.model.graphics.PolylineData;
import lcsb.mapviewer.model.map.reaction.*;
import lcsb.mapviewer.wikipathway.model.Edge;
import lcsb.mapviewer.wikipathway.model.Interaction;
import lcsb.mapviewer.wikipathway.utils.Geo;
class ReactionLayoutFinder {
@SuppressWarnings("unused")
private Logger logger = LogManager.getLogger();
private LineTransformation lt = new LineTransformation();
private PointTransformation pt = new PointTransformation();
private Interaction interaction;
/**
* Return coordinates where all {@link Reactant} should end, all
* {@link Modifier} should end, and all {@link Product} should start.
*
* @param interaction
* GPML {@link Interaction}
* @return map consisted of three entries for classes: {@link Product},
* {@link Reactant}, {@link Modifier}
*/
public Map<Class<?>, Point2D> getNodeStartPoints(Interaction interaction) {
this.interaction = interaction;
List<Pair<Class<?>, Point2D>> possiblePoints = getPointsInOrder(interaction);
Point2D modifierPoint = getModifierPoint(possiblePoints);
Point2D reactantPoint = getReactantPoint(possiblePoints, modifierPoint);
Point2D productPoint = getProductPoint(possiblePoints, modifierPoint);
Map<Class<?>, Point2D> result = new HashMap<>();
result.put(Reactant.class, reactantPoint);
result.put(Product.class, productPoint);
result.put(Modifier.class, modifierPoint);
return result;
}
public Map<Class<?>, PolylineData> getNodeLines(Interaction interaction) {
Map<Class<?>, Point2D> points = getNodeStartPoints(interaction);
Map<Class<?>, PolylineData> result = new HashMap<>();
PolylineData reactantLine = getSubline(interaction.getLine(), interaction.getLine().getBeginPoint(),
points.get(Reactant.class));
PolylineData inputReactionLine = getSubline(interaction.getLine(), points.get(Reactant.class),
points.get(Modifier.class));
inputReactionLine.trimEnd(ReactionCellDesignerConverter.RECT_SIZE / 2);
PolylineData outputReactionLine = getSubline(interaction.getLine(),
points.get(Modifier.class), points.get(Product.class));
outputReactionLine.trimBegin(ReactionCellDesignerConverter.RECT_SIZE / 2);
PolylineData productLine = getSubline(interaction.getLine(),
points.get(Product.class), interaction.getLine().getEndPoint());
PolylineData modifierLine = new PolylineData(pt.copyPoint(inputReactionLine.getEndPoint()),
pt.copyPoint(outputReactionLine.getBeginPoint()));
modifierLine.setColor(interaction.getColor());
modifierLine.setWidth(productLine.getWidth());
result.put(Reactant.class, reactantLine);
result.put(Product.class, productLine);
result.put(Modifier.class, modifierLine);
result.put(AndOperator.class, inputReactionLine);
result.put(SplitOperator.class, outputReactionLine);
return result;
}
private List<Pair<Class<?>, Point2D>> getPointsInOrder(Interaction interaction) {
PolylineData pd = interaction.getLine();
List<Pair<Class<?>, Point2D>> possiblePoints = new ArrayList<>();
for (Edge e : interaction.getReactants()) {
if (pointOnPolyline(pd, e.getLine().getEndPoint())) {
possiblePoints.add(new Pair<>(Reactant.class, e.getLine().getEndPoint()));
} else if (pointOnPolyline(pd, e.getLine().getBeginPoint())) {
possiblePoints.add(new Pair<>(Reactant.class, e.getLine().getBeginPoint()));
}
}
for (Edge e : interaction.getProducts()) {
if (pointOnPolyline(pd, e.getLine().getEndPoint())) {
possiblePoints.add(new Pair<>(Product.class, e.getLine().getEndPoint()));
} else if (pointOnPolyline(pd, e.getLine().getBeginPoint())) {
possiblePoints.add(new Pair<>(Product.class, e.getLine().getBeginPoint()));
}
}
for (Edge e : interaction.getModifiers()) {
if (pointOnPolyline(pd, e.getLine().getEndPoint())) {
possiblePoints.add(new Pair<>(Modifier.class, e.getLine().getEndPoint()));
} else if (pointOnPolyline(pd, e.getLine().getBeginPoint())) {
possiblePoints.add(new Pair<>(Modifier.class, e.getLine().getBeginPoint()));
}
}
Comparator<Pair<Class<?>, Point2D>> comparator = new Comparator<Pair<Class<?>, Point2D>>() {
@Override
public int compare(Pair<Class<?>, Point2D> o1, Pair<Class<?>, Point2D> o2) {
double dist1 = distanceFromPolylineStart(pd, o1.getRight());
double dist2 = distanceFromPolylineStart(pd, o2.getRight());
return new DoubleComparator(Configuration.EPSILON).compare(dist1, dist2);
}
};
Collections.sort(possiblePoints, comparator);
return possiblePoints;
}
private Point2D getProductPoint(List<Pair<Class<?>, Point2D>> possiblePoints, Point2D modifierPoint) {
Point2D result = null;
for (Pair<Class<?>, Point2D> pair : possiblePoints) {
if (pair.getLeft().equals(Product.class)) {
if (distanceFromPolylineStart(interaction.getLine(),
modifierPoint) < distanceFromPolylineStart(interaction.getLine(), pair.getRight())) {
result = pair.getRight();
break;
}
}
}
if (result == null) {
for (Point2D point : interaction.getLine().getPoints()) {
if (distanceFromPolylineStart(interaction.getLine(),
modifierPoint) < distanceFromPolylineStart(interaction.getLine(), point)) {
double x = modifierPoint.getX() + (point.getX() - modifierPoint.getX()) / 2;
double y = modifierPoint.getY() + (point.getY() - modifierPoint.getY()) / 2;
result = new Point2D.Double(x, y);
break;
}
}
}
return result;
}
private Point2D getReactantPoint(List<Pair<Class<?>, Point2D>> possiblePoints, Point2D modifierPoint) {
Point2D result = null;
for (Pair<Class<?>, Point2D> pair : possiblePoints) {
if (pair.getLeft().equals(Reactant.class)) {
if (distanceFromPolylineStart(interaction.getLine(),
modifierPoint) > distanceFromPolylineStart(interaction.getLine(), pair.getRight())) {
result = pair.getRight();
}
}
}
if (result == null) {
for (Point2D point : interaction.getLine().getPoints()) {
if (distanceFromPolylineStart(interaction.getLine(),
modifierPoint) > distanceFromPolylineStart(interaction.getLine(), point)) {
double x = modifierPoint.getX() + (point.getX() - modifierPoint.getX()) / 2;
double y = modifierPoint.getY() + (point.getY() - modifierPoint.getY()) / 2;
result = new Point2D.Double(x, y);
}
}
}
return result;
}
private Point2D getModifierPoint(List<Pair<Class<?>, Point2D>> points) {
List<Pair<Class<?>, Point2D>> possiblePoints = new ArrayList<>(points);
int productPoints = 0;
int reactantPoints = 0;
for (Pair<Class<?>, Point2D> pair : possiblePoints) {
if (pair.getLeft().equals(Product.class)) {
productPoints++;
} else if (pair.getLeft().equals(Reactant.class)) {
reactantPoints++;
}
}
int countedReactants = 0;
int countedProducts = 0;
int score = Integer.MIN_VALUE;
Point2D point = null;
for (Pair<Class<?>, Point2D> pair : possiblePoints) {
if (pair.getLeft().equals(Product.class)) {
countedProducts++;
} else if (pair.getLeft().equals(Reactant.class)) {
countedReactants++;
} else if (pair.getLeft().equals(Modifier.class)) {
int currentScore = countedReactants + (productPoints - countedProducts);
if (point == null || score < currentScore) {
score = currentScore;
point = pair.getRight();
}
}
}
if (point == null) {
possiblePoints.add(0, new Pair<Class<?>, Point2D>(null, interaction.getLine().getBeginPoint()));
possiblePoints.add(new Pair<Class<?>, Point2D>(null, interaction.getLine().getEndPoint()));
countedReactants = 0;
countedProducts = 0;
Point2D previousPoint = null;
for (Pair<Class<?>, Point2D> pair : possiblePoints) {
if (previousPoint != null) {
int currentScore = countedReactants + (productPoints - countedProducts);
if (point == null || score < currentScore) {
score = currentScore;
if (reactantPoints == 0) { // shift to most right possible line when there are no other reactants
point = getRightCenterPoint(interaction.getLine(), previousPoint, pair.getRight());
} else if (productPoints == 0) {
point = getLeftCenterPoint(interaction.getLine(), previousPoint, pair.getRight());
} else {
point = getCenterPoint(interaction.getLine(), previousPoint, pair.getRight());
}
}
}
if (pair.getLeft() == Product.class) {
countedProducts++;
} else if (pair.getLeft() == Reactant.class) {
countedReactants++;
}
previousPoint = pair.getRight();
}
}
return point;
}
private Point2D getCenterPoint(PolylineData originalPolylineData, Point2D startPoint, Point2D endPoint) {
PolylineData pd = getSubline(originalPolylineData, startPoint, endPoint);
return getCenterPoint(pd);
}
private Point2D getLeftCenterPoint(PolylineData originalPolylineData, Point2D startPoint, Point2D endPoint) {
PolylineData pd = getSubline(originalPolylineData, startPoint, endPoint);
double x = pd.getPoints().get(0).getX() + (pd.getPoints().get(1).getX() - pd.getPoints().get(0).getX()) / 2;
double y = pd.getPoints().get(0).getY() + (pd.getPoints().get(1).getY() - pd.getPoints().get(0).getY()) / 2;
return new Point2D.Double(x, y);
}
private Point2D getRightCenterPoint(PolylineData originalPolylineData, Point2D startPoint, Point2D endPoint) {
PolylineData pd = getSubline(originalPolylineData, startPoint, endPoint);
int points = pd.getPoints().size();
double x = pd.getPoints().get(points - 2).getX()
+ (pd.getPoints().get(points - 1).getX() - pd.getPoints().get(points - 2).getX()) / 2;
double y = pd.getPoints().get(points - 2).getY()
+ (pd.getPoints().get(points - 1).getY() - pd.getPoints().get(points - 2).getY()) / 2;
return new Point2D.Double(x, y);
}
private PolylineData getSubline(PolylineData originalPolylineData, Point2D startPoint, Point2D endPoint) {
int start = 0;
int end = originalPolylineData.getPoints().size() - 1;
for (int i = 0; i < originalPolylineData.getPoints().size(); i++) {
if (distanceFromPolylineStart(originalPolylineData, startPoint) > distanceFromPolylineStart(originalPolylineData,
originalPolylineData.getPoints().get(i))) {
start = i;
}
if (distanceFromPolylineStart(originalPolylineData, endPoint) < distanceFromPolylineStart(originalPolylineData,
originalPolylineData.getPoints().get(i))) {
end = Math.min(end, i);
}
}
PolylineData result = originalPolylineData.getSubline(start, end + 1);
result.setStartPoint(pt.copyPoint(startPoint));
result.setEndPoint(pt.copyPoint(endPoint));
return PolylineDataFactory.removeCollinearPoints(result);
}
private Point2D getCenterPoint(PolylineData pd) {
List<Line2D> lines = pd.getLines();
double lenght = 0.0;
for (Line2D line : lines) {
lenght += Geo.lineLen(line);
}
double tmp = 0.0;
for (Line2D line : lines) {
if (tmp + Geo.lineLen(line) > lenght / 2) {
double x = line.getX1() + (line.getX2() - line.getX1()) / 2;
double y = line.getY1() + (line.getY2() - line.getY1()) / 2;
return new Point2D.Double(x, y);
} else {
tmp += Geo.lineLen(line);
}
}
return pt.copyPoint(pd.getEndPoint());
}
double distanceFromPolylineStart(PolylineData line, Point2D point) {
double distance = 0;
for (Line2D l : line.getLines()) {
if (lt.distBetweenPointAndLineSegment(l, point) <= Configuration.EPSILON) {
return distance + point.distance(l.getP1());
}
distance += l.getP1().distance(l.getP2());
}
throw new InvalidArgumentException("Point doesn't lay on the line");
}
private boolean pointOnPolyline(PolylineData line, Point2D point) {
for (Line2D l : line.getLines()) {
if (lt.distBetweenPointAndLineSegment(l, point) <= Configuration.EPSILON) {
return true;
}