// Medusa is a graph viewer which allows interactive editing of networks
// (edges and nodes) and also connects to databases.
//
// Copyright (C) 2006 Sean Hooper
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or (at
// your option) any later version.
//
// 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA 02111-1307 USA
package medusa.display;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import medusa.graph.Node;
/**
* Convenience class containing methods of
* painting nodes and edges. Medusa will most often
* use these methods. If you want to implement your own
* methods, simply modify the methods paintNode
* and paintEdge in the graph panels.
*/
public class PaintTools {
private static void drawArrow(Graphics2D g,int x1,int y1,int x2,int y2,
double orientation, double offset){
double theta2;
int lengthdeltaX;
int lengthdeltaY;
int widthdeltaX;
int widthdeltaY;
int headLength=9;
int headWidth=6;
theta2=getTheta(x1,x2,y1,y2);
//theta+=orientation*offset;
theta2-=orientation*offset;
lengthdeltaX=-(int)(Math.cos(theta2)*headLength);
lengthdeltaY=-(int)(Math.sin(theta2)*headLength);
widthdeltaX=(int)(Math.sin(theta2)*headWidth);
widthdeltaY=(int)(Math.cos(theta2)*headWidth);
x2-=(int)(Math.cos(theta2)*headLength);
y2-=(int)(Math.sin(theta2)*headLength);
//g.drawLine(x1,y1,x2,y2);
//g.drawLine(x2,y2,x2+lengthdeltaX+widthdeltaX,y2+lengthdeltaY-widthdeltaY);
//g.drawLine(x2,y2,x2+lengthdeltaX-widthdeltaX,y2+lengthdeltaY+widthdeltaY);
int[] xpoints={x2+lengthdeltaX+widthdeltaX,x2,x2+lengthdeltaX-widthdeltaX};
int[] ypoints={y2+lengthdeltaY-widthdeltaY,y2,y2+lengthdeltaY+widthdeltaY};
int npoints=3;
g.fillPolygon(xpoints, ypoints, npoints);
}
/**
* The theta parameter is used when drawing bezier
* curves.
* @param x1
* @param x2
* @param y1
* @param y2
* @return theta: the "attack" parameter of bezier curves
*/
public static double getTheta(int x1, int x2, int y1, int y2){
int deltaX=(x2-x1);
int deltaY=(y2-y1);
double theta;
theta=Math.atan((double)(deltaY)/(double)(deltaX));
if(deltaX<0.0) {
theta=theta+=Math.PI;
}
return theta;
}
// this draws a bar at the end of the line
// this is at the moment a hack!
private static void drawStop(Graphics2D g,int x1,int y1,int x2,int y2,
double orientation, double offset){
double theta2;
int lengthdeltaX;
int lengthdeltaY;
int widthdeltaX;
int widthdeltaY;
int headLength=9;
int headWidth=6;
theta2=getTheta(x1,x2,y1,y2);
//theta+=orientation*offset;
theta2-=orientation*offset;
lengthdeltaX=-(int)(Math.cos(theta2)*headLength);
lengthdeltaY=-(int)(Math.sin(theta2)*headLength);
widthdeltaX=(int)(Math.sin(theta2)*headWidth);
widthdeltaY=(int)(Math.cos(theta2)*headWidth);
x2-=(int)(Math.cos(theta2)*headLength);
y2-=(int)(Math.sin(theta2)*headLength);
g.drawLine(x2+lengthdeltaX+widthdeltaX,y2+lengthdeltaY-widthdeltaY,
x2+lengthdeltaX-widthdeltaX,y2+lengthdeltaY+widthdeltaY);
}
/**
* Draws a rhomboid node shape.
* @param x
* @param y
* @param nodeSize
* @return
*/
public static Polygon rhomb(int x, int y, int nodeSize){
//Polygon rhombP = new Polygon();
int offset=2;
int x1 = (int)(x+nodeSize/2);
int x2 = (int)(x+nodeSize+offset);
int y1 = (int)(y+nodeSize/2);
int y2 = (int)(y+nodeSize);
int[] xpoints={x-offset,x1,x2,x1};
int[] ypoints={y1,y2,y1,y};
int npoints=4;
return new Polygon(xpoints,ypoints,npoints);
}
public static Polygon triangle(int x, int y, int nodeSize){
int x1 = (int) (x+nodeSize/2);
int x2 = (int) (x+nodeSize);
int y2 = (int) (y+nodeSize);
int[] xpoints = {x,x2,x1};
int[] ypoints = {y2,y2,y};
int npoints = 3;
return new Polygon(xpoints, ypoints, npoints);
}
public static Shape getShape(int i, int x, int y, int nodeSize){
switch (i){
case 0: return new Ellipse2D.Double (x, y, nodeSize, nodeSize);
case 1: return new Rectangle2D.Double(x,y,nodeSize,nodeSize);
case 2: return PaintTools.triangle(x,y,nodeSize);
case 3: return PaintTools.rhomb(x,y,nodeSize);
}
return new Ellipse2D.Double (x, y, nodeSize, nodeSize);
}
/**
* A composite node has two or more colors. Nodes have
* additional data storage for additional colors.
* A composite node will be split into two halves with
* each half in a different color.
* @param g2d
* @param n
* @param nodeSize
*/
public static void drawCompositeNode(Graphics2D g2d, final Node n, final int nodeSize){
int corr=(int) (nodeSize/2.);
int x = (int)n.getX();
int y = (int)n.getY();
Color c2 = n.getColor2();
Color c3 = n.getColor3();
Rectangle2D.Double nodeR = new Rectangle2D.Double(x-corr,y-corr,nodeSize,nodeSize);
// draw basic shape
g2d.setColor(n.getColor());
g2d.fill(nodeR);
// draw remaining colors
if (c2!=null){
g2d.setColor(c2);
g2d.fill(new Rectangle2D.Double(x,y-corr,nodeSize-corr,nodeSize));
}
if (c3!=null){
g2d.setColor(c3);
g2d.fill(new Rectangle2D.Double(x-corr,y,nodeSize,nodeSize-corr));
}
if (n.isFixed())
g2d.setColor(Color.yellow);
else
g2d.setColor(Color.black);
g2d.draw(nodeR);
}
// hack!
private static void drawCircle(Graphics2D g,int x1,int y1,int x2,int y2,
double orientation, double offset){
double theta2;
int lengthdeltaX;
int lengthdeltaY;
//int widthdeltaX;
//int widthdeltaY;
int headLength=12;
int headWidth=6;
int circleWidth=6;
theta2=getTheta(x1,x2,y1,y2);
//theta+=orientation*offset;
theta2-=orientation*offset;
lengthdeltaX=-(int)(Math.cos(theta2)*headLength);
lengthdeltaY=-(int)(Math.sin(theta2)*headLength);
//widthdeltaX=(int)(Math.sin(theta2)*headWidth);
//widthdeltaY=(int)(Math.cos(theta2)*headWidth);
x2-=(int)(Math.cos(theta2)*headLength);
x2-=circleWidth/2;
y2-=(int)(Math.sin(theta2)*headLength);
y2-=circleWidth/2;
Ellipse2D.Double circle =
new Ellipse2D.Double (x2, y2, circleWidth, circleWidth);
g.fill(circle);
}
/**
* Paints the cubic curve between (x1,y1) and (x2,y2).
* The offset parameter allows multiple edges to be
* shown concurrently. The orientation decides how steep
* the curve should be, and the offset parameter is just a
* tweak to make the curve less "blunt"
* @param g2d
* @param x1
* @param y1
* @param x2
* @param y2
* @param orientation
* @param offset
* @param arrow
*/
public static void paintPath(Graphics2D g2d, int x1,
int y1, int x2, int y2, double orientation,
double offset, boolean arrow){
paintPath(g2d, x1, y1, x2, y2, orientation, offset, arrow, false);
}
public static void paintPath(Graphics2D g2d, int x1,
int y1, int x2, int y2, double orientation,
double offset, boolean arrow, boolean label){
double deltaX=(x2-x1);
double deltaY=(y2-y1);
double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
double radius=len/4.0;
// First, the line should be drawn. When this is done,
// calculate the angle of the arrow
if (orientation!=0.){
double[] cPoints = controlPoints(x1,y1,x2,y2,orientation,offset);
CubicCurve2D.Double path =
new CubicCurve2D.Double(x1,y1,cPoints[0],cPoints[1],cPoints[2],cPoints[3],x2,y2);
g2d.draw(path);
}
else{
g2d.drawLine(x1,y1,x2,y2);
//if (label){
}
if (arrow)
drawArrow(g2d, x1,y1,x2,y2,orientation, offset);
}
/**
*
* @param g2d
* @param x1
* @param y1
* @param x2
* @param y2
* @param orientation
* @param offset
* @param arrow
* @param label
*/
public static void PaintPathWithConfLabel(Graphics2D g2d, int x1,
int y1, int x2, int y2, double orientation,
double offset, boolean arrow, boolean label){
// just draw the line
g2d.drawLine(x1,y1,x2,y2);
// on half points, add conf label
int halfx=(int)(x1+x2)/2;
int halfy=(int)(y1+y2)/2;
g2d.setColor(Color.WHITE);
g2d.drawRect(halfx,halfy,10,10);
}
// this returns the control points for e.g. drawing paths in postscript
// as int
/**
* Calculates the control points between (x1,y1) and (x2,y2)
* for the cubic curve
* @param x1
* @param y1
* @param x2
* @param y2
* @param orientation
* @param offset
* @return
*/
public static double[] controlPoints(int x1, int y1, int x2,
int y2, double orientation,double offset){
double deltaX=(x2-x1);
double deltaY=(y2-y1);
double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
double radius=len/4.0;
double theta,theta2;
theta=Math.atan(deltaY/deltaX);
if(deltaX<0.0) {
theta=theta+=Math.PI;
}
//else theta2=theta;
theta2=theta+Math.PI;
theta+=orientation*offset;
theta2-=orientation*offset;
double[] result = new double[4];
result[2]=x2+Math.cos(theta2)*radius;
result[3]=y2+Math.sin(theta2)*radius;
result[0]=x1+Math.cos(theta)*radius;
result[1]=y1+Math.sin(theta)*radius;
return result;
}
public static int[] intControlPoints(int x1, int y1, int x2,
int y2, double orientation,double offset){
double[] db = controlPoints(x1,y1,x2,y2,orientation,offset);
int[] ib = new int[4];
ib[0]=(int) db[0];
ib[1]=(int) db[1];
ib[2]=(int) db[2];
ib[3]=(int) db[3];
return ib;
}
/**
* Not implemented. An example of the kind of edges that can be drawn.
* This method draws the standard "inhibition"/"activation"
* interactions.
* set shape = 1 for an arrow
* shape = 2 for bar
* shape = 3 for circle
*/
public static void paintRegulatoryPath(Graphics2D g2d, int x1,
int y1, int x2, int y2, double orientation,
double offset, int shape){
double deltaX=(x2-x1);
double deltaY=(y2-y1);
double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
double radius=len/4.0;
// First, the line should be drawn. When this is done,
// calculate the angle of the arrow
if (orientation!=0.){
double theta,theta2;
theta=Math.atan(deltaY/deltaX);
if(deltaX<0.0) {
theta=theta+=Math.PI;
}
//else theta2=theta;
theta2=theta+Math.PI;
theta+=orientation*offset;
theta2-=orientation*offset;
//System.out.println(theta+" "+theta2);
double cx2=x2+Math.cos(theta2)*radius;
double cy2=y2+Math.sin(theta2)*radius;
double cx1=x1+Math.cos(theta)*radius;
double cy1=y1+Math.sin(theta)*radius;
CubicCurve2D.Double path =
new CubicCurve2D.Double(x1,y1,cx1,cy1,cx2,cy2,x2,y2);
g2d.draw(path);
}
else{
g2d.drawLine(x1,y1,x2,y2);
}
switch (shape){
case 1:
drawArrow(g2d, x1,y1,x2,y2,orientation, offset);
break;
case 2:
drawStop(g2d, x1,y1,x2,y2,orientation, offset);
break;
case 3:
drawCircle(g2d, x1,y1,x2,y2,orientation, offset);
break;
}
}
// for creating postscript
/**
* PostScript method for drawing rhomboids
* @param x
* @param y
* @param nodeSize
* @return
*/
public static String psRhomb(int x, int y, int nodeSize){
//Polygon rhombP = new Polygon();
int correct = (int)(nodeSize/2.0);
StringBuffer sb=new StringBuffer();
int offset=2;
int x1 = x-correct;
int x2 = x;
int x3 = x+correct;
int y1 = y;
int y2 = y+correct;
int y3 = y-correct;
sb.append("newpath\n");
sb.append(x1+" "+y1+" moveto\n");
sb.append(x2+" "+y2+" lineto\n");
sb.append(x3+" "+y1+" lineto\n");
sb.append(x2+" "+y3+" lineto\n");
sb.append("closepath\n");
sb.append("gsave\n");
sb.append("fill\n");
sb.append("grestore\n");
sb.append("0 0 0 setrgbcolor\n");
sb.append("1 setlinewidth\n");
sb.append("stroke\n");
return sb.toString();
}
/**
* PostScript method for drawing triangles
*/
public static String psTriangle(int x, int y, int nodeSize){
StringBuffer sb=new StringBuffer();
int correct = (int)(nodeSize/2.0);
int x1 = x-correct;
int x2 = x;
int x3 = x+correct;
int y1 = y-correct;
int y2 = y+correct;
sb.append("newpath\n");
sb.append(x1+" "+y1+" moveto\n");
sb.append(x2+" "+y2+" lineto\n");
sb.append(x3+" "+y1+" lineto\n");
sb.append("closepath\n");
sb.append("gsave\n");
sb.append("fill\n");
sb.append("grestore\n");
sb.append("1 setlinewidth\n");
sb.append("0 0 0 setrgbcolor\n");
sb.append("stroke\n");
return sb.toString();
}
/**
* PostScript method for rectangular nodes
*/
public static String psRectangle(int x, int y, int nodeSize){
StringBuffer sb=new StringBuffer();
int correct = (int)(nodeSize/2.0);
int x1 = x-correct;
int x2 = x+correct;
int y1 = y-correct;
int y2 = y+correct;
sb.append("newpath\n");
sb.append(x1+" "+y1+" moveto\n");
sb.append(x1+" "+y2+" lineto\n");
sb.append(x2+" "+y2+" lineto\n");
sb.append(x2+" "+y1+" lineto\n");
sb.append("closepath\n");
sb.append("gsave\n");
sb.append("fill\n");
sb.append("grestore\n");
sb.append("1 setlinewidth\n");
sb.append("0 0 0 setrgbcolor\n");
sb.append("stroke\n");
return sb.toString();
}
public static String psCircle(int x, int y, int nodeSize){
StringBuffer sb=new StringBuffer();
int correct = (int)(nodeSize/2.0);
//int x1 = x-correct;
//int x2 = x+correct;
sb.append("newpath\n");
sb.append(x+" "+y+" "+correct+" 0 360 arc closepath fill stroke\n");
sb.append("0 0 0 setrgbcolor\n");
sb.append("1 setlinewidth\n");
sb.append(x+" "+y+" "+correct+" 0 360 arc closepath stroke\n");
return sb.toString();
}
}
Ads help maintain this website.