package fi.csc.microarray.client.visualisation.methods.threed; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.LinkedList; import java.util.concurrent.PriorityBlockingQueue; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.event.MouseInputListener; import fi.csc.microarray.client.ClientApplication; import fi.csc.microarray.client.Session; import fi.csc.microarray.client.selection.SelectionEvent; import fi.csc.microarray.constants.VisualConstants; /** * The class that takes care of the user manipulation of the scatterplot, basically * handles selections, rotations etc. The base of the implemention is Threed-visualisation * from Viski library (http://www.cs.helsinki.fi/group/viski/ ). * * @author Petri Klemelä */ public class CoordinateArea extends JComponent implements ActionListener, MouseInputListener, MouseWheelListener, PropertyChangeListener { private static final double KINETIC_SPEED_FACTOR = 0.002; private static long MOVE_TIME_LIMIT = 10; //ms private JMenuItem hideSelected; private JMenuItem showAll; private JMenuItem invertSelection; private JMenuItem saveAs; public enum PaintMode { SPHERE("Sphere"), RECT("Rectangle"); private String name; PaintMode(String name) { this.name = name; } @Override public String toString() { return name; } }; public PaintMode paintMode = PaintMode.SPHERE; //Just a little toy private boolean kineticMoveMode = false; public PaintMode getPaintMode() { return paintMode; } public void setPaintMode(PaintMode mode) { paintMode = mode; this.repaint(); } private LinkedList<DataPoint> selectedPoints; private Projection projection; protected AutomatedMovement movement; private ClientApplication application = Session.getSession().getApplication(); Scatterplot3D controller; //Any number to make rotation speed slow enough final double ANGLE_INCREMENT = Math.PI/(360*4); private AutomatedMovement.RotationTask kineticMovement; private long lastDragEventTime = -1; /** * * @param controller */ public CoordinateArea(Scatterplot3D controller) { this.controller = controller; this.createPopupMenu(); addMouseListener(this); addMouseMotionListener(this); addMouseWheelListener(this); setOpaque(true); setFocusable(true); this.setBackground(Color.black); selectedPoints = new LinkedList<DataPoint>(); projection = new Projection(controller.getDataModel()); movement = new AutomatedMovement(projection, this); movement.start(); kineticMovement = movement.startKineticMove(25, 0.95); this.updateSelectedFromApplication(); application.addClientEventListener(this); } public Projection getProjection(){ return projection; } /** * * @param g */ protected void paintComponent(Graphics g) { if(this.getHeight() <= this.getWidth()){ projection.setViewWindowWidth(projection.getViewWindowHeight() * this.getWidth() / (double)this.getHeight()); } else { projection.setViewWindowHeight(projection.getViewWindowWidth() * this.getHeight() / (double)this.getWidth()); } Graphics2D g2d = (Graphics2D)g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); PriorityBlockingQueue<Drawable> points = projection.doProjection(); g2d.setColor(this.getBackground()); g2d.fillRect(0, 0, getWidth(), getHeight()); Drawable p = null; while ((p = points.poll()) != null) { p.draw(g2d, getWidth(), getHeight(), getPaintMode()); } if(this.mouseDragged){ g2d.setColor(Color.WHITE); g2d.setStroke(VisualConstants.dashLine); int x = mousePressX < mouseX ? mousePressX : mouseX; int y = mousePressY < mouseY ? mousePressY : mouseY; int w = Math.abs(mouseX - mousePressX); int h = Math.abs(mouseY - mousePressY); g2d.setColor(this.getForeground()); g2d.drawRect(x,y,w,h); } } //Methods required by the MouseInputListener interface. /** * * @param e */ public void mouseClicked(MouseEvent e) { //double deg = 0; switch (e.getButton()) { case MouseEvent.BUTTON1: if (e.isControlDown()) { addToSelections(e); } else { selectOne(e); } break; case MouseEvent.BUTTON2: break; case MouseEvent.BUTTON3: break; default: } this.repaint(); } public void rotateWithDrag(int mouseX, int mouseY, MouseEvent e){ double xfactor = Math.abs(mouseX- mousePressX); double yfactor = Math.abs(mouseY- mousePressY); controller.stopAutoRotation(); if(e.isShiftDown()){ double deg = projection.getZAxisRotation(); if (mousePressX - mouseX < 0) { projection.setZAxisRotation(deg +ANGLE_INCREMENT*xfactor); } else if (mousePressX - mouseX > 0) { projection.setZAxisRotation(deg - ANGLE_INCREMENT*xfactor); } this.zoom((mouseY - mousePressY) / 100.0 ); } else { if(!kineticMoveMode){ kineticMovement.stop(); double deg = projection.getYAxisRotation(); if (mousePressX - mouseX < 0) { projection.setYAxisRotation(deg + ANGLE_INCREMENT*xfactor); } else if (mousePressX - mouseX > 0) { projection.setYAxisRotation(deg -ANGLE_INCREMENT*xfactor); } deg = projection.getXAxisRotation(); if (mousePressY - mouseY < 0) { projection.setXAxisRotation(deg + ANGLE_INCREMENT*yfactor); } else if (mousePressY - mouseY > 0) { projection.setXAxisRotation(deg -ANGLE_INCREMENT*yfactor); } kineticMovement.setAngleIncs(KINETIC_SPEED_FACTOR*(mouseY - mousePressY), KINETIC_SPEED_FACTOR*( mouseX-mousePressX), 0); } } mousePressX = mouseX; mousePressY = mouseY; lastDragEventTime = System.currentTimeMillis(); if(!kineticMoveMode){ this.repaint(); } } public void moveWithDrag(int mouseX, int mouseY, MouseEvent e){ double[] orig = projection.getPointOfView(); //TODO //Folloving equation is a flight of fantasy and doesn't work even right double divider = //Math.abs(orig[2]) * projection.getDistanceOfProjectionPlaneFromOrigin() * projection.getDistanceOfProjectionPlaneFromOrigin(); //projection.getViewWindowWidth(); orig[0] -= (mouseX - mousePressX) / divider; orig[1] -= (mouseY - mousePressY) / divider; projection.setPointOfView(orig); mousePressX = mouseX; mousePressY = mouseY; this.repaint(); } public void mouseMoved(MouseEvent e) { } public void mouseExited(MouseEvent e) { } /** * * @param e */ public void mouseEntered(MouseEvent e) { this.requestFocus(); } private int mousePressX = 0; private int mousePressY = 0; //For selection rectangle drawing private int mouseX; private int mouseY; /** * * @param e */ public void mousePressed(MouseEvent e) { this.requestFocus(); kineticMovement.stop(); mousePressX = e.getX(); mousePressY = e.getY(); //kineticMoveMode = false; } /** * * @param e */ public void mouseDragged(MouseEvent e) { if(controller.getTool() == Scatterplot3D.Tool.ROTATE){ this.rotateWithDrag(e.getX(), e.getY(), e); } else if(controller.getTool() == Scatterplot3D.Tool.MOVE){ this.moveWithDrag(e.getX(), e.getY(), e); } else if(controller.getTool() == Scatterplot3D.Tool.SELECT){ mouseDragged = true; mouseX = e.getX(); mouseY = e.getY(); this.repaint(); } } boolean mouseDragged = false; /** * * @param e */ public void mouseReleased(MouseEvent e) { if(System.currentTimeMillis() - lastDragEventTime < MOVE_TIME_LIMIT){ kineticMovement = movement.restartKineticMove(); } else { kineticMovement.stop(); } if (e.getButton() == MouseEvent.BUTTON1 && mouseDragged == true) { selectGroup(e, mousePressX, mousePressY); mouseDragged = false; this.repaint(); } } /** * * @param e */ public void mouseWheelMoved(MouseWheelEvent e) { this.zoom(e.getWheelRotation() / 10.0); } private void zoom(double value){ double[] pov = projection.getPointOfView(); pov[2] -= value; projection.setPointOfView(pov); this.repaint(); } private void clearSelections() { if (selectedPoints == null) return; for (DataPoint dp : selectedPoints) { if (dp != null) dp.selected = false; } selectedPoints.clear(); controller.getAnnotateList().setSelectedListContentAsDataPoints(selectedPoints, this, false, controller.getFrame().getDatas().get(0)); } private void selectOne(MouseEvent e) { clearSelections(); selectedPoints = DataPoint.getNearest(e.getX(), e.getY(), controller.getDataModel().getDataArray(), 8); for (DataPoint dp : selectedPoints) { if (dp != null) dp.selected = true; } controller.getAnnotateList().setSelectedListContentAsDataPoints(selectedPoints, this, true, controller.getFrame().getDatas().get(0)); } private void addToSelections(MouseEvent e) { LinkedList<DataPoint> newSelection = DataPoint.getNearest(e.getX(), e.getY(), controller.getDataModel().getDataArray(), 4); for (DataPoint dp : newSelection) { if (dp != null) { if (dp.selected == true) { dp.selected = false; selectedPoints.remove(dp); } else { dp.selected = true; selectedPoints.add(dp); } } } controller.getAnnotateList().setSelectedListContentAsDataPoints(selectedPoints, this, true, controller.getFrame().getDatas().get(0)); } private void selectGroup(MouseEvent e, int x1, int y1) { clearSelections(); selectedPoints = DataPoint.getGroup(x1, y1, e.getX(), e.getY(), controller.getDataModel().getDataArray()); for (DataPoint dp : selectedPoints) { if (dp != null) dp.selected = true; } controller.getAnnotateList().setSelectedListContentAsDataPoints(selectedPoints, this, true, controller.getFrame().getDatas().get(0)); } class PopupListener extends MouseAdapter { JPopupMenu popup; PopupListener(JPopupMenu popupMenu) { popup = popupMenu; } public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { int x = e.getX(); int y = e.getY(); if (selectedPoints.isEmpty()) { hideSelected.setEnabled(false); invertSelection.setEnabled(false); showAll.setEnabled(false); } else { hideSelected.setEnabled(true); invertSelection.setEnabled(true); showAll.setEnabled(true); } popup.show(e.getComponent(), x, y); } } } /** * * @param e */ public void actionPerformed(ActionEvent e) { if (e.getSource() == hideSelected) { for (Drawable d : selectedPoints) { d.hidden = true; } } else if (e.getSource() == showAll) { for (Drawable d : controller.getDataModel().getDataArray()) { d.hidden = false; } } else if (e.getSource() == invertSelection) { selectedPoints = new LinkedList<DataPoint>(); for (Drawable d : controller.getDataModel().getDataArray()) { d.selected = !d.selected; if (d instanceof DataPoint && d.selected) selectedPoints.add((DataPoint)d); } } else if (e.getSource() == saveAs) { this.controller.saveAs(); } this.repaint(); } public void createPopupMenu() { //Create the popup menu. JPopupMenu popup = new JPopupMenu(); hideSelected = new JMenuItem("Hide selected"); hideSelected.addActionListener(this); popup.add(hideSelected); showAll = new JMenuItem("Show all points"); showAll.addActionListener(this); popup.add(showAll); invertSelection = new JMenuItem("Invert selection"); invertSelection.addActionListener(this); popup.add(invertSelection); saveAs = new JMenuItem("Save as..."); saveAs.addActionListener(this); popup.add(saveAs); //Add listener to the text area so the popup menu can come up. MouseListener popupListener = new PopupListener(popup); this.addMouseListener(popupListener); } public void propertyChange(PropertyChangeEvent evt) { if(evt instanceof SelectionEvent && !evt.getSource().equals(this) && ((SelectionEvent)evt).getData() == controller.getFrame().getDatas().get(0)){ updateSelectedFromApplication(); this.repaint(); } } private void updateSelectedFromApplication() { Drawable[] drawables = controller.getDataModel().getDataArray(); this.clearSelections(); for (int index : application.getSelectionManager(). getSelectionManager(controller.getFrame().getDatas().get(0)).getSelectionAsRows()){ for(Drawable drawable : drawables){ if(drawable instanceof DataPoint){ DataPoint dp = (DataPoint)drawable; if(dp.getIndex() == index){ dp.selected = true; selectedPoints.add(dp); break; } } } } controller.getAnnotateList().setSelectedListContentAsDataPoints(selectedPoints, this, false, controller.getFrame().getDatas().get(0)); } }