/*
* 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.io.*;
import java.util.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.font.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import static org.eurocarbdb.application.glycanbuilder.Geometry.*;
/**
Objects of this class are used to create a graphical representation
of a {@link Residue} object given the current graphic options
({@link GraphicOptions}. The rules to draw the residue in the
different notations are stored in the {@link
ResidueStyleDictionary}.
@author Alessio Ceroni (a.ceroni@imperial.ac.uk)
*/
public class ResidueRenderer {
protected ResidueStyleDictionary theResidueStyleDictionary;
protected GraphicOptions theGraphicOptions;
/**
Empty constructor.
*/
public ResidueRenderer() {
theResidueStyleDictionary = new ResidueStyleDictionary();
theGraphicOptions = new GraphicOptions();
}
/**
Create a new residue renderer copying the style dictionary and
graphic options from the <code>src</code> object.
*/
public ResidueRenderer(GlycanRenderer src) {
theResidueStyleDictionary = src.getResidueStyleDictionary();
theGraphicOptions = src.getGraphicOptions();
}
/**
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;
}
/**
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;
}
// --- Data access
/**
Return a graphical representation of a residue type as an icon
of <code>max_y_size</code> height.
*/
public Icon getIcon(ResidueType type, int max_y_size) {
int orientation = theGraphicOptions.ORIENTATION;
theGraphicOptions.ORIENTATION = GraphicOptions.RL;
// compute bounding box
Residue node = new Residue(type);
Rectangle bbox = computeBoundingBox(node,false,4,4,new ResAngle(),max_y_size-8,max_y_size-8);
// Create an image that supports transparent pixels
BufferedImage img = GraphicUtils.createCompatibleImage(bbox.width+8, bbox.height+8, false);
// create a graphic context
Graphics2D g2d = img.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
g2d.setBackground(new Color(255,255,255,0));
// paint the residue
paint(g2d,node,false,false,null,bbox,null,new ResAngle());
theGraphicOptions.ORIENTATION = orientation;
return new ImageIcon(img);
}
public Image getImage(ResidueType type, int max_y_size) {
int orientation = theGraphicOptions.ORIENTATION;
theGraphicOptions.ORIENTATION = GraphicOptions.RL;
// compute bounding box
Residue node = new Residue(type);
Rectangle bbox = computeBoundingBox(node,false,4,4,new ResAngle(),max_y_size-8,max_y_size-8);
// Create an image that supports transparent pixels
BufferedImage img = GraphicUtils.createCompatibleImage(bbox.width+8, bbox.height+8, false);
// create a graphic context
Graphics2D g2d = img.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
g2d.setBackground(new Color(255,255,255,0));
// paint the residue
paint(g2d,node,false,false,null,bbox,null,new ResAngle());
theGraphicOptions.ORIENTATION = orientation;
return Toolkit.getDefaultToolkit().createImage(img.getSource());
}
//-----------
// Bounding box
/**
Return the text to be written in the residue representation
given the residue style in the current notation.
*/
public String getText(Residue node) {
if( node==null )
return "";
ResidueType type = node.getType();
ResidueStyle style = theResidueStyleDictionary.getStyle(node);
String text = style.getText();
return (text!=null) ?text :type.getResidueName();
}
/**
Return the text to be written in the residue representation
given the residue style in the current notation.
@param on_border <code>true</code> if the residue is displayed
on the border of its parent, used for substitutions and
modifications
*/
public String getText(Residue node, boolean on_border) {
// special cases
if( node==null )
return "";
if( on_border && node.isSpecial() && !node.isLCleavage() )
return "*";
// get text
String text = null;
if( on_border && node.isLCleavage() )
text = getText(node.getCleavedResidue());
else
text = getText(node);
// add linkage
if( on_border && !node.getParentLinkage().hasUncertainParentPositions() && theGraphicOptions.SHOW_INFO )
text = node.getParentLinkage().getParentPositionsString() + text;
// add brackets for cleavages
if( on_border && node.isLCleavage() )
text = "(" + text + ")";
return text;
}
protected Rectangle computeBoundingBox(Residue node, boolean on_border, int x, int y, ResAngle orientation, int node_size, int max_y_size) {
// get style
ResidueStyle style = theResidueStyleDictionary.getStyle(node);
String shape = style.getShape();
// compute dimensions
if( max_y_size<node_size )
node_size = max_y_size;
//if( shape==null )
Dimension dim;
if( shape==null || on_border ) {
String text = getText(node,on_border);
int font_size = theGraphicOptions.NODE_FONT_SIZE;
int x_size = textBounds(text,theGraphicOptions.NODE_FONT_FACE,font_size).width;
if( x_size > node_size )
dim = new Dimension(x_size,node_size);
else
dim = new Dimension(node_size,node_size);
orientation = theGraphicOptions.getOrientationAngle();
}
else if( shape.equals("startrep") || shape.equals("endrep") ) {
int size = Math.min(node_size*2,max_y_size);
int font_size = theGraphicOptions.LINKAGE_INFO_SIZE;
dim = new Dimension(size/2,size+2*font_size);
}
else if( shape.equals("point") )
dim = new Dimension(1,1);
else
dim = new Dimension(node_size,node_size);
// return bounding box
if( orientation.equals(0) || orientation.equals(180) )
return new Rectangle(x,y,dim.width,dim.height);
return new Rectangle(x,y,dim.height,dim.width);
}
// ----------
// Painting
static private int sat(int v, int t) {
if( v>t )
return t;
return v;
}
static private int sig(int v) {
return 128+v/2;
//return v/2;
}
/**
Draw a residue on a graphic context using the specified
bounding box.
@param g2d the graphic context
@param node the residue to be drawn
@param selected <code>true</code> if the residue should be
shown as selected
@param on_border <code>true</code> if the residue should be
drawn on the border of its parent
@param par_bbox the bounding box of the parent residue
@param cur_bbox the bounding box of the current residue
@param sup_bbox the bounding box used to decide the spatial
orientation of the residue
@param orientation the orientation of the residue
*/
public void paint(Graphics2D g2d, Residue node, boolean selected, boolean on_border, Rectangle par_bbox, Rectangle cur_bbox, Rectangle sup_bbox, ResAngle orientation) {
paint(g2d,node,selected,true,on_border,par_bbox,cur_bbox,sup_bbox,orientation);
}
/**
Draw a residue on a graphic context using the specified
bounding box.
@param g2d the graphic context
@param node the residue to be drawn
@param selected <code>true</code> if the residue should be
shown as selected
@param active <code>true</code> if the residue should be
shown as active
@param on_border <code>true</code> if the residue should be
drawn on the border of its parent
@param par_bbox the bounding box of the parent residue
@param cur_bbox the bounding box of the current residue
@param sup_bbox the bounding box used to decide the spatial
orientation of the residue
@param orientation the orientation of the residue
*/
public void paint(Graphics2D g2d, Residue node, boolean selected, boolean active, boolean on_border, Rectangle par_bbox, Rectangle cur_bbox, Rectangle sup_bbox, ResAngle orientation) {
if( node==null )
return;
ResidueStyle style = theResidueStyleDictionary.getStyle(node);
// draw shape
Shape shape = createShape(node,par_bbox,cur_bbox,sup_bbox,orientation);
Shape text_shape = createTextShape(node,par_bbox,cur_bbox,sup_bbox,orientation);
Shape fill_shape = createFillShape(node,cur_bbox);
Color shape_color = style.getShapeColor();
Color fill_color = style.getFillColor();
Color text_color = style.getTextColor();
if( selected )
fill_color = new Color(sig(fill_color.getRed()),sig(fill_color.getGreen()),sig(fill_color.getBlue()));
if( !active ) {
shape_color = new Color(sig(shape_color.getRed()),sig(shape_color.getGreen()),sig(shape_color.getBlue()));
fill_color = new Color(sig(fill_color.getRed()),sig(fill_color.getGreen()),sig(fill_color.getBlue()));
text_color = new Color(sig(text_color.getRed()),sig(text_color.getGreen()),sig(text_color.getBlue()));
}
if( shape!=null && !on_border ) {
if( fill_shape!=null ) {
//Object old_hint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
//g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
Shape old_clip = g2d.getClip();
g2d.clip(shape);
g2d.setColor((style.isFillNegative()) ?fill_color :Color.white);
g2d.fill(shape);
g2d.setColor((style.isFillNegative()) ?Color.white :fill_color);
g2d.fill(fill_shape);
//if( old_hint!=null )
//g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,old_hint);
g2d.setColor(shape_color);
g2d.draw(fill_shape);
g2d.setClip(old_clip);
}
// draw contour
g2d.setStroke( (selected) ?new BasicStroke(2) :new BasicStroke(1));
g2d.setColor(shape_color);
g2d.draw(shape);
g2d.setStroke(new BasicStroke(1));
}
else if( selected ) {
// draw selected contour for empty shape
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(shape_color);
g2d.draw(cur_bbox);
g2d.setStroke(new BasicStroke(1));
}
// add text shape
if( text_shape!=null ) {
g2d.setColor(shape_color);
g2d.fill(text_shape);
}
// draw text
if( shape==null || on_border || style.getText()!=null ) {
if( shape==null || on_border )
orientation = theGraphicOptions.getOrientationAngle();
else if( style.getText()!=null )
orientation = new ResAngle(0);
String text = getText(node,on_border);
// set font
//int font_size = sat(9*cur_bbox.width/text.length()/10,theGraphicOptions.NODE_FONT_SIZE);
int font_size = theGraphicOptions.NODE_FONT_SIZE;
int x_size = textBounds(text,theGraphicOptions.NODE_FONT_FACE,font_size).width;
if( shape!=null )
font_size = sat(8 * font_size * cur_bbox.width / x_size / 10,font_size);
Font new_font = new Font(theGraphicOptions.NODE_FONT_FACE,Font.PLAIN,font_size);
Font old_font = g2d.getFont();
g2d.setFont(new_font);
// compute bounding rect
Rectangle2D.Double text_bound = new Rectangle2D.Double();
text_bound.setRect(new TextLayout(text,new_font,g2d.getFontRenderContext()).getBounds());
// draw text
g2d.setColor(text_color);
if( orientation.equals(0) || orientation.equals(180) ) {
Rectangle2D.Double text_rect = new Rectangle2D.Double(midx(cur_bbox)-text_bound.width/2,midy(cur_bbox)-text_bound.height/2,text_bound.width,text_bound.height);
if( shape==null || fill_shape==null )
g2d.clearRect((int)text_rect.x,(int)text_rect.y,(int)text_rect.width,(int)text_rect.height);
g2d.drawString(text,(int)text_rect.x,(int)(text_rect.y+text_rect.height));
}
else {
Rectangle2D.Double text_rect = new Rectangle2D.Double(midx(cur_bbox)-text_bound.height/2,midy(cur_bbox)-text_bound.width/2,text_bound.height,text_bound.width);
if( shape==null || fill_shape==null )
g2d.clearRect((int)text_rect.x,(int)text_rect.y,(int)text_rect.width,(int)text_rect.height);
g2d.rotate(-Math.PI/2.0);
g2d.drawString(text,-(int)(text_rect.y+text_rect.height),(int)(text_rect.x+text_rect.width));
g2d.rotate(+Math.PI/2.0);
}
g2d.setFont(old_font);
}
//g2d.setColor(Color.black);
//g2d.drawString(""+node.id,left(cur_bbox),bottom(cur_bbox));
}
//------------
// Shape
static private Polygon createDiamond(double x, double y, double w, double h) {
if( (w%2)==1 )
w++;
if( (h%2)==1 )
h++;
Polygon p = new Polygon();
p.addPoint((int)(x+w/2), (int)(y));
p.addPoint((int)(x+w), (int)(y+h/2));
p.addPoint((int)(x+w/2), (int)(y+h));
p.addPoint((int)(x), (int)(y+h/2));
return p;
}
static private Shape createHatDiamond(double angle, double x, double y, double w, double h) {
GeneralPath f = new GeneralPath();
// append diamond
f.append(createDiamond(x,y,w,h),false);
// append hat
Polygon p = new Polygon();
p.addPoint((int)(x-2),(int)(y+h/2-2));
p.addPoint((int)(x+w/2-2),(int)(y-2));
f.append(p,false);
return f;
}
static private Shape createRHatDiamond(double angle, double x, double y, double w, double h) {
GeneralPath f = new GeneralPath();
// append diamond
f.append(createDiamond(x,y,w,h),false);
// append hat
Polygon p = new Polygon();
p.addPoint((int)(x+w+2),(int)(y+h/2-2));
p.addPoint((int)(x+w/2+2),(int)(y-2));
f.append(p,false);
return f;
}
static private Polygon createRhombus(double x, double y, double w, double h) {
Polygon p = new Polygon();
p.addPoint((int)(x+0.50*w), (int)(y));
p.addPoint((int)(x+0.85*w), (int)(y+0.50*h));
p.addPoint((int)(x+0.50*w), (int)(y+h));
p.addPoint((int)(x+0.15*w), (int)(y+0.50*h));
return p;
}
static private Polygon createTriangle(double angle, double x, double y, double w, double h) {
Polygon p = new Polygon();
if( angle>=-Math.PI/4. && angle<=Math.PI/4. ) {
//pointing right
p.addPoint((int)(x+w), (int)(y+h/2));
p.addPoint((int)(x), (int)(y+h));
p.addPoint((int)(x), (int)(y));
}
else if( angle>=Math.PI/4. && angle<=3.*Math.PI/4. ) {
//pointing down
p.addPoint((int)(x+w/2), (int)(y+h));
p.addPoint((int)(x), (int)(y));
p.addPoint((int)(x+w), (int)(y));
}
else if( angle>=-3.*Math.PI/4. && angle<=-Math.PI/4. ) {
// pointing up
p.addPoint((int)(x+w/2), (int)(y));
p.addPoint((int)(x+w), (int)(y+h));
p.addPoint((int)(x), (int)(y+h));
}
else {
//pointing left
p.addPoint((int)(x), (int)(y+h/2));
p.addPoint((int)(x+w), (int)(y+h));
p.addPoint((int)(x+w), (int)(y));
}
return p;
}
static private Polygon createStar(double x, double y, double w, double h, int points) {
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
double step = Math.PI/(double)points;
double nstep = Math.PI/2.-2.*step;
double mrx = rx/(Math.cos(step)+Math.sin(step)/Math.tan(nstep));
double mry = ry/(Math.cos(step)+Math.sin(step)/Math.tan(nstep));
Polygon p = new Polygon();
for(int i=0; i<=2*points; i++ ) {
if( (i%2)==0 )
p.addPoint((int)(cx+rx*Math.cos(i*step-Math.PI/2.)),(int)(cy+ry*Math.sin(i*step-Math.PI/2.)));
else
p.addPoint((int)(cx+mrx*Math.cos(i*step-Math.PI/2.)),(int)(cy+mry*Math.sin(i*step-Math.PI/2.)));
}
return p;
}
static private Polygon createPentagon(double x, double y, double w, double h) {
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
double step = Math.PI/2.5;
Polygon p = new Polygon();
for(int i=0; i<=5; i++ ) {
p.addPoint((int)(cx+rx*Math.cos(i*step-Math.PI/2.)),(int)(cy+ry*Math.sin(i*step-Math.PI/2.)));
}
return p;
}
static private Polygon createHexagon(double x, double y, double w, double h) {
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
double step = Math.PI/3.;
Polygon p = new Polygon();
for(int i=0; i<=6; i++ ) {
p.addPoint((int)(cx+rx*Math.cos(i*step)),(int)(cy+ry*Math.sin(i*step)));
}
return p;
}
static private Polygon createHeptagon(double x, double y, double w, double h) {
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
double step = Math.PI/3.5;
Polygon p = new Polygon();
for(int i=0; i<=7; i++ ) {
p.addPoint((int)(cx+rx*Math.cos(i*step-Math.PI/2.)),(int)(cy+ry*Math.sin(i*step-Math.PI/2.)));
}
return p;
}
static private Shape createLine(double angle, double x, double y, double w, double h) {
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
Polygon p = new Polygon();
double x1 = cx+rx*Math.cos(angle-Math.PI/2.);
double y1 = cy+ry*Math.sin(angle-Math.PI/2.);
p.addPoint((int)x1,(int)y1);
double x2 = cx+rx*Math.cos(angle+Math.PI/2.);
double y2 = cy+ry*Math.sin(angle+Math.PI/2.);
p.addPoint((int)x2,(int)y2);
return p;
}
static private Shape createCleavage(double angle, double x, double y, double w, double h, boolean has_oxygen) {
GeneralPath f = new GeneralPath();
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
// create cut
double x1 = cx+rx*Math.cos(angle+Math.PI/2.);
double y1 = cy+ry*Math.sin(angle+Math.PI/2.);
double x2 = cx+rx*Math.cos(angle-Math.PI/2.);
double y2 = cy+ry*Math.sin(angle-Math.PI/2.);
double x3 = x2+rx*Math.cos(angle);
double y3 = y2+ry*Math.sin(angle);
Polygon p = new Polygon();
p.addPoint((int)x1,(int)y1);
p.addPoint((int)x2,(int)y2);
p.addPoint((int)x3,(int)y3);
p.addPoint((int)x2,(int)y2);
f.append(p,false);
if( has_oxygen ) {
// create oxygen
double ox = cx+rx*Math.cos(angle);
double oy = cy+ry*Math.sin(angle);
Shape o = new Ellipse2D.Double(ox-rx/3.,oy-ry/3.,rx/1.5,ry/1.5);
f.append(o,false);
}
return f;
}
static private Shape createCrossRingCleavage(double angle, double x, double y, double w, double h, int first_pos, int last_pos) {
//return createArc(x,y,w,h,first_pos,last_pos);
GeneralPath c = new GeneralPath();
//c.append(createLine(0,x,y,w,h),false);
// add hexagon
c.append(createHexagon(x+1,y+1,w-2,h-2),false);
//return c;
// add line
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
Polygon p1 = new Polygon();
p1.addPoint((int)cx,(int)cy);
p1.addPoint((int)(cx+1.2*rx*Math.cos(angle+first_pos*Math.PI/3-Math.PI/6)),
(int)(cy+1.2*ry*Math.sin(angle+first_pos*Math.PI/3-Math.PI/6)));
c.append(p1,false);
Polygon p2 = new Polygon();
p2.addPoint((int)cx,(int)cy);
p2.addPoint((int)(cx+1.2*rx*Math.cos(angle+last_pos*Math.PI/3-Math.PI/6)),
(int)(cy+1.2*ry*Math.sin(angle+last_pos*Math.PI/3-Math.PI/6)));
c.append(p2,false);
return c;
/*
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
// add half hexagon
double step = Math.PI/3.;
Polygon p = new Polygon();
p.addPoint((int)(cx+0.866*rx*Math.cos(angle-Math.PI/2)),(int)(cy+0.866*ry*Math.sin(angle-Math.PI/2)));
p.addPoint((int)(cx+rx*Math.cos(angle-Math.PI/3)),(int)(cy+ry*Math.sin(angle-Math.PI/3)));
p.addPoint((int)(cx+rx*Math.cos(angle)),(int)(cy+ry*Math.sin(angle)));
p.addPoint((int)(cx+rx*Math.cos(angle+Math.PI/3)),(int)(cy+ry*Math.sin(angle+Math.PI/3)));
p.addPoint((int)(cx+0.866*rx*rx*Math.cos(angle+Math.PI/2)),(int)(cy+0.866*ry*Math.sin(angle+Math.PI/2)));
c.append(p,false);
*/
// add pos
/*
AffineTransform t = new AffineTransform();
int fs = (int)(h/3);
double tx1 = cx + rx*Math.cos(angle+Math.PI/2) + fs*Math.cos(angle+Math.PI);
double ty1 = cy + ry*Math.sin(angle+Math.PI/2) + fs*Math.sin(angle+Math.PI);
t.setToTranslation(tx1,ty1);
c.append(t.createTransformedShape(getTextShape("" + first_pos, theGraphicOptions.LINKAGE_INFO_FONT_FACE, fs)),false);
double tx2 = cx + rx*Math.cos(angle-Math.PI/2) + fs*Math.cos(angle+Math.PI) + fs*Math.cos(angle+Math.PI/2);
double ty2 = cy + ry*Math.sin(angle-Math.PI/2) + fs*Math.sin(angle+Math.PI) + fs*Math.sin(angle+Math.PI/2);
t.setToTranslation(tx2,ty2);
c.append(t.createTransformedShape(getTextShape("" + last_pos, theGraphicOptions.LINKAGE_INFO_FONT_FACE, fs)),false);
*/
}
static private Shape createEnd(double angle, double x, double y, double w, double h) {
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
// start point
double x1 = cx+rx*Math.cos(angle-Math.PI/2.);
double y1 = cy+ry*Math.sin(angle-Math.PI/2.);
// end point
double x2 = cx+rx*Math.cos(angle+Math.PI/2.);
double y2 = cy+ry*Math.sin(angle+Math.PI/2.);
// ctrl point 1
double cx1 = cx+0.5*rx*Math.cos(angle-Math.PI/2.);
double cy1 = cy+0.5*ry*Math.sin(angle-Math.PI/2.);
double tx1 = cx1+0.5*rx*Math.cos(angle-Math.PI);
double ty1 = cy1+0.5*ry*Math.sin(angle-Math.PI);
// ctrl point 2
double cx2 = cx+0.5*rx*Math.cos(angle+Math.PI/2.);
double cy2 = cy+0.5*ry*Math.sin(angle+Math.PI/2.);
double tx2 = cx2+0.5*rx*Math.cos(angle);
double ty2 = cy2+0.5*ry*Math.sin(angle);
/*Polygon p = new Polygon();
p.addPoint((int)x1,(int)y1);
p.addPoint((int)x2,(int)y2);
return p;*/
return new CubicCurve2D.Double(x1,y1,tx1,ty1,tx2,ty2,x2,y2);
}
static private Shape createBracket(double angle, double x, double y, double w, double h) {
double rx = w/2.;
double ry = h/2.;
double cx = x+w/2.;
double cy = y+h/2.;
// first start point
double x11 = cx+rx*Math.cos(angle-Math.PI/2.)+0.2*rx*Math.cos(angle);
double y11 = cy+ry*Math.sin(angle-Math.PI/2.)+0.2*ry*Math.sin(angle);
// first ctrl point 1
double tx11 = cx+0.9*rx*Math.cos(angle-Math.PI/2.)+0.2*rx*Math.cos(angle-Math.PI);
double ty11 = cy+0.9*ry*Math.sin(angle-Math.PI/2.)+0.2*ry*Math.sin(angle-Math.PI);
// first ctrl point 2;
double tx21 = cx+0.1*rx*Math.cos(angle-Math.PI/2.)+0.2*rx*Math.cos(angle);
double ty21 = cy+0.1*ry*Math.sin(angle-Math.PI/2.)+0.2*ry*Math.sin(angle);
// first end point
double x21 = cx+0.2*rx*Math.cos(angle-Math.PI);
double y21 = cy+0.2*ry*Math.sin(angle-Math.PI);
// first shape
Shape s1 = new CubicCurve2D.Double(x11,y11,tx11,ty11,tx21,ty21,x21,y21);
// second start point
double x12 = cx+rx*Math.cos(angle+Math.PI/2.)+0.2*rx*Math.cos(angle);
double y12 = cy+ry*Math.sin(angle+Math.PI/2.)+0.2*ry*Math.sin(angle);
// second ctrl point 1
double tx12 = cx+0.9*rx*Math.cos(angle+Math.PI/2.)+0.2*rx*Math.cos(angle-Math.PI);
double ty12 = cy+0.9*ry*Math.sin(angle+Math.PI/2.)+0.2*ry*Math.sin(angle-Math.PI);
// second ctrl point 2;
double tx22 = cx+0.1*rx*Math.cos(angle+Math.PI/2.)+0.2*rx*Math.cos(angle);
double ty22 = cy+0.1*ry*Math.sin(angle+Math.PI/2.)+0.2*ry*Math.sin(angle);
// second end point
double x22 = cx+0.2*rx*Math.cos(angle-Math.PI);
double y22 = cy+0.2*ry*Math.sin(angle-Math.PI);
// second shape
Shape s2 = new CubicCurve2D.Double(x12,y12,tx12,ty12,tx22,ty22,x22,y22);
// generate bracket
GeneralPath b = new GeneralPath();
b.append(s1,false);
b.append(s2,false);
return b;
}
private Shape createRepetition(double angle, double x, double y, double w, double h) {
double r = Math.min(w,h);
double cx = x+w/2.;
double cy = y+h/2.;
//-----
// create shape
Polygon p = new Polygon();
// first point
double x1 = cx+r*Math.cos(angle-Math.PI/2.)+r/4.*Math.cos(angle+Math.PI);
double y1 = cy+r*Math.sin(angle-Math.PI/2.)+r/4.*Math.sin(angle+Math.PI);
p.addPoint((int)x1,(int)y1);
// second point
double x2 = cx+r*Math.cos(angle-Math.PI/2.);
double y2 = cy+r*Math.sin(angle-Math.PI/2.);
p.addPoint((int)x2,(int)y2);
// third point
double x3 = cx+r*Math.cos(angle+Math.PI/2.);
double y3 = cy+r*Math.sin(angle+Math.PI/2.);
p.addPoint((int)x3,(int)y3);
// fourth point
double x4 = cx+r*Math.cos(angle+Math.PI/2.)+r/4.*Math.cos(angle+Math.PI);
double y4 = cy+r*Math.sin(angle+Math.PI/2.)+r/4.*Math.sin(angle+Math.PI);
p.addPoint((int)x4,(int)y4);
// close shape
p.addPoint((int)x3,(int)y3);
p.addPoint((int)x2,(int)y2);
return p;
}
private Shape createShape(Residue node, Rectangle par_bbox, Rectangle cur_bbox, Rectangle sup_bbox, ResAngle orientation) {
ResidueStyle style = theResidueStyleDictionary.getStyle(node);
String shape = style.getShape();
if( shape==null || shape.equals("none") || shape.equals("-") )
return null;
double x = (double)cur_bbox.getX();
double y = (double)cur_bbox.getY();
double w = (double)cur_bbox.getWidth();
double h = (double)cur_bbox.getHeight();
// non-oriented shapes
if( shape.equals("point") )
return new Rectangle2D.Double(x+w/2.,y+h/2.,0,0);
if( shape.equals("square") )
return new Rectangle2D.Double(x,y,w,h);
if( shape.equals("circle") )
return new Ellipse2D.Double(x,y,w,h);
if( shape.equals("diamond") )
return createDiamond(x,y,w,h);
if( shape.equals("rhombus") )
return createRhombus(x,y,w,h);
if( shape.equals("star") )
return createStar(x,y,w,h,5);
if( shape.equals("sixstar") )
return createStar(x,y,w,h,6);
if( shape.equals("sevenstar") )
return createStar(x,y,w,h,7);
if( shape.equals("pentagon") )
return createPentagon(x,y,w,h);
if( shape.equals("hexagon") )
return createHexagon(x,y,w,h);
if( shape.equals("heptagon") )
return createHeptagon(x,y,w,h);
Point pp = ( par_bbox!=null ) ?center(par_bbox) :center(cur_bbox);
Point pc = center(cur_bbox);
Point ps = ( sup_bbox!=null ) ?center(sup_bbox) :center(cur_bbox);
// partially oriented shapes
if( shape.equals("triangle") )
return createTriangle(angle(pp,ps),x,y,w,h);
if( shape.equals("hatdiamond") )
return createHatDiamond(angle(pp,ps),x,y,w,h);
if( shape.equals("rhatdiamond") )
return createRHatDiamond(angle(pp,ps),x,y,w,h);
if( shape.equals("bracket") )
return createBracket(orientation.opposite().getAngle(),x,y,w,h);
if( shape.equals("startrep") )
return createRepetition(orientation.opposite().getAngle(),x,y,w,h);
if( shape.equals("endrep") )
return createRepetition(orientation.getAngle(),x,y,w,h);
// totally oriented shapes
if( shape.startsWith("acleavage") ) {
Vector<String> tokens = TextUtils.tokenize(shape,"_");
int first_pos = Integer.parseInt(tokens.elementAt(1));
int last_pos = Integer.parseInt(tokens.elementAt(2));
return createCrossRingCleavage(angle(pc,ps),x,y,w,h,first_pos,last_pos);
}
if( shape.equals("bcleavage") )
return createCleavage(angle(ps,pc),x,y,w,h,false);
if( shape.equals("ccleavage") )
return createCleavage(angle(ps,pc),x,y,w,h,true);
if( shape.startsWith("xcleavage") ) {
Vector<String> tokens = TextUtils.tokenize(shape,"_");
int first_pos = Integer.parseInt(tokens.elementAt(1));
int last_pos = Integer.parseInt(tokens.elementAt(2));
return createCrossRingCleavage(angle(pp,pc),x,y,w,h,first_pos,last_pos);
}
if( shape.equals("ycleavage") )
return createCleavage(angle(pp,pc),x,y,w,h,true);
if( shape.equals("zcleavage") )
return createCleavage(angle(pp,pc),x,y,w,h,false);
if( shape.equals("end") )
return createEnd(angle(pp,ps),x,y,w,h);
return cur_bbox;
}
private Shape createRepetitionText(double angle, double x, double y, double w, double h, int min, int max) {
double r = Math.min(w,h);
double cx = x+w/2.;
double cy = y+h/2.;
double x2 = cx+r*Math.cos(angle-Math.PI/2.);
double y2 = cy+r*Math.sin(angle-Math.PI/2.);
double x3 = cx+r*Math.cos(angle+Math.PI/2.);
double y3 = cy+r*Math.sin(angle+Math.PI/2.);
GeneralPath ret = new GeneralPath();
//--------
// add min repetition
if( min>=0 || max>=0 ) {
String text = (min>=0) ?""+min :"0";
Dimension tb = textBounds(text,theGraphicOptions.LINKAGE_INFO_FONT_FACE,theGraphicOptions.LINKAGE_INFO_SIZE);
double dist = (isUp(angle) || isDown(angle)) ?tb.width/2+4 :tb.height/2+4;
double xmin,ymin;
if( isLeft(angle) || isUp(angle) ) {
xmin = x2+dist*Math.cos(angle-Math.PI/2.)-tb.width/2.;
ymin = y2+dist*Math.sin(angle-Math.PI/2.)+tb.height/2.;
}
else {
xmin = x3+dist*Math.cos(angle+Math.PI/2.)-tb.width/2.;
ymin = y3+dist*Math.sin(angle+Math.PI/2.)+tb.height/2.;
}
ret.append(getTextShape(xmin,ymin,text,theGraphicOptions.LINKAGE_INFO_FONT_FACE,theGraphicOptions.LINKAGE_INFO_SIZE),false);
}
//--------
// add max repetition
if( min>=0 || max>=0 ) {
String text = (max>=0) ?""+max :"+inf";
Dimension tb = textBounds(text,theGraphicOptions.LINKAGE_INFO_FONT_FACE,theGraphicOptions.LINKAGE_INFO_SIZE);
double dist = (isUp(angle) || isDown(angle)) ?tb.width/2+4 :tb.height/2+4;
double xmax,ymax;
if( isLeft(angle) || isUp(angle) ) {
xmax = x3+dist*Math.cos(angle+Math.PI/2.)-tb.width/2.;
ymax = y3+dist*Math.sin(angle+Math.PI/2.)+tb.height/2.;
}
else {
xmax = x2+dist*Math.cos(angle-Math.PI/2.)-tb.width/2.;
ymax = y2+dist*Math.sin(angle-Math.PI/2.)+tb.height/2.;
}
ret.append(getTextShape(xmax,ymax,text,theGraphicOptions.LINKAGE_INFO_FONT_FACE,theGraphicOptions.LINKAGE_INFO_SIZE),false);
}
return ret;
}
private Shape createTextShape(Residue node, Rectangle par_bbox, Rectangle cur_bbox, Rectangle sup_bbox, ResAngle orientation) {
ResidueStyle style = theResidueStyleDictionary.getStyle(node);
String shape = style.getShape();
if( shape==null || shape.equals("none") || shape.equals("-") )
return null;
double x = (double)cur_bbox.getX();
double y = (double)cur_bbox.getY();
double w = (double)cur_bbox.getWidth();
double h = (double)cur_bbox.getHeight();
if( shape.equals("endrep") )
return createRepetitionText(orientation.getAngle(),x,y,w,h,node.getMinRepetitions(),node.getMaxRepetitions());
return null;
}
//--------------
// Fill
static private Shape createTriangle(double x1, double y1, double x2, double y2, double x3, double y3) {
Polygon p = new Polygon();
p.addPoint((int)x1,(int)y1);
p.addPoint((int)x2,(int)y2);
p.addPoint((int)x3,(int)y3);
return p;
}
static private Shape createCheckered(double x, double y, double w, double h) {
GeneralPath c = new GeneralPath();
c.append(new Rectangle2D.Double(x+w/2.,y,w/2.,h/2.),false);
c.append(new Rectangle2D.Double(x,y+h/2.,w/2.,h/2.),false);
return c;
}
static private Shape createArc(double x, double y, double w, double h, int start_pos, int end_pos) {
return new Arc2D.Double(x-0.5*w,y-0.5*h,2*w,2*h,-end_pos*60.+30.,-((start_pos-end_pos+6)%6)*60.,Arc2D.PIE);
}
private Shape createFillShape(Residue node, Rectangle cur_bbox) {
ResidueStyle style = theResidueStyleDictionary.getStyle(node);
String fillstyle = style.getFillStyle();
double x = (double)cur_bbox.x;
double y = (double)cur_bbox.y;
double w = (double)cur_bbox.width;
double h = (double)cur_bbox.height;
if( fillstyle.equals("empty") )
return null;
if( fillstyle.equals("full") )
return cur_bbox;
if( fillstyle.equals("left") )
return new Rectangle2D.Double(x,y,w/2.,h);
if( fillstyle.equals("top") )
return new Rectangle2D.Double(x,y,w,h/2.);
if( fillstyle.equals("right") )
return new Rectangle2D.Double(x+w/2.,y,w/2.,h);
if( fillstyle.equals("bottom") )
return new Rectangle2D.Double(x,y+h/2.,w,h/2.);
if( fillstyle.equals("topleft") )
return createTriangle(x,y,x+w,y,x,y+h);
if( fillstyle.equals("topright") )
return createTriangle(x,y,x+w,y,x+w,y+h);
if( fillstyle.equals("bottomright") )
return createTriangle(x+w,y,x+w,y+h,x,y+h);
if( fillstyle.equals("bottomleft") )
return createTriangle(x,y,x+w,y+h,x,y+h);
double cx = x+w/2.;
double cy = y+h/2.;
double rx = w/6.;
double ry = h/6.;
if( fillstyle.equals("circle") )
return new Ellipse2D.Double(cx-rx,cy-ry,2.*rx,2.*ry);
if( fillstyle.equals("checkered") )
return createCheckered(x,y,w,h);
if( fillstyle.startsWith("arc") ) {
Vector<String> tokens = TextUtils.tokenize(fillstyle,"_");
int first_pos = Integer.parseInt(tokens.elementAt(1));
int last_pos = Integer.parseInt(tokens.elementAt(2));
return createArc(x,y,w,h,first_pos,last_pos);
}
return null;
}
}