All Downloads are FREE. Search and download functionalities are using the official Maven repository.

umcg.genetica.graphics.ScatterPlot Maven / Gradle / Ivy

There is a newer version: 1.0.7
Show newest version
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package umcg.genetica.graphics;

import com.lowagie.text.DocumentException;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import umcg.genetica.math.stats.Regression;
import umcg.genetica.util.Primitives;

/**
 *
 * @author harmjan
 */
public class ScatterPlot {

    private BufferedImage bi;
    private Graphics2D g2d;
    private int graphHeight;
    private int graphWidth;
    private int drawWidth;
    private int drawHeight;
    private int margin = 50;
    private Color color;
    private Font font;
    private double unitX = Double.NaN;
    private double unitY = Double.NaN;
    private double[] x;
    private double[] y;
    private double maxX;
    private double maxY;
    private double minY;
    private double minX;
    private double rangeX;
    private double rangeY;
    private int fontheight;
    private OUTPUTFORMAT format;
    private String outfilename;
    private com.lowagie.text.Document document = null;
    private com.lowagie.text.pdf.PdfWriter writer = null;
    private PdfContentByte cb;
    private int[] category;
    private Color[] colors;
    private String[] categoryDescriptions;
    private boolean crossAxisAtZero = true;
    private String title;
    private String subtitle;

    public enum OUTPUTFORMAT {

        PDF, PNG, JPG
    }

    public ScatterPlot(int sizeX, int sizeY, double[] x, double[] y, OUTPUTFORMAT format, String outfile) {
        if (x.length != y.length) {
            throw new IllegalArgumentException("Error initializing scatterplot: X and Y do not have the same length! " + x.length + "x" + y.length);
        }
        graphHeight = sizeY;
        graphWidth = sizeX;

        this.outfilename = outfile;
        this.format = format;

        this.x = x;
        this.y = y;
        init();
        plot();
        write();
    }

    public ScatterPlot(int sizeX, int sizeY, double unitX, double unitY, double[] x, double[] y, OUTPUTFORMAT format, String outfile) {
        if (x.length != y.length) {
            throw new IllegalArgumentException("Error initializing scatterplot: X and Y do not have the same length! " + x.length + "x" + y.length);
        }
        graphHeight = sizeY;
        graphWidth = sizeX;
        this.format = format;
        this.outfilename = outfile;

        this.unitX = unitX;
        this.unitY = unitY;

        this.x = x;
        this.y = y;

        init();
        plot();
        write();
    }

    public ScatterPlot(int sizeX, int sizeY, double[] x, double[] y, int[] category, String[] categoryDescriptions, Color[] categoryColors, OUTPUTFORMAT format, String title, String subtitle, String outfile, boolean crossAxisAtZero) {
        if (x.length != y.length) {
            throw new IllegalArgumentException("Error initializing scatterplot: X and Y do not have the same length! " + x.length + "x" + y.length);
        }
        if (categoryColors.length != categoryDescriptions.length) {
            throw new IllegalArgumentException("Error initializing scatterplot: did not provide the same number of colors as there are categories! " + categoryColors.length + " - " + categoryDescriptions.length);
        }
        graphHeight = sizeY;
        graphWidth = sizeX;

        this.crossAxisAtZero = crossAxisAtZero;
        this.outfilename = outfile;
        this.format = format;

        this.category = category;
        this.colors = categoryColors;
        this.categoryDescriptions = categoryDescriptions;
        this.x = x;
        this.y = y;
        this.title = title;
        this.subtitle = subtitle;

        init();
        plot();
        plotTitles();
        plotCategoryDescriptions();
        plotCategoryTrendLines();
        write();
    }

