package statalign.postprocess.gui;
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.JPanel;
import javax.swing.JScrollPane;
import statalign.postprocess.plugins.contree.CNetwork;
import statalign.postprocess.plugins.contree.CNetworkEdge;
import statalign.postprocess.plugins.contree.CNetworkNode;
import statalign.postprocess.plugins.contree.CNetworkSplit;
import statalign.postprocess.utils.DisplayString;
/**
* The graphical interface for showing the current network.
*
* @author wood, novak
*
*/
public class CNetworkView extends JPanel 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 intial zoom
public int visX,visY;
// zoom factor
public int zoomFactor=20;
// Graphic
public Graphics2D g;
// Boolean if stored directions in array or not (saves recalculating twice)
public boolean putInArray = false;
// Graphic window variables
public int height;
public int width;
public JScrollPane scroll;
public final int zoomDivisor = 20;
// Input network!
public CNetwork network;
// Added to comply to the TreeView interface
/**
* Initialise network viewer
*
* @param scroll Pane into which to place network graphic
*
* */
public CNetworkView(JScrollPane scroll) {
this.addMouseWheelListener(this);
this.scroll = scroll;
lines = new ArrayList<Integer[]>();
leaflabel = new ArrayList<DisplayString>();
}
// End of additions
/**
* It repaints the graphics by grabbing the new data from the network.
*
* @param gr Graphic to repaint
*/
@Override
public void paintComponent(Graphics gr){
// clear arrays
leaflabel.clear();
lines.clear();
g = (Graphics2D)gr.create();
g.setBackground(Color.WHITE);
g.setFont(new Font("MONOSPACED", Font.PLAIN, 12));
g.clearRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.BLACK);
// if no data yet...
if (network == null) {
g.drawString("Waiting for data..", 30, 30);
return;
}
if (putInArray == false){
// 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?
// Add in the edges
for(CNetworkSplit currentSplit : network.splits){
for(CNetworkEdge currentEdge : currentSplit.edges){
lines.add(new Integer[]{(int)(1000*currentEdge.xPosA),(int)(1000*currentEdge.yPosA),(int)(1000*currentEdge.xPosB),(int)(1000*currentEdge.yPosB)});
}
}
// Make sure we scale it to fit screen by setting initial max & min values
if(network.nodes.size()>0){
minX=(int)(1000*network.nodes.get(0).xPos);
minY=(int)(1000*network.nodes.get(0).yPos);
maxX=(int)(1000*network.nodes.get(0).xPos);
maxY=(int)(1000*network.nodes.get(0).yPos);
}
// Add in the nodes
for(CNetworkNode currentNode : network.nodes){
// update max & min values
minX = ((int)(1000*currentNode.xPos)<minX)?(int)(1000*currentNode.xPos):minX;
maxX = ((int)(1000*currentNode.xPos)>maxX)?(int)(1000*currentNode.xPos):maxX;
minY = ((int)(1000*currentNode.yPos)<minY)?(int)(1000*currentNode.yPos):minY;
maxY = ((int)(1000*currentNode.yPos)>maxY)?(int)(1000*currentNode.yPos):maxY;
// Remove if statement below to leave in internal node names...
if(currentNode.joins.size()==1){
String printName = (currentNode.Taxaname.length()> 10)?currentNode.Taxaname.substring(0, 10)+"...":currentNode.Taxaname;
DisplayString addNow = new DisplayString(printName,(int)(1000*currentNode.xPos),(int)(1000*currentNode.yPos));
leaflabel.add(addNow);
}
}
}
redraw();
}
/**
* 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 = (this.getHeight()-30);
width = (this.getWidth()-100);
double xMultiple = (double)Math.abs((double)width/(double)(maxX-minX));
double yMultiple = (double)Math.abs((double)height/(double)(maxY-minY));
int xBorder = 10;
int yBorder = 20;
for(int i=0;i<lines.size();i++){
Integer[] curLine = lines.get(i);
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);
}
}
/**
* It listens for mousewheel movement and sets the drawing appropriately.
*/
public void mouseWheelMoved(MouseWheelEvent e) {
int oldZoomFactor = zoomFactor;
zoomFactor -= e.getWheelRotation();
zoomFactor = (zoomFactor > zoomDivisor * 3) ? zoomDivisor * 3 : zoomFactor;
zoomFactor = (zoomFactor < zoomDivisor) ? zoomDivisor : zoomFactor;
// calculate all the positions in the unscaled co-ords:
double xMousePos = ((double) (scroll.getViewport().getViewPosition().x + MouseInfo.getPointerInfo().getLocation().x - scroll.getLocationOnScreen().x)) / ((double) oldZoomFactor / (double) zoomDivisor);
double yMousePos = ((double) (scroll.getViewport().getViewPosition().y + MouseInfo.getPointerInfo().getLocation().y - scroll.getLocationOnScreen().y)) / ((double) oldZoomFactor / (double) zoomDivisor);
double xNewView = (xMousePos - (double) scroll.getWidth() / (2 * (double) zoomFactor / (double) zoomDivisor));
double yNewView = (yMousePos - (double) scroll.getHeight() / (2 * (double) zoomFactor / (double) 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 / (double) zoomDivisor), (int) (yNewView * zoomFactor / (double) zoomDivisor)));
scroll.repaint();
scroll.setViewportView(this);
}
}