package versusSNP.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JWindow;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.MouseInputAdapter;
import versusSNP.Document;
import versusSNP.blast.util.SNPList;
import versusSNP.genome.Genome;
import versusSNP.genome.SNP;
import versusSNP.util.Utils;
public class ScatterPlotPanel extends JPanel implements Observer {
private static final long serialVersionUID = 7769309007033963799L;
public static final byte DOTSIZE_SMALL = 1;
public static final byte DOTSIZE_MEDIUM = 2;
public static final byte DOTSIZE_LARGE = 3;
public static final int KUNIT = 1000;
public static final int MUNIT = 1000000;
public static final Color SYNONYMOUS = Color.BLUE;
public static final Color NON_SYNONYMOUS = Color.RED;
public static final Color INSERTION = Color.YELLOW;
public static final Color DELETION = Color.GREEN;
private int xOffset, yOffset, unit, x, y;
private int coordUp, coordDown, coordBias;
private byte dotSize;
private int[] coords_x, coords_y;
private boolean[] dotShow;
private double ratio_x, ratio_y;
private Graphics g;
private Dimension plotSize;
private String xName, yName;
private SNPList sSNPList, nsSNPList, inSNPList, delSNPList;
private Tipster tipster;
class Tipster extends MouseInputAdapter implements ActionListener {
public static final int OFFSET = 2;
private JWindow toolTip;
private JLabel label;
private int pos_x, pos_y;
private JPopupMenu popupMenu;
private JRadioButtonMenuItem smallItem;
private JRadioButtonMenuItem mediumItem;
private JRadioButtonMenuItem largeItem;
private JCheckBoxMenuItem sItem, nsItem, inItem, delItem;
public Tipster() {
super();
toolTip = new JWindow();
label = new JLabel();
popupMenu = new JPopupMenu();
smallItem = new JRadioButtonMenuItem(UICaption.popup_menuitem_plot_dot_small);
mediumItem = new JRadioButtonMenuItem(UICaption.popup_menuitem_plot_dot_medium);
largeItem = new JRadioButtonMenuItem(UICaption.popup_menuitem_plot_dot_large);
sItem = new JCheckBoxMenuItem(UICaption.popup_menuitem_plot_dot_s);
nsItem = new JCheckBoxMenuItem(UICaption.popup_menuitem_plot_dot_ns);
inItem = new JCheckBoxMenuItem(UICaption.popup_menuitem_plot_dot_in);
delItem = new JCheckBoxMenuItem(UICaption.popup_menuitem_plot_dot_del);
ButtonGroup group = new ButtonGroup();
group.add(smallItem);
group.add(mediumItem);
group.add(largeItem);
popupMenu.add(smallItem);
popupMenu.add(mediumItem);
popupMenu.add(largeItem);
popupMenu.addSeparator();
popupMenu.add(sItem);
popupMenu.add(nsItem);
popupMenu.add(inItem);
popupMenu.add(delItem);
smallItem.addActionListener(this);
mediumItem.addActionListener(this);
largeItem.addActionListener(this);
sItem.addActionListener(this);
nsItem.addActionListener(this);
inItem.addActionListener(this);
delItem.addActionListener(this);
label.setHorizontalAlignment(JLabel.CENTER);
label.setOpaque(true);
label.setBackground(UIManager.getColor("ToolTip.background"));
label.setBorder(UIManager.getBorder("ToolTip.border"));
toolTip.add(label);
setOpaque(true);
}
@Override
public void mouseMoved(MouseEvent e) {
if (ScatterPlotPanel.this.x == 0 || ScatterPlotPanel.this.y == 0 || plotSize == null)
return;
Point p = e.getPoint();
if (p.x < xOffset || p.x > plotSize.width - xOffset) return;
if (p.y < yOffset || p.y > plotSize.height - yOffset) return;
pos_x = new Double((p.x - xOffset) / ratio_x).intValue();
pos_y = new Double((plotSize.height - p.y - yOffset) / ratio_y).intValue();
showToolTip(p);
}
@Override
public void mousePressed(MouseEvent e) {
if (e.getModifiers() == MouseEvent.BUTTON3_MASK) {
smallItem.setText(UICaption.popup_menuitem_plot_dot_small);
mediumItem.setText(UICaption.popup_menuitem_plot_dot_medium);
largeItem.setText(UICaption.popup_menuitem_plot_dot_large);
sItem.setText(UICaption.popup_menuitem_plot_dot_s);
nsItem.setText(UICaption.popup_menuitem_plot_dot_ns);
inItem.setText(UICaption.popup_menuitem_plot_dot_in);
delItem.setText(UICaption.popup_menuitem_plot_dot_del);
smallItem.setSelected(ScatterPlotPanel.this.dotSize == DOTSIZE_SMALL);
mediumItem.setSelected(ScatterPlotPanel.this.dotSize == DOTSIZE_MEDIUM);
largeItem.setSelected(ScatterPlotPanel.this.dotSize == DOTSIZE_LARGE);
sItem.setSelected(ScatterPlotPanel.this.dotShow[0]);
nsItem.setSelected(ScatterPlotPanel.this.dotShow[1]);
inItem.setSelected(ScatterPlotPanel.this.dotShow[2]);
delItem.setSelected(ScatterPlotPanel.this.dotShow[3]);
popupMenu.show(ScatterPlotPanel.this, e.getX(), e.getY());
}
super.mousePressed(e);
}
public void showToolTip(Point p) {
label.setText(toCoord());
toolTip.pack();
toolTip.setLocation(ScatterPlotPanel.this.getLocationOnScreen().x + p.x + OFFSET,
ScatterPlotPanel.this.getLocationOnScreen().y + p.y + OFFSET);
toolTip.setVisible(true);
}
private String toCoord() {
return new StringBuffer("<html>").append(ScatterPlotPanel.this.xName)
.append(" ").append(pos_x).append("<br>").append(ScatterPlotPanel.this.yName)
.append(" ").append(pos_y).append("</html>").toString();
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == smallItem)
ScatterPlotPanel.this.dotSize = DOTSIZE_SMALL;
else if (e.getSource() == mediumItem)
ScatterPlotPanel.this.dotSize = DOTSIZE_MEDIUM;
else if (e.getSource() == largeItem)
ScatterPlotPanel.this.dotSize = DOTSIZE_LARGE;
else if (e.getSource() == sItem)
ScatterPlotPanel.this.dotShow[0] =! ScatterPlotPanel.this.dotShow[0];
else if (e.getSource() == nsItem)
ScatterPlotPanel.this.dotShow[1] =! ScatterPlotPanel.this.dotShow[1];
else if (e.getSource() == inItem)
ScatterPlotPanel.this.dotShow[2] =! ScatterPlotPanel.this.dotShow[2];
else if (e.getSource() == delItem)
ScatterPlotPanel.this.dotShow[3] =! ScatterPlotPanel.this.dotShow[3];
ScatterPlotPanel.this.repaint();
}
}
public ScatterPlotPanel() {
super();
setBackground(Color.WHITE);
setOpaque(true);
xOffset = 20;
yOffset = 20;
coordUp = 5;
coordDown = -15;
coordBias = -5;
unit = KUNIT;
dotSize = 2;
dotShow = new boolean[]{true,true,true,true};
tipster = new Tipster();
addMouseListener(tipster);
addMouseMotionListener(tipster);
}
public ScatterPlotPanel(int x, int y) {
this();
coords(x, y);
}
public void setQuerySubjectGenome(Genome qGenome, Genome sGenome) {
this.x = qGenome.getSize();
this.y = sGenome.getSize();
this.xName = qGenome.getName();
this.yName = sGenome.getName();
}
@Override
public void paint(Graphics g) {
super.paint(g);
if (x == 0 || y == 0) return;
this.g = g;
unit();
coords();
int[] temp_x = Utils.arraycopy(coords_x);
int[] temp_y = Utils.arraycopy(coords_y);
adjustCoords(temp_x, temp_y);
g.setColor(Color.BLACK);
drawCoords(temp_x, coords_x, SwingConstants.HORIZONTAL);
drawCoords(temp_y, coords_x, SwingConstants.VERTICAL);
drawLine(temp_x, SwingConstants.HORIZONTAL);
drawLine(temp_y, SwingConstants.VERTICAL);
drawCaption(xName, SwingConstants.HORIZONTAL);
drawCaption(yName, SwingConstants.VERTICAL);
if (sSNPList == null && nsSNPList == null && inSNPList == null && delSNPList == null) return;
if (dotShow[0])
drawSNP(sSNPList);
if (dotShow[1])
drawSNP(nsSNPList);
if (dotShow[2])
drawSNP(inSNPList);
if (dotShow[3])
drawSNP(delSNPList);
}
@Override
public void update(Observable o, Object arg) {
Document document = (Document)o;
if (document.getQueryGenome() != null && document.getSubjectGenome() != null) {
setQuerySubjectGenome(document.getQueryGenome(), document.getSubjectGenome());
sSNPList = document.getQueryGenome().getSSNPList(document.getSubjectGenome());
nsSNPList = document.getQueryGenome().getNsSNPList(document.getSubjectGenome());
inSNPList = document.getQueryGenome().getInSNPList(document.getSubjectGenome());
delSNPList = document.getQueryGenome().getDelSNPList(document.getSubjectGenome()); }
}
public void hideTipster() {
if (tipster.toolTip != null && tipster.toolTip.isVisible())
tipster.toolTip.setVisible(false);
}
private void drawLine(int[] coords, int constant) {
switch (constant) {
case SwingConstants.VERTICAL:
g.drawLine(xOffset, plotSize.height-yOffset, xOffset, plotSize.height-yOffset-coords[coords.length-1]); break;
case SwingConstants.HORIZONTAL:
g.drawLine(xOffset, plotSize.height-yOffset, xOffset+coords[coords.length-1], plotSize.height-yOffset); break;
}
}
private void drawCaption(String name, int constant) {
switch (constant) {
case SwingConstants.VERTICAL:
g.drawString(name, xOffset, yOffset); break;
case SwingConstants.HORIZONTAL:
Graphics2D g2d = (Graphics2D)g;
g2d.translate(plotSize.width-xOffset+10, plotSize.height-yOffset);
g2d.rotate(-Math.PI/2);
g2d.drawString(name, 0, 0);
// g2d.drawString(name, plotSize.width-xOffset, plotSize.height-yOffset);
g2d.rotate(Math.PI/2);
g2d.translate(xOffset-plotSize.width-10, yOffset-plotSize.height);
break;
}
}
private void drawCoords(int[] temp_coords, int[] coords, int constant) {
for (int i = 1; i < temp_coords.length; i++) {
int coord = temp_coords[i];
switch (constant) {
case SwingConstants.VERTICAL:
g.drawLine(xOffset, plotSize.height-coord-yOffset, xOffset-coordUp, plotSize.height-coord-yOffset);
g.drawString(Integer.toString(coords[i]/unit), xOffset+coordDown, plotSize.height-coord-yOffset-coordBias);
break;
case SwingConstants.HORIZONTAL:
g.drawLine(coord+xOffset, plotSize.height-yOffset, coord+xOffset, plotSize.height-yOffset+coordUp);
g.drawString(Integer.toString(coords[i]/unit), coord+xOffset+coordBias, plotSize.height-yOffset-coordDown);
break;
}
}
}
private void drawSNP(SNPList snpList) {
for (Iterator<SNP> iter = snpList.iterator(); iter.hasNext();) {
SNP snp = iter.next();
dot(adjustDot(snp.getQPosInGenome(), SwingConstants.HORIZONTAL, ratio_x),
adjustDot(snp.getSPosInGenome(), SwingConstants.VERTICAL, ratio_y), snp.getByteType());
}
}
private void adjustCoords() {
adjustCoords(coords_x[coords_x.length-1], coords_y[coords_y.length-1]);
}
private void adjustCoords(int[] coords_x, int[] coords_y) {
adjustCoords(coords_x, coords_y, coords_x[coords_x.length-1], coords_y[coords_y.length-1]);
}
private void adjustCoords(int x, int y) {
adjustCoords(coords_x, coords_y, x, y);
}
private void adjustCoords(int[] coords_x, int[] coords_y, double x, double y) {
plotSize = getSize();
if (x == 0 || y == 0) return;
ratio_x = plotSize.width > xOffset * 2 ? (plotSize.width - xOffset * 2) / x : 1;
ratio_y = plotSize.height > yOffset * 2 ? (plotSize.height - yOffset * 2) / y : 1;
adjust(coords_x, ratio_x);
adjust(coords_y, ratio_y);
}
private void unit(int x, int y) {
if (x / KUNIT > 100)
unit = MUNIT;
else
unit = KUNIT;
}
private void unit() {
unit(x, y);
}
private void coords(int x, int y) {
coords_x = x%unit == 0 ? new int[x/unit + 1] : new int[x/unit + 2];
coords_y = y%unit == 0 ? new int[y/unit + 1] : new int[y/unit + 2];
coord(coords_x, x);
coord(coords_y, y);
}
private void coords() {
coords(x, y);
}
private void coord(int[] coords, int l) {
int j = 0;
for (int i = 0; i < l+unit; i+=unit)
coords[j++] = i;
}
private void adjust(int[] coords, double ratio) {
for (int i = 0; i < coords.length; i++)
coords[i] = ratio(coords[i], ratio);
}
private int adjustDot(int i, int constant, double ratio) {
switch (constant) {
case SwingConstants.VERTICAL: return plotSize.height-yOffset-ratio(i, ratio);
case SwingConstants.HORIZONTAL: return xOffset+ratio(i, ratio);
}
return 0;
}
private int ratio(int i, double ratio) {
return new Double(i * ratio).intValue();
}
private void dot(int x, int y) {
g.fillRect(x, y, dotSize, dotSize);
}
private void dot(int x, int y, Color color) {
g.setColor(color);
dot(x, y);
}
private void dot(int x, int y, byte type) {
switch (type) {
case SNP.SYNONYMOUS: dot(x, y, SYNONYMOUS); break;
case SNP.NON_SYNONYMOUS: dot(x, y, NON_SYNONYMOUS); break;
case SNP.INSERTION: dot(x, y, INSERTION); break;
case SNP.DELETION: dot(x, y, DELETION); break;
}
}
}