gov.nih.nci.cma.web.graphing.CMAPrincipalComponentAnalysisPlot.java Source code

Java tutorial

Introduction

Here is the source code for gov.nih.nci.cma.web.graphing.CMAPrincipalComponentAnalysisPlot.java

Source

/*L
 *  Copyright SAIC
 *
 *  Distributed under the OSI-approved BSD 3-Clause License.
 *  See http://ncip.github.com/cma/LICENSE.txt for details.
 */

package gov.nih.nci.cma.web.graphing;

import gov.nih.nci.caintegrator.ui.graphing.data.DataRange;
import gov.nih.nci.caintegrator.ui.graphing.data.principalComponentAnalysis.PrincipalComponentAnalysisDataPoint.PCAcomponent;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.LegendItemSource;
import org.jfree.chart.annotations.XYShapeAnnotation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.imagemap.StandardToolTipTagFragmentGenerator;
import org.jfree.chart.imagemap.StandardURLTagFragmentGenerator;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.util.StringUtils;
import org.jfree.data.Range;

/**
 * This class generates a plot for the PrincipalComponentAnalysis graph
 * that fits CMA requirement.
 *
 *
 */

public class CMAPrincipalComponentAnalysisPlot {

    //private ColorByType colorBy;
    private PCAcomponent component1;
    private PCAcomponent component2;
    private Map<String, Color> colorMap = new HashMap<String, Color>();
    private Collection<CMAPCADataPoint> dataPoints;
    private JFreeChart pcaChart = null;
    private NumberFormat nf = NumberFormat.getNumberInstance();
    private Map<String, String> sampleGroupNames;

    public CMAPrincipalComponentAnalysisPlot(Collection<CMAPCADataPoint> dataPoints, PCAcomponent component1,
            PCAcomponent component2, Map<String, String> sampleGroupNames) { //, ColorByType colorBy) {
        //this.colorBy = colorBy;
        this.component1 = component1;
        this.component2 = component2;
        this.dataPoints = dataPoints;
        this.nf.setMaximumFractionDigits(1);
        this.sampleGroupNames = sampleGroupNames;
        colorMap.put("1", Color.GREEN);
        colorMap.put("2", Color.BLUE);
        colorMap.put("3", Color.YELLOW);
        colorMap.put("4", Color.CYAN);
        colorMap.put("5", Color.MAGENTA);
        colorMap.put("6", Color.ORANGE);
        colorMap.put("7", Color.PINK);
        colorMap.put("8", Color.RED);
        colorMap.put("9", Color.GRAY);

        createChart();

    }

    private void createChart() {

        String xLabel = component1.toString();
        String yLabel = component2.toString();

        pcaChart = ChartFactory.createScatterPlot("Principal Component Analysis", xLabel, yLabel, null,
                PlotOrientation.VERTICAL, true, true, false);

        XYPlot plot = (XYPlot) pcaChart.getPlot();

        buildLegend();

        plot.setNoDataMessage(null);
        XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer();
        renderer.setToolTipGenerator(new StandardXYToolTipGenerator());
        renderer.setUseOutlinePaint(true);
        plot.setRangeCrosshairVisible(false);
        plot.setDomainCrosshairVisible(false);

        //        XYShapeAnnotation annotation = new XYShapeAnnotation(new Rectangle2D.Double(25.0, 25.0, 5, 5));
        //
        //        plot.addAnnotation(annotation);

        NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
        NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();

        //should determine axis range using datapoints.
        DataRange component1Range = getDataRange(dataPoints, PCAcomponent.PC1);
        DataRange component2Range = getDataRange(dataPoints, PCAcomponent.PC2);
        DataRange component3Range = getDataRange(dataPoints, PCAcomponent.PC3);

        Double pc1AbsMax = Math.max(Math.abs(component1Range.getMaxRange()),
                Math.abs(component1Range.getMinRange()));
        Double pc2AbsMax = Math.max(Math.abs(component2Range.getMaxRange()),
                Math.abs(component2Range.getMinRange()));
        Double pc3AbsMax = Math.max(Math.abs(component3Range.getMaxRange()),
                Math.abs(component3Range.getMinRange()));

        Double maxAbsVal = Math.max(pc1AbsMax, pc2AbsMax);

        maxAbsVal = Math.max(maxAbsVal, pc3AbsMax);

        //maxAbsVal = Math.max(100.0, maxAbsVal);

        domainAxis.setAutoRangeIncludesZero(false);

        double tickUnit = 25.0;

        if (maxAbsVal <= 50.0 && maxAbsVal >= 25.0) {
            tickUnit = 10.0; //5.0;
        } else if (maxAbsVal <= 25.0) {
            tickUnit = 5.0;
        }

        domainAxis.setTickUnit(new NumberTickUnit(tickUnit));
        rangeAxis.setTickUnit(new NumberTickUnit(tickUnit));

        double glyphScaleFactor = (maxAbsVal * 2.0) / 600.0; //assuming 600 pixels for the graph

        double adjAbsVal = Math.ceil(maxAbsVal + (glyphScaleFactor * 8.0));

        //domainAxis.setRange(-maxAbsVal, maxAbsVal);
        domainAxis.setRange(-adjAbsVal, adjAbsVal);

        //rangeAxis.setRange(-maxAbsVal, maxAbsVal);
        rangeAxis.setRange(-adjAbsVal, adjAbsVal);

        createGlyphsAndAddToPlot(plot); //, glyphScaleFactor);

        // Paint p = new GradientPaint(0, 0, Color.white, 1000, 0, Color.green);
        //try and match the UI e9e9e9
        Paint p = new Color(233, 233, 233);

        pcaChart.setBackgroundPaint(p);
    }