    private void init() {

        if (margin > graphHeight || margin > graphWidth) {
            throw new IllegalArgumentException("Size of graph should be > " + (margin * 2) + " pixels in both directions");
        }

        drawWidth = graphWidth - (2 * margin);
        drawHeight = graphHeight - (2 * margin);

        if (format == OUTPUTFORMAT.PDF) {
            Rectangle rectangle = new Rectangle(graphWidth, graphHeight);
            document = new com.lowagie.text.Document(rectangle);

            if (!outfilename.toLowerCase().endsWith(".pdf")) {
                outfilename += ".pdf";
            }
            try {
                writer = com.lowagie.text.pdf.PdfWriter.getInstance(document, new java.io.FileOutputStream(outfilename));

            } catch (DocumentException e) {
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            document.open();
            cb = writer.getDirectContent();
            cb.saveState();

//            com.lowagie.text.pdf.DefaultFontMapper fontMap = new com.lowagie.text.pdf.DefaultFontMapper();
            g2d = cb.createGraphics(graphWidth, graphHeight);
        } else {
            bi = new java.awt.image.BufferedImage(graphWidth, graphHeight, java.awt.image.BufferedImage.TYPE_INT_RGB);
            g2d = bi.createGraphics();
        }

        color = new Color(255, 255, 255);

        g2d.setColor(color);
        g2d.setFont(new Font("Verdana", Font.PLAIN, 10));
        FontMetrics fontmetrics = g2d.getFontMetrics();
        fontheight = fontmetrics.getHeight();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        g2d.fillRect(0, 0, graphWidth, graphHeight);

        // draw axis
        color = new Color(0, 0, 0);
        g2d.setColor(color);
//        g2d.drawRect(margin, margin, graphHeight - (2 * margin), graphWidth - (2 * margin));

        g2d.setColor(color);

        // 
        determineRange();

        // draw axes
        drawAxis();
    }

    private void plot() {

        for (int i = 0; i < x.length; i++) {

            if (category != null && colors != null) {
                g2d.setColor(colors[category[i]]);
            } else {
                g2d.setColor(new Color(0, 128, 255, 64));
            }

            double xval = x[i];
            double yval = y[i];

            if (Double.isInfinite(xval)) {
                if (xval < 0) {
                    xval = -Double.MAX_VALUE;
                } else {
                    xval = -Double.MAX_VALUE;
                }
            }

            if (Double.isInfinite(yval)) {
                if (yval < 0) {
                    yval = -Double.MAX_VALUE;
                } else {
                    yval = -Double.MAX_VALUE;
                }
            }

            int posX = margin + (int) Math.ceil((Math.abs(minX - xval) / rangeX) * drawWidth);
            int posY = margin + drawHeight - (int) Math.ceil((Math.abs(minY - yval) / rangeY) * drawHeight);

            g2d.fillOval(posX - 1, posY - 1, 2, 2);
        }
    }

    private void write() {
        try {
            g2d.dispose();
            if (format == OUTPUTFORMAT.JPG) {
                if (!outfilename.toLowerCase().endsWith(".jpg")) {
                    outfilename += ".jpg";
                }
                javax.imageio.ImageIO.write(bi, "jpg", new File(outfilename));
            } else if (format == OUTPUTFORMAT.PNG) {
                if (!outfilename.toLowerCase().endsWith(".png")) {
                    outfilename += ".png";
                }
                javax.imageio.ImageIO.write(bi, "png", new File(outfilename));
            } else {
                cb.restoreState();
                document.close();
                writer.close();
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println(e.getStackTrace());
        }

    }

    private void plotCategoryDescriptions() {
        Color originalColor = g2d.getColor();
        Font oriFont = g2d.getFont();

// draw categories top right
        g2d.setFont(new Font("SansSerif", Font.PLAIN, 10));
        g2d.setColor(Color.gray);
        FontMetrics fontmetrics = g2d.getFontMetrics();
        int tickFontHeight = fontmetrics.getHeight();

        int minPixel = graphWidth;
        for (String categoryName : categoryDescriptions) {
            int nrPixelsString = g2d.getFontMetrics().stringWidth(categoryName);
            int positionX = graphWidth - margin - nrPixelsString;

            if (positionX < minPixel) {
                minPixel = positionX;
            }
        }

        for (int i = 0; i < categoryDescriptions.length; i++) {
            String categoryName = categoryDescriptions[i];

            int positionY = margin + (i * tickFontHeight) + 5;
            g2d.setColor(colors[i]);
            g2d.fillRect(minPixel - 5 - 10, positionY, 10, 10);
            g2d.drawString(categoryName, minPixel, positionY + fontheight);

        }

        g2d.setFont(oriFont);
        g2d.setColor(originalColor);
    }

    private void plotCategoryTrendLines() {

        Color originalColor = g2d.getColor();
        Font oriFont = g2d.getFont();

// draw categories top right
        g2d.setFont(new Font("SansSerif", Font.PLAIN, 10));
        g2d.setColor(Color.gray);
        FontMetrics fontmetrics = g2d.getFontMetrics();
        int fontHeight = fontmetrics.getHeight();

        for (int cat = 0; cat < categoryDescriptions.length; cat++) {

            g2d.setColor(colors[cat]);

            ArrayList xvals = new ArrayList();
            ArrayList yvals = new ArrayList();

            double minXVal = Double.MAX_VALUE;
            double maxXVal = -Double.MAX_VALUE;
            for (int v = 0; v < x.length; v++) {
                if (category[v] == cat) {
                    if(x[v] > maxXVal){
                        maxXVal = x[v];
                    } 
                    if(x[v]= 0) {
            // all Y values above 0, X axis starts at bottom left: |_
            g2d.drawLine(margin, margin + drawHeight, margin + drawWidth, margin + drawHeight);
            yposXAxis = margin + drawHeight;
        } else if (maxY <= 0) {
            // all Y values below 0, X axis starts at top left 
            g2d.drawLine(margin, margin, margin + drawWidth, margin);
            yposXAxis = margin;
        } else {
            // X-axis crosses the y-axis somewhere, at DIFF
            // this method does show some rounding errors at the moment..
            int diff = (int) Math.ceil((Math.abs(minY) / rangeY) * drawHeight);
            yposXAxis = margin + drawHeight - diff;
            g2d.drawLine(margin, yposXAxis, margin + drawWidth, yposXAxis);

        }

        // y-axis
        if (minX > 0) {
            // all X values above 0, Y-axis starts at top left
            g2d.drawLine(margin, margin, margin, margin + drawHeight);
            xposYAxis = margin;
        } else if (maxX <= 0) {
            // all X values below 0, axis starts at top right 
            g2d.drawLine(margin + drawWidth, margin, margin + drawWidth, margin + drawHeight);
            xposYAxis = margin + drawWidth;
        } else {
            // Y-axis crosses the X-axis somewhere, at DIFF
            // this method does show some rounding errors at the moment..
            int diff = (int) Math.ceil((Math.abs(minX) / rangeX) * drawWidth);
            xposYAxis = margin + diff;
            g2d.drawLine(margin + diff, margin, margin + diff, margin + drawHeight);

        }

        // X-ticks
        if (!Double.isNaN(unitX)) {
            // start drawing from minX, add one unitX at a time..
            // first round minX using the unitX
            double tickX = minX - (minX % unitX);

            while (tickX <= maxX) {

                double nonroundedPerc = Math.abs(minX - tickX) / rangeX;
//                double roundedPerc = roundToDecimals((nonroundedPerc), 2);
                int diff = (int) Math.floor(nonroundedPerc * drawWidth); // for the position relative to min and maxX

                String tickLabelFormatted = null;
                if (unitX > 10000 || unitX < 0.001) {
                    tickLabelFormatted = new DecimalFormat("0.#E0").format(tickX);
                } else {
                    tickLabelFormatted = new DecimalFormat("#.###").format(tickX);
                }

                if (tickX == 0) {
                    g2d.drawLine(xposYAxis, yposXAxis - 3, xposYAxis, yposXAxis + 3);
                    int nrPixelsString = g2d.getFontMetrics().stringWidth(tickLabelFormatted) / 2;
                    g2d.drawString(tickLabelFormatted, margin + diff - nrPixelsString, yposXAxis + tickFontHeight + 3);

                } else {
                    g2d.drawLine(margin + diff, yposXAxis - 3, margin + diff, yposXAxis + 3);
                    int nrPixelsString = g2d.getFontMetrics().stringWidth(tickLabelFormatted) / 2;
                    g2d.drawString(tickLabelFormatted, margin + diff - nrPixelsString, yposXAxis + tickFontHeight + 3);

                }

                tickX += unitX;

            }
        }

        // Y-ticks
        if (!Double.isNaN(unitY)) {
            double tickY = minY - (minY % unitY);
            while (tickY <= maxY) {

                double nonroundedPerc = Math.abs(minY - tickY) / rangeY;

//                double perc = roundToDecimals((Math.abs(minY - tickY) / rangeY), 2);
                int diff = (int) Math.floor(nonroundedPerc * drawHeight);
                String tickLabelFormatted = null;
                if (unitY > 100000 || unitY < 0.0001) {
                    tickLabelFormatted = new DecimalFormat("0.#E0").format(tickY);
                } else {
                    tickLabelFormatted = new DecimalFormat("#.###").format(tickY);
                }

                if (tickY == 0) {
                    g2d.drawLine(xposYAxis - 3, yposXAxis, xposYAxis + 3, yposXAxis);
                    g2d.drawString(tickLabelFormatted, xposYAxis - g2d.getFontMetrics().stringWidth(tickLabelFormatted) - 5, margin + drawHeight - diff + (tickFontHeight / 2) - 3);
                } else {
                    g2d.drawLine(xposYAxis - 3, margin + drawHeight - diff, xposYAxis + 3, margin + drawHeight - diff);
                    g2d.drawString(tickLabelFormatted, xposYAxis - g2d.getFontMetrics().stringWidth(tickLabelFormatted) - 5, margin + drawHeight - diff + (tickFontHeight / 2) - 3);
                }

                tickY += unitY;
            }
        }
        g2d.setFont(oriFont);
        g2d.setColor(originalColor);
    }

    private double roundToDecimals(double d, int c) {
        int temp = (int) ((d * Math.pow(10, c)));
        return (((double) temp) / Math.pow(10, c));
    }

    private void determineRange() {
        maxX = Primitives.max(x);
        maxY = Primitives.max(y);
        minX = Primitives.min(x);
        minY = Primitives.min(y);

        if (crossAxisAtZero) {
            if (minY > 0) {
                minY = 0;
            }
            if (minX > 0) {
                minX = 0;
            }

            if (maxY < 0) {
                maxY = 0;
            }
            if (maxX < 0) {
                maxX = 0;
            }
        }

        if (Double.isInfinite(maxX)) {
            maxX = Double.MAX_VALUE;
        }

        if (Double.isInfinite(minX)) {
            minX = -Double.MAX_VALUE;
        }

        if (Double.isInfinite(maxY)) {
            maxY = Double.MAX_VALUE;
        }

        if (Double.isInfinite(minY)) {
            minY = -Double.MAX_VALUE;
        }

        rangeX = Math.abs(minX - maxX);
        rangeY = Math.abs(minY - maxY);
//

//        System.out.println("MinX: " + minX + "\nMaxX: " + maxX + "\nMinY: " + minY + "\nMaxY: " + maxY + "\nRangeX: " + rangeX + "\nRangeY: " + rangeY + "\nUnitX: " + unitX + "\nUnitY: " + unitY);
        if (Double.isNaN(unitX)) {
            unitX = determineUnit(rangeX);
            if (unitX >= Math.abs(minX) && unitX >= Math.abs(maxX)) {
                unitX /= 10;
            }
        }
        if (Double.isNaN(unitY)) {
            unitY = determineUnit(rangeY);
            // prevent excessive tickmarking..
            if (unitY >= Math.abs(minY) && unitY >= Math.abs(maxY)) {
                unitY /= 10;
            }
        }

        // round off the limits towards the unit
        double remainder = Math.abs(maxX) % unitX;
        if (remainder > 0d && maxX != 0) {
            double diff = unitX - remainder;
            maxX += diff;
        }

        remainder = Math.abs(minX) % unitX;
//        System.out.println(unitX + " min: " + minX + " rem: " + remainder);
        if (remainder > 0d && minX != 0) {
            if (minX < 0) {
                minX -= (unitX - remainder);
            } else {
                minX -= remainder;
            }

        }

        rangeX = Math.abs(minX - maxX);
        if (rangeX == 0) {
            maxX = unitX;
        }

        // ensure the max Y is rounded off by to the next unitY
        remainder = Math.abs(maxY) % unitY;
        if (remainder > 0d && maxY != 0d) {
            double diff = unitY - remainder;
            maxY += diff;
        }

        remainder = Math.abs(minY) % unitY; // -8 , unit == 10, remainder = 8 // diff = 2
        System.out.println(minY + "\t" + unitY + "\t" + remainder);
        if (remainder > 0d && minY != 0) {
            if (minY < 0) {
                minY -= (unitY - remainder);
            } else {
                minY -= remainder;
            }
        }
        System.out.println(minY + "\t" + unitY + "\t" + remainder);
        rangeY = Math.abs(minY - maxY);
        if (rangeY == 0) {
            maxY = unitY;
        }

        if (Double.isInfinite(maxY)) {
            maxY = Double.MAX_VALUE;
        }

        if (Double.isInfinite(minY)) {
            minY = -Double.MAX_VALUE;
        }

        if (Double.isInfinite(maxX)) {
            maxX = Double.MAX_VALUE;
        }

        if (Double.isInfinite(minX)) {
            minX = -Double.MAX_VALUE;
        }

        rangeX = Math.abs(minX - maxX);
        rangeY = Math.abs(minY - maxY);

//        System.out.println("");
        System.out.println("MinX: " + minX + "\nMaxX: " + maxX + "\nMinY: " + minY + "\nMaxY: " + maxY + "\nRangeX: " + rangeX + "\nRangeY: " + rangeY + "\nUnitX: " + unitX + "\nUnitY: " + unitY);
    }

    private double determineUnit(double range) {

        double divisor = Math.log10(range);
        divisor = Math.floor(divisor);
        divisor = Math.pow(10, divisor);
        return divisor;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy