Commit 6ca0573d authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '1055-copasi-does-not-preserve-elements-shape' into 'master'

Resolve "COPASI does not preserve elements shape"

Closes #1055

See merge request !1282
parents 8ab015f3 32db43a5
Pipeline #38542 failed with stage
in 31 minutes and 14 seconds
......@@ -15,6 +15,9 @@ minerva (16.0.0~alpha.1) stable; urgency=medium
(#1376)
* Small improvement: context menu exporting map is more precise about scope
(#1447)
* Small improvement: styling of GENERIC search tab title improved (#1364)
* Small improvement: SBML render contains information about species shapes
(#1055)
* Bug fix: refreshing page after removing last project on the page redirect to
proper page (#1051)
* Bug fix: removing plugin that does not exist anymore does not raise an error
......
......@@ -54,7 +54,9 @@ public class SbmlExporter {
return stream.toString("UTF-8")
// TODO bug: https://github.com/sbmlteam/jsbml/issues/158
.replace("<listOfSpeciesFeatures>", "<multi:listOfSpeciesFeatures>")
.replace("</listOfSpeciesFeatures>", "</multi:listOfSpeciesFeatures>");
.replace("</listOfSpeciesFeatures>", "</multi:listOfSpeciesFeatures>")
//TODO bug: https://github.com/sbmlteam/jsbml/pull/222
.replace("render:basepoint", "render:basePoint");
} catch (UnsupportedEncodingException | XMLStreamException e) {
throw new InvalidStateException(e);
}
......
package lcsb.mapviewer.converter.model.sbml.species;
import java.awt.Color;
import java.lang.reflect.Method;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
......@@ -11,11 +11,14 @@ import org.sbml.jsbml.ext.layout.*;
import org.sbml.jsbml.ext.multi.*;
import org.sbml.jsbml.ext.render.*;
import lcsb.mapviewer.common.exception.InvalidStateException;
import lcsb.mapviewer.converter.model.sbml.SbmlElementExporter;
import lcsb.mapviewer.converter.model.sbml.SbmlExtension;
import lcsb.mapviewer.converter.model.sbml.compartment.SbmlCompartmentExporter;
import lcsb.mapviewer.converter.model.sbml.extension.multi.BioEntityFeature;
import lcsb.mapviewer.converter.model.sbml.extension.multi.MultiPackageNamingUtils;
import lcsb.mapviewer.model.LogMarker;
import lcsb.mapviewer.model.ProjectLogEntryType;
import lcsb.mapviewer.model.map.InconsistentModelException;
import lcsb.mapviewer.model.map.MiriamData;
import lcsb.mapviewer.model.map.species.*;
......@@ -23,6 +26,13 @@ import lcsb.mapviewer.model.map.species.field.*;
public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.jsbml.Species> {
private static final double ION_CHANNEL_WIDTH = 20;
private static final double DEGRADED_CROSS_LINE_EXTENDED_LENGTH = 7;
private static final double RECTANGLE_CORNER_ARC_SIZE = 5;
private static final double COMPLEX_TRIMMED_CORNER_SIZE = 5;
private static final double DRUG_OFFSET_BETWEEN_BORDERS = 4;
private static int idCounter = 0;
/**
......@@ -74,7 +84,8 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
} else if (element instanceof Rna) {
assignModificationResiduesToMulti(((Rna) element).getModificationResidues(), multiExtension, speciesType);
} else if (element instanceof AntisenseRna) {
assignModificationResiduesToMulti(((AntisenseRna) element).getModificationResidues(), multiExtension, speciesType);
assignModificationResiduesToMulti(((AntisenseRna) element).getModificationResidues(), multiExtension,
speciesType);
}
}
......@@ -446,39 +457,343 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
protected LocalStyle createStyle(Species element) {
LocalStyle style = super.createStyle(element);
style.getGroup().setStrokeWidth(element.getLineWidth());
style.getGroup().setStroke(getColorDefinition(element.getBorderColor()).getId());
List<GraphicalPrimitive1D> shapes = new ArrayList<>();
if (element instanceof Chemical) {
Ellipse ellipse = new Ellipse();
ellipse.setAbsoluteRx(false);
ellipse.setAbsoluteRy(false);
ellipse.setAbsoluteCx(false);
ellipse.setAbsoluteCy(false);
ellipse.setCx(50);
ellipse.setCy(50.0);
ellipse.setRx(50.0);
ellipse.setRy(50.0);
ellipse.setStroke(getColorDefinition(Color.BLACK).getId());
style.getGroup().addElement(ellipse);
shapes.add(createChemicalShape());
} else if (element instanceof Unknown) {
shapes.add(createUnknownShape((Unknown) element));
} else if (element instanceof Rna) {
shapes.add(createRnaShape());
} else if (element instanceof Drug) {
shapes.addAll(createDrugShapes((Drug) element));
} else if (element instanceof GenericProtein) {
shapes.add(createGenericProteinShape((GenericProtein) element));
} else if (element instanceof ReceptorProtein) {
shapes.add(createReceptorProteinShape());
} else if (element instanceof TruncatedProtein) {
shapes.add(createTruncatedProteinShape((TruncatedProtein) element));
} else if (element instanceof IonChannelProtein) {
shapes.addAll(createIonChannelShapes((IonChannelProtein) element));
} else if (element instanceof Complex) {
shapes.add(createComplexShape((Complex) element));
} else if (element instanceof Gene) {
shapes.add(createGeneShape());
} else if (element instanceof Phenotype) {
shapes.add(createPhenotypeShape((Phenotype) element));
} else if (element instanceof AntisenseRna) {
shapes.add(createAntisenseRnaShape());
} else if (element instanceof Degraded) {
shapes.addAll(createDegradedShapes((Degraded) element));
} else {
Rectangle rectangle = new Rectangle();
rectangle.setAbsoluteRx(false);
rectangle.setAbsoluteRy(false);
rectangle.setAbsoluteX(false);
rectangle.setAbsoluteY(false);
rectangle.setAbsoluteHeight(false);
rectangle.setAbsoluteWidth(false);
rectangle.setRx(0);
rectangle.setRy(0);
rectangle.setX(0);
rectangle.setY(0);
rectangle.setWidth(100);
rectangle.setHeight(100);
rectangle.setStroke(getColorDefinition(Color.BLACK).getId());
style.getGroup().addElement(rectangle);
logger.warn(new LogMarker(ProjectLogEntryType.EXPORT_ISSUE, element),
"Don't know how to represent shape for: " + element.getClass());
shapes.add(createDeafultShape());
}
for (GraphicalPrimitive1D shape : shapes) {
style.getGroup().addElement(shape);
}
return style;
}
private List<GraphicalPrimitive1D> createDegradedShapes(Degraded degraded) {
List<GraphicalPrimitive1D> result = new ArrayList<>();
double diameter = Math.min(degraded.getWidth(), degraded.getHeight()) - 2 * DEGRADED_CROSS_LINE_EXTENDED_LENGTH;
if (diameter < 0) {
logger.warn("Diameter cannot be negative...");
diameter = 0;
}
Ellipse ellipse = new Ellipse();
ellipse.setAbsoluteRx(true);
ellipse.setAbsoluteRy(true);
ellipse.setAbsoluteCx(true);
ellipse.setAbsoluteCy(true);
ellipse.setCx(degraded.getWidth() / 2);
ellipse.setCy(diameter / 2 + DEGRADED_CROSS_LINE_EXTENDED_LENGTH);
ellipse.setRx(diameter / 2);
ellipse.setRy(diameter / 2);
result.add(ellipse);
Polygon polygon = new Polygon();
createAbsolutePoint(polygon, degraded.getWidth() / 2 + DEGRADED_CROSS_LINE_EXTENDED_LENGTH, 0);
createAbsolutePoint(polygon, degraded.getWidth() / 2 - DEGRADED_CROSS_LINE_EXTENDED_LENGTH,
diameter + 2 * DEGRADED_CROSS_LINE_EXTENDED_LENGTH);
result.add(polygon);
return result;
}
private List<GraphicalPrimitive1D> createIonChannelShapes(IonChannelProtein ionChannel) {
List<GraphicalPrimitive1D> result = new ArrayList<>();
result.add(createRoundedRect(0, 0,
ionChannel.getWidth() - ION_CHANNEL_WIDTH - 1, ionChannel.getHeight(),
RECTANGLE_CORNER_ARC_SIZE));
result.add(createRoundedRect(ionChannel.getWidth() - ION_CHANNEL_WIDTH, 0,
ION_CHANNEL_WIDTH, ionChannel.getHeight(),
RECTANGLE_CORNER_ARC_SIZE));
return result;
}
private List<GraphicalPrimitive1D> createDrugShapes(Drug drug) {
List<GraphicalPrimitive1D> result = new ArrayList<>();
Polygon polygon = new Polygon();
double arcWidth = drug.getHeight() / 2;
createAbsolutePoint(polygon, arcWidth, 0);
createAbsolutePoint(polygon, drug.getWidth() - arcWidth, 0);
createAbsoluteBezierPoint(polygon,
drug.getWidth() - arcWidth, drug.getHeight(),
drug.getWidth() + 0.3 * arcWidth, 0,
drug.getWidth() + 0.3 * arcWidth, drug.getHeight());
createAbsolutePoint(polygon, arcWidth, drug.getHeight());
createAbsoluteBezierPoint(polygon,
arcWidth, 0,
-0.3 * arcWidth, drug.getHeight(),
-0.3 * arcWidth, 0);
result.add(polygon);
polygon = new Polygon();
arcWidth -= DRUG_OFFSET_BETWEEN_BORDERS;
createAbsolutePoint(polygon, arcWidth + DRUG_OFFSET_BETWEEN_BORDERS, 0 + DRUG_OFFSET_BETWEEN_BORDERS);
createAbsolutePoint(polygon, drug.getWidth() - arcWidth - DRUG_OFFSET_BETWEEN_BORDERS, DRUG_OFFSET_BETWEEN_BORDERS);
createAbsoluteBezierPoint(polygon,
drug.getWidth() - DRUG_OFFSET_BETWEEN_BORDERS - arcWidth, drug.getHeight() - DRUG_OFFSET_BETWEEN_BORDERS,
drug.getWidth() - DRUG_OFFSET_BETWEEN_BORDERS + 0.3 * arcWidth, DRUG_OFFSET_BETWEEN_BORDERS,
drug.getWidth() - DRUG_OFFSET_BETWEEN_BORDERS + 0.3 * arcWidth, drug.getHeight() - DRUG_OFFSET_BETWEEN_BORDERS);
createAbsolutePoint(polygon, arcWidth + DRUG_OFFSET_BETWEEN_BORDERS,
drug.getHeight() - DRUG_OFFSET_BETWEEN_BORDERS);
createAbsoluteBezierPoint(polygon,
arcWidth + DRUG_OFFSET_BETWEEN_BORDERS, DRUG_OFFSET_BETWEEN_BORDERS,
-0.3 * arcWidth + DRUG_OFFSET_BETWEEN_BORDERS, drug.getHeight() - DRUG_OFFSET_BETWEEN_BORDERS,
-0.3 * arcWidth + DRUG_OFFSET_BETWEEN_BORDERS, DRUG_OFFSET_BETWEEN_BORDERS);
result.add(polygon);
return result;
}
private GraphicalPrimitive1D createGenericProteinShape(GenericProtein protein) {
double x = 0;
double y = 0;
double width = protein.getWidth();
double height = protein.getHeight();
double arcSize = RECTANGLE_CORNER_ARC_SIZE;
return createRoundedRect(x, y, width, height, arcSize);
}
private Polygon createRoundedRect(double x, double y, double width, double height, double arcSize) {
Polygon polygon = new Polygon();
createAbsolutePoint(polygon, x + arcSize, y);
createAbsolutePoint(polygon, x + width - arcSize, y);
createArc(polygon, x + width - arcSize, y,
x + width, y + arcSize,
x + width - arcSize, y + arcSize);
createAbsolutePoint(polygon, x + width, y + height - arcSize);
createArc(polygon, x + width, y + height - arcSize,
x + width - arcSize, y + height,
x + width - arcSize, y + height - arcSize);
createAbsolutePoint(polygon, x + arcSize, y + height);
createArc(polygon, x + arcSize, y + height,
x, y + height - arcSize,
x + arcSize, y + height - arcSize);
createAbsolutePoint(polygon, x, y + arcSize);
createArc(polygon, x, y + arcSize,
x + arcSize, y,
x + arcSize, y + arcSize);
return polygon;
}
private GraphicalPrimitive1D createTruncatedProteinShape(TruncatedProtein protein) {
Polygon polygon = new Polygon();
createAbsolutePoint(polygon, 10, 0);
createAbsolutePoint(polygon, protein.getWidth(), 0);
createAbsolutePoint(polygon, protein.getWidth(), protein.getHeight() * 3 / 5);
createAbsolutePoint(polygon, protein.getWidth() * 4 / 5, protein.getHeight() * 2 / 5);
createAbsolutePoint(polygon, protein.getWidth() * 4 / 5, protein.getHeight());
createAbsolutePoint(polygon, 10, protein.getHeight());
createAbsoluteBezierPoint(polygon, 0, protein.getHeight() - 10,
5, protein.getHeight() - 2,
2, protein.getHeight() - 5);
createAbsolutePoint(polygon, 0, 10);
createAbsoluteBezierPoint(polygon, 10, 0,
2, 5,
5, 2);
return polygon;
}
private GraphicalPrimitive1D createComplexShape(Complex complex) {
Polygon polygon = new Polygon();
createAbsolutePoint(polygon, COMPLEX_TRIMMED_CORNER_SIZE, 0);
createAbsolutePoint(polygon, complex.getWidth() - COMPLEX_TRIMMED_CORNER_SIZE, 0);
createAbsolutePoint(polygon, complex.getWidth(), COMPLEX_TRIMMED_CORNER_SIZE);
createAbsolutePoint(polygon, complex.getWidth(), complex.getHeight() - COMPLEX_TRIMMED_CORNER_SIZE);
createAbsolutePoint(polygon, complex.getWidth() - COMPLEX_TRIMMED_CORNER_SIZE, complex.getHeight());
createAbsolutePoint(polygon, COMPLEX_TRIMMED_CORNER_SIZE, complex.getHeight());
createAbsolutePoint(polygon, 0, complex.getHeight() - COMPLEX_TRIMMED_CORNER_SIZE);
createAbsolutePoint(polygon, 0, COMPLEX_TRIMMED_CORNER_SIZE);
return polygon;
}
private GraphicalPrimitive1D createPhenotypeShape(Phenotype complex) {
Polygon polygon = new Polygon();
createAbsolutePoint(polygon, 0, complex.getHeight() / 2);
createAbsolutePoint(polygon, complex.getHeight() / 2, 0);
createAbsolutePoint(polygon, complex.getWidth() - complex.getHeight() / 2, 0);
createAbsolutePoint(polygon, complex.getWidth(), complex.getHeight() / 2);
createAbsolutePoint(polygon, complex.getWidth() - complex.getHeight() / 2, complex.getHeight());
createAbsolutePoint(polygon, complex.getHeight() / 2, complex.getHeight());
return polygon;
}
private void createArc(Polygon polygon, double x1, double y1, double x4, double y4, double xc, double yc) {
// math taken from https://stackoverflow.com/a/44829356/1127920
double ax = x1 - xc;
double ay = y1 - yc;
double bx = x4 - xc;
double by = y4 - yc;
double q1 = ax * ax + ay * ay;
double q2 = q1 + ax * bx + ay * by;
double k2 = (4 / 3) * (Math.sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx);
double x2 = xc + ax - k2 * ay;
double y2 = yc + ay + k2 * ax;
double x3 = xc + bx + k2 * by;
double y3 = yc + by - k2 * bx;
createAbsoluteBezierPoint(polygon, x4, y4, x2, y2, x3, y3);
}
private Rectangle createGeneShape() {
return createDeafultShape();
}
private Rectangle createDeafultShape() {
Rectangle rectangle = new Rectangle();
rectangle.setAbsoluteRx(false);
rectangle.setAbsoluteRy(false);
rectangle.setAbsoluteX(false);
rectangle.setAbsoluteY(false);
rectangle.setAbsoluteHeight(false);
rectangle.setAbsoluteWidth(false);
rectangle.setRx(0);
rectangle.setRy(0);
rectangle.setX(0);
rectangle.setY(0);
rectangle.setWidth(100);
rectangle.setHeight(100);
return rectangle;
}
private Ellipse createEllipseShape() {
Ellipse ellipse = new Ellipse();
ellipse.setAbsoluteRx(false);
ellipse.setAbsoluteRy(false);
ellipse.setAbsoluteCx(false);
ellipse.setAbsoluteCy(false);
ellipse.setCx(50);
ellipse.setCy(50.0);
ellipse.setRx(50.0);
ellipse.setRy(50.0);
return ellipse;
}
private Ellipse createChemicalShape() {
return createEllipseShape();
}
private Ellipse createUnknownShape(Unknown unknown) {
Ellipse result = createEllipseShape();
result.setStroke(getColorDefinition(unknown.getFillColor()).getId());
return result;
}
private Polygon createRnaShape() {
Polygon polygon = new Polygon();
createRelativePoint(polygon, 25, 0);
createRelativePoint(polygon, 100, 0);
createRelativePoint(polygon, 75, 100);
createRelativePoint(polygon, 0, 100);
return polygon;
}
private Polygon createReceptorProteinShape() {
Polygon polygon = new Polygon();
createRelativePoint(polygon, 0, 0);
createRelativePoint(polygon, 50, 20);
createRelativePoint(polygon, 100, 0);
createRelativePoint(polygon, 100, 80);
createRelativePoint(polygon, 50, 100);
createRelativePoint(polygon, 0, 80);
return polygon;
}
private Polygon createAntisenseRnaShape() {
Polygon polygon = new Polygon();
createRelativePoint(polygon, 0, 0);
createRelativePoint(polygon, 75, 0);
createRelativePoint(polygon, 100, 100);
createRelativePoint(polygon, 25, 100);
return polygon;
}
private void createRelativePoint(Polygon polygon, double x, double y) {
RenderPoint p1 = polygon.createRenderPoint();
p1.setAbsoluteX(false);
p1.setAbsoluteY(false);
p1.setX(x);
p1.setY(y);
}
private void createAbsolutePoint(Polygon polygon, double x, double y) {
RenderPoint p1 = polygon.createRenderPoint();
p1.setAbsoluteX(true);
p1.setAbsoluteY(true);
p1.setX(Math.round(x));
p1.setY(Math.round(y));
}
private void createAbsoluteBezierPoint(Polygon polygon, double x, double y, double x1, double y1, double x2,
double y2) {
RenderCubicBezier result = (RenderCubicBezier) polygon.createRenderCubicBezier();
result.setAbsoluteX(true);
result.setAbsoluteY(true);
result.setX(Math.round(x));
result.setY(Math.round(y));
result.setAbsoluteX1(true);
result.setAbsoluteY1(true);
result.setX1(Math.round(x1));
result.setY1(Math.round(y1));
result.setAbsoluteX2(true);
result.setAbsoluteY2(true);
result.setX2(Math.round(x2));
result.setY2(Math.round(y2));
try {
Method method = RenderCurveSegment.class.getDeclaredMethod("setType", RenderCurveSegment.Type.class);
method.setAccessible(true);
method.invoke(result, RenderCurveSegment.Type.RENDER_CUBIC_BEZIER);
} catch (Exception e) {
throw new InvalidStateException("Problem with JSBML", e);
}
}
@Override
protected void assignLayoutToGlyph(Species element, AbstractReferenceGlyph speciesGlyph) {
super.assignLayoutToGlyph(element, speciesGlyph);
......
......@@ -5,7 +5,11 @@ import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({ SbmlSpeciesExporterTest.class, SbmlSpeciesParserTest.class })
@SuiteClasses({
SbmlSpeciesExporterRenderShapeTest.class,
SbmlSpeciesExporterTest.class,
SbmlSpeciesParserTest.class })
public class AllSbmlSpeciesTests {
}
package lcsb.mapviewer.converter.model.sbml.species;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.sbml.jsbml.ext.render.LocalStyle;
import lcsb.mapviewer.converter.model.sbml.SbmlTestFunctions;
import lcsb.mapviewer.model.map.model.Model;
import lcsb.mapviewer.model.map.model.ModelFullIndexed;
import lcsb.mapviewer.model.map.species.Species;
import lcsb.mapviewer.modelutils.map.ElementUtils;
@RunWith(Parameterized.class)
public class SbmlSpeciesExporterRenderShapeTest extends SbmlTestFunctions {
static Logger logger = LogManager.getLogger();
private Species species;
public SbmlSpeciesExporterRenderShapeTest(String name, Species species) {
this.species = species;
}
SbmlSpeciesExporterTestHelper helper;
@Before
public void setUp() {
helper = new SbmlSpeciesExporterTestHelper();
}
@Parameters(name = "{index} : {0}")
public static Collection<Object[]> data() throws Exception {
Collection<Object[]> data = new ArrayList<Object[]>();
for (Class<?> clazz : new ElementUtils().getAvailableElementSubclasses()) {
if (Species.class.isAssignableFrom(clazz)) {
Species species = (Species) (clazz.getConstructor(String.class).newInstance("s1"));
species.setX(10);
species.setY(20);
species.setWidth(50);
species.setHeight(50);
data.add(new Object[] { species.getClass().getName(), species });
}
}
return data;
}
@Test
public void createModelTest() throws Exception {
Model model = new ModelFullIndexed(null);
model.addElement(species);
SbmlSpeciesExporter exporter = helper.createExporter(model);
LocalStyle style = exporter.createStyle(species);
assertNotNull(style);
assertEquals("Style was not generated without issues", 0, super.getWarnings().size());
}
}
......@@ -4,15 +4,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.Arrays;
import java.util.Collection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.sbml.jsbml.SBMLDocument;
import lcsb.mapviewer.converter.model.sbml.*;
import lcsb.mapviewer.converter.model.sbml.compartment.SbmlCompartmentExporter;
import lcsb.mapviewer.converter.model.sbml.SbmlExtension;
import lcsb.mapviewer.converter.model.sbml.SbmlTestFunctions;
import lcsb.mapviewer.model.map.InconsistentModelException;
import lcsb.mapviewer.model.map.compartment.Compartment;
import lcsb.mapviewer.model.map.model.Model;
......@@ -24,7 +23,12 @@ public class SbmlSpeciesExporterTest extends SbmlTestFunctions {