/** * Jeff Rupp * Master's Thesis * 2005 * * This class implements a generic data graphing panel * */ package jdr.mobisim; import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.*; import org.apache.log4j.*; import jdr.utils.*; public class TopographyPanel extends JPanel implements ComponentListener { private static Logger m_logger = Logger.getLogger(TopographyPanel.class); boolean m_doVisualization = true; private WaitToRedrawThread m_resizeThread; private Object m_threadLock = new Object(); private DrawThread m_drawThread = null; private LiveRedrawThread m_liveRedrawThread = null; // Members for doing double-buffering. private boolean m_safeToCreateImage = false; private java.awt.image.BufferedImage m_offScreen = null; private Graphics2D m_offScreenGraphics = null; private String m_startXLabel = ""; private String m_stopXLabel = ""; private String m_centerXLabel = ""; // area to the right to display labels, no grid private static final int LABEL_WIDTH = 0; private static final int LABEL_HEIGHT = 0; public Stroke m_dashedStroke; private java.text.DecimalFormat m_dblFmt = new java.text.DecimalFormat("0.0000E00"); private NodeIF m_highlitedNode = null; private FloatPoint m_sinkLoc = new FloatPoint(0, 0); public TopographyPanel() { super(); addComponentListener(this); float [] dashPattern = new float[2]; dashPattern[0] = 4.0F; dashPattern[1] = 6.0F; m_dashedStroke = new BasicStroke(2.0F, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0F, dashPattern, 2.0F); addMouseListener(new MouseAdapter(){ public void mouseClicked(MouseEvent e) { if(e.isAltDown() && e.isControlDown()) { ShowTransmitRings(e, false); ShowClusterMates(e, false); } else if(e.isAltDown()) { ShowNodeInfo(e); } else if(e.isControlDown()) { ShowTransmitRings(e); } else { ShowClusterMates(e); } } }); m_liveRedrawThread = new LiveRedrawThread(); m_liveRedrawThread.start(); } public void paint(Graphics g) { // have to do some double buffered action to first draw to buffer, // then transfer that buffer to the screen if(m_offScreenGraphics != null) { synchronized(m_offScreenGraphics) { ((Graphics2D)g).drawImage(m_offScreen, 0, 0, this); } } m_safeToCreateImage = true; } /** * shows cluster mates when a node is clicked by marking clustered nodes */ public void ShowClusterMates(MouseEvent e) { ShowClusterMates(e, true); } public void ShowClusterMates(MouseEvent e, boolean show) { // need to do the same scaling we do when drawing if(m_offScreenGraphics == null) { return; } // remove the highlite from the previous node if(m_highlitedNode != null) { m_highlitedNode.SetHighliteCluster(false); m_highlitedNode.SetTransmitRingsOn(false); } // not showing, just un-highlite if(!show) { m_highlitedNode = null; return; } // we have the node that was closest to the mouse click, now tell that node and its clustermates // to draw themselves in a different color (cluster head in a unique color) NodeIF closestNode = GetClosestNode(e.getX(), e.getY()); if(closestNode != null) { closestNode.SetHighliteCluster(true); reDraw(); } m_highlitedNode = closestNode; } public void ShowTransmitRings(MouseEvent e) { ShowTransmitRings(e, true); } public void ShowTransmitRings(MouseEvent e, boolean show) { // need to do the same scaling we do when drawing if(m_offScreenGraphics == null) { return; } // remove the highlite from the previous node if(m_highlitedNode != null) { m_highlitedNode.SetTransmitRingsOn(false); m_highlitedNode.SetHighliteCluster(false); } // not showing, just un-highlite if(!show) { m_highlitedNode = null; return; } // we have the node that was closest to the mouse click, now tell that node and its clustermates // to draw themselves in a different color (cluster head in a unique color) NodeIF closestNode = GetClosestNode(e.getX(), e.getY()); if(closestNode != null) { closestNode.SetTransmitRingsOn(true); closestNode.SetHighliteCluster(true); reDraw(); } m_highlitedNode = closestNode; } /** * pops up a modal dialog showing the clustering info (cluster head, if a child, all * clustered nodes if the node clicked was a cluster head) */ public void ShowNodeInfo(MouseEvent e) { NodeIF closestNode = GetClosestNode(e.getX(), e.getY()); if(closestNode != null) { JOptionPane.showMessageDialog(this, closestNode.GetClusterInfo()); } } /** * returns the node closest to where a mouse click occured. * Automatically adjusts the moust coordinates to the node coordinates */ protected NodeIF GetClosestNode(int x, int y) { Rectangle currentBounds = getBounds(); Rectangle trimmedBounds = currentBounds; trimmedBounds.width = currentBounds.width - LABEL_WIDTH; trimmedBounds.height = currentBounds.height - LABEL_HEIGHT; // scale the points to the trimmedBounds float xScale = (float)(AllNodes.Instance().getWidth() / (trimmedBounds.width * 0.95)); float yScale = (float)(AllNodes.Instance().getHeight() / (trimmedBounds.height * 0.95)); // find the node closest to where the click occurred FloatPoint clickLoc = new FloatPoint(x * xScale, y * yScale); HashMap nodes = new HashMap(0); double dist = 1; while(nodes.size() < 1) { nodes = AllNodes.Instance().GetNodesNear(clickLoc, dist); if(++dist > 1000) // ensure termination in all cases { break; } } int numNodes = nodes.size(); if(numNodes < 1) { m_logger.error("Could not find any nodes near mouse click at ["+x+", "+y+"]"); return null; } Iterator iter = nodes.keySet().iterator(); NodeIF closestNode = (NodeIF)nodes.get(iter.next()); if(numNodes > 1) // got more than 1 node, try to find closest { FloatPoint currNodeLoc = closestNode.getLocation(); double currNodeDist = Math.sqrt(Math.pow((currNodeLoc.getX() - x), 2) + Math.pow((currNodeLoc.getY() - y), 2)); NodeIF thisNode = null; while(iter.hasNext()) { thisNode = (NodeIF)nodes.get(iter.next()); FloatPoint loc = thisNode.getLocation(); double thisNodeDist = Math.sqrt(Math.pow((loc.getX() - x), 2) + Math.pow((loc.getY() - y), 2)); if(thisNodeDist < currNodeDist) { closestNode = thisNode; currNodeDist = thisNodeDist; } } } return closestNode; } public void SetSinkLocation(FloatPoint sinkLoc) { m_sinkLoc = sinkLoc; } private void ClearScreen() { m_offScreenGraphics.setColor(Color.black); Rectangle currentBounds = getBounds(); // clear by drawing a filled rect in the current bounds m_offScreenGraphics.fillRect(0,0, currentBounds.width, currentBounds.height); } private void DrawGrid() { m_offScreenGraphics.setColor(new Color(0, 0xaa, 0)); Rectangle currentBounds = getBounds(); int width = currentBounds.width - LABEL_WIDTH; int height = currentBounds.height - LABEL_HEIGHT; // draw vertical lines int inc = width / 10; for(int i = 0; i < width; i += inc) { m_offScreenGraphics.drawLine(i, 0, i, height); } // draw horizontal lines inc = height/10; for(int i = 0; i < height; i += inc) { m_offScreenGraphics.drawLine(0, i, width, i); } } public void componentResized(java.awt.event.ComponentEvent e) { // start a thread that waits for a second after the resizing // has finished before re-doing the drawing synchronized(m_threadLock) { if(m_resizeThread == null) { m_resizeThread = new WaitToRedrawThread(); m_resizeThread.start(); } else { m_resizeThread.setStillResizing(true); } } } public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentShown(ComponentEvent e) { } public void FinishedWithResizeThread() { synchronized(m_threadLock) { m_resizeThread = null; } } /** * redraw from the given vector of LineInfo s */ public void reDraw() { if(m_drawThread == null) { m_drawThread = new DrawThread(); m_drawThread.start(); m_drawThread.setDrawRequested(true); } else { m_drawThread.setDrawRequested(true); } } public void setStartXLabel(String label) { m_startXLabel = label; } public void setStopXLabel(String label) { m_stopXLabel = label; } public void setCenterXLabel(String label) { m_centerXLabel = label; } protected class WaitToRedrawThread extends Thread { public boolean m_stillResizing = true; public Object m_threadLock = new Object(); public WaitToRedrawThread() { super("GraphWaitToRedraw"); } public void run() { while(true) { synchronized(m_threadLock) { if(m_stillResizing) { m_stillResizing = false; } else { break; } } try { sleep(200); } catch(InterruptedException ignore) { } } // invoke the re-draw reDraw(); FinishedWithResizeThread(); } public void setStillResizing(boolean isResizing) { synchronized(m_threadLock) { m_stillResizing = isResizing; } } } protected class DrawThread extends Thread { private boolean m_drawRequested = false; private boolean m_okToKeepRunning = true; public DrawThread() { super("GraphDraw"); } public void setDrawRequested(boolean wantDraw) { m_drawRequested = wantDraw; } public boolean getDrawRequested() { return m_drawRequested; } public void run() { while(m_okToKeepRunning) { try { if(!m_drawRequested) { try { sleep(200); continue; } catch(InterruptedException ignore) { continue; } } // have to do some double buffered action to first draw to buffer, // then transfer that buffer to the screen if(m_offScreenGraphics != null) { m_offScreenGraphics.dispose(); } Rectangle currentBounds = getBounds(); if((currentBounds.width < 1) || (currentBounds.height < 1)) { continue; } m_offScreen = (java.awt.image.BufferedImage)createImage(currentBounds.width, currentBounds.height); if(m_offScreen == null) { continue; } m_offScreenGraphics = m_offScreen.createGraphics(); m_offScreenGraphics.setFont(new Font("Times New Roman", Font.PLAIN, 10)); // If we're really smart we'll see about leaving the image alone, and // just adding to the lines already there // initially just re-draw the whole image each time ClearScreen(); DrawGrid(); Rectangle trimmedBounds = currentBounds; trimmedBounds.width = currentBounds.width - LABEL_WIDTH; trimmedBounds.height = currentBounds.height - LABEL_HEIGHT; m_offScreenGraphics.setColor(Color.yellow); // scale the points to the trimmedBounds double xScale = AllNodes.Instance().getWidth() / (trimmedBounds.width * 0.95); double yScale = AllNodes.Instance().getHeight() / (trimmedBounds.height * 0.95); AllNodes.Instance().DrawNodes(m_offScreenGraphics, trimmedBounds, xScale, yScale); PropagationIF propagation = PropagationIF.getInstance(); if(propagation != null) { propagation.DrawTransmitsAtTime(m_offScreenGraphics, trimmedBounds, xScale, yScale, Scheduler.getInstance().GetSimulationTime()); if(System.getProperty("jdr.DrawTransmitHistory") != null) { int historyCount = Integer.parseInt(System.getProperty("jdr.DrawTransmitHistory")); propagation.DrawTransmitHistory(historyCount, m_offScreenGraphics, trimmedBounds, xScale, yScale); } } if(m_sinkLoc != null) { // draw the sink node location m_offScreenGraphics.setColor(Color.pink); m_offScreenGraphics.fillRect((int)(m_sinkLoc.getX() / xScale), (int)(m_sinkLoc.getY() / yScale), 7, 7); } // ??? need to show the comm happening if(m_doVisualization) { } repaint(); m_drawRequested = false; } catch(Exception ex) { ex.printStackTrace(); } } } } protected class LiveRedrawThread extends Thread { private boolean m_okToKeepRunning = true; public LiveRedrawThread() { super("LiveRedraw"); } public void run() { while(m_okToKeepRunning) { if(m_doVisualization) { reDraw(); try { sleep(100); // just for the heck of it, inc the sched clock 1 tic //Scheduler.getInstance().IncrementSimulationTics(Scheduler.getInstance().GetSimulationTime(), 1); } catch(InterruptedException iex) { } } } } public void StopThread() { m_okToKeepRunning = false; } } public void setVisualizationEnabled(boolean enableTopo) { m_doVisualization = enableTopo; } }