/* * Copyright 2011 University of Toronto * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package medsavant.wikipathways.app; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.*; import javax.swing.border.Border; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import com.healthmarketscience.sqlbuilder.BinaryCondition; import com.healthmarketscience.sqlbuilder.ComboCondition; import com.healthmarketscience.sqlbuilder.Condition; import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn; import medsavant.wikipathways.WikiPathwaysConditionGenerator; import org.apache.batik.swing.JSVGCanvas; import org.apache.batik.swing.gvt.GVTTreeRendererAdapter; import org.apache.batik.swing.gvt.GVTTreeRendererEvent; import org.ut.biolab.medsavant.client.project.ProjectController; import org.ut.biolab.medsavant.client.view.util.ViewUtil; import org.w3c.dom.*; import org.xml.sax.SAXException; import org.ut.biolab.medsavant.shared.db.TableSchema; import org.ut.biolab.medsavant.shared.format.BasicVariantColumns; import org.ut.biolab.medsavant.shared.model.RangeCondition; import org.ut.biolab.mfiume.query.view.SearchConditionItemView; /** * * @author AndrewBrook */ public class Viewer extends JPanel { private static final Logger LOG = Logger.getLogger(Viewer.class.getName()); private Loader loader; private PathwaysBrowser browser; private JScrollPane scrollPane; private JScrollPane infoScroll; private JPanel infoPanel; private JLabel infoLabel; private JScrollPane treeScroll; private JPanel rightPanel; private JTree dataTree; //private JLabel jumpLocationButton; private JLabel linkOutButton; private JLabel jumpPathwayButton; private ExtendedJSVGCanvas svgCanvas; private Document gpmlDoc; private Node pathway; //private NodeList comments; //private NodeList dataNodes; //private NodeList lines; private ArrayList<DataNode> dataNodes = new ArrayList<DataNode>(); private ArrayList<Rectangle> recs = new ArrayList<Rectangle>(); private String version; private Gene jumpGene; private String jumpPathway; private String linkOutUrl; private Point start; private int initialVerticalScroll = 0; private int initialHorizontalScroll = 0; private boolean hasPathway = false; private String pathwayString; List<Gene> genes; DbColumn positionCol; DbColumn chromCol; Viewer(Loader loader) { TableSchema table = ProjectController.getInstance().getCurrentVariantTableSchema(); positionCol = table.getDBColumn(BasicVariantColumns.POSITION.getColumnName()); chromCol = table.getDBColumn(BasicVariantColumns.CHROM.getColumnName()); this.loader = loader; this.setLayout(new BorderLayout()); this.setBackground(Color.white); //scrollPane scrollPane = new JScrollPane(); scrollPane.setMinimumSize(new Dimension(200, 50)); scrollPane.setPreferredSize(new Dimension(10000, 10000)); scrollPane.getViewport().setBackground(Color.white); this.add(scrollPane, BorderLayout.CENTER); //svgCanvas svgCanvas = new ExtendedJSVGCanvas(); svgCanvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC); scrollPane.getViewport().add(svgCanvas); //treeScroll treeScroll = new JScrollPane(); treeScroll.getViewport().setLayout(new FlowLayout(FlowLayout.LEFT)); treeScroll.getViewport().setBackground(Color.white); treeScroll.setMinimumSize(new Dimension(200, 200)); //infoScroll infoScroll = new JScrollPane(); infoScroll.setBackground(Color.white); infoScroll.getViewport().setBackground(Color.white); infoScroll.setMaximumSize(new Dimension(100, 100)); infoScroll.setPreferredSize(new Dimension(100, 100)); //infoPanel infoPanel = new JPanel(); infoPanel.setLayout(new GridBagLayout()); infoPanel.setBackground(Color.white); infoScroll.getViewport().add(infoPanel); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.0; gbc.weighty = 0.0; gbc.gridx = 0; gbc.gridy = 0; Border buttonBorder = BorderFactory.createEmptyBorder(3, 5, 0, 5); //jumpLocationButton /*jumpLocationButton = new JLabel("<HTML><B>Jump to Gene Location</B></HTML>"); jumpLocationButton.setForeground(Color.BLUE); jumpLocationButton.setBackground(Color.WHITE); jumpLocationButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); jumpLocationButton.setMaximumSize(new Dimension(25, 200)); jumpLocationButton.setBorder(BorderFactory.createCompoundBorder(buttonBorder,buttonBorder)); jumpLocationButton.setVisible(false); jumpLocationButton.addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent e) { jumpToGene(); } public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} }); infoPanel.add(jumpLocationButton, gbc);*/ //jumpPathwayButton jumpPathwayButton = new JLabel("<HTML><B>Jump to Pathway</B></HTML>"); jumpPathwayButton.setForeground(Color.BLUE); jumpPathwayButton.setBackground(Color.WHITE); jumpPathwayButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); jumpPathwayButton.setMaximumSize(new Dimension(25, 200)); jumpPathwayButton.setBorder(BorderFactory.createCompoundBorder(buttonBorder, buttonBorder)); jumpPathwayButton.setVisible(false); jumpPathwayButton.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { jumpToPathway(); } }); gbc.gridy = 1; infoPanel.add(jumpPathwayButton, gbc); //linkOutButton linkOutButton = new JLabel("<HTML><B>Link to Web Page</B></HTML>"); linkOutButton.setForeground(Color.BLUE); linkOutButton.setBackground(Color.WHITE); linkOutButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); linkOutButton.setMaximumSize(new Dimension(25, 200)); linkOutButton.setBorder(BorderFactory.createCompoundBorder(buttonBorder, buttonBorder)); linkOutButton.setVisible(false); linkOutButton.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { linkOut(); } }); gbc.gridy = 2; infoPanel.add(linkOutButton, gbc); //infoLabel infoLabel = new JLabel(); infoLabel.setBackground(Color.white); Border paddingBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5); infoLabel.setBorder(BorderFactory.createCompoundBorder(paddingBorder, paddingBorder)); gbc.gridy = 3; infoPanel.add(infoLabel, gbc); //filler JPanel filler = new JPanel(); filler.setBackground(Color.white); gbc.gridy = 4; gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; infoPanel.add(filler, gbc); //rightPanel rightPanel = new JPanel(); rightPanel.setLayout(new BorderLayout()); rightPanel.add(treeScroll, BorderLayout.NORTH); rightPanel.add(infoScroll, BorderLayout.CENTER); rightPanel.setMinimumSize(new Dimension(350, 350)); rightPanel.setPreferredSize(new Dimension(350, 350)); rightPanel.setMaximumSize(new Dimension(350, 350)); this.add(rightPanel, BorderLayout.EAST); svgCanvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() { @Override public void gvtRenderingCompleted(GVTTreeRendererEvent e) { revalidate(); } }); svgCanvas.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { tryClick(e.getPoint()); } @Override public void mousePressed(MouseEvent e) { start = e.getLocationOnScreen(); initialVerticalScroll = scrollPane.getVerticalScrollBar().getValue(); initialHorizontalScroll = scrollPane.getHorizontalScrollBar().getValue(); } }); svgCanvas.addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent e) { int x = (int) (e.getLocationOnScreen().getX() - start.getX()); int y = (int) (e.getLocationOnScreen().getY() - start.getY()); int newVert = Math.max(Math.min(scrollPane.getVerticalScrollBar().getMaximum(), initialVerticalScroll - y), 0); int newHor = Math.max(Math.min(scrollPane.getHorizontalScrollBar().getMaximum(), initialHorizontalScroll - x), 0); scrollPane.getVerticalScrollBar().setValue(newVert); scrollPane.getHorizontalScrollBar().setValue(newHor); } @Override public void mouseMoved(MouseEvent e) { svgCanvas.setCursor(new Cursor(Cursor.HAND_CURSOR)); } }); } //must always be set public void setBrowser(PathwaysBrowser browser) { this.browser = browser; } public void setPathway(URI svgUri, URI gpmlUri, String pathway) { this.pathwayString = pathway; hasPathway = false; jumpGene = null; jumpPathway = null; linkOutUrl = null; clearInfo(); svgCanvas.setURI(svgUri.toString()); getGPML(gpmlUri); getGeneInfo(); //applyFilter(); hasPathway = true; String gString = ""; genes = new ArrayList<Gene>(); List<String> geneNames = new ArrayList<String>(); for (DataNode n : dataNodes) { if (n.hasGene()) { genes.add(n.getGene()); geneNames.add(n.getGene().getName()); } } String explanation = "WikiPathways searches for variants in genes whose gene products occur in the selected pathway.<br/><br/>" + "Pathway " + pathway + " maps to the following genes <i>" + ViewUtil.ellipsizeListAfter(geneNames,20) + "</i>"; WikiPathwaysConditionGenerator.getInstance().setPathway(pathway, explanation); } public void applyFilter() { if (!hasPathway) { return; } loader.setVisible(true); loader.setMessage("Applying Filter"); genes = new ArrayList<Gene>(); for (DataNode n : dataNodes) { if (n.hasGene()) { genes.add(n.getGene()); } } TableSchema table = null; table = ProjectController.getInstance().getCurrentVariantTableSchema(); positionCol = table.getDBColumn(BasicVariantColumns.POSITION.getColumnName()); chromCol = table.getDBColumn(BasicVariantColumns.CHROM.getColumnName()); loader.setVisible(false); } public String getPathway() { return pathwayString; } public Condition getCondition() { Condition[] results = new Condition[genes.size()]; int i = 0; for (Gene g : genes) { Condition[] current = new Condition[2]; current[0] = BinaryCondition.equalTo(chromCol, "chr" + g.getChromosome()); current[1] = new RangeCondition(positionCol, Math.min(g.getStart(), g.getEnd()), Math.max(g.getStart(), g.getEnd())); results[i++] = ComboCondition.and(current); } return ComboCondition.or(results); } private void getGeneInfo() { loader.setMessage("Getting gene information"); ArrayList<DataNode> entrezNodes = new ArrayList<DataNode>(); //determine url String urlString = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=gene&id="; for (DataNode n : dataNodes) { String db = n.getAttribute("Xref", "Database"); String id = n.getAttribute("Xref", "ID"); if (db == null || id == null) { continue; } if (db.equals("Entrez Gene")) { urlString += id + ","; entrezNodes.add(n); } else if (db.equals("Ensembl") || db.equals("Ensembl Human")) { //FIXME: this is a massive hack...is there a better way? //START ENSEMBL LOOKUP////////////////////////////////////////// boolean success = false; int retries = 5; String ensemblUrlString = "http://www.ensembl.org/Gene/Summary?g=" + id; String rangeString = ""; while (!success && retries > 0) { try { URL url = new URL(ensemblUrlString); HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); httpConnection.setInstanceFollowRedirects(false); httpConnection.connect(); int responseCode = httpConnection.getResponseCode(); String header = httpConnection.getHeaderField("Location"); if (header.contains(";r=")) { success = true; rangeString = header.substring(header.indexOf(";r=") + 3); if (rangeString.contains(";")) { rangeString = rangeString.substring(0, rangeString.indexOf(";")); } } else { if (header.startsWith("http://")) { ensemblUrlString = header; } else if (header.startsWith("/")) { ensemblUrlString = url.getProtocol() + "://" + url.getHost() + header; } else { retries = 0; } } } catch (MalformedURLException ex) { LOG.log(Level.SEVERE, null, ex); } catch (IOException ex) { LOG.log(Level.SEVERE, null, ex); } retries--; } if (success) { String chrom = rangeString.substring(0, rangeString.indexOf(":")); String startRange = rangeString.substring(rangeString.indexOf(":") + 1, rangeString.indexOf("-")); String endRange = rangeString.substring(rangeString.indexOf("-") + 1, rangeString.length()); n.setEnsemblGeneInfo(id, chrom, startRange, endRange); } //END ENSEMBL LOOKUP//////////////////////////////////////////// } } urlString += "&retmode=xml"; if (entrezNodes.isEmpty()) { return; } //get xml string BufferedReader reader; String xmlString = ""; try { reader = new BufferedReader(new InputStreamReader(new URL(urlString).openStream())); String line = reader.readLine(); while (line != null) { if (!line.startsWith("<?xml") && !line.startsWith("<!DOCTYPE")) { xmlString += line; } line = reader.readLine(); } } catch (MalformedURLException e) { //todo return; } catch (IOException e) { //todo return; } //create document Element root = null; try { root = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(xmlString.getBytes())).getDocumentElement(); } catch (ParserConfigurationException ex) { Logger.getLogger(Viewer.class.getName()).log(Level.SEVERE, null, ex); return; } catch (SAXException ex) { Logger.getLogger(Viewer.class.getName()).log(Level.SEVERE, null, ex); return; } catch (IOException ex) { Logger.getLogger(Viewer.class.getName()).log(Level.SEVERE, null, ex); return; } //TODO: can we really make assumptions about ordering of xml? NodeList docSumList = root.getElementsByTagName("DocSum"); for (int i = 0; i < docSumList.getLength(); i++) { entrezNodes.get(i).setEntrezGeneInfo((Element) docSumList.item(i)); } } private void getGPML(URI uri) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); gpmlDoc = db.parse(uri.toString()); } catch (ParserConfigurationException pce) { //ClientLogger.log(Viewer.class,"ERROR",Level.SEVERE); //TODO } catch (SAXException se) { //ClientLogger.log(Viewer.class,"ERROR",Level.SEVERE); //TODO } catch (IOException ioe) { //ClientLogger.log(Viewer.class,"ERROR",Level.SEVERE); //TODO } parseGPML(); generateLinks(); createTree(); } private void parseGPML() { gpmlDoc.getDocumentElement().normalize(); NodeList nodes = gpmlDoc.getElementsByTagName("Pathway"); pathway = nodes.item(0); version = ((Element) pathway).getAttribute("xmlns"); if (version == null || version.equals("")) { version = "unknown"; } else if (version.indexOf("2008") != -1) { version = "2008"; } else if (version.indexOf("2010") != -1) { version = "2010"; } else { version = "unknown"; } //comments = ((Element) pathway).getElementsByTagName("Comment"); dataNodes.clear(); NodeList dataNodeList = ((Element) pathway).getElementsByTagName("DataNode"); for (int i = 0; i < dataNodeList.getLength(); i++) { dataNodes.add(new DataNode((Element) (dataNodeList.item(i)))); } //lines = ((Element) pathway).getElementsByTagName("Line"); } private void generateLinks() { double scale = 1.0; if (version.equals("2008")) { scale = 0.0667; } recs.clear(); for (int i = 0; i < dataNodes.size(); i++) { DataNode node = dataNodes.get(i); if (!node.hasSubNode("Graphics")) { recs.add(null); continue; } float centerX = Float.valueOf(node.getAttribute("Graphics", "CenterX")); float centerY = Float.valueOf(node.getAttribute("Graphics", "CenterY")); float width = Float.valueOf(node.getAttribute("Graphics", "Width")); float height = Float.valueOf(node.getAttribute("Graphics", "Height")); recs.add(new Rectangle((int) (scale * (centerX - width / 2.0)), (int) (scale * (centerY - height / 2.0)), (int) (scale * width), (int) (scale * height))); } } private void tryClick(Point p) { int i = searchShapes(p); if (i == -1) { return; } fillInfo(dataNodes.get(i)); } private void clearInfo() { //jumpLocationButton.setVisible(false); jumpPathwayButton.setVisible(false); linkOutButton.setVisible(false); infoLabel.setText(""); rightPanel.revalidate(); } private void fillInfo(DataNode dataNode) { /*if(dataNode.hasGene()){ jumpLocationButton.setVisible(true); jumpGene = dataNode.getGene(); } else { jumpLocationButton.setVisible(false); jumpGene = null; }*/ if (dataNode.hasWikiPathway()) { jumpPathwayButton.setVisible(true); jumpPathway = dataNode.getWikiPathway(); } else { jumpPathwayButton.setVisible(false); jumpPathway = null; } linkOutUrl = dataNode.getLinkOut(); linkOutButton.setVisible(linkOutUrl != null); infoLabel.setText(dataNode.getInfoString()); rightPanel.revalidate(); } private void createTree() { DefaultMutableTreeNode root = new DefaultMutableTreeNode("dataTree"); Map<String, ArrayList<DataNode>> treeMap = new HashMap<String, ArrayList<DataNode>>(); for (DataNode n : dataNodes) { String type = n.getType(); if (treeMap.get(type) == null) { treeMap.put(type, new ArrayList<DataNode>()); } treeMap.get(type).add(n); } Iterator it = treeMap.keySet().iterator(); while (it.hasNext()) { String key = (String) it.next(); ArrayList<DataNode> list = treeMap.get(key); Collections.sort(list); DefaultMutableTreeNode node = new DefaultMutableTreeNode(key); for (DataNode n : list) { node.add(new DefaultMutableTreeNode(n)); } root.add(node); } dataTree = new JTree(root); dataTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); dataTree.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { int selRow = dataTree.getRowForLocation(e.getX(), e.getY()); TreePath selPath = dataTree.getPathForLocation(e.getX(), e.getY()); if (selRow != -1) { if (e.getClickCount() == 2) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getLastPathComponent(); if (node == null) { return; } if (node.isLeaf()) { DataNode dataNode = (DataNode) node.getUserObject(); fillInfo(dataNode); treeScroll.getVerticalScrollBar().setValue(treeScroll.getVerticalScrollBar().getMinimum()); } } } } }); dataTree.setRootVisible(false); treeScroll.getViewport().removeAll(); treeScroll.getViewport().add(dataTree); // rightPanel.setDividerLocation(0.5); } private void jumpToGene() { if (jumpGene == null) { return; } int startGene = jumpGene.getStart(); int endGene = jumpGene.getEnd(); if (startGene > endGene) { int temp = startGene; startGene = endGene; endGene = temp; } //TODO: what if references don't start with "chr"? //TODO: change to plugin api //if(LocationController.getInstance().isGenomeLoaded()){ // LocationController.getInstance().setLocation("chr" + jumpGene.getChromosome(), new Range(startGene, endGene)); //} } private void jumpToPathway() { if (jumpPathway == null) { return; } browser.loadPathway(jumpPathway); } private void linkOut() { if (linkOutUrl == null) { return; } if (!java.awt.Desktop.isDesktopSupported()) { JOptionPane.showMessageDialog(this, "<HTML>This operation is not supported by your computer.<BR>Web page: " + linkOutUrl + "</HTML>"); return; } java.awt.Desktop desktop = java.awt.Desktop.getDesktop(); try { java.net.URI uri = new java.net.URI(linkOutUrl); desktop.browse(uri); } catch (Exception e) { //TODO: something } } private int searchShapes(Point p) { for (int i = 0; i < recs.size(); i++) { if (recs.get(i) != null && recs.get(i).contains(p)) { return i; } } return -1; } private class ExtendedJSVGCanvas extends JSVGCanvas { ExtendedJSVGCanvas() { super(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.red); for (Rectangle rec : recs) { if (rec == null) { continue; } g.drawRect((int) rec.getX(), (int) rec.getY(), (int) rec.getWidth(), (int) rec.getHeight()); } } } }