    /**
     * Build the legend
     *
     */
    private void buildLegend() {

        LegendTitle legend = pcaChart.getLegend();
        LegendItemSource[] sources = new LegendItemSource[1];
        PcaLegendItemSource legendSrc = new PcaLegendItemSource();
        LegendItem item = null;

        Ellipse2D.Double circle = new Ellipse2D.Double(0, 0, 8, 8);

        //Rect=survival less than 10 months
        Set<String> groupNames = sampleGroupNames.keySet();
        for (String name : groupNames) {
            Color color = colorMap.get(sampleGroupNames.get(name));
            if (color == null)
                color = Color.BLACK;
            item = new LegendItem(name, null, null, null, circle, color);
            legendSrc.addLegendItem(item);
        }
        sources[0] = legendSrc;
        legend.setSources(sources);
    }

    /**
     * This chart uses the XYAnnotation as a glyph to represent
     * a single pca data point. Glyph shape is determined by survival time.
     * Survival of more than 10 months is represented by a circle. 10 months or less
     * is represented by a square. Component1 values are represented by X
     * Component2 values are represented by Y
     */

    private void createGlyphsAndAddToPlot(XYPlot plot) { //, double glyphScaleFactor) {
        XYShapeAnnotation glyph;
        Shape glyphShape = null;
        Color glyphColor;
        NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
        Range r = domainAxis.getRange();
        double upperBound = r.getUpperBound();

        double dotSize = 0.7;
        if (upperBound >= 50 && upperBound <= 100)
            dotSize = 1d;
        else if (upperBound > 100 && upperBound <= 150)
            dotSize = 2d;
        else if (upperBound > 150 && upperBound <= 200)
            dotSize = 3d;
        else if (upperBound > 200)
            dotSize = 5d;

        //PrincipalComponentAnalysisDataPoint pcaPoint;
        double x, y;
        //for (Iterator i=dataPoints.iterator(); i.hasNext(); ) {
        for (CMAPCADataPoint pcaPoint : dataPoints) {
            x = pcaPoint.getComponentValue(component1);
            y = pcaPoint.getComponentValue(component2);

            String sampleGroupName = pcaPoint.getSampleGroupName();
            String order = sampleGroupNames.get(sampleGroupName);
            glyphShape = getShapeForDataPoint(order, x, y, dotSize);

            glyphColor = colorMap.get(order);
            if (glyphColor == null)
                glyphColor = Color.black;
            glyph = new XYShapeAnnotation(glyphShape, new BasicStroke(1.0f), Color.BLACK, glyphColor);
            String tooltip = "";

            tooltip = pcaPoint.getSampleId() + " " + pcaPoint.getSampleGroupName();

            glyph.setToolTipText(tooltip);
            plot.addAnnotation(glyph);
        }
    }

    private Shape getShapeForDataPoint(String order, double x, double y, double dotSize) {
        Shape glyphShape;
        Ellipse2D.Double circle = new Ellipse2D.Double();
        circle.setFrameFromCenter(x, y, x + dotSize, y + dotSize);
        glyphShape = circle;
        //Now we only use one shape for the plot.
        /*
        if(order.equalsIgnoreCase("1"))   {
           //glyphShape = ShapeUtilities.createDiamond(new Float(x));
           Rectangle2D.Double rect = new Rectangle2D.Double();
           rect.setFrameFromCenter(x,y, x+2,y+2);
           glyphShape = rect;
           Shape gShape = ShapeUtilities.rotateShape(glyphShape, new Double(0.785398163), new Float(x),new Float(y));
           glyphShape = gShape;
        }
        else if (order.equalsIgnoreCase("2")) {
           Rectangle2D.Double rect = new Rectangle2D.Double();
           rect.setFrameFromCenter(x,y, x+2,y+2);
           glyphShape = rect;
        }
        else if (order.equalsIgnoreCase("3")) {
           Ellipse2D.Double circle = new Ellipse2D.Double();
           circle.setFrameFromCenter(x,y, x+2, y+2);
           glyphShape = circle;
        }
        else if (order.equalsIgnoreCase("4")) {
           Ellipse2D.Double circle = new Ellipse2D.Double();
           circle.setFrameFromCenter(x,y, x+2, y+2);
           glyphShape = circle;
        }
        else if (order.equalsIgnoreCase("5")) {
           Ellipse2D.Double circle = new Ellipse2D.Double();
           circle.setFrameFromCenter(x,y, x+2, y+2);
           glyphShape = circle;
        }
        else if (order.equalsIgnoreCase("6")) {
           Ellipse2D.Double circle = new Ellipse2D.Double();
           circle.setFrameFromCenter(x,y, x+2, y+2);
           glyphShape = circle;
        }
        else {
           GeneralPath gp = new GeneralPath();
           float xf = (float)x;
           float yf = (float)y;
           //make a triangle
           gp.moveTo(xf,yf);
           gp.lineTo(xf+3.0f,yf-3.0f);
           gp.lineTo(xf-3.0f,yf-3.0f);
           gp.closePath();
           glyphShape = gp;
        }
        */
        return glyphShape;
    }

