org.jax.bham.test.PhylogenyAssociationTestGraphPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.jax.bham.test.PhylogenyAssociationTestGraphPanel.java

Source

/*
 * 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;
    }
}