org.jax.haplotype.analysis.visualization.GenomicGraphFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.jax.haplotype.analysis.visualization.GenomicGraphFactory.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.haplotype.analysis.visualization;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.jax.geneticutil.data.BasePairInterval;
import org.jax.geneticutil.data.RealValuedBasePairInterval;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.SymbolAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CombinedRangeXYPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYBlockRenderer;
import org.jfree.data.Range;
import org.jfree.data.xy.DefaultXYZDataset;
import org.jfree.data.xy.XIntervalSeries;
import org.jfree.data.xy.XIntervalSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleInsets;

/**
 * My little factory for creating graphs from SNP intervals
 * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
 */
public class GenomicGraphFactory {
    /**
     * our logger
     */
    private static final Logger LOG = Logger.getLogger(GenomicGraphFactory.class.getName());

    /**
     * Creates a plot which contains multiple chromosome histograms
     * @param chromosomeHistograms
     *          the chromosome histograms
     * @param xAxisLabel
     *          the X axis label
     * @param yAxisLabel
     *          the Y axis label
     * @return
     *          the object for the multiple chromosome plots
     */
    public JFreeChart createMultiChromosomeHistogram(
            final List<? extends ChromosomeHistogramValues> chromosomeHistograms, final String xAxisLabel,
            final String yAxisLabel) {
        // create the y-axis that is shared by all of the chromosomes
        NumberAxis yAxis = new NumberAxis();
        if (yAxisLabel != null) {
            yAxis.setLabel(yAxisLabel);
        }

        // the combined plot makes all of the chromosomes to share the same
        // y-axis but to have thier own x-axis
        CombinedRangeXYPlot combinedChromosomePlot = new CombinedRangeXYPlot(yAxis);
        combinedChromosomePlot.setGap(4.0);

        // iterate through the chromosomes adding a new plot for each one
        for (ChromosomeHistogramValues currChromHist : chromosomeHistograms) {
            // use a weight to ensure that the amount of space that the
            // subgraph gets on the x-axis is proportional to the chromosome's
            // extent
            final int currWeight = (int) (currChromHist.getExtentInBasePairs() / 1000 + 1);

            final NumberAxis currXAxis = new NumberAxis();
            currXAxis.setAutoRangeIncludesZero(false);
            currXAxis.setTickLabelsVisible(false);
            currXAxis.setTickMarksVisible(false);
            currXAxis.setLabel(Integer.toString(currChromHist.getChromosomeNumber()));

            final XYPlot currPlot = this.createSnpIntervalHistogramPlot(currChromHist.getIntervals(),
                    currChromHist.getVisualInterval(), currXAxis, null);

            combinedChromosomePlot.add(currPlot, currWeight);
        }

        JFreeChart multiChromosomeChart = new JFreeChart(combinedChromosomePlot);
        multiChromosomeChart.removeLegend();

        return multiChromosomeChart;
    }

    /**
     * Create a snp interval histogram without any axes
     * @param intervals
     *          the intervals to use
     * @param startInBasePairs
     *          where should we start the graph?
     * @param extentInBasePairs
     *          how far should the graph extend
     * @param visualInterval
     *          the visual interval to use
     * @param xAxisLabel
     *          the x axis title to use
     * @param yAxisLabel
     *          the y axis title to use
     * @return
     *          the histogram
     */
    public JFreeChart createSnpIntervalHistogram(final List<? extends RealValuedBasePairInterval> intervals,
            final long startInBasePairs, final long extentInBasePairs, final HighlightedSnpInterval visualInterval,
            final String xAxisLabel, final String yAxisLabel) {
        // create the axes
        NumberAxis xAxis = new NumberAxis();
        xAxis.setAutoRangeIncludesZero(false);
        xAxis.setRange(new Range(startInBasePairs, startInBasePairs + extentInBasePairs));
        if (xAxisLabel != null) {
            xAxis.setLabel(xAxisLabel);
        }

        NumberAxis yAxis = new NumberAxis();
        if (yAxisLabel != null) {
            yAxis.setLabel(yAxisLabel);
        }

        // create the plot
        XYPlot plot = this.createSnpIntervalHistogramPlot(intervals, visualInterval, xAxis, yAxis);

        // create the final chart
        JFreeChart histogram = new JFreeChart(plot);
        histogram.removeLegend();

        return histogram;
    }