    public JFreeChart getChart() {
        return pcaChart;
    }

    /**
     * Same interface as ChartUtilities.writeImageMap. This version will find the
     * bounding rectangles for the entities in the ChartRenderingInfo object and will write those
     * to the image map.
     * @param writer
     * @param name
     * @param info
     * @param useOverlibToolTip
     */
    public void writeBoundingRectImageMap(PrintWriter writer, String name, ChartRenderingInfo info,
            boolean useOverlibToolTip) {
        EntityCollection collection = info.getEntityCollection();
        Collection entities = collection.getEntities();

        Collection<ChartEntity> boundingEntities = getBoundingEntities(entities);
        writeBoundingRectImageMap(writer, name, boundingEntities, useOverlibToolTip);

    }

    /**
     * Write the image map for the collection of bounding entities.
     * @param writer
     * @param name
     * @param boundingEntities
     * @param useOverlibToolTip
     */
    private void writeBoundingRectImageMap(PrintWriter writer, String name,
            Collection<ChartEntity> boundingEntities, boolean useOverlibToolTip) {
        System.out.println("Num entities=" + boundingEntities.size());
        StringBuffer sb = new StringBuffer();
        ChartEntity chartEntity;
        String areaTag;

        StandardToolTipTagFragmentGenerator ttg = new StandardToolTipTagFragmentGenerator();
        StandardURLTagFragmentGenerator urlg = new StandardURLTagFragmentGenerator();
        sb.append("<map id=\"" + name + "\" name=\"" + name + "\">");
        sb.append(StringUtils.getLineSeparator());
        for (Iterator i = boundingEntities.iterator(); i.hasNext();) {
            chartEntity = (ChartEntity) i.next();
            areaTag = chartEntity.getImageMapAreaTag(ttg, urlg).trim();
            if (areaTag.length() > 0) {
                sb.append(chartEntity.getImageMapAreaTag(ttg, urlg));
                sb.append(StringUtils.getLineSeparator());
            }
        }
        sb.append("</map>");
        writer.println(sb.toString());
    }

    /**
     * Get a collection of entities with the area shape equal to the bounding rectangle
     * for the shape of original entity. This is necessary because the Javascript for the sample
     * selection lasso can only handle rect objects.
     * @param entities
     * @return a collection of entities containing the bounding rectangles of the original entities
     */
    private Collection<ChartEntity> getBoundingEntities(Collection entities) {
        ChartEntity entity;
        ChartEntity boundingEntity;
        Shape shape;
        Rectangle2D boundingRect;
        Collection<ChartEntity> boundingEntities = new ArrayList<ChartEntity>();
        for (Iterator i = entities.iterator(); i.hasNext();) {
            entity = (ChartEntity) i.next();
            shape = entity.getArea();
            boundingRect = shape.getBounds2D();
            boundingEntity = new ChartEntity(boundingRect, entity.getToolTipText(), entity.getURLText());
            boundingEntities.add(boundingEntity);
        }
        return boundingEntities;
    }

    /**
     * Get the range of values for a given clinical factor
     * @param dataPoints
     * @param factor
     * @return
     */
    private DataRange getDataRange(Collection<CMAPCADataPoint> dataPoints, PCAcomponent component) {
        double maxValue = Double.MIN_VALUE;
        double minValue = Double.MAX_VALUE;
        double value;

        for (CMAPCADataPoint dataPoint : dataPoints) {
            value = dataPoint.getComponentValue(component);
            if (value < minValue) {
                minValue = value;
            }

            if (value > maxValue) {
                maxValue = value;
            }
        }

        DataRange range = new DataRange(minValue, maxValue);
        return range;
    }

    /**
     * A class for building the legend
     * @author harrismic
     *
     */
    private class PcaLegendItemSource implements LegendItemSource {

        private LegendItemCollection items = new LegendItemCollection();

        public void addLegendItem(LegendItem item) {
            items.add(item);
        }

        public LegendItemCollection getLegendItems() {
            return items;
        }

    }
}