/* * EuroCarbDB, a framework for carbohydrate bioinformatics * * Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * A copy of this license accompanies this distribution in the file LICENSE.txt. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * Last commit: $Rev: 1930 $ by $Author: david@nixbioinf.org $ on $Date:: 2010-07-29 #$ */ package org.eurocarbdb.application.glycanbuilder; import java.util.*; import java.text.*; import java.io.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.awt.datatransfer.*; import java.awt.image.*; import javax.swing.*; import org.w3c.dom.*; import static org.eurocarbdb.application.glycanbuilder.Geometry.*; /** Objects of this class are used to create a graphical representation of a {@link Glycan} object given the current graphic options ({@link GraphicOptions}). The rules to draw the structures in the different notations are stored in the style dictionaries: {@link ResidueStyleDictionary}, {@link LinkageStyleDictionary} and {@link ResiduePlacementDictionary}. The classes {@link ResidueRenderer} and {@link LinkageRenderer} are used to draw the different parts of the structure. The graphical representation is created in three steps: first the position of each residue around the parent is computed using the rules on residue placements from the {@link ResiduePlacementDictionary} and stored in the {@link PositionManager}; second the bounding box of each residue in the structure is computed from its position and the parent's bounding box, and the values are stored in a {@link BBoxManager}; third the residues are drawn inside their bounding boxes and the linkages are drawn by connecting the centers of the bounding boxes. The output can be directed to a {@link Graphics2D} object or to an image. @author Alessio Ceroni (a.ceroni@imperial.ac.uk) */ public class GlycanRenderer { protected ResidueRenderer theResidueRenderer; protected LinkageRenderer theLinkageRenderer; // style protected ResiduePlacementDictionary theResiduePlacementDictionary; protected ResidueStyleDictionary theResidueStyleDictionary; protected LinkageStyleDictionary theLinkageStyleDictionary; protected GraphicOptions theGraphicOptions; /** Empty constructor. */ public GlycanRenderer() { theResiduePlacementDictionary = new ResiduePlacementDictionary(); theResidueStyleDictionary = new ResidueStyleDictionary(); theLinkageStyleDictionary = new LinkageStyleDictionary(); theResidueRenderer = new ResidueRenderer(this); theLinkageRenderer = new LinkageRenderer(this); } /** Copy constructor. All dictionaries and options are copied from the <code>src</code> object. */ public GlycanRenderer(GlycanRenderer src) { if( src==null ) { theResiduePlacementDictionary = new ResiduePlacementDictionary(); theResidueStyleDictionary = new ResidueStyleDictionary(); theLinkageStyleDictionary = new LinkageStyleDictionary(); theGraphicOptions = new GraphicOptions(); } else { theResiduePlacementDictionary = src.theResiduePlacementDictionary; theResidueStyleDictionary = src.theResidueStyleDictionary; theLinkageStyleDictionary = src.theLinkageStyleDictionary; theGraphicOptions = src.theGraphicOptions.clone(); } theResidueRenderer = new ResidueRenderer(this); theLinkageRenderer = new LinkageRenderer(this); } // --- /** Return the residue renderer used by this object. */ public ResidueRenderer getResidueRenderer() { return theResidueRenderer; } /** Set the residue renderer used by this object. */ public void setResidueRenderer(ResidueRenderer r) { theResidueRenderer = r; } /** Return the linkage renderer used by this object. */ public LinkageRenderer getLinkageRenderer() { return theLinkageRenderer; } /** Set the linkage renderer used by this object. */ public void setLinkageRenderer(LinkageRenderer r) { theLinkageRenderer = r; } /** Return the graphic options used by this object. */ public GraphicOptions getGraphicOptions() { return theGraphicOptions; } /** Set the graphic options used by this object. */ public void setGraphicOptions(GraphicOptions opt) { theGraphicOptions = opt; theResidueRenderer.setGraphicOptions(theGraphicOptions); theLinkageRenderer.setGraphicOptions(theGraphicOptions); } /** Return the residue placement dictionary used by this object. */ public ResiduePlacementDictionary getResiduePlacementDictionary() { return theResiduePlacementDictionary; } /** Set the residue placement dictionary used by this object. */ public void setResiduePlacementDictionary(ResiduePlacementDictionary residuePlacementDictionary) { theResiduePlacementDictionary = residuePlacementDictionary; } /** Return the residue style dictionary used by this object. */ public ResidueStyleDictionary getResidueStyleDictionary() { return theResidueStyleDictionary; } /** Set the residue style dictionary used by this object. */ public void setResidueStyleDictionary(ResidueStyleDictionary residueStyleDictionary) { theResidueStyleDictionary = residueStyleDictionary; theResidueRenderer.setResidueStyleDictionary(theResidueStyleDictionary); } /** Return the linkage style dictionary used by this object. */ public LinkageStyleDictionary getLinkageStyleDictionary() { return theLinkageStyleDictionary; } /** Set the linkage style dictionary used by this object. */ public void setLinkageStyleDictionary(LinkageStyleDictionary linkageStyleDictionary) { theLinkageStyleDictionary = linkageStyleDictionary; theLinkageRenderer.setLinkageStyleDictionary(theLinkageStyleDictionary); } //----------------- // Painting /** Draw a glycan structure on a graphics context using the calculated bounding boxes. @param g2d the graphic context @param structure the glycan structure to be drawn @param selected_residues the set of residues that must be shown as selected @param selected_linkages the set of linkages that must be shown as selected @param show_mass <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the reducing end marker should be displayed @param posManager the object used to spatially arrange the residues @param bboxManager the object used to store the residue bounding boxes */ public void paint(Graphics2D g2d, Glycan structure, HashSet<Residue> selected_residues, HashSet<Linkage> selected_linkages, boolean show_mass, boolean show_redend, PositionManager posManager, BBoxManager bboxManager) { paint(g2d,structure,selected_residues,selected_linkages,null,show_mass,show_redend,posManager,bboxManager); } /** Draw a glycan structure on a graphics context using the calculated bounding boxes. @param g2d the graphic context @param structure the glycan structure to be drawn @param selected_residues the set of residues that must be shown as selected @param selected_linkages the set of linkages that must be shown as selected @param active_residues the set of residues that are active, all the others will be displayed with less bright colors @param show_mass <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the reducing end marker should be displayed @param posManager the object used to spatially arrange the residues @param bboxManager the object used to store the residue bounding boxes */ public void paint(Graphics2D g2d, Glycan structure, HashSet<Residue> selected_residues, HashSet<Linkage> selected_linkages, Collection<Residue> active_residues, boolean show_mass, boolean show_redend, PositionManager posManager, BBoxManager bboxManager) { if( structure==null || structure.isEmpty() ) return; selected_residues = (selected_residues!=null) ?selected_residues :new HashSet<Residue>(); selected_linkages = (selected_linkages!=null) ?selected_linkages :new HashSet<Linkage>(); if( structure.isComposition() ) paintComposition(g2d,structure.getRoot(),structure.getBracket(),selected_residues,posManager,bboxManager); else { paintResidue(g2d,structure.getRoot(show_redend),selected_residues,selected_linkages,active_residues,posManager,bboxManager); paintBracket(g2d,structure.getBracket(),selected_residues,selected_linkages,active_residues,posManager,bboxManager); } if( show_mass ) displayMass(g2d,structure,show_redend,bboxManager); } protected void displayMass(Graphics2D g2d, Glycan structure, boolean show_redend, BBoxManager bboxManager) { Rectangle structure_all_bbox = bboxManager.getComplete(structure.getRoot(show_redend)); g2d.setColor(Color.black); g2d.setFont(new Font(theGraphicOptions.MASS_TEXT_FONT_FACE,Font.PLAIN,theGraphicOptions.MASS_TEXT_SIZE)); String text = getMassText(structure); g2d.drawString(text, Geometry.left(structure_all_bbox), Geometry.bottom(structure_all_bbox)+theGraphicOptions.MASS_TEXT_SPACE+theGraphicOptions.MASS_TEXT_SIZE); } private String getMassText(Glycan structure) { StringBuilder sb = new StringBuilder(); DecimalFormat df = new DecimalFormat("0.0000"); double mz = structure.computeMZ(); sb.append("m/z: "); if( mz<0. ) sb.append("???"); else sb.append(df.format(mz)); sb.append(" ["); sb.append(structure.getMassOptions().toString()); sb.append("]"); return sb.toString(); } private void paintComposition(Graphics2D g2d, Residue root, Residue bracket, HashSet<Residue> selected_residues, PositionManager posManager, BBoxManager bboxManager) { ResAngle orientation = posManager.getOrientation(root); String text = makeCompositionText(root,bracket,orientation,true); Rectangle text_rect = bboxManager.getCurrent(bracket); // draw selected contour if( selected_residues.contains(bracket) ) { float[] dashes = {5.f,5.f}; g2d.setStroke(new BasicStroke(2.f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND,1.f,dashes,0.f)); g2d.setColor(Color.black); g2d.draw(text_rect); g2d.setStroke(new BasicStroke(1)); } Font font = new Font(theGraphicOptions.COMPOSITION_FONT_FACE,Font.PLAIN,theGraphicOptions.COMPOSITION_FONT_SIZE); StyledTextCellRenderer stcr = new StyledTextCellRenderer(false); stcr.getRendererComponent(font,Color.black,Color.white,text); BufferedImage img = SVGUtils.getImage(stcr,false); if( orientation.equals(0) || orientation.equals(180) ) g2d.drawImage(img,null,text_rect.x,text_rect.y); else { g2d.rotate(-Math.PI/2.0); g2d.drawImage(img,null,-text_rect.y-text_rect.height,text_rect.x); g2d.rotate(Math.PI/2.0); } } private void paintResidue(Graphics2D g2d, Residue node, HashSet<Residue> selected_residues, HashSet<Linkage> selected_linkages, Collection<Residue> active_residues, PositionManager posManager, BBoxManager bboxManager) { if( node==null ) return; Rectangle parent_bbox = bboxManager.getParent(node); Rectangle node_bbox = bboxManager.getCurrent(node); Rectangle border_bbox = bboxManager.getBorder(node); Rectangle support_bbox = bboxManager.getSupport(node); if( node_bbox==null ) // not shown return; // paint edges for(Linkage link : node.getChildrenLinkages() ) { Residue child = link.getChildResidue(); Rectangle child_bbox = bboxManager.getCurrent(child); Rectangle child_border_bbox = bboxManager.getBorder(child); if( child_bbox!=null && !posManager.isOnBorder(child) ) { boolean selected = (selected_residues.contains(node) && selected_residues.contains(child)) || selected_linkages.contains(link); boolean active = (active_residues==null || (active_residues.contains(node) && active_residues.contains(child))); theLinkageRenderer.paintEdge(g2d,link,selected,node_bbox,border_bbox,child_bbox,child_border_bbox); } } // paint node boolean selected = selected_residues.contains(node); boolean active = (active_residues==null || active_residues.contains(node)); theResidueRenderer.paint(g2d,node,selected,active,posManager.isOnBorder(node),parent_bbox,node_bbox,support_bbox,posManager.getOrientation(node)); // paint children for(Linkage link : node.getChildrenLinkages() ) paintResidue(g2d,link.getChildResidue(),selected_residues,selected_linkages,active_residues,posManager,bboxManager); // paint info for(Linkage link : node.getChildrenLinkages() ) { Residue child = link.getChildResidue(); Rectangle child_bbox = bboxManager.getCurrent(child); Rectangle child_border_bbox = bboxManager.getBorder(child); if( child_bbox!=null && !posManager.isOnBorder(child) ) theLinkageRenderer.paintInfo(g2d,link,node_bbox,border_bbox,child_bbox,child_border_bbox); } } private void paintBracket(Graphics2D g2d, Residue bracket, HashSet<Residue> selected_residues, HashSet<Linkage> selected_linkages, Collection<Residue> active_residues, PositionManager posManager, BBoxManager bboxManager) { if( bracket==null ) return; Rectangle parent_bbox = bboxManager.getParent(bracket); Rectangle bracket_bbox = bboxManager.getCurrent(bracket); Rectangle support_bbox = bboxManager.getSupport(bracket); // paint bracket boolean selected = selected_residues.contains(bracket); boolean active = (active_residues==null || active_residues.contains(bracket)); theResidueRenderer.paint(g2d,bracket,selected,active,false,parent_bbox,bracket_bbox,support_bbox,posManager.getOrientation(bracket)); // paint antennae for( Linkage link : bracket.getChildrenLinkages() ) { Residue child = link.getChildResidue(); int quantity = bboxManager.getLinkedResidues(child).size()+1; Rectangle node_bbox = bboxManager.getParent(child); Rectangle child_bbox = bboxManager.getCurrent(child); Rectangle child_border_bbox = bboxManager.getBorder(child); if( child_bbox!=null ) { // paint edge if( !posManager.isOnBorder(child) ) { selected = (selected_residues.contains(bracket) && selected_residues.contains(child)) || selected_linkages.contains(link); active = (active_residues==null || (active_residues.contains(bracket) && active_residues.contains(child))); theLinkageRenderer.paintEdge(g2d,link,selected,node_bbox,node_bbox,child_bbox,child_border_bbox); } // paint child paintResidue(g2d,child,selected_residues,selected_linkages,active_residues,posManager,bboxManager); // paint info if( !posManager.isOnBorder(child) ) theLinkageRenderer.paintInfo(g2d,link,node_bbox,node_bbox,child_bbox,child_border_bbox); // paint quantity if( quantity>1 ) paintQuantity(g2d,child,quantity,bboxManager); } } } protected void paintQuantity(Graphics2D g2d, Residue antenna, int quantity, BBoxManager bboxManager) { ResAngle orientation = theGraphicOptions.getOrientationAngle(); // get dimensions String text; if( orientation.equals(0) || orientation.equals(-90)) text = "x" + quantity; else text = quantity + "x"; Dimension text_dim = textBounds(text,theGraphicOptions.NODE_FONT_FACE,theGraphicOptions.NODE_FONT_SIZE); // retrieve bounding box Rectangle text_rect = null; Rectangle antenna_bbox = bboxManager.getComplete(antenna); if( orientation.equals(0) ) text_rect = new Rectangle(right(antenna_bbox)+3,midy(antenna_bbox)-1-text_dim.height/2,text_dim.width,text_dim.height); // left to right else if( orientation.equals(180) ) text_rect = new Rectangle(left(antenna_bbox)-3-text_dim.width,midy(antenna_bbox)-1-text_dim.height/2,text_dim.width,text_dim.height); // right to left else if( orientation.equals(90) ) text_rect = new Rectangle(midx(antenna_bbox)-text_dim.height/2,bottom(antenna_bbox)+3,text_dim.height,text_dim.width); // top to bottom else text_rect = new Rectangle(midx(antenna_bbox)-text_dim.height/2,top(antenna_bbox)-3-text_dim.width,text_dim.height,text_dim.width); // bottom to top // paint text g2d.setColor(Color.black); g2d.setFont(new Font(theGraphicOptions.NODE_FONT_FACE,Font.PLAIN,theGraphicOptions.NODE_FONT_SIZE)); if( orientation.equals(0) || orientation.equals(180) ) g2d.drawString(text,left(text_rect),bottom(text_rect)); else { g2d.rotate(-Math.PI/2.0); g2d.drawString(text,-bottom(text_rect),right(text_rect)); //g2d.drawString(text,-(int)(text_rect.y+text_rect.height),(int)(text_rect.x+text_rect.width)); g2d.rotate(+Math.PI/2.0); } } //----------------- // Positioning /** Add the margins to a structure bounding box. */ public Dimension computeSize(Rectangle all_bbox) { if( all_bbox==null || all_bbox.width==0 || all_bbox.height==0 ) return new Dimension(1,1); //return new Dimension(theGraphicOptions.MARGIN_LEFT+theGraphicOptions.MARGIN_RIGHT,theGraphicOptions.MARGIN_TOP+theGraphicOptions.MARGIN_BOTTOM); return new Dimension(theGraphicOptions.MARGIN_LEFT+all_bbox.width+theGraphicOptions.MARGIN_RIGHT,theGraphicOptions.MARGIN_TOP+all_bbox.height+theGraphicOptions.MARGIN_BOTTOM); } /** Compute the residue bounding boxes for a set of structures. @param structures the list of structures to be displayed @param show_masses <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the reducing end marker should be displayed @param posManager the object used to spatially arrange the residues @param bboxManager the object used to store the residue bounding boxes */ public Rectangle computeBoundingBoxes(Collection<Glycan> structures, boolean show_masses, boolean show_redend, PositionManager posManager, BBoxManager bboxManager) { return computeBoundingBoxes(structures,show_masses,show_redend,posManager,bboxManager,true); } /** Compute the residue bounding boxes for a set of structures. @param structures the list of structures to be displayed @param show_masses <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the reducing end marker should be displayed @param posManager the object used to spatially arrange the residues @param bboxManager the object used to store the residue bounding boxes @param reset <code>true</code> if the bounding boxes manager should be re-initialized */ public Rectangle computeBoundingBoxes(Collection<Glycan> structures, boolean show_masses, boolean show_redend, PositionManager posManager, BBoxManager bboxManager, boolean reset) { if( reset ) { // init bboxes posManager.reset(); bboxManager.reset(); } // compute bounding boxes; Rectangle all_bbox = new Rectangle(theGraphicOptions.MARGIN_TOP,theGraphicOptions.MARGIN_LEFT,0,0); int cur_top = theGraphicOptions.MARGIN_TOP; for( Iterator<Glycan> i=structures.iterator(); i.hasNext(); ) { // compute glycan bbox Rectangle glycan_bbox = computeBoundingBoxes(i.next(),theGraphicOptions.MARGIN_LEFT,cur_top,show_masses,show_redend,posManager,bboxManager); all_bbox = Geometry.union(all_bbox,glycan_bbox); cur_top = Geometry.bottom(all_bbox) + theGraphicOptions.STRUCTURES_SPACE; } return all_bbox; } /** Compute the residue bounding boxes for a single structures. @param structure the structure to be displayed @param cur_left the left position where to display the structure @param cur_top the top position where to display the structure @param show_mass <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the reducing end marker should be displayed @param posManager the object used to spatially arrange the residues @param bboxManager the object used to store the residue bounding boxes */ public Rectangle computeBoundingBoxes(Glycan structure, int cur_left, int cur_top, boolean show_mass, boolean show_redend, PositionManager posManager, BBoxManager bboxManager) { if( structure==null ) return new Rectangle(cur_left,cur_top,0,0); try { bboxManager.setGraphicOptions(theGraphicOptions); if( !structure.isEmpty() ) { Residue root,bracket; ResAngle orientation = theGraphicOptions.getOrientationAngle(); if( structure.isComposition() ) { root = structure.getRoot(); bracket = structure.getBracket(); // assign positions assignPositionComposition(root,posManager); assignPositionComposition(bracket,posManager); // compute bounding boxes computeBoundingBoxesComposition(root,bracket,posManager,bboxManager); } else { root = structure.getRoot(show_redend); bracket = structure.getBracket(); // assign positions posManager.add(root,new ResAngle(),orientation,false,true); assignPosition(root,false,orientation,root,posManager); posManager.add(bracket,new ResAngle(),orientation,false,true); assignPosition(bracket,false,orientation,bracket,posManager); // compute bounding boxes computeBoundingBoxes(root,posManager,bboxManager); computeBoundingBoxesBracket(bracket,root,theGraphicOptions.COLLAPSE_MULTIPLE_ANTENNAE,posManager,bboxManager); } // add bracket bbox Rectangle bbox = union(bboxManager.getComplete(root),bboxManager.getComplete(bracket)); bboxManager.setComplete(root,bbox); // translate if necessary bboxManager.translate(cur_left-bbox.x,cur_top-bbox.y,root); bboxManager.translate(cur_left-bbox.x,cur_top-bbox.y,bracket); bbox.translate(cur_left-bbox.x,cur_top-bbox.y); // add masses if( show_mass ) { Dimension d = textBounds(getMassText(structure),theGraphicOptions.MASS_TEXT_FONT_FACE,theGraphicOptions.MASS_TEXT_SIZE); Rectangle text_bbox = new Rectangle(cur_left,bottom(bbox)+theGraphicOptions.MASS_TEXT_SPACE,d.width,d.height); bbox = union(bbox,text_bbox); } return bbox; } } catch(Exception e) { LogUtils.report(e); } return new Rectangle(cur_left,cur_top,0,0); } /** Compute the positions of each residue and store them in the position manager. */ public void assignPositions(Glycan structure, PositionManager posManager) { if( structure==null ) return; try { ResAngle orientation = theGraphicOptions.getOrientationAngle(); Residue root = structure.getRoot(true); Residue bracket = structure.getBracket(); posManager.add(root,new ResAngle(),orientation,false,true); assignPosition(root,false,orientation,root,posManager); posManager.add(bracket,new ResAngle(),orientation,false,true); assignPosition(bracket,false,orientation,bracket,posManager); } catch(Exception e) { LogUtils.report(e); } } private void assignPositionComposition(Residue current, PositionManager posManager) throws Exception { if( current==null ) return; posManager.add(current,theGraphicOptions.getOrientationAngle(),new ResAngle(),false,false); for( Linkage l : current.getChildrenLinkages() ) assignPositionComposition(l.getChildResidue(),posManager); } private void assignPosition(Residue current, boolean sticky, ResAngle orientation, Residue turning_point, PositionManager posManager) throws Exception { if( current==null ) return; // init positions BookingManager bookManager = new BookingManager(posManager.getAvailablePositions(current,orientation)); // add children to the booking manager for(Iterator<Linkage> i=current.iterator(); i.hasNext(); ) { Linkage link = i.next(); Residue child = link.getChildResidue(); Residue matching_child = (child.getCleavedResidue()!=null) ?child.getCleavedResidue() :child; // get placement ResiduePlacement placement = matching_child.getPreferredPlacement(); if( placement==null || (!current.isSaccharide() && !current.isBracket()) || !bookManager.isAvailable(placement) ) placement = theResiduePlacementDictionary.getPlacement(current,link,matching_child,sticky); // set placement bookManager.add(child,placement); } // place children bookManager.place(); // store positions for(Iterator<Linkage> i=current.iterator(); i.hasNext(); ) { Residue child = i.next().getChildResidue(); ResiduePlacement child_placement = bookManager.getPlacement(child); ResAngle child_pos = bookManager.getPosition(child); posManager.add(child,orientation,child_pos,child_placement.isOnBorder(),child_placement.isSticky()); ResAngle child_orientation = posManager.getOrientation(child); Residue child_turning_point = (child_orientation.equals(orientation)) ?turning_point :child; assignPosition(child,child_placement.isSticky(),child_orientation,child_turning_point,posManager); } } //---------------- // Bounding boxe /** Return a string representation of the composition of a glycan structure @param styled <code>true</code> if the returned text is displayed in a {@link StyledTextCellRenderer} */ public String makeCompositionText(Glycan g, boolean styled) { return makeCompositionText(g,theGraphicOptions.getOrientationAngle(theGraphicOptions.RL),styled); } /** Return a string representation of the composition of a glycan structure */ static public String makeCompositionTextPlain(Glycan g) { if( !g.isComposition() ) g = g.getComposition(); return makeCompositionText(g.getRoot(),g.getBracket(),new ResAngle(0),false); } /** Return a string representation of the composition of a glycan structure @param orientation the orientation at which the text will be displayed @param styled <code>true</code> if the returned text is displayed in a {@link StyledTextCellRenderer} */ static public String makeCompositionText(Glycan g, ResAngle orientation, boolean styled) { if( !g.isComposition() ) g = g.getComposition(); return makeCompositionText(g.getRoot(),g.getBracket(),orientation,styled); } static private String makeCompositionText(Residue root, Residue bracket, ResAngle orientation, boolean styled) { Vector<String> cleavages = new Vector<String>(); TreeMap<String,Integer> residues = new TreeMap<String,Integer>(); // get components for( Linkage l : bracket.getChildrenLinkages() ) { Residue r = l.getChildResidue(); if( r.isCleavage() ) { if( !r.getType().isLCleavage() ) cleavages.add( r.getCleavageType() ); } else { String type = r.getResidueName(); Integer old_num = residues.get(type); if( old_num==null ) residues.put(type,1); else residues.put(type,old_num+1); } } // build name StringBuilder text = new StringBuilder(); // write left/top part if( orientation.equals(180) || orientation.equals(90) ) { if( cleavages.size()>0 ) { for( String s : cleavages ) text.append(s); text.append('-'); } } else { if( !root.getTypeName().equals("freeEnd") ) { if( root.isCleavage() ) text.append(root.getCleavageType()); else text.append(root.getResidueName()); text.append('-'); } } // write middle for( Map.Entry<String,Integer> e : residues.entrySet() ) { text.append(e.getKey()); if( styled ) { text.append("_{"); text.append(e.getValue()); text.append('}'); } else text.append(e.getValue()); } // write right/bottom part if( orientation.equals(180) || orientation.equals(90) ) { if( !root.getTypeName().equals("freeEnd") ) { text.append('-'); if( root.isCleavage() ) text.append(root.getCleavageType()); else text.append(root.getResidueName()); } } else { if( cleavages.size()>0 ) { text.append('-'); for( String s : cleavages ) text.append(s); } } return text.toString(); } private void computeBoundingBoxesComposition(Residue root, Residue bracket, PositionManager posManager, BBoxManager bboxManager) { ResAngle orientation = posManager.getOrientation(root); String text = makeCompositionText(root,bracket,orientation,true); Font font = new Font(theGraphicOptions.COMPOSITION_FONT_FACE,Font.PLAIN,theGraphicOptions.COMPOSITION_FONT_SIZE); StyledTextCellRenderer stcr = new StyledTextCellRenderer(false); stcr.getRendererComponent(font,Color.black,Color.white,text); Dimension d = stcr.getPreferredSize(); if( orientation.equals(0) || orientation.equals(180) ) bboxManager.setAllBBoxes(bracket,new Rectangle(0,0,d.width,d.height)); else bboxManager.setAllBBoxes(bracket,new Rectangle(0,0,d.height,d.width)); bboxManager.linkSubtree(bracket,root); bboxManager.linkSubtree(bracket,bracket); } private void computeBoundingBoxes(Residue node, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( node==null ) return; // compute all bboxes ResAngle orientation = posManager.getOrientation(node); if( orientation.equals(0) ) computeBoundingBoxesLR(node,posManager,bboxManager); else if( orientation.equals(180) ) computeBoundingBoxesRL(node,posManager,bboxManager); else if( orientation.equals(90) ) computeBoundingBoxesTB(node,posManager,bboxManager); else if( orientation.equals(-90) ) computeBoundingBoxesBT(node,posManager,bboxManager); else throw new Exception("Invalid orientation " + orientation + " at node " + node.id); } private void computeBoundingBoxesLR(Residue node, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( node==null ) return; Rectangle node_bbox = theResidueRenderer.computeBoundingBox(node,posManager.isOnBorder(node),0,0,posManager.getOrientation(node),theGraphicOptions.NODE_SIZE,Integer.MAX_VALUE); bboxManager.setAllBBoxes(node,node_bbox); //----------------- // place substituents Vector<Residue> region_m90b = posManager.getChildrenAtPosition(node,new ResAngle(-90),true); for( int i=0; i<region_m90b.size(); i++ ) { computeBoundingBoxes(region_m90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignBottomsOnRight(region_m90b.subList(0,i),region_m90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } Vector<Residue> region_p90b = posManager.getChildrenAtPosition(node,new ResAngle(90),true); for( int i=0; i<region_p90b.size(); i++ ) { computeBoundingBoxes(region_p90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignTopsOnLeft(region_p90b.subList(0,i),region_p90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } // ---------------- // place positions // position -90 (top) Vector<Residue> region_m90 = posManager.getChildrenAtPosition(node,new ResAngle(-90),false); for( int i=0; i<region_m90.size(); i++ ) { computeBoundingBoxes(region_m90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignBottomsOnRight(region_m90.subList(0,i),region_m90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position +90 (bottom) Vector<Residue> region_p90 = posManager.getChildrenAtPosition(node,new ResAngle(90),false); for( int i=0; i<region_p90.size(); i++ ) { computeBoundingBoxes(region_p90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignTopsOnLeft(region_p90.subList(0,i),region_p90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 0 (right) Vector<Residue> region_0 = posManager.getChildrenAtPosition(node,new ResAngle(0)); for( int i=0; i<region_0.size(); i++ ) { computeBoundingBoxes(region_0.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignLeftsOnBottom(region_0.subList(0,i),region_0.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position -45 (top right) Vector<Residue> region_m45 = posManager.getChildrenAtPosition(node,new ResAngle(-45)); for( int i=0; i<region_m45.size(); i++ ) { computeBoundingBoxes(region_m45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignLeftsOnBottom(region_m45.subList(0,i),region_m45.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 45 (bottom right) Vector<Residue> region_p45 = posManager.getChildrenAtPosition(node,new ResAngle(45)); for( int i=0; i<region_p45.size(); i++ ) { computeBoundingBoxes(region_p45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignLeftsOnBottom(region_p45.subList(0,i),region_p45.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // ---------------- // align substituents with node bboxManager.alignCentersOnTop(node_bbox,region_m90b,theGraphicOptions.NODE_SUB_SPACE); bboxManager.alignCentersOnBottom(node_bbox,region_p90b,theGraphicOptions.NODE_SUB_SPACE); Union<Residue> border_nodes = new Union<Residue>(region_m90b).and(region_p90b); Rectangle border_bbox = bboxManager.getComplete(border_nodes.and(node)); // align position 0,45,-45 with node to not clash with 90b,-90b if( region_0.size()>0 ) { bboxManager.alignLeftsOnTop(region_0,region_m45,theGraphicOptions.NODE_SPACE); bboxManager.alignLeftsOnBottom(region_0,new Union<Residue>(region_0).and(region_m45),region_p45,region_p45,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnRight(node_bbox,border_nodes,region_0,new Union<Residue>(region_0).and(region_m45).and(region_p45),theGraphicOptions.NODE_SPACE); } else if( region_m45.size()==0 ) bboxManager.alignCornersOnRightAtBottom(node_bbox,border_nodes,region_p45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else if( region_p45.size()==0 ) bboxManager.alignCornersOnRightAtTop(node_bbox,border_nodes,region_m45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else bboxManager.alignSymmetricOnRight(node_bbox,border_nodes,region_m45,region_p45,theGraphicOptions.NODE_SPACE,2*theGraphicOptions.NODE_SPACE+theGraphicOptions.NODE_SIZE); // align positions -90 and 90 with node to not clash with the rest Union<Residue> ex_nodes = new Union<Residue>(region_0).and(region_m45).and(region_p45).and(border_nodes); bboxManager.alignCentersOnTop(node_bbox,ex_nodes,region_m90,region_m90,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnBottom(node_bbox,ex_nodes,region_p90,region_p90,theGraphicOptions.NODE_SPACE); // ---------------- // set rectangles Union<Residue> all_nodes = ex_nodes.and(region_m90).and(region_p90).and(node); bboxManager.setCurrent(node,node_bbox); bboxManager.setBorder(node,border_bbox); bboxManager.setComplete(node,bboxManager.getComplete(all_nodes)); if( node.hasChildren() ) bboxManager.setSupport(node,new Rectangle(midx(node_bbox)+theGraphicOptions.NODE_SPACE+theGraphicOptions.NODE_SIZE,midy(node_bbox),0,0)); for(Iterator<Linkage> i=node.iterator(); i.hasNext(); ) bboxManager.setParent(i.next().getChildResidue(),node_bbox); } private void computeBoundingBoxesRL(Residue node, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( node==null ) return; Rectangle node_bbox = theResidueRenderer.computeBoundingBox(node,posManager.isOnBorder(node),0,0,posManager.getOrientation(node),theGraphicOptions.NODE_SIZE,Integer.MAX_VALUE); bboxManager.setAllBBoxes(node,node_bbox); //----------------- // place substituents // -90 border (bottom) Vector<Residue> region_m90b = posManager.getChildrenAtPosition(node,new ResAngle(-90),true); for( int i=0; i<region_m90b.size(); i++ ) { computeBoundingBoxes(region_m90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignTopsOnLeft(region_m90b.subList(0,i),region_m90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } // +90 border (top) Vector<Residue> region_p90b = posManager.getChildrenAtPosition(node,new ResAngle(90),true); for( int i=0; i<region_p90b.size(); i++ ) { computeBoundingBoxes(region_p90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignBottomsOnRight(region_p90b.subList(0,i),region_p90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } // ---------------- // place positions // position -90 (bottom) Vector<Residue> region_m90 = posManager.getChildrenAtPosition(node,new ResAngle(-90),false); for( int i=0; i<region_m90.size(); i++ ) { computeBoundingBoxes(region_m90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignTopsOnLeft(region_m90.subList(0,i),region_m90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position +90 (top) Vector<Residue> region_p90 = posManager.getChildrenAtPosition(node,new ResAngle(90),false); for( int i=0; i<region_p90.size(); i++ ) { computeBoundingBoxes(region_p90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignBottomsOnRight(region_p90.subList(0,i),region_p90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 0 (left) Vector<Residue> region_0 = posManager.getChildrenAtPosition(node,new ResAngle(0),false); for( int i=0; i<region_0.size(); i++ ) { computeBoundingBoxes(region_0.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignRightsOnTop(region_0.subList(0,i),region_0.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position -45 (bottom left) Vector<Residue> region_m45 = posManager.getChildrenAtPosition(node,new ResAngle(-45),false); for( int i=0; i<region_m45.size(); i++ ) { computeBoundingBoxes(region_m45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignRightsOnTop(region_m45.subList(0,i),region_m45.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 45 (top left) Vector<Residue> region_p45 = posManager.getChildrenAtPosition(node,new ResAngle(45),false); for( int i=0; i<region_p45.size(); i++ ) { computeBoundingBoxes(region_p45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignRightsOnTop(region_p45.subList(0,i),region_p45.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // ---------------- // align substituents with node bboxManager.alignCentersOnBottom(node_bbox,region_m90b,theGraphicOptions.NODE_SUB_SPACE); bboxManager.alignCentersOnTop(node_bbox,region_p90b,theGraphicOptions.NODE_SUB_SPACE); Union<Residue> border_nodes = new Union<Residue>(region_m90b).and(region_p90b); Rectangle border_bbox = bboxManager.getComplete(border_nodes.and(node)); // align position 0,45,-45 with node to not clash with 90b,-90b if( region_0.size()>0 ) { bboxManager.alignRightsOnBottom(region_0,region_m45,theGraphicOptions.NODE_SPACE); bboxManager.alignRightsOnTop(region_0,new Union<Residue>(region_0).and(region_m45),region_p45,region_p45,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnLeft(node_bbox,border_nodes,region_0,new Union<Residue>(region_0).and(region_m45).and(region_p45),theGraphicOptions.NODE_SPACE); } else if( region_m45.size()==0 ) bboxManager.alignCornersOnLeftAtTop(node_bbox,border_nodes,region_p45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else if( region_p45.size()==0 ) bboxManager.alignCornersOnLeftAtBottom(node_bbox,border_nodes,region_m45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else bboxManager.alignSymmetricOnLeft(node_bbox,border_nodes,region_p45,region_m45,theGraphicOptions.NODE_SPACE,2*theGraphicOptions.NODE_SPACE+theGraphicOptions.NODE_SIZE); // align positions -90 and 90 with node to not clash with the rest Union<Residue> ex_nodes = new Union<Residue>(region_0).and(region_m45).and(region_p45).and(border_nodes); bboxManager.alignCentersOnBottom(node_bbox,ex_nodes,region_m90,region_m90,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnTop(node_bbox,ex_nodes,region_p90,region_p90,theGraphicOptions.NODE_SPACE); // ---------------- // set rectangles Union<Residue> all_nodes = ex_nodes.and(region_m90).and(region_p90).and(node); bboxManager.setCurrent(node,node_bbox); bboxManager.setBorder(node,border_bbox); bboxManager.setComplete(node,bboxManager.getComplete(all_nodes)); if( node.hasChildren() ) bboxManager.setSupport(node,new Rectangle(midx(node_bbox)-theGraphicOptions.NODE_SPACE-theGraphicOptions.NODE_SIZE,midy(node_bbox),0,0)); for(Iterator<Linkage> i=node.iterator(); i.hasNext(); ) bboxManager.setParent(i.next().getChildResidue(),node_bbox); } private void computeBoundingBoxesTB(Residue node, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( node==null ) return; Rectangle node_bbox = theResidueRenderer.computeBoundingBox(node,posManager.isOnBorder(node),0,0,posManager.getOrientation(node),theGraphicOptions.NODE_SIZE,Integer.MAX_VALUE); bboxManager.setAllBBoxes(node,node_bbox); //----------------- // place substituents // -90 border (right) Vector<Residue> region_m90b = posManager.getChildrenAtPosition(node,new ResAngle(-90),true); for( int i=0; i<region_m90b.size(); i++ ) { computeBoundingBoxes(region_m90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignLeftsOnBottom(region_m90b.subList(0,i),region_m90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } // +90 border (left) Vector<Residue> region_p90b = posManager.getChildrenAtPosition(node,new ResAngle(90),true); for( int i=0; i<region_p90b.size(); i++ ) { computeBoundingBoxes(region_p90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignRightsOnTop(region_p90b.subList(0,i),region_p90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } // ---------------- // place positions // position -90 (right) Vector<Residue> region_m90 = posManager.getChildrenAtPosition(node,new ResAngle(-90),false); for( int i=0; i<region_m90.size(); i++ ) { computeBoundingBoxes(region_m90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignLeftsOnBottom(region_m90.subList(0,i),region_m90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position +90 (left) Vector<Residue> region_p90 = posManager.getChildrenAtPosition(node,new ResAngle(90),false); for( int i=0; i<region_p90.size(); i++ ) { computeBoundingBoxes(region_p90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignRightsOnTop(region_p90.subList(0,i),region_p90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 0 (bottom) Vector<Residue> region_0 = posManager.getChildrenAtPosition(node,new ResAngle(0)); for( int i=0; i<region_0.size(); i++ ) { computeBoundingBoxes(region_0.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignTopsOnLeft(region_0.subList(0,i),region_0.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position -45 (bottom right) Vector<Residue> region_m45 = posManager.getChildrenAtPosition(node,new ResAngle(-45)); for( int i=0; i<region_m45.size(); i++ ) { computeBoundingBoxes(region_m45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignTopsOnLeft(region_m45.subList(0,i),region_m45.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 45 (bottom left) Vector<Residue> region_p45 = posManager.getChildrenAtPosition(node,new ResAngle(45)); for( int i=0; i<region_p45.size(); i++ ) { computeBoundingBoxes(region_p45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignTopsOnLeft(region_p45.subList(i,i+1),region_p45.subList(0,i),theGraphicOptions.NODE_SPACE); } // ---------------- // align substituents with node bboxManager.alignCentersOnRight(node_bbox,region_m90b,theGraphicOptions.NODE_SUB_SPACE); bboxManager.alignCentersOnLeft(node_bbox,region_p90b,theGraphicOptions.NODE_SUB_SPACE); Union<Residue> border_nodes = new Union<Residue>(region_m90b).and(region_p90b); Rectangle border_bbox = bboxManager.getComplete(border_nodes.and(node)); // align position 0,45,-45 with node to not clash with 90b,-90b if( region_0.size()>0 ) { bboxManager.alignTopsOnRight(region_0,region_m45,theGraphicOptions.NODE_SPACE); bboxManager.alignTopsOnLeft(region_0,new Union<Residue>(region_0).and(region_m45),region_p45,region_p45,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnBottom(node_bbox,border_nodes,region_0,new Union<Residue>(region_0).and(region_m45).and(region_p45),theGraphicOptions.NODE_SPACE); } else if( region_m45.size()==0 ) bboxManager.alignCornersOnBottomAtLeft(node_bbox,border_nodes,region_p45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else if( region_p45.size()==0 ) bboxManager.alignCornersOnBottomAtRight(node_bbox,border_nodes,region_m45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else bboxManager.alignSymmetricOnBottom(node_bbox,border_nodes,region_p45,region_m45,2*theGraphicOptions.NODE_SPACE,2*theGraphicOptions.NODE_SPACE+theGraphicOptions.NODE_SIZE); // align positions -90 and 90 with node to not clash with the rest Union<Residue> ex_nodes = new Union<Residue>(region_0).and(region_m45).and(region_p45).and(border_nodes); bboxManager.alignCentersOnRight(node_bbox,ex_nodes,region_m90,region_m90,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnLeft(node_bbox,ex_nodes,region_p90,region_p90,theGraphicOptions.NODE_SPACE); // ---------------- // set rectangles Union<Residue> all_nodes = ex_nodes.and(region_m90).and(region_p90).and(node); bboxManager.setCurrent(node,node_bbox); bboxManager.setBorder(node,border_bbox); bboxManager.setComplete(node,bboxManager.getComplete(all_nodes)); if( node.hasChildren() ) bboxManager.setSupport(node,new Rectangle(midx(node_bbox),midy(node_bbox)+theGraphicOptions.NODE_SPACE+theGraphicOptions.NODE_SIZE,0,0)); for(Iterator<Linkage> i=node.iterator(); i.hasNext(); ) bboxManager.setParent(i.next().getChildResidue(),node_bbox); } private void computeBoundingBoxesBT(Residue node, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( node==null ) return; Rectangle node_bbox = theResidueRenderer.computeBoundingBox(node,posManager.isOnBorder(node),0,0,posManager.getOrientation(node),theGraphicOptions.NODE_SIZE,Integer.MAX_VALUE); bboxManager.setAllBBoxes(node,node_bbox); //----------------- // place substituents // -90 border (left) Vector<Residue> region_m90b = posManager.getChildrenAtPosition(node,new ResAngle(-90),true); for( int i=0; i<region_m90b.size(); i++ ) { computeBoundingBoxes(region_m90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignRightsOnTop(region_m90b.subList(0,i),region_m90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } // +90 border (right) Vector<Residue> region_p90b = posManager.getChildrenAtPosition(node,new ResAngle(90),true); for( int i=0; i<region_p90b.size(); i++ ) { computeBoundingBoxes(region_p90b.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignLeftsOnBottom(region_p90b.subList(0,i),region_p90b.subList(i,i+1),theGraphicOptions.NODE_SUB_SPACE); } // ---------------- // place positions // position -90 (left) Vector<Residue> region_m90 = posManager.getChildrenAtPosition(node,new ResAngle(-90),false); for( int i=0; i<region_m90.size(); i++ ) { computeBoundingBoxes(region_m90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignRightsOnTop(region_m90.subList(0,i),region_m90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position +90 (right) Vector<Residue> region_p90 = posManager.getChildrenAtPosition(node,new ResAngle(90),false); for( int i=0; i<region_p90.size(); i++ ) { computeBoundingBoxes(region_p90.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignLeftsOnBottom(region_p90.subList(0,i),region_p90.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 0 (top) Vector<Residue> region_0 = posManager.getChildrenAtPosition(node,new ResAngle(0)); for( int i=0; i<region_0.size(); i++ ) { computeBoundingBoxes(region_0.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignBottomsOnRight(region_0.subList(0,i),region_0.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position -45 (top left) Vector<Residue> region_m45 = posManager.getChildrenAtPosition(node,new ResAngle(-45)); for( int i=0; i<region_m45.size(); i++ ) { computeBoundingBoxes(region_m45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignBottomsOnRight(region_m45.subList(0,i),region_m45.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // position 45 (top right) Vector<Residue> region_p45 = posManager.getChildrenAtPosition(node,new ResAngle(45)); for( int i=0; i<region_p45.size(); i++ ) { computeBoundingBoxes(region_p45.elementAt(i),posManager,bboxManager); if( i>0 ) bboxManager.alignBottomsOnRight(region_p45.subList(0,i),region_p45.subList(i,i+1),theGraphicOptions.NODE_SPACE); } // ---------------- // align substituents with node bboxManager.alignCentersOnLeft(node_bbox,region_m90b,theGraphicOptions.NODE_SUB_SPACE); bboxManager.alignCentersOnRight(node_bbox,region_p90b,theGraphicOptions.NODE_SUB_SPACE); Union<Residue> border_nodes = new Union<Residue>(region_m90b).and(region_p90b); Rectangle border_bbox = bboxManager.getComplete(border_nodes.and(node)); // align position 0,45,-45 with node to not clash with 90b,-90b if( region_0.size()>0 ) { bboxManager.alignBottomsOnLeft(region_0,region_m45,theGraphicOptions.NODE_SPACE); bboxManager.alignBottomsOnRight(region_0,new Union<Residue>(region_0).and(region_m45),region_p45,region_p45,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnTop(node_bbox,border_nodes,region_0,new Union<Residue>(region_0).and(region_m45).and(region_p45),theGraphicOptions.NODE_SPACE); } else if( region_m45.size()==0 ) bboxManager.alignCornersOnTopAtRight(node_bbox,border_nodes,region_p45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else if( region_p45.size()==0 ) bboxManager.alignCornersOnTopAtLeft(node_bbox,border_nodes,region_m45,theGraphicOptions.NODE_SPACE,theGraphicOptions.NODE_SPACE); else bboxManager.alignSymmetricOnTop(node_bbox,border_nodes,region_m45,region_p45,theGraphicOptions.NODE_SPACE,2*theGraphicOptions.NODE_SPACE+theGraphicOptions.NODE_SIZE); // align positions -90 and 90 with node to not clash with the rest Union<Residue> ex_nodes = new Union<Residue>(region_0).and(region_m45).and(region_p45).and(border_nodes); bboxManager.alignCentersOnLeft(node_bbox,ex_nodes,region_m90,region_m90,theGraphicOptions.NODE_SPACE); bboxManager.alignCentersOnRight(node_bbox,ex_nodes,region_p90,region_p90,theGraphicOptions.NODE_SPACE); // ---------------- // set rectangles Union<Residue> all_nodes = ex_nodes.and(region_m90).and(region_p90).and(node); bboxManager.setCurrent(node,node_bbox); bboxManager.setBorder(node,border_bbox); bboxManager.setComplete(node,bboxManager.getComplete(all_nodes)); if( node.hasChildren() ) bboxManager.setSupport(node,new Rectangle(midx(node_bbox),midy(node_bbox)-theGraphicOptions.NODE_SPACE-theGraphicOptions.NODE_SIZE,0,0)); for(Iterator<Linkage> i=node.iterator(); i.hasNext(); ) bboxManager.setParent(i.next().getChildResidue(),node_bbox); } //---------------- // Bounding boxes bracket private void computeBoundingBoxesBracket(Residue bracket, Residue root, boolean collapse_multiple_antennae, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( bracket==null || root==null ) return; // compute all bboxes ResAngle orientation = posManager.getOrientation(bracket); if( orientation.equals(0) ) computeBoundingBoxesBracketLR(bracket,root,collapse_multiple_antennae,posManager,bboxManager); else if( orientation.equals(180) ) computeBoundingBoxesBracketRL(bracket,root,collapse_multiple_antennae,posManager,bboxManager); else if( orientation.equals(90) ) computeBoundingBoxesBracketTB(bracket,root,collapse_multiple_antennae,posManager,bboxManager); else if( orientation.equals(-90) ) computeBoundingBoxesBracketBT(bracket,root,collapse_multiple_antennae,posManager,bboxManager); else throw new Exception("Invalid orientation " + orientation); } private void computeBoundingBoxesBracketLR(Residue bracket, Residue root, boolean collapse_multiple_antennae, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( bracket==null || root==null ) return; ResAngle orientation = posManager.getOrientation(bracket); // compute children bounding boxes int id=0; int max_quantity = 1; Vector<Residue> antennae = new Vector<Residue>(); TreeMap<String,Pair<Residue,Integer>> unique_antennae = new TreeMap<String,Pair<Residue,Integer>>(); for( int i=0; i<bracket.getNoChildren(); i++ ) { Residue child = bracket.getChildAt(i); // avoid concurrent modification of iterator!! String child_str = (collapse_multiple_antennae) ?GWSParser.writeSubtree(child,false) : (""+(id++)); Pair<Residue,Integer> value = unique_antennae.get(child_str); if( value==null ) { Residue antenna = child; // create fake attachment point for non-border residues if( !posManager.isOnBorder(antenna) ) { antenna = ResidueDictionary.newResidue("#attach"); child.insertParent(antenna); posManager.add(antenna,orientation,new ResAngle(),false,true); } // set child bbox computeBoundingBoxesLR(antenna,posManager,bboxManager); if( antennae.size()>0 ) bboxManager.alignLeftsOnBottom(antennae.lastElement(),antenna,theGraphicOptions.NODE_SPACE); // add antenna to the list antennae.add(antenna); unique_antennae.put(child_str,new Pair<Residue,Integer>(child,1)); } else { // link to other antenna bboxManager.linkSubtrees(value.getFirst(),child); // update quantity for repeated antenna int new_quantity = value.getSecond() + 1; unique_antennae.put(child_str,new Pair<Residue,Integer>(value.getFirst(),new_quantity)); max_quantity = Math.max(max_quantity,new_quantity); } } // compute bracket bbox Rectangle structure_bbox = new Rectangle(bboxManager.getComplete(root)); Rectangle bracket_bbox = new Rectangle(right(structure_bbox),top(structure_bbox),theGraphicOptions.NODE_SIZE,structure_bbox.height); // align antennae if( antennae.size()>0 ) bboxManager.alignCentersOnRight(bracket_bbox,antennae,0); Rectangle antennae_bbox = (antennae.size()>0) ?new Rectangle(bboxManager.getComplete(antennae)) :null; Rectangle all_bbox = union(bracket_bbox,antennae_bbox); // compute bbox for quantities if( max_quantity>1 ) { Dimension quantity_text_dim = textBounds(max_quantity + "x" ,theGraphicOptions.NODE_FONT_FACE,theGraphicOptions.NODE_FONT_SIZE); all_bbox.width += quantity_text_dim.width+2; } // restore linkages for( Residue antenna: antennae ) { if( !posManager.isOnBorder(antenna) ) bracket.removeChild(antenna); } // set bboxes bboxManager.setParent(bracket,structure_bbox); bboxManager.setCurrent(bracket,bracket_bbox); bboxManager.setBorder(bracket,bracket_bbox); bboxManager.setComplete(bracket,all_bbox); bboxManager.setSupport(bracket,bracket_bbox); } private void computeBoundingBoxesBracketRL(Residue bracket, Residue root, boolean collapse_multiple_antennae, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( bracket==null || root==null ) return; ResAngle orientation = posManager.getOrientation(bracket); // compute children bounding boxes int id=0; int max_quantity = 1; Vector<Residue> antennae = new Vector<Residue>(); TreeMap<String,Pair<Residue,Integer>> unique_antennae = new TreeMap<String,Pair<Residue,Integer>>(); for( int i=0; i<bracket.getNoChildren(); i++ ) { Residue child = bracket.getChildAt(i); // avoid concurrent modification of iterator!! String child_str = (collapse_multiple_antennae) ?GWSParser.writeSubtree(child,false) : (""+(id++)); Pair<Residue,Integer> value = unique_antennae.get(child_str); if( value==null ) { Residue antenna = child; // create fake attachment point for non-border residues if( !posManager.isOnBorder(antenna) ) { antenna = ResidueDictionary.newResidue("#attach"); child.insertParent(antenna); posManager.add(antenna,orientation,new ResAngle(),false,true); } // set child bbox computeBoundingBoxesRL(antenna,posManager,bboxManager); if( antennae.size()>0 ) bboxManager.alignRightsOnTop(antennae.lastElement(),antenna,theGraphicOptions.NODE_SPACE); // add antenna to the list antennae.add(antenna); unique_antennae.put(child_str,new Pair<Residue,Integer>(child,1)); } else { // link to other antenna bboxManager.linkSubtrees(value.getFirst(),child); // update quantity for repeated antenna int new_quantity = value.getSecond() + 1; unique_antennae.put(child_str,new Pair<Residue,Integer>(value.getFirst(),new_quantity)); max_quantity = Math.max(max_quantity,new_quantity); } } // compute bracket bbox Rectangle structure_bbox = new Rectangle(bboxManager.getComplete(root)); Rectangle bracket_bbox = new Rectangle(left(structure_bbox)-theGraphicOptions.NODE_SIZE,top(structure_bbox),theGraphicOptions.NODE_SIZE,structure_bbox.height); // align antennae if( antennae.size()>0 ) bboxManager.alignCentersOnLeft(bracket_bbox,antennae,0); Rectangle antennae_bbox = (antennae.size()>0) ?new Rectangle(bboxManager.getComplete(antennae)) :null; Rectangle all_bbox = union(bracket_bbox,antennae_bbox); // compute bbox for quantities if( max_quantity>1 ) { Dimension quantity_text_dim = textBounds(max_quantity + "x" ,theGraphicOptions.NODE_FONT_FACE,theGraphicOptions.NODE_FONT_SIZE); all_bbox.x -= quantity_text_dim.width+2; all_bbox.width += quantity_text_dim.width+2; } // restore linkages for( Residue antenna: antennae ) { if( !posManager.isOnBorder(antenna) ) bracket.removeChild(antenna); } // set bboxes bboxManager.setParent(bracket,structure_bbox); bboxManager.setCurrent(bracket,bracket_bbox); bboxManager.setBorder(bracket,bracket_bbox); bboxManager.setComplete(bracket,all_bbox); bboxManager.setSupport(bracket,bracket_bbox); } private void computeBoundingBoxesBracketTB(Residue bracket, Residue root, boolean collapse_multiple_antennae, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( bracket==null || root==null ) return; ResAngle orientation = posManager.getOrientation(bracket); // compute children bounding boxes int id=0; int max_quantity = 1; Vector<Residue> antennae = new Vector<Residue>(); TreeMap<String,Pair<Residue,Integer>> unique_antennae = new TreeMap<String,Pair<Residue,Integer>>(); for( int i=0; i<bracket.getNoChildren(); i++ ) { Residue child = bracket.getChildAt(i); // avoid concurrent modification of iterator!! String child_str = (collapse_multiple_antennae) ?GWSParser.writeSubtree(child,false) : (""+(id++)); Pair<Residue,Integer> value = unique_antennae.get(child_str); if( value==null ) { Residue antenna = child; // create fake attachment point for non-border residues if( !posManager.isOnBorder(antenna) ) { antenna = ResidueDictionary.newResidue("#attach"); child.insertParent(antenna); posManager.add(antenna,orientation,new ResAngle(),false,true); } // set child bbox computeBoundingBoxesTB(antenna,posManager,bboxManager); if( antennae.size()>0 ) bboxManager.alignBottomsOnLeft(antennae.lastElement(),antenna,theGraphicOptions.NODE_SPACE); // add antenna to the list antennae.add(antenna); unique_antennae.put(child_str,new Pair<Residue,Integer>(child,1)); } else { // link to other antenna bboxManager.linkSubtrees(value.getFirst(),child); // update quantity for repeated antenna int new_quantity = value.getSecond() + 1; unique_antennae.put(child_str,new Pair<Residue,Integer>(value.getFirst(),new_quantity)); max_quantity = Math.max(max_quantity,new_quantity); } } // compute bracket bbox Rectangle structure_bbox = new Rectangle(bboxManager.getComplete(root)); Rectangle bracket_bbox = new Rectangle(left(structure_bbox),bottom(structure_bbox),width(structure_bbox),theGraphicOptions.NODE_SIZE); // align antennae if( antennae.size()>0 ) bboxManager.alignCentersOnBottom(bracket_bbox,antennae,0); Rectangle antennae_bbox = (antennae.size()>0) ?new Rectangle(bboxManager.getComplete(antennae)) :null; Rectangle all_bbox = union(bracket_bbox,antennae_bbox); // compute bbox for quantities if( max_quantity>1 ) { Dimension quantity_text_dim = textBounds(max_quantity + "x" ,theGraphicOptions.NODE_FONT_FACE,theGraphicOptions.NODE_FONT_SIZE); all_bbox.height += quantity_text_dim.width+2; // the string is rotated } // restore linkages for( Residue antenna: antennae ) { if( !posManager.isOnBorder(antenna) ) bracket.removeChild(antenna); } // set bboxes bboxManager.setParent(bracket,structure_bbox); bboxManager.setCurrent(bracket,bracket_bbox); bboxManager.setBorder(bracket,bracket_bbox); bboxManager.setComplete(bracket,all_bbox); bboxManager.setSupport(bracket,bracket_bbox); } private void computeBoundingBoxesBracketBT(Residue bracket, Residue root, boolean collapse_multiple_antennae, PositionManager posManager, BBoxManager bboxManager) throws Exception { if( bracket==null || root==null ) return; ResAngle orientation = posManager.getOrientation(bracket); // compute children bounding boxes int id=0; int max_quantity = 1; Vector<Residue> antennae = new Vector<Residue>(); TreeMap<String,Pair<Residue,Integer>> unique_antennae = new TreeMap<String,Pair<Residue,Integer>>(); for( int i=0; i<bracket.getNoChildren(); i++ ) { Residue child = bracket.getChildAt(i); // avoid concurrent modification of iterator!! String child_str = (collapse_multiple_antennae) ?GWSParser.writeSubtree(child,false) : (""+(id++)); Pair<Residue,Integer> value = unique_antennae.get(child_str); if( value==null ) { Residue antenna = child; // create fake attachment point for non-border residues if( !posManager.isOnBorder(antenna) ) { antenna = ResidueDictionary.newResidue("#attach"); child.insertParent(antenna); posManager.add(antenna,orientation,new ResAngle(),false,true); } // set child bbox computeBoundingBoxesBT(antenna,posManager,bboxManager); if( antennae.size()>0 ) bboxManager.alignTopsOnRight(antennae.lastElement(),antenna,theGraphicOptions.NODE_SPACE); // add antenna to the list antennae.add(antenna); unique_antennae.put(child_str,new Pair<Residue,Integer>(child,1)); } else { // link to other antenna bboxManager.linkSubtrees(value.getFirst(),child); // update quantity for repeated antenna int new_quantity = value.getSecond() + 1; unique_antennae.put(child_str,new Pair<Residue,Integer>(value.getFirst(),new_quantity)); max_quantity = Math.max(max_quantity,new_quantity); } } // compute bracket bbox Rectangle structure_bbox = new Rectangle(bboxManager.getComplete(root)); Rectangle bracket_bbox = new Rectangle(left(structure_bbox),top(structure_bbox)-theGraphicOptions.NODE_SIZE,width(structure_bbox),theGraphicOptions.NODE_SIZE); // align antennae if( antennae.size()>0 ) bboxManager.alignCentersOnTop(bracket_bbox,antennae,0); Rectangle antennae_bbox = (antennae.size()>0) ?new Rectangle(bboxManager.getComplete(antennae)) :null; Rectangle all_bbox = union(bracket_bbox,antennae_bbox); // compute bbox for quantities if( max_quantity>1 ) { Dimension quantity_text_dim = textBounds(max_quantity + "x" ,theGraphicOptions.NODE_FONT_FACE,theGraphicOptions.NODE_FONT_SIZE); all_bbox.y -= quantity_text_dim.width+2; // the string is rotated all_bbox.height += quantity_text_dim.width+2; } // restore linkages for( Residue antenna: antennae ) { if( !posManager.isOnBorder(antenna) ) bracket.removeChild(antenna); } // set bboxes bboxManager.setParent(bracket,structure_bbox); bboxManager.setCurrent(bracket,bracket_bbox); bboxManager.setBorder(bracket,bracket_bbox); bboxManager.setComplete(bracket,all_bbox); bboxManager.setSupport(bracket,bracket_bbox); } //------------------------- // Export graphics /** Return a graphical representation of a structure as an image object. @param structure the structure to be displayed @param opaque <code>true</code> if a non transparent background should be used @param show_masses <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the reducing end marker should be displayed */ public BufferedImage getImage(Glycan structure, boolean opaque, boolean show_masses, boolean show_redend) { Vector<Glycan> structures = new Vector<Glycan>(); if( structure!=null ) structures.add(structure); return getImage(structures,opaque,show_masses,show_redend,1.); } /** Return a graphical representation of a structure as an image object. @param structure the structure to be displayed @param opaque <code>true</code> if a non transparent background should be used @param show_masses <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the end marker should be displayed @param scale the scale factor that should be applied to all dimensions */ public BufferedImage getImage(Glycan structure, boolean opaque, boolean show_masses, boolean show_redend, double scale) { Vector<Glycan> structures = new Vector<Glycan>(); if( structure!=null ) structures.add(structure); return getImage(structures,opaque,show_masses,show_redend,scale); } /** Return a graphical representation of a set of structures as an image object. @param structures the set of structures to be displayed @param opaque <code>true</code> if a non transparent background should be used @param show_masses <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the end marker should be displayed */ public BufferedImage getImage(Collection<Glycan> structures, boolean opaque, boolean show_masses, boolean show_redend) { return getImage(structures,opaque,show_masses,show_redend,1.); } /** Return a graphical representation of a set of structures as an image object. @param structures the set of structures to be displayed @param opaque <code>true</code> if a non transparent background should be used @param show_masses <code>true</code> if the mass information about the structure should be displayed @param show_redend <code>true</code> if the end marker should be displayed @param scale the scale factor that should be applied to all dimensions */ public BufferedImage getImage(Collection<Glycan> structures, boolean opaque, boolean show_masses, boolean show_redend, double scale) { if( structures == null ) structures = new Vector<Glycan>(); // set scale GraphicOptions view_opt = theGraphicOptions; boolean old_flag = view_opt.SHOW_INFO; view_opt.SHOW_INFO = (old_flag && scale==1.); view_opt.setScale(scale*view_opt.SCALE_CANVAS); // compute bounding boxes; PositionManager posManager = new PositionManager(); BBoxManager bboxManager = new BBoxManager(); Rectangle all_bbox = computeBoundingBoxes(structures,show_masses,show_redend,posManager,bboxManager); // Create an image that supports transparent pixels Dimension d = computeSize(all_bbox); BufferedImage img = GraphicUtils.createCompatibleImage(d.width,d.height,opaque); // prepare graphics context Graphics2D g2d = img.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); if( opaque ) { g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); // clear background g2d.setBackground(Color.white); g2d.clearRect(0, 0, d.width, d.height); //g2d.setColor(Color.white); //g2d.fillRect(0, 0, d.width, d.height); } else { g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g2d.setBackground(new Color(255,255,255,0)); } // paint structures for( Glycan s : structures ) paint(g2d,s,null,null,show_masses,show_redend,posManager,bboxManager); // reset scale view_opt.setScale(1.); view_opt.SHOW_INFO = old_flag; return img; } }