juicebox.tools.utils.juicer.apa.APAPlotter.java Source code

Java tutorial

Introduction

Here is the source code for juicebox.tools.utils.juicer.apa.APAPlotter.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2015 Broad Institute, Aiden Lab
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */

package juicebox.tools.utils.juicer.apa;

import juicebox.tools.clt.juicer.APA;
import org.apache.commons.math.linear.RealMatrix;
import org.tc33.jheatchart.HeatChart;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;

/**
 * Helper class to wrap heat map plotting and handle apa plots
 * The static plot method should be called all the necessary inputs.
 */
class APAPlotter {

    /**
     * apa heat map plots range between red (max value) and white (0)
     */
    private static final Color[] gradientColors = { Color.RED, Color.WHITE };
    private static final float[] gradientFractions = { 0.0f, 1.0f };

    /**
     * used for comparisons
     */
    private static final double epsilon = 1e-6;

    /**
     * heat map dimension defaults
     */
    private static int fullHeight = 500;
    private static int heatmapWidth = 500;
    private static int colorScaleWidth = 40;
    private static int colorScaleHorizontalMargin = 10;
    private static int colorScaleVerticalMargin = 100;
    private static int extraWidthBuffer = 20;
    private static int fullWidth = heatmapWidth + colorScaleWidth + extraWidthBuffer;
    private static int numDivisions = 6;

