package genegui; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import javax.swing.*; import java.util.*; import affectors.*; import main.*; /** NetworkPaintPanel.java A JPanel with paint over-ridden for painting the nextwork connections. Note that NodeDisplay and AffectorDisplay reside within this panel and are painted when super.paint is called. Only the connections are painted here where the coordinant system is that of the whole paint panel. @author WJS */ public class NetworkPaintPanel extends JPanel { /** Default color for drawing an Affector */ private Color AffectorColor = Color.gray; /** An array of all affector displays. Index same as AffectorTemplates index */ private AffectorDisplay[] AffectorDisplays; /** A hash for associating AffectorAttemplates and AffectorDisplays */ private Hashtable AffectorDisplaysHashtable; /** The color for a selected affector */ private Color AffectorSelectedColor = Color.green; /** Polygon for storing the arrowhead shape used for drawing paths to nodes */ private Polygon ArrowHead; /** The default width for the network diagram node layout (affectors may be further out) */ private Dimension DefaultDiagramDimension = new Dimension(180,180); /** The default offset (center) for the diagram node layout */ private Point DefaultDiagramOffset = new Point(210,210); /** The calculated center of all the nodes on the diagram */ private Point DiagramCenter; // Not yet used. /** Multiplier used for exp color maping*/ private float ExpMultiplier = 1f; /** Default color for lines drawn from affectors to nodes */ private Color LineIncomingColor = Color.gray; /** The color for lines drawn into a node when the node is selected */ private Color LineSelectedIncomingColor = Color.cyan; /** Default color for paths drwan from nodes to affectors */ private Color LineOutgoingColor = Color.gray; /** The color for lines drawn from nodes to affectors when the node is selected */ private Color LineSelectedOutgoingColor = Color.green; /** Multiplier used for log color maping*/ private float LogMultiplier = 1f; /** Specifies what transformation function should be applied to the graph color */ private int GraphFunction = NetworkViewer2.GRAPH_FUNCTION_LINEAR; /** An array of all NodeDisplays */ private NodeDisplay[] NodeDisplays; /** The color of node borders */ private Color NodeBorderColor = Color.black; /** A hash for associating NodeTemplates and NodeDIsplays */ private Hashtable NodeDisplaysHashtable; /** The fill color for painting a selected node */ private Color NodeSelectedColor = Color.cyan; /** An array of all the NodeTemplates */ private NodeTemplate[] NodeTemplates; /** The Viewer/Editor holding this panel */ private NetworkViewer2 Parent; /** The insets around the painted network */ private Insets PaintInsets = new Insets(20,20,20,20); /** Self reference */ private JPanel SaveThis = this; /** The selected Affector display */ private AffectorDisplay SelectedAffector = null; /** The CellState for the selected cell in active display mode */ private CellState SelectedCellState; /** The selected NodeDisplay */ private NodeDisplay SelectedNode = null; /** A pointer to the Network */ private Network TheNetwork = null; /** The threshold value used by the threshold colormap */ private float ThresholdValue = 0.5f; public NetworkPaintPanel(NetworkViewer2 parent) { super(); setLayout(null); setBackground(Color.white); // Make arrowhead used to draw pointers to nodes ArrowHead = new Polygon(); ArrowHead.addPoint(0,0); ArrowHead.addPoint(-10,-5); ArrowHead.addPoint(-10,+5); ArrowHead.addPoint(0,0); Parent = parent; // Add a mouse listener whose job for the time being is to un-select // Nodes and or Affectors addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent me) { SelectedAffector = null; SelectedNode = null; repaint(); if (Parent!=null) Parent.displaySelected(null); } }); } /** Automatically re-arranges all display elements to a certain "optimal" display pattern. @author WJS */ public void autoArrange() { int n = TheNetwork.getNumNodes(); Point p = new Point(); // First arrange all nodes according to default values. for (int i=0;i0) otherNodeDisplay = (NodeDisplay)NodeDisplaysHashtable.get(TheNetwork.getNode(nodeNames[0])); else { boolean done = false; Enumeration enum = affectorTemplate.getEnhancerTemplates(); while(!done && enum.hasMoreElements()) { AffectorTemplate template = (AffectorTemplate)enum.nextElement(); if (template.getNodeNames()!=null&&template.getNodeNames().length > 0) { done = true; try { otherNodeDisplay = (NodeDisplay)NodeDisplaysHashtable.get(TheNetwork.getNode(template.getNodeNames()[0])); } catch (Exception ex) { } } } if (!done) { // Don't plot p = nodeDisplay.getLocation(); display.setLocation(p); return; } } } else if (affector instanceof DecayAff) { p = nodeDisplay.getLocation(); p.y += nodeDisplay.getHeight(); display.setLocation(p); return; } else if (nodeNames==null||nodeNames.length==0||nodeNames.length==1&&nodeNames[0].equals(nodeTemplate.getName())) { // This could be a function affector? Just draw above p = nodeDisplay.getCenter(); p.x -= display.getWidth()/2; p.y -= nodeDisplay.getHeight()/2+30; for (int i=0;i1) { otherNodeDisplay = (NodeDisplay)NodeDisplaysHashtable.get(TheNetwork.getNode(nodeNames[1])); } } int dx = nodeDisplay.getCenter().x - otherNodeDisplay.getCenter().x; int dy = nodeDisplay.getCenter().y - otherNodeDisplay.getCenter().y; // Move the intersection point slightly normal to the connecting line so lines going both // ways between nodes are separated. int offsetX = -dy; int offsetY = dx; float dist = (float)Math.sqrt(offsetX*offsetX+offsetY*offsetY); offsetX = (int)(offsetX*25/dist); offsetY = (int)(offsetY*25/dist); p.x = otherNodeDisplay.getCenter().x +(int)Math.round(dx*0.6)+offsetX; p.y = otherNodeDisplay.getCenter().y +(int)Math.round(dy*0.6)+offsetY; display.setLocation(p); } catch(Exception e) { System.out.println("Error calculating default affector position " + affectorTemplate.getAffectorName() + ": " + e.toString()); } } /** Calculates the center of the nodes in the newteork diagram @author WJS */ private void calculateDiagramCenter() { Point p = new Point(0,0); for (int i=0;iw) w = r.x+r.width; if (r.y+r.height>h) h = r.y+r.height; } // Scan through all AffectorDisplays. for (int i=0;iw) w = r.x+r.width; if (r.y+r.height>h) h = r.y+r.height; } r.x = x; r.y = y; r.width = w-x; r.height = h-y; return(r); } /** Over-rides paint to paint the connections bewteen nodes and affectors @param Graphics g @author WJS */ public void paint(Graphics g) { super.paint(g); paintConnections(g); } /** Paint the connections from all the affectors to their parent nodes and calls paintOutgoingConnections to paint connections to other affectors/nodes @param Graphics g0 - The graphics context. @author WJS */ private void paintConnections(Graphics g0) { Graphics2D g = (Graphics2D)g0; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); NodeDisplay nodeDisplay; Point adPoint; Point ndPoint; double polar; int x; int y; Polygon arrowHead; // Go through the array of all affectors and paint the line to the parent // node with an arrow on the end and all other outgoing lines. for (int i=0;i1) { if (p.x1) { y = p.y - s*d; if (NodeShape.contains(p.x-getX(),y-getY())) dh = d; else dl = d; d = (dh+dl)/2; } // Back off the line distance a little so it doesn't penetrate the node d -= 1.5; y = p.y - s*d; return(new Point(p.x,(int)y)); } } /** When we move a node in the network viewer, we also move the attached nodes a bit. This method accepts x and y offsets from the node movement and uses them to calculate the direction and amount to move the affector. Note the DecayAff is a special case that stays just below the node so it moves the same distance/direction. @param AffectorTemplate affectorTemplate - The template for the affector to be shifted. @param int offsetX - The X axis offset. @param int offsetY - The Y axis offset. @auther WJS */ public Point getNodeShift(Affector affector, int offsetX, int offsetY) { Point p = new Point(); if (affector instanceof DecayAff) { // Special case, just move the affector display along with it p.x = (offsetX); p.y = (offsetY); } else { // Caculate distance and direction node moved double d = Math.sqrt(Math.pow(offsetX,2)+Math.pow(offsetY,2)); double theta = Math.atan2(offsetY,offsetX); p.x = (int)(Math.cos(theta)*d/2.0); p.y = (int)(Math.sin(theta)*d/2.0); } return(p); } /** Returns the NodeTemplate for this NodeDisplay. @result NodeTemplate @author WJS */ public NodeTemplate getNodeTemplate() { return(TheNodeTemplate); } /** Over-ride the paint method to paint the offscreen image. @param Graphics g - The graphics context. @author WJS */ public void paint(Graphics g0) { if (OffScreenImage==null) { OffScreenImage=createImage((int)Width+1,(int)Height+1); paintOffScreen(true); } // This line will cause the paint thread to yield if the paintOffScreen // method is running. It is crucial to making sure painting doesn't // mess up especially when the viewer is being dragged around. if (Redrawing) Thread.yield(); g0.drawImage(OffScreenImage,0,0,this); } /** Paints the node image in an offscreen buffer @param boolean forcePaint - If true the node must repaint @result boolean - Returns true if the node needs to be repainted. @author WJS */ public boolean paintOffScreen(boolean forcePaint) { Redrawing = true; boolean dirty = false; if (OffScreenImage==null) OffScreenImage = createImage((int)Width+1,(int)Height+1); Graphics2D g = (Graphics2D)OffScreenImage.getGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); if (forcePaint) { g.setColor(Color.white); g.fillRect(0,0,(int)Width+1,(int)Height+1); } Color c; float v; int red = 0; int green = 0; int blue = 0; if (Parent.getActiveDisplayMode()) { c = TheNodeTemplate.getColor(); v = SelectedCellState.getNodeValue(TheNodeTemplate.getNodeNum()); if (v<0) v=0; else if (v>1.0) v=1; switch (GraphFunction) { case NetworkViewer2.GRAPH_FUNCTION_LOG: try { v = (float)((Math.log(v*LogMultiplier)+1.0)/(Math.log(LogMultiplier)+1)); if (v>1) v = 1f; else if (v<0) v = 0f; } catch (Exception ex) { v = 0; } break; case NetworkViewer2.GRAPH_FUNCTION_EXP: v = (float)(Math.exp(ExpMultiplier*v)/Math.exp(ExpMultiplier)); break; case NetworkViewer2.GRAPH_FUNCTION_THRESH: if (v>ThresholdValue) v = 1; else v = 0; break; } if (forcePaint||(Math.abs(v-LastPaintedValue)>Parent.RefreshReject)) { LastPaintedValue = v; if (GraphFunction==NetworkViewer2.GRAPH_FUNCTION_PSUEDO) { // TODO Math not quite right. Find proper LUT functions. red = (int)((1-v)*255); if (v<0.5) green = (int)(v*511); else green = (int)((1-v)*511); blue = (int)(v*255); } else { red = (int)(v*c.getRed()); green = (int)(v*c.getGreen()); blue = (int)(v*c.getBlue()); } g.setColor(new Color(red,green,blue)); g.fill(NodeShape); g.setColor(NodeBorderColor); g.draw(NodeShape); dirty = true; } } else { if (SelectedNode==ThisDisplay) { g.setColor(NodeSelectedColor); g.fill(NodeShape); } g.setColor(NodeBorderColor); g.draw(NodeShape); dirty = true; } // If the node name hasn't been painted yet, use font metrics to // get the size of the text. if (TextLocation==null) { TextLocation = new Point(); FontMetrics fm = g.getFontMetrics(); TextLocation.x = (int)((Width-fm.stringWidth(Name))/2.0); TextLocation.y = (int)((Height+fm.getAscent()+fm.getDescent())/2.0); } // Paint the node name if (dirty) { if (Parent.getActiveDisplayMode()) g.setColor(Color.white); else g.setColor(Color.black); g.drawString(Name, TextLocation.x, TextLocation.y); } g.dispose(); Redrawing = false; return(dirty); } } /** AffectorDisplay.java An inner class for representing affector nodes. @author WJS */ public class AffectorDisplay extends JPanel { /** Drag offset used for drag operations. Distance from mouse pointer and UL of window */ private Point DragOffset; /** Height of image*/ private int Height = 30; /** Set to true is user can drag the display */ private boolean Moveable = true; /** Radius for circle used to render many types */ private int Radius = 5; /** The affector that this is a display for */ private Affector TheAffector; /** The NodeDisplay that this AffectorDisplay is related to */ private NodeDisplay TheNodeDisplay; /** The Affector template */ private AffectorTemplate TheTemplate; /** Self reference */ private AffectorDisplay ThisDisplay = this; /** Width of image*/ private int Width = 40; /** Simple constructor. @param AffectorTemplate template - The AffectorTemplate this display represents. @param NodeDisplay display - The NodeDisplay the represents the NodeTemplate that this affector belongs to. @author WJS */ public AffectorDisplay(AffectorTemplate template, NodeDisplay display) { super(); TheAffector = template.getAffector(); TheNodeDisplay = display; TheTemplate = template; if (TheAffector instanceof DecayAff) { Width = 40; Height = 20; Moveable = false; } else { Width = 10; Height = 10; } // Add mouse listeners for moving around the display addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent me) { DragOffset = me.getPoint(); SelectedAffector = ThisDisplay; SelectedNode = null; SaveThis.repaint(); if (Parent!=null) Parent.displaySelected(ThisDisplay); } public void mouseReleased(MouseEvent me) { } }); // Only add the dragging code if the element can be dragged. if (Moveable) { addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent me) { Point p = getLocation(); setLocation(p.x+me.getX()-DragOffset.x,p.y+me.getY()-DragOffset.y); SaveThis.repaint(); } }); } setOpaque(false); setSize(Width,Height); } /** Returns true if an arrowhead should be drawn for lines coming from this affector to its parent node. @return boolean @author WJS */ private boolean drawArrowHead() { if (TheAffector instanceof DecayAff) return(false); else return(true); } /** Returns the affector for the Affector Template that this AffectorDisplay represents. @return Affector @author WJS */ private Affector getAffector() { return(TheAffector); } /** Rerturns the AffectorTemplate that this AffectorDisplay represents. @author WJS */ public AffectorTemplate getAffectorTemplate() { return(TheTemplate); } /** Returns the center point of the affector display @return Point - The center of the display. @author WJS */ private Point getCenter() { if (TheAffector instanceof DecayAff) return(new Point(getX()+20,getY()+14)); else return(new Point(getX()+Width/2,getY()+Height/2)); } /** Finds the point at which a line defined by the pass point p and the center of the Affector intersects the Affector outline. @param Point p - The start point for a line passing through the center of the Affector. @author WJS */ private Point getIntersection(Point p) { if (TheAffector instanceof DecayAff) return(new Point(getX()+20,getY()+14)); else { Point q = new Point(); Point c = getCenter(); double theta = Math.atan2(c.y-p.y,c.x-p.x); q.x = (int)(c.x-Math.cos(theta)*Radius); q.y = (int)(c.y-Math.sin(theta)*Radius); return(q); } } /** Returns the NodeDisplay for the NodeTemplate that this Affector template belongs to. @result NodeDisplay @author WJS */ public NodeDisplay getNodeDisplay() { return(TheNodeDisplay); } /** Paints the affector. @param Graphics g - THe graphics context. @author WJS */ public void paint(Graphics g0) { Graphics2D g = (Graphics2D)g0; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); if (ThisDisplay==SelectedAffector) g.setColor(AffectorSelectedColor); else if (TheNodeDisplay==SelectedNode) g.setColor(LineSelectedIncomingColor); else g.setColor(AffectorColor); // The Decay affector if (TheAffector instanceof DecayAff) { // Draw a grounding symbol g.drawLine(20, 14, 20, 20); g.drawLine(12, 14, 28, 14); g.drawLine(15, 16, 25, 16); g.drawLine(17, 18, 23, 18); } else if (TheTemplate.getNodeNames()==null) { // Draw a box at the end g.fillRect(0,0,6,6); } else { // All other affectors g.fillOval(Width/2-Radius,Height/2-Radius,Radius*2,Radius*2); } } } }