From 16eda45fdb5e115572fd64ee2af3332fd5c671b7 Mon Sep 17 00:00:00 2001 From: Dan Vasilescu Date: Thu, 24 Oct 2024 13:03:23 -0400 Subject: [PATCH] molecular details: compartments, axes, links, zoom --- .../MolecularStructuresPropertiesPanel.java | 29 +-- .../graph/SpeciesContextSpecLargeShape.java | 242 +++++++++++++----- .../vcell/mapping/SpeciesContextSpec.java | 32 ++- 3 files changed, 219 insertions(+), 84 deletions(-) diff --git a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java index b4e2dfd0ae..0c05dc7955 100644 --- a/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java +++ b/vcell-client/src/main/java/cbit/vcell/mapping/gui/MolecularStructuresPropertiesPanel.java @@ -79,9 +79,12 @@ private void initialize() { @Override public void paintComponent(Graphics g) { super.paintComponent(g); - if (scsls != null) { - scsls.paintSelf(g); + if (speciesContextSpec == null || speciesContextSpec.getSpeciesContext() == null) { + return; } + System.out.println(speciesContextSpec.getSpeciesContext().getName() + ": painting shape"); + scsls = new SpeciesContextSpecLargeShape(speciesContextSpec, shapePanel, speciesContextSpec, issueManager); + scsls.paintSelf(g); } @Override public DisplayMode getDisplayMode() { @@ -132,13 +135,14 @@ public void mouseMoved(MouseEvent e) { // shapePanel.setToolTipText("View-Only panel"); } }); + shapePanel.setPreferredSize(new Dimension(2000, 800)); shapePanel.setBackground(new Color(0xe0e0e0)); shapePanel.setZoomFactor(-2); shapePanel.setEditable(false); scrollPane = new JScrollPane(shapePanel); - scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); - scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // ----------------------------------------------------------------------------------------- @@ -171,7 +175,6 @@ public void mouseMoved(MouseEvent e) { gbc.insets = new Insets(4, 4, 4, 10); optionsPanel.add(new JLabel(""), gbc); - // ------------------------------------------------------------------------------------------ JPanel containerOfScrollPanel = new JPanel(); containerOfScrollPanel.setLayout(new BorderLayout()); @@ -182,9 +185,6 @@ public void mouseMoved(MouseEvent e) { add(containerOfScrollPanel, BorderLayout.CENTER); setBackground(Color.white); setName("MolecularStructuresPropertiesPanel"); - - - } catch (java.lang.Throwable ivjExc) { handleException(ivjExc); } @@ -197,18 +197,15 @@ private void updateInterface() { updateShape(); } - public static final int xOffsetInitial = 20; - public static final int yOffsetInitial = 10; private void updateShape() { if(speciesContextSpec == null || speciesContextSpec.getSpeciesContext() == null) { return; } - SpeciesPattern sp = speciesContextSpec.getSpeciesContext().getSpeciesPattern(); - System.out.println(sp.getNameShort()); - scsls = new SpeciesContextSpecLargeShape(xOffsetInitial, yOffsetInitial, -1, speciesContextSpec, shapePanel, speciesContextSpec, issueManager); -// -// Dimension preferredSize = new Dimension(spls.getRightEnd()+40, yOffsetInitial+80); -// shapePanel.setPreferredSize(preferredSize); + System.out.println(speciesContextSpec.getSpeciesContext().getName() + ": painting shape"); + scsls = new SpeciesContextSpecLargeShape(speciesContextSpec, shapePanel, speciesContextSpec, issueManager); + +// shapePanel.setPreferredSize(scsls.getMaxSize()); + shapePanel.repaint(); } diff --git a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java index a3eb0fb1d9..f2460cf660 100644 --- a/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java +++ b/vcell-core/src/main/java/cbit/vcell/graph/SpeciesContextSpecLargeShape.java @@ -8,6 +8,8 @@ import org.vcell.model.rbm.*; import org.vcell.util.Coordinate; import org.vcell.util.Displayable; +import org.vcell.util.Pair; +import org.vcell.util.springsalad.NamedColor; import java.awt.*; import java.util.*; @@ -15,15 +17,14 @@ public class SpeciesContextSpecLargeShape extends AbstractComponentShape implements HighlightableShapeInterface { - private static final double nmToPixelRatio = 20; - private static final double DEFAULT_UPPER_CORNER = 4; // default screen coordinates where we want to display the first site + private static final double NmToPixelRatio = 25; + private static final double DEFAULT_UPPER_CORNER = 3; // default screen coordinates where we want to display the first site private static final double DEFAULT_LEFT_CORNER = 10; // in nm - // x, y positions where we want to begin drawing the shape (pixels from top and left of painting area) - private int xPos = 0; // we use these and the sites coordinates to compute offset, to nicely center the molecule - private int yPos = 0; - private double x_offset; - private double y_offset; + // x, y positions where we want to begin drawing the shape (nm from top and left of painting area) + private double x_offset = DEFAULT_LEFT_CORNER; + private double y_offset = DEFAULT_UPPER_CORNER; + private double nmToPixelRatio = NmToPixelRatio; // private int nameOffset = 0; // offset upwards from yPos where we may write some text, like the expression of the sp @@ -47,23 +48,19 @@ public class SpeciesContextSpecLargeShape extends AbstractComponentShape impleme private boolean hasIntracellularSite = false; // we use these to compute some offset from top and left, so that the molecule will look nicely centered on screen - private double minX = 0; // coodrdinate of the leftmost site + private double minX = 0; // coordinate of the leftmost site private MolecularComponentPattern leftmostSite = null; private double minY = 0; // coordinate of the topmost site private MolecularComponentPattern topmostSite = null; // if more sites qualify we just keep the first we find + private double maxX = 0; // coordinate of the rightmost site + private double maxY = 0; // coordinate of the bottommost site - - public SpeciesContextSpecLargeShape(int xPos, int yPos, - int height, // we may not need this - SpeciesContextSpec scs, - LargeShapeCanvas shapePanel, Displayable owner, IssueListProvider issueListProvider) { + public SpeciesContextSpecLargeShape(SpeciesContextSpec scs, LargeShapeCanvas shapePanel, + Displayable owner, IssueListProvider issueListProvider) { super(issueListProvider); this.owner = owner; this.scs = scs; - this.xPos = xPos; - this.yPos = yPos; -// this.height = height; this.shapePanel = shapePanel; if(scs != null) { @@ -75,9 +72,11 @@ public SpeciesContextSpecLargeShape(int xPos, int yPos, } if(sp != null) { if(sp.getMolecularTypePatterns().size() != 1) { - throw new RuntimeException("Number of Molecules must be exactly 1"); + return; } this.mtp = sp.getMolecularTypePatterns().get(0); + } else { + return; } Map sasMap = scs.getSiteAttributesMap(); @@ -109,6 +108,8 @@ public SpeciesContextSpecLargeShape(int xPos, int yPos, minY = y; leftmostSite = mcp; topmostSite = mcp; + maxX = x; + maxY = y; } else { if(x < minX) { minX = x; @@ -118,6 +119,12 @@ public SpeciesContextSpecLargeShape(int xPos, int yPos, minY = y; topmostSite = mcp; } + if(x > maxX) { + maxX = x; + } + if(y > maxY) { + maxY = y; + } } counter++; } @@ -131,23 +138,36 @@ private boolean isPlanarYZ() { // we only show entities that are 2D in the YZ return true; // TODO: check } + public Dimension getMaxSize() { + int z = shapePanel.getZoomFactor(); + nmToPixelRatio = NmToPixelRatio + z; + int width = (int) (nmToPixelRatio * (x_offset+maxX+10)); + int height = (int) (nmToPixelRatio * (y_offset+maxY+6)); + return new Dimension(width, height); + } - private void paintDummy(Graphics g, int xPos, int yPos) { + private void paintDummy(Graphics g) { return; // implement later } private void paintCompartments(Graphics g) { + Graphics2D g2 = (Graphics2D)g; Color colorOld = g2.getColor(); Paint paintOld = g2.getPaint(); Font fontOld = g2.getFont(); + RenderingHints hintsOld = g2.getRenderingHints(); + Stroke strokeOld = g2.getStroke(); Font font; - int w; // width of compartment shape, adjusted continuously based on zoom factor - String name = structure.getName(); int z = shapePanel.getZoomFactor(); + nmToPixelRatio = NmToPixelRatio + z; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + String name = structure.getName(); if(z > -3) { font = fontOld.deriveFont(Font.BOLD); g.setFont(font); + } else { font = fontOld; g.setFont(font); @@ -166,105 +186,201 @@ private void paintCompartments(Graphics g) { x = membraneX+x_offset; y = 1; g2.drawString("Membrane Intracellular", - (int)(nmToPixelRatio * (x-membraneRadius)), // move it slightly to the left + (int)(nmToPixelRatio * x), +// (int)(nmToPixelRatio * (x-membraneRadius)), // move it slightly to the left (int)(nmToPixelRatio * y)); - g2.setColor(Color.gray); + g2.setColor(NamedColor.darker(Color.orange, 0.8)); + float thickness = 4.0f; + g2.setStroke(new BasicStroke(thickness)); g2.drawLine((int)(nmToPixelRatio * x), // centered on the Anchor (int)(nmToPixelRatio * (y+1)), (int)(nmToPixelRatio * x), - (int)(nmToPixelRatio * (y+6))); - + (int)(nmToPixelRatio * (maxY+y_offset+6))); } - - + g2.setStroke(strokeOld); + g2.setRenderingHints(hintsOld); g2.setFont(fontOld); g2.setPaint(paintOld); g2.setColor(colorOld); - - return; // implement later } + private void paintAxes(Graphics g) { - return; // implement later + + Graphics2D g2 = (Graphics2D) g; + Color colorOld = g2.getColor(); + Paint paintOld = g2.getPaint(); + Font fontOld = g2.getFont(); + RenderingHints hintsOld = g2.getRenderingHints(); + Stroke strokeOld = g2.getStroke(); + + Font font; + int z = shapePanel.getZoomFactor(); + nmToPixelRatio = NmToPixelRatio + z; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + if(z > -3) { + font = fontOld.deriveFont(Font.BOLD); + g.setFont(font); + } else { + font = fontOld; + g.setFont(font); + } + + int startX = 15; // coordinates for the arrow line (z-axis) + int startY = 15; + int endX = 60; + int endY = 15; + g2.setColor(Color.black); + g2.drawString("Z", endX+5, endY+3); // coord name + g2.setColor(Color.gray); + g2.drawLine(startX, startY, endX, endY); // draw the arrow line + int arrowSize = 10; // draw the arrow head + int[] xPoints = {endX, endX - arrowSize, endX - arrowSize}; + int[] yPoints = {endY, endY - arrowSize, endY + arrowSize}; + g2.fillPolygon(xPoints, yPoints, 3); + + startX = 15; // coordinates for the arrow line (Y-axis) + startY = 15; + endX = 15; + endY = 60; + g2.setColor(Color.black); + g2.drawString("Y", endX-2, endY+12); + g2.setColor(Color.gray); + g2.drawLine(startX, startY, endX, endY); + int[] xPoints2 = {endX, endX - arrowSize, endX + arrowSize}; + int[] yPoints2 = {endY, endY - arrowSize, endY - arrowSize}; + g2.fillPolygon(xPoints2, yPoints2, 3); + + startX = 15; // coordinates for the arrow line (Y-axis) + startY = 100; + endX = 15 + (int)nmToPixelRatio; + int offset = 3; + g2.setColor(Color.black); + float thickness = 2.0f; + g2.setStroke(new BasicStroke(thickness)); + g2.drawLine(startX, startY, endX, startY); + g2.drawLine(startX, startY-offset, startX, startY+offset); + g2.drawLine(endX, startY-offset, endX, startY+offset); + g2.setStroke(strokeOld); + g2.drawString("1 nm", endX+10, startY+offset); + + g2.setStroke(strokeOld); + g2.setRenderingHints(hintsOld); + g2.setFont(fontOld); + g2.setPaint(paintOld); + g2.setColor(colorOld); } - public void drawCenteredCircle(Graphics2D g, int x, int y, int r) { - x = x-(r/2); - y = y-(r/2); - g.fillOval(x,y,r,r); + public void drawCenteredCircle(Graphics g, NamedColor namedColor, double xd, double yd, double rd) { + + Graphics2D g2 = (Graphics2D)g; Color oldColor = g.getColor(); + RenderingHints hintsOld = g2.getRenderingHints(); + + int z = shapePanel.getZoomFactor(); + nmToPixelRatio = NmToPixelRatio + z; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + xd = xd-(rd/2); + yd = yd-(rd/2); + + int x = (int)(nmToPixelRatio * xd); + int y = (int)(nmToPixelRatio * yd); + int r = (int)(nmToPixelRatio * rd); + +// g.setColor(namedColor.darker(0.9)); + g.setColor(namedColor.getColor()); + g.fillOval(x,y,r,r); // colored circle + g.setColor(Color.black); - g.drawOval(x, y, r, r); + g.drawOval(x, y, r, r); // black contour + + g2.setRenderingHints(hintsOld); + g.setColor(oldColor); + } + + public void drawLink(Graphics g, double x1d, double y1d, double x2d, double y2d) { + + Graphics2D g2 = (Graphics2D)g; + Color oldColor = g.getColor(); + RenderingHints hintsOld = g2.getRenderingHints(); + Stroke strokeOld = g2.getStroke(); + + int z = shapePanel.getZoomFactor(); + nmToPixelRatio = NmToPixelRatio + z; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + int x1 = (int)(nmToPixelRatio * x1d); + int y1 = (int)(nmToPixelRatio * y1d); + int x2 = (int)(nmToPixelRatio * x2d); + int y2 = (int)(nmToPixelRatio * y2d); + + g.setColor(Color.gray); + float thickness = 1.4f; + g2.setStroke(new BasicStroke(thickness)); + g2.drawLine(x1, y1, x2, y2); + + g2.setStroke(strokeOld); + g2.setRenderingHints(hintsOld); g.setColor(oldColor); } - public void paintSelf(Graphics g, boolean bPaintContour) { + public void paintSelf(Graphics g, boolean bPaintContour) { if(isPlanarYZ() == false) { return; } - Graphics2D g2 = (Graphics2D) g; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - Color oldColor = g.getColor(); - paintCompartments(g); paintAxes(g); - if(mtp == null || mtp.getComponentPatternList().size() == 0) { // paint empty dummy - paintDummy(g, xPos, yPos); + paintDummy(g); + return; } - Map sasMap = scs.getSiteAttributesMap(); - MolecularType mt = mtp.getMolecularType(); + Set internalLinkSet = scs.getInternalLinkSet(); + for(MolecularInternalLinkSpec mils : internalLinkSet) { + Pair link = mils.getLink(); + SiteAttributesSpec sas1 = sasMap.get(link.one); + SiteAttributesSpec sas2 = sasMap.get(link.two); + double x1 = x_offset + sas1.getCoordinate().getZ(); + double x2 = x_offset + sas2.getCoordinate().getZ(); + double y1 = y_offset + sas1.getCoordinate().getY(); + double y2 = y_offset + sas2.getCoordinate().getY(); + drawLink(g, x1, y1, x2, y2); + } for(MolecularComponentPattern mcp : mtp.getComponentPatternList()) { -// for(Map.Entry entry : siteAttributesMap.entrySet()) { -// MolecularComponentPattern mcp = entry.getKey(); -// SiteAttributesSpec sas = entry.getValue(); - MolecularComponent mc = mcp.getMolecularComponent(); SiteAttributesSpec sas = sasMap.get(mcp); Coordinate coord = sas.getCoordinate(); double radius = sas.getRadius(); - Color color = sas.getColor().getColor(); - g.setColor(color); + NamedColor color = sas.getColor(); double x = x_offset + coord.getZ(); double y = y_offset + coord.getY(); - System.out.println("Painting site at " + x + ", " + y + " (nm)"); - drawCenteredCircle((Graphics2D)g, - (int)(nmToPixelRatio * x), // transform nm to pixels - (int)(nmToPixelRatio * y), - (int)(nmToPixelRatio * radius)); + drawCenteredCircle(g, color, x, y, radius); } - g.setColor(oldColor); } - - @Override public void paintSelf(Graphics g) { paintSelf(g, true); } - @Override public boolean isHighlighted() { return false; } - @Override public void setHighlight(boolean highlight, boolean param) { - } - @Override public void turnHighlightOffRecursive(Graphics g) { - } @Override public String getDisplayName() { return null; } - @Override public String getDisplayType() { return null; } + } diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java index 68d79daaa1..440cdb1400 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/SpeciesContextSpec.java @@ -59,6 +59,7 @@ import net.sourceforge.interval.ia_math.RealInterval; import org.vcell.util.springsalad.Colors; import org.vcell.util.springsalad.NamedColor; +import org.vcell.util.springsalad.GraphContinuity; @SuppressWarnings("serial") public class SpeciesContextSpec implements Matchable, ScopedSymbolTable, Serializable, SimulationContextEntity, IssueSource, @@ -1298,12 +1299,33 @@ public void gatherIssues(IssueContext issueContext, List issueVector){ return; } } - if(mcpList.size() > 1 && mcpList.size() > getInternalLinkSet().size() + 1){ - String msg = "Link chain within the molecule has at least one discontinuity."; - String tip = "One or more links are missing"; - issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING)); - return; + if(mcpList.size() > 1 && getInternalLinkSet().size() > 0) { + GraphContinuity.Graph graph = new GraphContinuity.Graph(mcpList.size()); + Map mcpMap = new LinkedHashMap<> (); + for(int i=0; i link = mils.getLink(); + int one = mcpMap.get(link.one); + int two = mcpMap.get(link.two); + graph.addEdge(one, two); + } + if(!graph.isConnected(GraphContinuity.Algorithm.DFS)) { // let's use DFS! + String msg = "Link chain within the molecule has at least one discontinuity."; + String tip = "One or more links are missing"; + issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING)); + return; + } } +// old way, imprecise! use graph above +// if(mcpList.size() > 1 && mcpList.size() > getInternalLinkSet().size() + 1){ +// String msg = "Link chain within the molecule has at least one discontinuity."; +// String tip = "One or more links are missing"; +// issueVector.add(new Issue(this, issueContext, IssueCategory.Identifiers, msg, tip, Issue.Severity.WARNING)); +// return; +// } for(MolecularInternalLinkSpec candidate : getInternalLinkSet()){ for(MolecularInternalLinkSpec other : getInternalLinkSet()){ if(candidate == other){