    /**
     * Create a snp interval histogram without any axes
     * @param intervals
     *          the intervals to use
     * @param startInBasePairs
     *          where should we start the graph?
     * @param extentInBasePairs
     *          how far should the graph extend
     * @param visualInterval
     *          the visual interval to use
     * @return
     *          the histogram
     */
    public JFreeChart createSnpIntervalHistogram(final List<? extends RealValuedBasePairInterval> intervals,
            final long startInBasePairs, final long extentInBasePairs,
            final HighlightedSnpInterval visualInterval) {
        // create the axes
        NumberAxis xAxis = new NumberAxis();
        xAxis.setAutoRangeIncludesZero(false);
        xAxis.setRange(new Range(startInBasePairs, startInBasePairs + extentInBasePairs));
        NumberAxis yAxis = new NumberAxis();

        // hide the axes
        xAxis.setVisible(false);
        yAxis.setVisible(false);

        // create the plot
        XYPlot plot = this.createSnpIntervalHistogramPlot(intervals, visualInterval, xAxis, yAxis);

        // more hiding
        plot.setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0));

        // create the final chart
        JFreeChart histogram = new JFreeChart(plot);
        histogram.removeLegend();

        return histogram;
    }

    private XYPlot createSnpIntervalHistogramPlot(final List<? extends RealValuedBasePairInterval> intervals,
            final HighlightedSnpInterval visualInterval, ValueAxis domainAxis, ValueAxis rangeAxis) {
        XYDataset dataset = this.createSnpIntervalHistogramData(intervals, visualInterval);
        XYBarRenderer renderer = new XYBarRenderer() {
            /**
             * every serializable is supposed to have one of these
             */
            private static final long serialVersionUID = -7907956889918704042L;

            /**
             * {@inheritDoc}
             */
            @Override
            public Paint getItemPaint(int row, int column) {
                // Always render in black
                return Color.BLACK;
            }
        };
        renderer.setShadowVisible(false);
        renderer.setDrawBarOutline(false);
        //        renderer.setGradientPaintTransformer(null);
        renderer.setBarPainter(new StandardXYBarPainter());
        renderer.setMargin(0.0);
        return new XYPlot(dataset, domainAxis, rangeAxis, renderer);
    }

    /**
     * Convert the intervals & values to a dataset that JFreeChart can use
     * @param snpIntervals
     *          the intervals
     * @param visualInterval
     *          the visual interval that we should use
     * @return
     *          the data set
     */
    private XYDataset createSnpIntervalHistogramData(final List<? extends RealValuedBasePairInterval> snpIntervals,
            final HighlightedSnpInterval visualInterval) {
        XIntervalSeriesCollection dataset = new XIntervalSeriesCollection();

        XIntervalSeries series = new XIntervalSeries("Interval Values", false, true);
        XIntervalSeries highlightSeries = new XIntervalSeries("Highlighted Interval Values", false, true);

        int endIndex = visualInterval.getEndIndex();
        int[] highlightIndices = visualInterval.getIndicesToHighlight();
        for (int i = visualInterval.getStartIndex(); i <= endIndex; i++) {
            RealValuedBasePairInterval currSnpInterval = snpIntervals.get(i);

            boolean highlight = false;
            for (int j = 0; j < highlightIndices.length; j++) {
                if (i == highlightIndices[j]) {
                    highlight = true;
                    break;
                }
            }

            if (highlight) {
                highlightSeries.add(currSnpInterval.getStartInBasePairs(), currSnpInterval.getStartInBasePairs(),
                        currSnpInterval.getEndInBasePairs(), currSnpInterval.getRealValue());
            } else {
                series.add(currSnpInterval.getStartInBasePairs(), currSnpInterval.getStartInBasePairs(),
                        currSnpInterval.getEndInBasePairs(), currSnpInterval.getRealValue());
            }
        }

        dataset.addSeries(series);
        dataset.addSeries(highlightSeries);
        return dataset;
    }

    /**
     * Create a SNP block graph for the given parameters. This graph
     * will show where the intervals do and don't exist. Interval lists
     * are organized with the X axis label matching the list's key
     * @param snpIntervals
     *          the blocks
     * @param startInBasePairs
     *          the x location to start the graph at
     * @param extentInBasePairs
     *          the extent to use for the graph
     * @param maximumImageBlockCount
     *          the max # of separate image blocks to use
     * @param renderAxes
     *          if true render the axes... otherwise dont
     * @param legendText
     *          the text to use for the legend
     * @param yAxisText
     *          the text to use for the y axis
     * @param trueColor
     *          the color to use for true
     * @param falseColor
     *          the color to use for false 
     * @return
     *          the graph
     */
    public JFreeChart createSnpIntervalGraph(
            final Map<String, ? extends List<? extends BasePairInterval>> snpIntervals, final long startInBasePairs,
            final long extentInBasePairs, final int maximumImageBlockCount, final boolean renderAxes,
            final String legendText, final String yAxisText, final Color trueColor, final Color falseColor) {
        XYDataset dataset = snpIntervalsToDataset(snpIntervals, startInBasePairs, extentInBasePairs,
                maximumImageBlockCount);

        NumberAxis xAxis = new NumberAxis("SNP Position (Base Pairs)");
        xAxis.setAutoRangeIncludesZero(false);
        xAxis.setRange(new Range(startInBasePairs, startInBasePairs + extentInBasePairs));
        String[] sortedStrainNames = extractSortedStrainNames(snpIntervals);
        for (int strainIndex = 0; strainIndex < sortedStrainNames.length; strainIndex++) {
            LOG.info("Strain Name: " + sortedStrainNames[strainIndex]);
        }
        SymbolAxis yAxis = new SymbolAxis(yAxisText == null ? "" : yAxisText, sortedStrainNames);

        XYPlot plot = new XYPlot(dataset, xAxis, yAxis, null);
        LegendItemCollection items = new LegendItemCollection();
        if (legendText != null) {
            items.add(new LegendItem(legendText, null, null, null, new Rectangle2D.Double(-3.0, -3.0, 6.0, 6.0),
                    trueColor, new BasicStroke(), Color.BLACK));
        }
        plot.setFixedLegendItems(items);

        XYBlockRenderer r = new XYBlockRenderer();
        SmoothPaintScale ps = new SmoothPaintScale(0.0, 1.0, falseColor, trueColor);

        r.setPaintScale(ps);
        r.setBlockHeight(1.0);
        r.setBlockWidth(1.0);
        plot.setRenderer(r);

        final JFreeChart chart;
        if (renderAxes) {
            chart = new JFreeChart("Identical By State Blocks", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
        } else {
            xAxis.setVisible(false);
            yAxis.setVisible(false);
            plot.setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0));
            chart = new JFreeChart(plot);
        }

        chart.setBackgroundPaint(Color.WHITE);

        return chart;
    }

    /**
     * Pull out the keys as a sorted array
     * @param snpIntervals
     *          the interval map (we only care about the keys)
     * @return
     *          the sorted array
     */
    private String[] extractSortedStrainNames(
            final Map<String, ? extends List<? extends BasePairInterval>> snpIntervals) {
        String[] sortedStrainNames = snpIntervals.keySet().toArray(new String[snpIntervals.size()]);
        Arrays.sort(sortedStrainNames);
        return sortedStrainNames;
    }

    /**
     * Convert the intervals to a dataset that JFreeChart can use
     * @param snpIntervals
     *          the intervals
     * @param startInBasePairs
     *          the starting point that we should use
     * @param extentInBasePairs
     *          the extent that we should use
     * @param maximumImageBlockCount
     *          the max number of blocks that we should use for the graph
     * @return
     *          the data set
     */
    private XYDataset snpIntervalsToDataset(
            final Map<String, ? extends List<? extends BasePairInterval>> snpIntervals, final long startInBasePairs,
            final long extentInBasePairs, final int maximumImageBlockCount) {
        DefaultXYZDataset dataset = new DefaultXYZDataset();

        final int imageBlockCount;
        final long basePairsPerImageBlock;
        if (maximumImageBlockCount > extentInBasePairs) {
            imageBlockCount = (int) extentInBasePairs;
            basePairsPerImageBlock = 1L;
        } else {
            imageBlockCount = maximumImageBlockCount;
            if (extentInBasePairs % maximumImageBlockCount == 0) {
                basePairsPerImageBlock = extentInBasePairs / maximumImageBlockCount;
            } else {
                basePairsPerImageBlock = (long) Math.ceil((extentInBasePairs / (double) maximumImageBlockCount));
            }
        }

        String[] sortedStrainNames = extractSortedStrainNames(snpIntervals);

        for (int strainIndex = 0; strainIndex < sortedStrainNames.length; strainIndex++) {
            List<? extends BasePairInterval> snpBlockList = snpIntervals.get(sortedStrainNames[strainIndex]);
            int snpBlockIndex = 0;
            BasePairInterval currSnpBlock;
            if (snpBlockList.isEmpty()) {
                currSnpBlock = null;
            } else {
                currSnpBlock = snpBlockList.get(snpBlockIndex);
            }

            double[][] series = new double[3][imageBlockCount];
            long imageBlockStartInBasePairs = startInBasePairs;
            for (int imageBlockIndex = 0; imageBlockIndex < imageBlockCount; imageBlockIndex++) {
                while (currSnpBlock != null && currSnpBlock.getEndInBasePairs() < imageBlockStartInBasePairs) {
                    snpBlockIndex++;
                    if (snpBlockIndex == snpBlockList.size()) {
                        currSnpBlock = null;
                    } else {
                        currSnpBlock = snpBlockList.get(snpBlockIndex);
                    }
                }

                final double colorValue;
                if (currSnpBlock == null) {
                    // no intersection
                    colorValue = 0.0;
                } else {
                    if (currSnpBlock.contains(imageBlockStartInBasePairs, basePairsPerImageBlock)) {
                        // full containment
                        colorValue = 1.0;
                    } else if (currSnpBlock.intersects(imageBlockStartInBasePairs, basePairsPerImageBlock)) {
                        // partial intersection
                        long cumulativeOverlapInBasePairs = 0;
                        for (int innerSnpBlockCursor = snpBlockIndex; innerSnpBlockCursor < snpBlockList
                                .size(); innerSnpBlockCursor++) {
                            BasePairInterval innerSnpBlock = snpBlockList.get(innerSnpBlockCursor);
                            long currOverlapInBasePairs = innerSnpBlock
                                    .getOverlapInBasePairs(imageBlockStartInBasePairs, basePairsPerImageBlock);
                            if (currOverlapInBasePairs == 0) {
                                break;
                            } else {
                                cumulativeOverlapInBasePairs += currOverlapInBasePairs;
                            }
                        }

                        double overlapRatio = cumulativeOverlapInBasePairs / (double) basePairsPerImageBlock;
                        colorValue = overlapRatio;
                    } else {
                        // no intersection
                        colorValue = 0.0;
                    }
                }

                series[0][imageBlockIndex] = imageBlockStartInBasePairs;
                series[1][imageBlockIndex] = strainIndex;
                series[2][imageBlockIndex] = colorValue;

                imageBlockStartInBasePairs += basePairsPerImageBlock;
            }
            dataset.addSeries(sortedStrainNames[strainIndex], series);
        }

        return dataset;
    }
}