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

Merge branch '1478-proper-complex-child-relation-in-sbml' into 'master'

Resolve "proper complex-child relation in sbml"

Closes #1478

See merge request !1429
parents 8714cde8 13758054
Pipeline #51964 failed with stage
in 46 minutes and 33 seconds
......@@ -7,6 +7,8 @@ minerva (16.1.0~alpha.0) stable; urgency=medium
(#219)
* Small improvement: possibility to provide image coords as json file (#1307)
* Small improvement: possibility to show reaction name (#1355)
* Small improvement: export to SBML provides information about parent-child
complex relation using multi package (#1478)
* Small improvement: converter name is case insensitive (#1615)
* Small improvement: modification residues can use different color then
species if format allows (SBGN, SBML) (#1141)
......
package lcsb.mapviewer.converter.model.sbml;
import org.sbml.jsbml.Species;
import lcsb.mapviewer.model.LogMarker;
import lcsb.mapviewer.model.ProjectLogEntryType;
import lcsb.mapviewer.model.map.model.Model;
public class SbmlLogMarker extends LogMarker {
/**
*
*/
private static final long serialVersionUID = 1L;
public SbmlLogMarker(final LogMarker marker) {
super(marker);
}
public SbmlLogMarker(final ProjectLogEntryType type, final Species species, final Model model) {
super(type, "Species", species.getId(), model.getName());
}
}
......@@ -131,8 +131,11 @@ public class SbmlParser extends Converter {
if (sbmlModel.getRuleCount() > 0) {
logger.warn("Rule not implemented for model");
}
if (layoutExists) {
addComplexRelationsBasedOnCoordinates(model);
} else if (sbmlModel.getExtension("multi") != null) {
speciesParser.addComplexRelationsBasedOnMulti();
}
createLayout(model, layout, params.isSizeAutoAdjust(), reactionParser);
return model;
......
......@@ -4,6 +4,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.sbml.jsbml.ext.multi.MultiSpeciesType;
import lcsb.mapviewer.model.map.species.Complex;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.Species;
import lcsb.mapviewer.model.map.species.field.AbstractSiteModification;
......@@ -35,7 +36,7 @@ public final class MultiPackageNamingUtils {
* minerva {@link Element} class.
* @return id of the {@link MultiSpeciesType} for a given minerva class
*/
public static final String getSpeciesTypeId(final Class<?> speciesClass) {
private static final String getSpeciesTypeId(final Class<?> speciesClass) {
return "minerva_species_type_" + speciesClass.getSimpleName();
}
......@@ -49,15 +50,24 @@ public final class MultiPackageNamingUtils {
* @return id of the {@link MultiSpeciesType} for a given minerva object
*/
public static final String getSpeciesTypeId(final Element element) {
return getSpeciesTypeId(element.getClass());
String result = getSpeciesTypeId(element.getClass());
if (element instanceof Complex) {
result += "_" + element.getElementId();
} else if (element instanceof Species) {
if (((Species) element).getComplex() != null) {
result += "_" + element.getElementId();
}
}
return result;
}
public static final String getFeatureId(final Class<?> speciesClass, final BioEntityFeature feature) {
return feature.getIdPrefix() + speciesClass.getSimpleName();
public static final String getFeatureId(final Element element, final BioEntityFeature feature) {
return feature.getIdPrefix() + getSpeciesTypeId(element).replace("minerva_species_type_", "");
}
public static String getFeatureId(final Species element, final BioEntityFeature feature) {
return getFeatureId(element.getClass(), feature);
return getFeatureId(element, feature);
}
public static boolean isFeatureId(final String featureTypeId, final BioEntityFeature feature) {
......@@ -76,7 +86,7 @@ public final class MultiPackageNamingUtils {
stateSuffix = "_" + ((TranscriptionSite) mr).getActive() + "_" + ((TranscriptionSite) mr).getDirection();
}
return MINERVA_MODIFICATION_TYPE_PREFIX + mr.getSpecies().getClass().getSimpleName() + "_"
return MINERVA_MODIFICATION_TYPE_PREFIX + getSpeciesTypeId(mr.getSpecies()).replace("minerva_species_type_", "") + "_"
+ mr.getClass().getSimpleName() + stateSuffix;
}
......
......@@ -67,7 +67,7 @@ public enum SBOTermSpeciesType {
this.hypothetical = hypothetical;
}
private static SBOTermSpeciesType getTypeSBOTerm(final String sboTerm, final org.sbml.jsbml.Species species) {
static SBOTermSpeciesType getTypeSBOTerm(final String sboTerm, final org.sbml.jsbml.Species species) {
if (sboTerm == null || sboTerm.isEmpty()) {
return SIMPLE_MOLECULE;
}
......@@ -125,7 +125,7 @@ public enum SBOTermSpeciesType {
return null;
}
private String getSBO() {
String getSBO() {
if (sboTerms.size() != 0) {
return sboTerms.get(0);
}
......
......@@ -22,6 +22,7 @@ import org.sbml.jsbml.ext.multi.PossibleSpeciesFeatureValue;
import org.sbml.jsbml.ext.multi.SpeciesFeature;
import org.sbml.jsbml.ext.multi.SpeciesFeatureType;
import org.sbml.jsbml.ext.multi.SpeciesFeatureValue;
import org.sbml.jsbml.ext.multi.SpeciesTypeInstance;
import org.sbml.jsbml.ext.render.Ellipse;
import org.sbml.jsbml.ext.render.GraphicalPrimitive1D;
import org.sbml.jsbml.ext.render.LocalStyle;
......@@ -380,9 +381,8 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
private void assignValueToFeature(final Species element, final MultiSpeciesPlugin multiExtension, final MultiSpeciesType speciesType,
final String value, final BioEntityFeature feature) {
SpeciesFeatureType structuralStateFeature = getFeature(element.getClass(), speciesType, feature);
PossibleSpeciesFeatureValue structuralStateFeatureValue = getPosibleFeatureIdByName(value,
structuralStateFeature);
SpeciesFeatureType structuralStateFeature = getFeature(element, speciesType, feature);
PossibleSpeciesFeatureValue structuralStateFeatureValue = getPosibleFeatureIdByName(value, structuralStateFeature);
addSpeciesFeatureValue(multiExtension, structuralStateFeature, structuralStateFeatureValue);
}
......@@ -795,7 +795,7 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
private void createAbsoluteBezierPoint(final Polygon polygon, final double x, final double y, final double x1, final double y1, final double x2,
final double y2) {
RenderCubicBezier result = (RenderCubicBezier) polygon.createRenderCubicBezier();
RenderCubicBezier result = polygon.createRenderCubicBezier();
result.setX(new RelAbsVector(Math.round(x)));
result.setY(new RelAbsVector(Math.round(y)));
result.setX1(new RelAbsVector(Math.round(x1)));
......@@ -826,8 +826,7 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
MultiSpeciesPlugin speciesExtension = (MultiSpeciesPlugin) sbmlSpecies.getExtension("multi");
SpeciesFeatureType structuralStateFeature = getFeature(element.getClass(), getMultiSpeciesType(element),
BioEntityFeature.STRUCTURAL_STATE);
SpeciesFeatureType structuralStateFeature = getFeature(element, getMultiSpeciesType(element), BioEntityFeature.STRUCTURAL_STATE);
PossibleSpeciesFeatureValue possibleSpeciesFeatureValue = getPosibleFeatureIdByName(
structuralState.getValue(), structuralStateFeature);
......@@ -877,20 +876,29 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
Class<?> clazz = element.getClass();
MultiSpeciesType speciesType = new MultiSpeciesType();
speciesType.setName(clazz.getSimpleName());
speciesType.setId(MultiPackageNamingUtils.getSpeciesTypeId(clazz));
multiPlugin.getListOfSpeciesTypes().add(speciesType);
speciesType.setId(MultiPackageNamingUtils.getSpeciesTypeId(element));
if (element instanceof Complex) {
for (Species child : ((Complex) element).getElements()) {
MultiSpeciesType childType = getMultiSpeciesType(child);
SpeciesTypeInstance speciesTypeInstance = new SpeciesTypeInstance();
speciesTypeInstance.setId("child_" + childType.getId());
speciesTypeInstance.setSpeciesType(childType.getId());
speciesType.addSpeciesTypeInstance(speciesTypeInstance);
}
}
multiPlugin.addSpeciesType(speciesType);
speciesType.setSBOTerm(SBOTermSpeciesType.getTermByType(element));
return speciesType;
}
private SpeciesFeatureType getFeature(final Class<? extends Element> clazz, final MultiSpeciesType speciesType,
private SpeciesFeatureType getFeature(final Element element, final MultiSpeciesType speciesType,
final BioEntityFeature bioEntityFeature) {
SpeciesFeatureType feature = speciesType
.getSpeciesFeatureType(MultiPackageNamingUtils.getFeatureId(clazz, bioEntityFeature));
.getSpeciesFeatureType(MultiPackageNamingUtils.getFeatureId(element, bioEntityFeature));
if (feature == null) {
feature = new SpeciesFeatureType();
feature.setName(bioEntityFeature.getFeatureName());
feature.setId(MultiPackageNamingUtils.getFeatureId(clazz, bioEntityFeature));
feature.setId(MultiPackageNamingUtils.getFeatureId(element, bioEntityFeature));
feature.setOccur(1);
if (bioEntityFeature.getDefaultValue() != null) {
addPosibleValueToFeature(feature, bioEntityFeature.getDefaultValue());
......
......@@ -34,8 +34,10 @@ import com.google.common.base.Objects;
import lcsb.mapviewer.common.Pair;
import lcsb.mapviewer.converter.InvalidInputDataExecption;
import lcsb.mapviewer.converter.model.sbml.SbmlElementParser;
import lcsb.mapviewer.converter.model.sbml.SbmlLogMarker;
import lcsb.mapviewer.converter.model.sbml.extension.multi.BioEntityFeature;
import lcsb.mapviewer.converter.model.sbml.extension.multi.MultiPackageNamingUtils;
import lcsb.mapviewer.model.ProjectLogEntryType;
import lcsb.mapviewer.model.graphics.HorizontalAlign;
import lcsb.mapviewer.model.graphics.VerticalAlign;
import lcsb.mapviewer.model.map.compartment.Compartment;
......@@ -338,14 +340,29 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
String sboTerm = species.getSBOTermID();
MultiSpeciesPlugin multiExtension = (MultiSpeciesPlugin) species.getExtension("multi");
MultiSpeciesType speciesType = getMultiSpeciesType(multiExtension);
if (speciesType != null) {
String sboTerm2 = speciesType.getSBOTermID();
if (sboTerm != null && !sboTerm.isEmpty() && !sboTerm2.equals(sboTerm)) {
logger.warn("Different SBO terms defining species and speciesType: " + species.getId() + ". " + sboTerm + ";"
+ sboTerm2);
logger.warn(new SbmlLogMarker(ProjectLogEntryType.PARSING_ISSUE, species, getMinervaModel()),
"Different SBO terms defining species and speciesType: " + species.getId() + ". " + sboTerm + ";"
+ sboTerm2);
} else {
sboTerm = sboTerm2;
}
if (speciesType.getSpeciesTypeInstanceCount() > 0) {
if (sboTerm == null || sboTerm.isEmpty()) {
sboTerm = SBOTermSpeciesType.COMPLEX.getSBO();
} else {
SBOTermSpeciesType type = SBOTermSpeciesType.getTypeSBOTerm(sboTerm2, species);
if (type != SBOTermSpeciesType.COMPLEX) {
logger.warn(new SbmlLogMarker(ProjectLogEntryType.PARSING_ISSUE, species, getMinervaModel()),
"SBO term defining species is not complex, but type is defined as complex.");
sboTerm = SBOTermSpeciesType.COMPLEX.getSBO();
}
}
}
}
return sboTerm;
}
......@@ -617,4 +634,60 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
return result;
}
public void addComplexRelationsBasedOnMulti() {
MultiModelPlugin modelPlugin = getMultiPlugin();
if (modelPlugin != null) {
Map<String, Complex> childParentRelation = new HashMap<>();
for (org.sbml.jsbml.Species species : getSbmlModel().getListOfSpecies()) {
Element element = getMinervaModel().getElementByElementId(species.getId());
if (element == null) {
logger.warn(new SbmlLogMarker(ProjectLogEntryType.PARSING_ISSUE, species, getMinervaModel()),
"Cannot find element in the model.");
continue;
}
if (element instanceof Complex) {
MultiSpeciesPlugin multiExtension = (MultiSpeciesPlugin) species.getExtension("multi");
MultiSpeciesType speciesType = getMultiSpeciesType(multiExtension);
if (speciesType != null) {
for (int i = 0; i < speciesType.getSpeciesTypeInstanceCount(); i++) {
String childType = speciesType.getSpeciesTypeInstance(i).getSpeciesType();
if (childParentRelation.get(childType) != null) {
logger.warn(new SbmlLogMarker(ProjectLogEntryType.PARSING_ISSUE, species, getMinervaModel()),
"More than one species use the same complex type. Impossible to determine child-parent relation.");
return;
} else {
childParentRelation.put(childType, (Complex) element);
}
}
}
}
}
for (org.sbml.jsbml.Species species : getSbmlModel().getListOfSpecies()) {
Element element = getMinervaModel().getElementByElementId(species.getId());
if (element == null) {
logger.warn(new SbmlLogMarker(ProjectLogEntryType.PARSING_ISSUE, species, getMinervaModel()),
"Cannot find element in the model.");
continue;
}
if (element instanceof Species) {
MultiSpeciesPlugin multiExtension = (MultiSpeciesPlugin) species.getExtension("multi");
MultiSpeciesType speciesType = getMultiSpeciesType(multiExtension);
if (speciesType != null) {
Complex complex = childParentRelation.get(speciesType.getId());
if (complex != null) {
complex.addSpecies((Species) element);
}
}
}
}
}
}
}
......@@ -18,6 +18,7 @@ import java.util.Set;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.text.StringEscapeUtils;
import org.junit.Before;
import org.junit.Test;
import org.reflections.Reflections;
import org.sbml.jsbml.SBMLDocument;
......@@ -59,7 +60,8 @@ public class SbmlExporterTest extends SbmlTestFunctions {
private ModelComparator comparator = new ModelComparator();
public SbmlExporterTest() {
@Before
public void setup() {
exporter.setProvideDefaults(false);
}
......
......@@ -19,9 +19,11 @@ import lcsb.mapviewer.converter.InvalidInputDataExecption;
import lcsb.mapviewer.converter.model.celldesigner.CellDesignerXmlParser;
import lcsb.mapviewer.model.map.compartment.Compartment;
import lcsb.mapviewer.model.map.model.Model;
import lcsb.mapviewer.model.map.model.ModelComparator;
import lcsb.mapviewer.model.map.reaction.Reactant;
import lcsb.mapviewer.model.map.reaction.Reaction;
import lcsb.mapviewer.model.map.reaction.ReactionNode;
import lcsb.mapviewer.model.map.species.Complex;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.Gene;
import lcsb.mapviewer.model.map.species.GenericProtein;
......@@ -294,4 +296,24 @@ public class SbmlParserTest extends SbmlTestFunctions {
parser.createModel(new ConverterParams().filename("testFiles/problem_in_sbml.xml"));
}
@Test
public void testMultiComplexes() throws Exception {
Model model = parser.createModel(new ConverterParams().filename("testFiles/multi/complex.xml"));
Complex complex = model.getElementByElementId("species_SMAD1_5_8");
Species child1 = model.getElementByElementId("species_SMAD1");
Species child2 = model.getElementByElementId("species_SMAD5");
Species child3 = model.getElementByElementId("species_SMAD8");
assertEquals(3, complex.getElements().size());
assertEquals(complex, child1.getComplex());
assertEquals(complex, child2.getComplex());
assertEquals(complex, child3.getComplex());
exporter.setProvideDefaults(false);
Model model2 = super.getModelAfterSerializing(model);
assertEquals(0, new ModelComparator().compare(model, model2));
}
}
......@@ -55,20 +55,16 @@ import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.GenericProtein;
public class SbmlTestFunctions extends TestUtils {
private Logger logger = LogManager.getLogger();
protected Logger logger = LogManager.getLogger();
private static int identifierCounter = 0;
@Rule
public UnitTestFailedWatcher unitTestFailedWatcher = new UnitTestFailedWatcher();
protected SbmlParser parser = new SbmlParser();
protected SbmlExporter exporter;
protected SbmlExporter exporter = new SbmlExporter();
private MinervaLoggerAppender appender;
public SbmlTestFunctions() {
exporter = new SbmlExporter();
}
protected static Model createEmptyModel() {
Model model = new ModelFullIndexed(null);
model.setWidth(1000);
......
<?xml version="1.0" encoding="UTF-8"?>
<sbml xmlns="http://www.sbml.org/sbml/level3/version1/core"
level="3" version="1"
xmlns:multi="http://www.sbml.org/sbml/level3/version1/multi/version1"
multi:required="true">
<model id="complex" name="Model with complex">
<!-- speciesType -->
<multi:listOfSpeciesTypes xmlns:multi="http://www.sbml.org/sbml/level3/version1/multi/version1">
<multi:speciesType multi:id="st_SMAD1"
multi:name=" SMAD1" />
<multi:speciesType multi:id="st_SMAD5"
multi:name=" SMAD5" />
<multi:speciesType multi:id="st_SMAD8"
multi:name=" SMAD8" />
<multi:speciesType multi:id="st_SMAD1_5_8"
multi:name=" SMAD1_5_8">
<multi:listOfSpeciesTypeInstances>
<multi:speciesTypeInstance
multi:id="sti_SMAD1" multi:speciesType="st_SMAD1" />
<multi:speciesTypeInstance
multi:id="sti_SMAD5" multi:speciesType="st_SMAD5" />
<multi:speciesTypeInstance
multi:id="sti_SMAD8" multi:speciesType="st_SMAD8" />
</multi:listOfSpeciesTypeInstances>
</multi:speciesType>
<!-- species -->
</multi:listOfSpeciesTypes>
<listOfSpecies>
<species boundaryCondition="false" compartment="default"
constant="false" hasOnlySubstanceUnits="false"
id="species_SMAD1_5_8" multi:speciesType="st_SMAD1_5_8" />
<species boundaryCondition="false" compartment="default"
constant="false" hasOnlySubstanceUnits="false" id="species_SMAD1"
multi:speciesType="st_SMAD1" />
<species boundaryCondition="false" compartment="default"
constant="false" hasOnlySubstanceUnits="false" id="species_SMAD5"
multi:speciesType="st_SMAD5" />
<species boundaryCondition="false" compartment="default"
constant="false" hasOnlySubstanceUnits="false" id="species_SMAD8"
multi:speciesType="st_SMAD8" />
</listOfSpecies>
</model>
</sbml>
\ No newline at end of file
......@@ -2688,7 +2688,7 @@ ServerConnector.addOverlayFromString = function (name, content) {
filename: fileName
});
var fileContent = new TextEncoder("UTF8").encode(content);
self = this;
var self = this;
return self.getProjectId().then(function (projectid) {
return ServerConnector.uploadFile({
......
......@@ -211,6 +211,19 @@ function createProjectData(options) {
removeDataOverlay: function (overlayId) {
return map.getServerConnector().removeOverlay({overlayId: overlayId, projectId: map.getProject().getProjectId()});
},
/**
*
* @param {string} param.name
* @param {string} param.content
* @param {string} param.description
* @param {string|null} [param.type]
* @param {string} [param.filename]
*
*
*
*
* @return {*}
*/
addDataOverlay: function (param) {
if (param.type === undefined || param.type === null) {
param.type = LayoutAlias.GENERIC;
......
......@@ -112,10 +112,12 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
modifyElementLocation(elements, null, minPoint, dimension);
modifyReactionLocation(reactions);
}
protected void modifyElementLocation(final Collection<Element> elements, final Compartment parent, final Point2D minPoint,
final Dimension2D dimension) {
Set<Compartment> compartments = new HashSet<>();
Set<Species> elementToAlign = new HashSet<>();
Map<Compartment, Set<Element>> elementsByStaticCompartment = new HashMap<>();
......@@ -123,7 +125,6 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
if (element.getWidth() == null || element.getWidth() == 0) {
element.setWidth(SPECIES_WIDTH);
element.setHeight(SPECIES_HEIGHT);
}
if (element.getCompartment() == null || element.getCompartment() == parent) {
if (element instanceof Compartment) {
......@@ -381,15 +382,10 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
} else {
element.setWidth(SPECIES_WIDTH);
element.setHeight(SPECIES_HEIGHT);
element.setX(elementCenterX - SPECIES_WIDTH / 2);
element.setY(elementCenterY - SPECIES_HEIGHT / 2);
}
element.setX(elementCenterX - SPECIES_WIDTH / 2);
element.setY(elementCenterY - SPECIES_HEIGHT / 2);
element.setNameX(element.getX());
element.setNameY(element.getY());
element.setNameWidth(element.getWidth());
element.setNameHeight(element.getHeight());
element.setNameHorizontalAlign(HorizontalAlign.CENTER);
element.setNameVerticalAlign(VerticalAlign.MIDDLE);
assignNameCoordinates(element);
index++;
}
}
......@@ -402,6 +398,19 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
}
private void assignNameCoordinates(final Species element) {
element.setNameX(element.getX());
element.setNameY(element.getY());
element.setNameWidth(element.getWidth());
element.setNameHeight(element.getHeight());
element.setNameHorizontalAlign(HorizontalAlign.CENTER);
if (element instanceof Complex) {
element.setNameVerticalAlign(VerticalAlign.BOTTOM);
} else {
element.setNameVerticalAlign(VerticalAlign.MIDDLE);
}
}
private void modifyElementModificationResiduesLocation(final SpeciesWithModificationResidue element) {
for (final ModificationResidue mr : element.getModificationResidues()) {
List<Point2D> usedPoints = new ArrayList<>();
......@@ -416,7 +425,8 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
Point2D findFarthestEmptyPositionOnRectangle(final Rectangle2D border, final List<Point2D> usedPoints) {
// list of points reduced to one dimension
// every point is taking two position - so we can easier solve wrapping issues
// every point is taking two position - so we can easier solve wrapping
// issues
List<Double> linearizedPoints = new ArrayList<>();
// there are two "guarding" points at the beginning and the end
linearizedPoints.add(0.0);
......@@ -549,11 +559,10 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
}
protected void modifyComplexLocation(final Complex complex, final double centerX, final double centerY) {
if (complex.getWidth() == null || complex.getHeight() == null) {
computeComplexSize(complex);
}
computeComplexSize(complex);
double childWidth = SPECIES_WIDTH;
double childHeight = SPECIES_WIDTH;
double childHeight = SPECIES_HEIGHT;
for (final Species species : complex.getElements()) {
childWidth = Math.max(childWidth, species.getWidth());
childHeight = Math.max(childHeight, species.getHeight());
......@@ -567,8 +576,9 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
int y = 0;
for (final Species species : complex.getElements()) {
species.setX(complex.getX() + (COMPLEX_PADDING + childWidth) * x);
species.setY(complex.getY() + (COMPLEX_PADDING + childHeight) * y);
species.setX(complex.getX() + COMPLEX_PADDING + (COMPLEX_PADDING + childWidth) * x);
species.setY(complex.getY() + COMPLEX_PADDING + (COMPLEX_PADDING + childHeight) * y);
x++;