Java tutorial
/* * Copyright (c) 2010 The Jackson Laboratory * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software. If not, see <http://www.gnu.org/licenses/>. */ package org.jax.bham.test; import java.awt.BorderLayout; import java.awt.Point; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JSeparator; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import org.jax.bham.BhamApplication; import org.jax.bham.infer.PlotPhylogeneticTreeAction; import org.jax.bham.util.JFreeChartUtil; import org.jax.geneticutil.data.BasePairInterval; import org.jax.geneticutil.data.CompositeRealValuedBasePairInterval; import org.jax.geneticutil.data.RealValuedBasePairInterval; import org.jax.geneticutil.gui.GoToMouseIntervalInCGDGBrowseAction; import org.jax.geneticutil.gui.GoToMouseIntervalInCGDSnpDatabaseAction; import org.jax.geneticutil.gui.GoToMouseIntervalInUCSCBrowserAction; import org.jax.haplotype.analysis.PhylogenyAssociationTest; import org.jax.haplotype.analysis.visualization.ChromosomeHistogramValues; import org.jax.haplotype.analysis.visualization.GenomicGraphFactory; import org.jax.haplotype.analysis.visualization.HighlightedSnpInterval; import org.jax.haplotype.analysis.visualization.OcclusionFilter; import org.jax.haplotype.phylogeny.data.PhylogenyTestResult; import org.jax.haplotype.phylogeny.data.PhylogenyTreeEdge; import org.jax.haplotype.phylogeny.data.PhylogenyTreeNode; import org.jax.util.concurrent.MultiTaskProgressPanel; import org.jax.util.datastructure.SequenceUtilities; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; /** * A panel for graphing phylogeney association results * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public class PhylogenyAssociationTestGraphPanel extends JPanel { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 618496235032301159L; private final PhylogenyAssociationTest testToPlot; private final GenomicGraphFactory graphFactory = new GenomicGraphFactory(); private final JComboBox chromosomeComboBox; private final ChartPanel chartPanel = new ChartPanel(null, true) { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 2353239091616674857L; /** * {@inheritDoc} */ @Override protected void displayPopupMenu(int x, int y) { PhylogenyAssociationTestGraphPanel.this.updateClickPosition(x, y); super.displayPopupMenu(x, y); } }; private volatile int lastClickedIntervalIndex; private volatile List<JComponent> lastMenuItems = Collections.emptyList(); private final Map<Integer, List<PhylogenyTestResult>> chromosomeResultsCache = Collections .synchronizedMap(new HashMap<Integer, List<PhylogenyTestResult>>()); /** * Constructor * @param testToPlot * the test that we're plotting */ public PhylogenyAssociationTestGraphPanel(PhylogenyAssociationTest testToPlot) { super(new BorderLayout()); this.testToPlot = testToPlot; this.chromosomeComboBox = new JComboBox(); this.initialize(); } private void updateClickPosition(int x, int y) { this.lastClickedIntervalIndex = this.getMaximalIntervalIndex(x, y); // remove the old menu items JPopupMenu popupMenu = this.chartPanel.getPopupMenu(); for (int i = 0; i < this.lastMenuItems.size(); i++) { popupMenu.remove(0); } // now replace the old menus with some new ones this.lastMenuItems = this.createContextMenuItems(); for (int i = 0; i < this.lastMenuItems.size(); i++) { popupMenu.insert(this.lastMenuItems.get(i), i); } } /** * Gets the index of the interval with the highest value that falls * under the given Java2D coordinates * @param x * the Java2D X coordinate * @param y * the Java2D Y coordinate * @return * the interval index */ private int getMaximalIntervalIndex(int x, int y) { int clickIndex = -1; int[] chromosomes = this.getSelectedChromosomes(); if (chromosomes.length == 1) { List<PhylogenyTestResult> selectedPhyloAssociationTests = this.chromosomeResultsCache .get(chromosomes[0]); if (selectedPhyloAssociationTests != null) { RealValuedBasePairInterval[] selectedValuesList = selectedPhyloAssociationTests .toArray(new RealValuedBasePairInterval[selectedPhyloAssociationTests.size()]); Point2D clickedGraphPoint = JFreeChartUtil.java2DPointToGraphPoint(new Point(x, y), this.chartPanel); // exhaustive search for the maximum clicked index (this could // be a binary search, but this seems to perform OK for now) double graphX = clickedGraphPoint.getX(); int valueCount = selectedValuesList.length; double biggestClickedValue = Double.NEGATIVE_INFINITY; for (int i = 0; i < valueCount; i++) { RealValuedBasePairInterval currValue = selectedValuesList[i]; if (currValue.getStartInBasePairs() < graphX && currValue.getEndInBasePairs() > graphX && currValue.getRealValue() > biggestClickedValue) { biggestClickedValue = currValue.getRealValue(); clickIndex = i; } } // if we didn't click on anything grab the nearest item // (again this could and should be faster) if (clickIndex == -1 && valueCount >= 1) { clickIndex = 0; double nearestDistance = Math.min( Math.abs(selectedValuesList[0].getStartInBasePairs() - graphX), Math.abs(selectedValuesList[0].getEndInBasePairs() - graphX)); for (int i = 1; i < valueCount; i++) { BasePairInterval currValue = selectedValuesList[i]; double currDistance = Math.min(Math.abs(currValue.getStartInBasePairs() - graphX), Math.abs(currValue.getEndInBasePairs() - graphX)); if (currDistance < nearestDistance) { nearestDistance = currDistance; clickIndex = i; } } } } } return clickIndex; } /** * a function to initialize the components for this panel */ private void initialize() { this.chromosomeComboBox.addItem("All Chromosomes"); List<Integer> chromoList = SequenceUtilities .toIntegerList(this.testToPlot.getPhylogenyDataSource().getAvailableChromosomes()); Collections.sort(chromoList); for (Integer chromoNum : chromoList) { this.chromosomeComboBox.addItem(chromoNum); } if (!chromoList.isEmpty()) { this.chromosomeComboBox.setSelectedIndex(1); } this.chromosomeComboBox.addItemListener(new ItemListener() { /** * {@inheritDoc} */ public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { PhylogenyAssociationTestGraphPanel.this.chromosomeSelectionChanged(); } } }); JToolBar toolBar = new JToolBar(); toolBar.add(new JLabel("Chromosome:")); // limit the size or the toolbar will try to make the drop-down huge this.chromosomeComboBox.setMaximumSize(this.chromosomeComboBox.getPreferredSize()); toolBar.add(this.chromosomeComboBox); this.add(toolBar, BorderLayout.PAGE_START); this.add(this.chartPanel, BorderLayout.CENTER); this.chromosomeSelectionChanged(); } private int[] getSelectedChromosomes() { // implementation assumes that the first item indicates that all // chromosomes should be displayed and that any other selection is a // specific chromosome if (this.chromosomeComboBox.getSelectedIndex() == 0) { // all are selected int[] allChromosomes = new int[this.chromosomeComboBox.getItemCount() - 1]; for (int i = 0; i < allChromosomes.length; i++) { allChromosomes[i] = (Integer) this.chromosomeComboBox.getItemAt(i + 1); } return allChromosomes; } else { // a single chromosome is selected return new int[] { (Integer) this.chromosomeComboBox.getSelectedItem() }; } } private void chromosomeSelectionChanged() { final int[] selectedChromosomes = this.getSelectedChromosomes(); Thread runTestsThread = new Thread(new Runnable() { /** * {@inheritDoc} */ public void run() { PhylogenyAssociationTestGraphPanel.this.cacheChromosomeTests(selectedChromosomes); SwingUtilities.invokeLater(new Runnable() { /** * {@inheritDoc} */ public void run() { PhylogenyAssociationTestGraphPanel.this.repaintGraphNow(); } }); } }); runTestsThread.start(); } private void cacheChromosomeTests(final int[] chromosomes) { List<Integer> chromosomesToCalculate = SequenceUtilities.toIntegerList(chromosomes); chromosomesToCalculate.removeAll(this.chromosomeResultsCache.keySet()); PerformPhylogenyAssociationTestTask testTask = new PerformPhylogenyAssociationTestTask(this.testToPlot, chromosomesToCalculate); MultiTaskProgressPanel progressTracker = BhamApplication.getInstance().getBhamFrame() .getMultiTaskProgress(); progressTracker.addTaskToTrack(testTask, true); while (testTask.hasMoreElements()) { int nextChromosome = testTask.getNextChromosome(); List<PhylogenyTestResult> results = testTask.nextElement(); List<PhylogenyTestResult> filteredResults = OcclusionFilter.filterOutOccludedIntervals(results, true); PhylogenyAssociationTestGraphPanel.this.chromosomeResultsCache.put(nextChromosome, filteredResults); } } private RealValuedBasePairInterval[] toNegLogResults(List<PhylogenyTestResult> filteredResults) { RealValuedBasePairInterval[] negLogResults = new RealValuedBasePairInterval[filteredResults.size()]; for (int i = 0; i < negLogResults.length; i++) { negLogResults[i] = new CompositeRealValuedBasePairInterval( filteredResults.get(i).getPhylogenyInterval().getInterval(), -Math.log10(filteredResults.get(i).getPValue())); } return negLogResults; } private void repaintGraphNow() { int[] chromosomes = this.getSelectedChromosomes(); if (chromosomes.length == 1) { final List<PhylogenyTestResult> intervals = this.chromosomeResultsCache.get(chromosomes[0]); if (intervals != null) { RealValuedBasePairInterval[] negLog10Intervals = this.toNegLogResults(intervals); HighlightedSnpInterval highlightedSnpInterval = new HighlightedSnpInterval(0, negLog10Intervals.length, new int[0]); long startPosition = negLog10Intervals[0].getStartInBasePairs(); long endPosition = startPosition; for (RealValuedBasePairInterval interval : negLog10Intervals) { long currEndPosition = interval.getEndInBasePairs(); if (currEndPosition > endPosition) { endPosition = currEndPosition; } } JFreeChart jFreeChart = this.graphFactory.createSnpIntervalHistogram( Arrays.asList(negLog10Intervals), startPosition, 1 + endPosition - startPosition, highlightedSnpInterval, "Base Pair Position", "-log10(p-value)"); jFreeChart.setTitle( this.testToPlot.getName() + " - Chromosome " + this.chromosomeComboBox.getSelectedItem()); this.chartPanel.setChart(jFreeChart); } } else { List<ChromosomeHistogramValues> chromoHistos = new ArrayList<ChromosomeHistogramValues>(); for (int chromosome : chromosomes) { final RealValuedBasePairInterval[] intervals = this .toNegLogResults(this.chromosomeResultsCache.get(chromosome)); if (intervals != null) { HighlightedSnpInterval highlightedSnpInterval = new HighlightedSnpInterval(0, intervals.length, new int[0]); long startPosition = intervals[0].getStartInBasePairs(); long endPosition = startPosition; for (RealValuedBasePairInterval interval : intervals) { long currEndPosition = interval.getEndInBasePairs(); if (currEndPosition > endPosition) { endPosition = currEndPosition; } } ChromosomeHistogramValues chromosomeHistogramValues = new ChromosomeHistogramValues(chromosome, Arrays.asList(intervals), startPosition, 1 + endPosition - startPosition, highlightedSnpInterval); chromoHistos.add(chromosomeHistogramValues); } } JFreeChart jFreeChart = this.graphFactory.createMultiChromosomeHistogram(chromoHistos, "Chromosome Base Pair Position", "-log10(p-value)"); jFreeChart.setTitle(this.testToPlot.getName() + " - All Chromosomes"); this.chartPanel.setChart(jFreeChart); } } private List<JComponent> createContextMenuItems() { List<JComponent> menuItems = new ArrayList<JComponent>(); int[] chromosomes = this.getSelectedChromosomes(); if (this.lastClickedIntervalIndex >= 0 && chromosomes.length == 1) { PhylogenyTestResult selectedInterval = this.chromosomeResultsCache.get(chromosomes[0]) .get(this.lastClickedIntervalIndex); menuItems.add(new JMenuItem(new GoToMouseIntervalInCGDSnpDatabaseAction(selectedInterval, BhamApplication.getInstance().getBhamFrame()))); menuItems.add(new JMenuItem(new GoToMouseIntervalInCGDGBrowseAction(selectedInterval, BhamApplication.getInstance().getBhamFrame()))); menuItems.add(new JMenuItem(new GoToMouseIntervalInUCSCBrowserAction(selectedInterval, BhamApplication.getInstance().getBhamFrame()))); menuItems.add(new JSeparator()); PhylogenyTreeNode phylogenyTree = selectedInterval.getPhylogenyInterval().getPhylogeny(); menuItems.add(new JMenuItem(new PlotPhylogeneticTreeAction(phylogenyTree, this.testToPlot))); Map<String, List<String>> strainGroups = this.extractStrainGroups(phylogenyTree); menuItems.add(new JMenuItem( new ShowPhenotypeEffectPlotAction(this.testToPlot.getPhenotypeDataSource(), strainGroups))); } return menuItems; } /** * Get all of the strain groups from the given tree * @param phylogenyTree * the tree * @return * the strains for each node */ private Map<String, List<String>> extractStrainGroups(PhylogenyTreeNode phylogenyTree) { Map<String, List<String>> strainGroups = new HashMap<String, List<String>>(); List<String> strains = phylogenyTree.getStrains(); if (!strains.isEmpty()) { assert (!strainGroups.containsKey(strains.get(0))); strainGroups.put(strains.get(0) + " Group", strains); } List<PhylogenyTreeEdge> children = phylogenyTree.getChildEdges(); for (PhylogenyTreeEdge childEdge : children) { strainGroups.putAll(this.extractStrainGroups(childEdge.getNode())); } return strainGroups; } }