package statalign.postprocess.gui.treeviews; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.MouseInfo; import java.awt.Point; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.ArrayList; import javax.swing.ImageIcon; import javax.swing.JScrollPane; import javax.swing.JToggleButton; import statalign.postprocess.plugins.TreeNode; import statalign.postprocess.utils.DisplayString; /** * The graphical interface for showing the current network. * * @author wood, novak * */ public class NetworkTreeView extends TreeView implements MouseWheelListener { private static final long serialVersionUID = 1L; // variables for using in storing network data to enable resizing and zooming public ArrayList<DisplayString> leaflabel; public ArrayList<Integer[]> lines; // network extent in construction space public int minX,minY,maxX,maxY; // position of visible screen on initial zoom public int visX,visY; // zoom factor public int zoomFactor=20; // Graphics info. public Graphics2D g; public boolean putInArray = false; public int height; public int width; public JScrollPane scroll; public final int zoomDivisor = 20; // Added to comply to the TreeView interface public NetworkTreeView() { addMouseWheelListener(this); } @Override public JToggleButton getToolBarButton() { JToggleButton button = new JToggleButton(new ImageIcon( ClassLoader.getSystemResource("icons/vert.png"))); button.setToolTipText("Network view"); return button; } @Override public void newSample(TreeNode root) { super.newSample(root); putInArray = false; } @Override public void setParent(JScrollPane parent) { scroll = parent; } // End of additions /** * It repaints the graphics */ @Override public void paintComponent(Graphics gr){ if (root == null) { return; } // Standard repainting... g = (Graphics2D)gr.create(); g.setBackground(Color.WHITE); g.setFont(new Font("MONOSPACED", Font.PLAIN, 12)); //g.setClip(0,0, panel.getWidth()*zoomFactor, panel.getHeight()*zoomFactor); g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(Color.BLACK); if(putInArray == false){ int nameLengthCount = root.countNameLengthSum(); root.countLeaves(); // 2D array to store the lines we want to draw // 2nd dimension: 0=x1,1=y1,2=x2,3=y2 //TODO: is it more efficient to do this as 4 separate lists? lines = new ArrayList<Integer[]>(root.leafCount); // 2D array to store the strings we want to join // 2nd dimension 0=ref to string in owner.mcmc.tree, 1=x, 2=y leaflabel = new ArrayList<DisplayString>(root.leafCount); // Initialise max & min minY=0; minX=0; maxY=0; maxX=0; // calculate the positions of objects... drawNetwork(root,0,0,0,1); // actually draw the network redraw(); // Note we have saved the data putInArray = true; } else{ redraw(); } } /** * * @param v The tree node we want to calculate the position of * @param x Current x position * @param y Current y position * @param dir The current tree direction * @param prop The proportion of 360 deg. angle available */ private void drawNetwork(TreeNode v, int x, int y, double dir, double prop){ double curDir; int curX,curY; // Test to see if this is a node or a leaf... if(v.isLeaf()){ // Prepare the string for output String s = ((v.name.trim()).length() > 10 ? v.name.substring(0, 10)+"..." : v.name); leaflabel.add(new DisplayString(s,x,y)); // Update the maximum and minimums... if(x<minX) minX=x; else if (x>maxX) maxX=x; if(y<minY) minY=y; else if (y>maxY) maxY=y; } else{ TreeNode left = v.children.get(0); TreeNode right = v.children.get(1); // for each vertex we want to head in this direction, note that y=0 is the zero direction curDir = dir+Math.PI*((prop/2)*((left.leafCount/v.leafCount)-1)); // save the vertex line to our draw line list // times by 500 as we are storing as integers and want something that scales well curX = (int)(x+Math.cos(curDir)*left.edgeLength*500); curY = (int)(y+Math.sin(curDir)*left.edgeLength*500); lines.add(new Integer[]{x, y,curX ,curY}); //check for the boundaries now to save going through the array later if(curX<minX) minX=curX; else if (curX>maxX) maxX=curX; if(curY<minY) minY=curY; else if (curY>maxY) maxY=curY; drawNetwork(left,curX,curY,curDir,prop*left.leafCount/v.leafCount); // now do the right side of the vertex curDir = dir-Math.PI*((prop/2)*((right.leafCount/v.leafCount)-1)); // save the vertex line to our draw line list curX = (int)(x+Math.cos(curDir)*right.edgeLength*500); curY = (int)(y+Math.sin(curDir)*right.edgeLength*500); lines.add(new Integer[]{x, y,curX ,curY}); //check for the boundaries now to save going through the array later if(curX<minX) minX=curX; else if (curX>maxX) maxX=curX; if(curY<minY) minY=curY; else if (curY>maxY) maxY=curY; drawNetwork(right,curX,curY,curDir,prop*right.leafCount/v.leafCount); } } /** * Gives the minimum size of the component */ @Override public Dimension getMinimumSize(){ return getPreferredSize(); } /** * It gives the preferred size of the component */ @Override public Dimension getPreferredSize() { return new Dimension((scroll.getWidth()*zoomFactor/zoomDivisor)-10,(scroll.getHeight()*zoomFactor/zoomDivisor)-10); } /** * It draws the network from our arrays to the correct size */ public void redraw(){ height = (getHeight()-30); width = (getWidth()-100); double xMultiple = Math.abs((double)width/(double)(maxX-minX)); double yMultiple = Math.abs((double)height/(double)(maxY-minY)); int xBorder = 10; int yBorder = 20; for(Integer[] curLine:lines){ g.drawLine((int)((curLine[0]-minX)*xMultiple)+xBorder,(int)((curLine[1]-minY)*yMultiple)+yBorder,(int)((curLine[2]-minX)*xMultiple)+xBorder,(int)((curLine[3]-minY)*yMultiple)+yBorder); } for(DisplayString curString:leaflabel){ g.drawString(curString.label,(int)((curString.x-minX)*xMultiple)+xBorder,(int)((curString.y-minY)*yMultiple)+yBorder); } } /** * Event detector for mouse wheel movement. */ public void mouseWheelMoved(MouseWheelEvent e) { int oldZoomFactor = zoomFactor; zoomFactor -= e.getWheelRotation(); zoomFactor = (zoomFactor > zoomDivisor * 3) ? zoomDivisor * 3 : zoomFactor; zoomFactor = (zoomFactor < zoomDivisor) ? zoomDivisor : zoomFactor; //networkGUI.setBounds(0, 0, (int)((double)(networkGUI.zoomFactor/(double)networkGUI.zoomDivisor)*2*networkGUI.scroll.getWidth()), (int)((double)(networkGUI.zoomFactor/(double)networkGUI.zoomDivisor)*networkGUI.scroll.getHeight())); // calculate all the positions in the unscaled co-ords: double xMousePos = ((scroll.getViewport().getViewPosition().x + MouseInfo.getPointerInfo().getLocation().x - scroll.getLocationOnScreen().x)) / ((double) oldZoomFactor / (double) zoomDivisor); double yMousePos = ((scroll.getViewport().getViewPosition().y + MouseInfo.getPointerInfo().getLocation().y - scroll.getLocationOnScreen().y)) / ((double) oldZoomFactor / (double) zoomDivisor); double xNewView = (xMousePos - scroll.getWidth() / (2 * (double) zoomFactor / zoomDivisor)); double yNewView = (yMousePos - scroll.getHeight() / (2 * (double) zoomFactor / zoomDivisor)); xNewView = (xNewView < 0) ? 0 : xNewView; yNewView = (yNewView < 0) ? 0 : yNewView; xNewView = (xNewView > scroll.getWidth()) ? scroll.getWidth() - (scroll.getWidth() / zoomFactor * zoomDivisor) : xNewView; yNewView = (yNewView > scroll.getHeight()) ? scroll.getHeight() - (scroll.getHeight() / zoomFactor * zoomDivisor) : yNewView; scroll.getViewport().setViewPosition(new Point((int) (xNewView * zoomFactor / zoomDivisor), (int) (yNewView * zoomFactor / zoomDivisor))); scroll.repaint(); scroll.setViewportView(this); } }