    /**
     * Method for plotting apa data
     *
     * @param data       for heat map
     * @param axesRange  initial values and increments to annotate axes [x0, dx, y0, dy]
     * @param outputFile where image will saved
     */
    public static void plot(RealMatrix data, int[] axesRange, File outputFile, String title) {

        APARegionStatistics apaStats = new APARegionStatistics(data);
        DecimalFormat df = new DecimalFormat("0.000");
        title += ", P2LL = " + df.format(apaStats.getPeak2LL());

        // initialize heat map
        HeatChart map = new HeatChart(data.getData());
        map.setLowValueColour(Color.WHITE);
        map.setHighValueColour(Color.RED);
        map.setXValues(axesRange[0], axesRange[1]);
        map.setYValues(axesRange[2], axesRange[3]);
        map.setTitle(title);

        try {
            // calculate dimensions for plot wrapper
            initializeSizes(map);

            // create blank white image
            BufferedImage apaImage = new BufferedImage(fullWidth, fullHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = apaImage.createGraphics();
            g2.setBackground(Color.WHITE);
            g2.fillRect(0, 0, fullWidth, fullHeight);

            // plot in heat map, color bar, etc
            g2.drawImage(map.getChartImage(), 0, 0, heatmapWidth, fullHeight, null);
            drawHeatMapBorder(g2, map);
            plotColorScaleBar(g2);
            plotColorScaleValues(g2, map);

            // top left, top right, bottom left, bottom right values (from apa)

            drawCornerRegions(g2, map, new Dimension(APA.regionWidth, APA.regionWidth),
                    apaStats.getRegionCornerValues());

            // save data
            ImageIO.write(apaImage, "png", outputFile);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Initialize dimensions used for plotting apa data
     *
     * @param heatMap object
     */
    private static void initializeSizes(HeatChart heatMap) {

        Dimension mapDimensions = getImageDimensions(heatMap.getChartImage());
        //fullHeight = (int) (mapDimensions.height*((double)heatmapWidth)/mapDimensions.width);

        fullHeight = mapDimensions.height;
        heatmapWidth = mapDimensions.width;
        colorScaleWidth = 40;
        colorScaleHorizontalMargin = 10;
        colorScaleVerticalMargin = fullHeight / 5;
        extraWidthBuffer = 30;
        fullWidth = heatmapWidth + colorScaleWidth + extraWidthBuffer;
        numDivisions = calculateIdealNumDivisions(heatMap.getPermissiveIntRange());
    }

    /**
     * Calculate raw dimensions of image
     *
     * @param image
     * @return dimension (x,y) size of image in pixels
     */
    private static Dimension getImageDimensions(Image image) {
        ImageIcon icon = new ImageIcon(image);
        return new Dimension(icon.getIconWidth(), icon.getIconHeight());
    }

    /**
     * Calculate optimal number of color map divisions based on range
     *
     * @param range of heat map data (max - min)
     * @return optimal number of color map divisions
     */
    private static int calculateIdealNumDivisions(int range) {
        // 33 ~ minimum number of pixels per division
        for (int n = fullHeight / 33; n > 3; n--) {
            if (range % n == 0)
                return n;
        }
        return 5;
    }

    /**
     * @param g2 graphics2D object
     */
    private static void plotColorScaleBar(Graphics2D g2) {
        // calculate color scale bar dimensions & location
        Point csBarTL = new Point(heatmapWidth + colorScaleHorizontalMargin, colorScaleVerticalMargin);
        Point csBarBL = new Point(heatmapWidth + colorScaleHorizontalMargin, fullHeight - colorScaleVerticalMargin);
        Rectangle csBar = new Rectangle(csBarTL.x, csBarTL.y, colorScaleWidth - 2 * colorScaleHorizontalMargin,
                fullHeight - 2 * colorScaleVerticalMargin);

        // plot the color scale linear gradient
        LinearGradientPaint gradient = new LinearGradientPaint(csBarTL, csBarBL, gradientFractions, gradientColors);
        g2.setPaint(gradient);
        g2.fill(csBar);

        // plot a border around color scale
        g2.setColor(Color.black);
        g2.drawRect(csBar.x, csBar.y, csBar.width, csBar.height);
    }

    /**
     * Plot number value axis for color scale bar.
     *
     * @param g2      graphics2D object
     * @param heatMap object
     */
    private static void plotColorScaleValues(Graphics2D g2, HeatChart heatMap) {
        // size, increment calculations
        double valIncrement = Math.max(heatMap.getDataRange() / ((double) numDivisions), epsilon);
        double depthIncrement = ((double) (fullHeight - 2 * colorScaleVerticalMargin)) / ((double) numDivisions);
        int verticalDepth = fullHeight - colorScaleVerticalMargin;
        int csBarRightEdgeX = fullWidth - colorScaleHorizontalMargin - extraWidthBuffer;

        // formatting
        g2.setFont(heatMap.getAxisValuesFont());
        DecimalFormat df = new DecimalFormat("0.#");

        // draw each tick mark and its value
        for (double i = heatMap.getLowValue(); i <= heatMap
                .getHighValue(); i += valIncrement, verticalDepth -= depthIncrement) {

            if (i > heatMap.getHighValue() - epsilon)
                verticalDepth = colorScaleVerticalMargin;
            g2.drawString(df.format(i), csBarRightEdgeX + 5, verticalDepth); // value
            g2.drawLine(csBarRightEdgeX - 5, verticalDepth, csBarRightEdgeX, verticalDepth); // tick mark
        }
    }

    /**
     * Draw black border around main heat map
     *
     * @param g2      graphics2D object
     * @param heatMap object
     */
    private static void drawHeatMapBorder(Graphics2D g2, HeatChart heatMap) {
        // calculate corners of heat map rectangle
        Point heatMapTL = heatMap.getHeatMapTL();
        Point heatMapBR = heatMap.getHeatMapBR();

        // plot border around heat map
        g2.setColor(Color.BLACK);
        g2.drawRect(heatMapTL.x, heatMapTL.y, heatMapBR.x - heatMapTL.x, heatMapBR.y - heatMapTL.y);
    }

    /**
     * Draw the corner boxes and their values as calculated by apa overlayed above the existing heat map
     *
     * @param g2                   graphics2D oObject
     * @param map                  heat map object
     * @param regionCellDimensions dimensions for the corner regions to be plotted in units of cells, not pixels
     *                             where each cell corresponds to a data point, usually 20px-by-20px (default)
     * @param regionAPAValues      apa results for each region in order of TL TR BL BR
     */
    private static void drawCornerRegions(Graphics2D g2, HeatChart map, Dimension regionCellDimensions,
            double[] regionAPAValues) {
        // retrieve corners of heat map
        Point topLeft = map.getHeatMapTL();
        Point topRight = map.getHeatMapTR();
        Point bottomLeft = map.getHeatMapBL();
        Point bottomRight = map.getHeatMapBR();

        // calculate dimensions of corner regions
        Dimension cellSize = map.getCellSize();
        int cornerWidth = regionCellDimensions.width * cellSize.width,
                cornerHeight = regionCellDimensions.height * cellSize.height;

        // slide to top left corner within each region
        topRight.translate(-cornerWidth, 0);
        bottomLeft.translate(0, -cornerHeight);
        bottomRight.translate(-cornerWidth, -cornerHeight);

        // plot the four region TL TR BL BR and their values
        Point[] points = { topLeft, topRight, bottomLeft, bottomRight };
        g2.setColor(Color.black);
        for (int i = 0; i < 4; i++) {
            // plot rectangle from upper left corner
            Point currPoint = points[i];
            g2.drawRect(currPoint.x, currPoint.y, cornerWidth, cornerHeight);

            // translate to center of rectangle
            currPoint.translate(cornerWidth / 2, cornerHeight / 2);
            drawCenteredDouble(g2, regionAPAValues[i], currPoint);
        }
    }

    /**
     * Plot double centered at a point (rather than from the upper left corner as is the default)
     *
     * @param g2       graphics2D object
     * @param value    to be plotted
     * @param position for value to be centered at
     */
    private static void drawCenteredDouble(Graphics2D g2, Double value, Point position) {
        DecimalFormat df = new DecimalFormat("0.000");
        drawCenteredString(g2, df.format(value), position);
    }

    /**
     * Plot text centered at a point (rather than from the upper left corner as is the default)
     *
     * @param g2       graphics2D object
     * @param text     to be plotted
     * @param position of where text will be centered at
     */
    private static void drawCenteredString(Graphics2D g2, String text, Point position) {
        FontMetrics fm = g2.getFontMetrics();
        int x2 = position.x - fm.stringWidth(text) / 2;
        int y2 = fm.getAscent() + (position.y - (fm.getAscent() + fm.getDescent()) / 2);
        g2.drawString(text, x2, y2);
    }
}