org.jax.pubarray.server.restful.GraphingResource.java Source code

Java tutorial

Introduction

Here is the source code for org.jax.pubarray.server.restful.GraphingResource.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.pubarray.server.restful;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;

import org.jax.pubarray.db.PersistenceManager;
import org.jax.pubarray.gwtcommon.client.QualifiedColumnMetadata;
import org.jax.pubarray.gwtcommon.client.TableColumnMetadata;
import org.jax.pubarray.gwtcommon.client.TableMetadata;
import org.jax.pubarray.gwtqueryapp.client.ProbeIntensityGraphConfiguration;
import org.jax.pubarray.gwtqueryapp.client.ProbeIntensityGraphConfiguration.GroupedGraphType;
import org.jax.pubarray.server.DatabaseServletContextListener;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.renderer.category.BoxAndWhiskerRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.ScatterRenderer;
import org.jfree.data.Range;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset;
import org.jfree.data.statistics.DefaultMultiValueCategoryDataset;

/**
 * A JAX-RS implementation of a table administration resource
 * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
 */
@Path("/graph-images")
public class GraphingResource {
    private static final Logger LOG = Logger.getLogger(GraphingResource.class.getName());
    private static final double LOG2_FACTOR = Math.log(2);

    @Context
    private ServletContext context;

    @Context
    private HttpServletRequest request;

    private final PersistenceManager persistenceManager = new PersistenceManager();

    /**
     * Constructor
     */
    public GraphingResource() {
    }

    /**
     * A RESTful interface for getting a graph image (well, we are cheating
     * by using an image key so it probably isn't true REST)
     * @param graphImageKey the graph image key
     * @param imageWidthPixles the image width in pixles
     * @param imageHeightPixles the image height in pixles
     * @return
     *          the image response
     */
    @GET
    @Path("/probe-intensity-graph-{graphImageKey}-" + "{imageWidthPixles}x{imageHeightPixles}.png")
    public Response getProbeIntensityGraph(@PathParam("graphImageKey") String graphImageKey,
            @PathParam("imageWidthPixles") int imageWidthPixles,
            @PathParam("imageHeightPixles") int imageHeightPixles) {
        try {
            LOG.info("creating a graph image for key: " + graphImageKey);

            ProbeIntensityGraphConfiguration graphConfig = this.getProbeIntensityGraphConfiguration(graphImageKey);

            LOG.info("using graph configuration: " + graphConfig);

            JFreeChart graph = this.createProbeIntensityGraph(graphConfig);

            LOG.info("done creating graph, now rendering to an image of size: " + imageWidthPixles + "x"
                    + imageHeightPixles);
            BufferedImage bi = graph.createBufferedImage(imageWidthPixles, imageHeightPixles);
            StreamingPNGOutput streamingImageOutput = new StreamingPNGOutput(bi);
            return Response.ok(streamingImageOutput).type("image/png").build();
        } catch (Exception ex) {
            LOG.log(Level.SEVERE, "failed to create probe intensity image", ex);
            return null;
        }
    }

    /**
     * Create a graph for the given configuration
     * @param graphConfiguration
     *          the key
     * @return
     *          the graph
     */
    @SuppressWarnings("unchecked")
    private JFreeChart createProbeIntensityGraph(ProbeIntensityGraphConfiguration graphConfiguration) {
        try {
            Connection connection = this.getConnection();

            String[] probeIds = graphConfiguration.getProbeIds();
            double[][] probeDataRows = new double[probeIds.length][];
            for (int i = 0; i < probeIds.length; i++) {
                probeDataRows[i] = this.persistenceManager.getDataRowForProbeID(connection, probeIds[i]);
            }

            TableColumnMetadata orderBy = graphConfiguration.getOrderProbesBy();
            final List<Comparable> orderByItems;
            if (orderBy != null) {
                LOG.info("We are ordering by: " + orderBy);
                orderByItems = this.persistenceManager.getDesignDataColumn(connection, orderBy);
            } else {
                orderByItems = null;
            }

            TableMetadata metadata = this.persistenceManager.getDataTableMetadata(connection);
            final CategoryDataset categoryDataset;
            if (graphConfiguration.getGroupReplicates()) {
                switch (graphConfiguration.getGroupedGraphType()) {
                case BOX_PLOT: {
                    categoryDataset = new DefaultBoxAndWhiskerCategoryDataset();
                }
                    break;

                case SCATTER_PLOT: {
                    categoryDataset = new DefaultMultiValueCategoryDataset();
                }
                    break;

                default:
                    throw new IllegalArgumentException(
                            "don't know how to deal with plot type: " + graphConfiguration.getGroupedGraphType());
                }
            } else {
                categoryDataset = new DefaultCategoryDataset();
            }

            // iterate through all of the selected probesets
            List<QualifiedColumnMetadata> termsOfInterest = Arrays.asList(graphConfiguration.getTermsOfInterest());
            for (int rowIndex = 0; rowIndex < probeDataRows.length; rowIndex++) {
                double[] currRow = probeDataRows[rowIndex];
                assert currRow.length == metadata.getColumnMetadata().length - 1;

                // should we log2 transform the data?
                if (graphConfiguration.getLog2TransformData()) {
                    for (int i = 0; i < currRow.length; i++) {
                        currRow[i] = Math.log(currRow[i]) / LOG2_FACTOR;
                    }
                }

                // iterate through the columns in the data table (each column
                // represents a different array)
                List<ComparableContainer<Double, Comparable>> rowElemList = new ArrayList<ComparableContainer<Double, Comparable>>();
                for (int colIndex = 0; colIndex < currRow.length; colIndex++) {
                    // we use +1 indexing here because we want to skip over
                    // the probesetId metadata and get right to the
                    // array intensity metadata
                    TableColumnMetadata colMeta = metadata.getColumnMetadata()[colIndex + 1];

                    // check to see if we need to skip this data
                    if (!graphConfiguration.getIncludeDataFromAllArrays()) {
                        // if it's one of the "terms of interest" we will keep
                        // it. we're using a brute force search here
                        boolean keepThisOne = false;
                        for (QualifiedColumnMetadata termOfInterest : termsOfInterest) {
                            if (termOfInterest.getTableName().equals(metadata.getTableName())
                                    && termOfInterest.getName().equals(colMeta.getName())) {
                                keepThisOne = true;
                                break;
                            }
                        }

                        if (!keepThisOne) {
                            continue;
                        }
                    }

                    final String columnName = colMeta.getName();
                    final Comparable columnKey;
                    if (orderByItems == null) {
                        columnKey = columnName;
                    } else {
                        // the ordering will be done on the selected
                        // "order by" design criteria
                        columnKey = new ComparableContainer<String, Comparable>(columnName,
                                orderByItems.get(colIndex), !graphConfiguration.getGroupReplicates());

                        // TODO remove me!!!!
                        System.out.println("For array " + columnName + " the order by " + "value is: "
                                + orderByItems.get(colIndex));
                        // end of remove me
                    }

                    rowElemList
                            .add(new ComparableContainer<Double, Comparable>(currRow[colIndex], columnKey, false));
                }

                Collections.sort(rowElemList);

                if (graphConfiguration.getGroupReplicates()) {
                    switch (graphConfiguration.getGroupedGraphType()) {
                    case BOX_PLOT: {
                        DefaultBoxAndWhiskerCategoryDataset dataset = (DefaultBoxAndWhiskerCategoryDataset) categoryDataset;
                        for (int i = 0; i < rowElemList.size(); i++) {
                            List<Double> groupList = new ArrayList<Double>();
                            groupList.add(rowElemList.get(i).getElement());
                            Comparable colKey = rowElemList.get(i).getComparable();

                            i++;
                            for (; i < rowElemList.size()
                                    && rowElemList.get(i).getComparable().equals(colKey); i++) {
                                groupList.add(rowElemList.get(i).getElement());
                            }
                            i--;

                            dataset.add(groupList, probeIds[rowIndex], colKey);
                        }
                    }
                        break;

                    case SCATTER_PLOT: {
                        DefaultMultiValueCategoryDataset dataset = (DefaultMultiValueCategoryDataset) categoryDataset;
                        for (int i = 0; i < rowElemList.size(); i++) {
                            List<Double> groupList = new ArrayList<Double>();
                            groupList.add(rowElemList.get(i).getElement());
                            Comparable colKey = rowElemList.get(i).getComparable();

                            i++;
                            for (; i < rowElemList.size()
                                    && rowElemList.get(i).getComparable().equals(colKey); i++) {
                                groupList.add(rowElemList.get(i).getElement());
                            }
                            i--;

                            dataset.add(groupList, probeIds[rowIndex], colKey);
                        }
                    }
                        break;
                    }
                } else {
                    DefaultCategoryDataset dataset = (DefaultCategoryDataset) categoryDataset;
                    for (ComparableContainer<Double, Comparable> rowElem : rowElemList) {
                        dataset.addValue(rowElem.getElement(), probeIds[rowIndex], rowElem.getComparable());
                    }
                }
            }

            CategoryAxis xAxis = new CategoryAxis();
            if (graphConfiguration.getGroupReplicates() && orderBy != null) {
                xAxis.setLabel(orderBy.getName());
            } else {
                if (orderBy != null) {
                    xAxis.setLabel("Arrays (Ordered By " + orderBy.getName() + ")");
                } else {
                    xAxis.setLabel("Arrays");
                }
            }
            xAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);

            final NumberAxis yAxis;
            if (graphConfiguration.getLog2TransformData()) {
                yAxis = new NumberAxis("log2(Intensity)");
            } else {
                yAxis = new NumberAxis("Intensity");
            }
            yAxis.setAutoRange(true);
            yAxis.setAutoRangeIncludesZero(false);

            // TODO: this is a HACK to deal with auto-range bug in JFreeChart
            //       which occurs when doing the grouped scatter plot
            if (graphConfiguration.getGroupReplicates()
                    && graphConfiguration.getGroupedGraphType() == GroupedGraphType.SCATTER_PLOT) {
                double minVal = Double.POSITIVE_INFINITY;
                double maxVal = Double.NEGATIVE_INFINITY;
                for (double[] dataRow : probeDataRows) {
                    for (double datum : dataRow) {
                        if (datum > maxVal) {
                            maxVal = datum;
                        }

                        if (datum < minVal) {
                            minVal = datum;
                        }
                    }

                    if (minVal != Double.POSITIVE_INFINITY && maxVal != Double.NEGATIVE_INFINITY
                            && minVal != maxVal) {
                        yAxis.setAutoRange(false);

                        double margin = (maxVal - minVal) * 0.02;
                        Range yRange = new Range(minVal - margin, maxVal + margin);
                        yAxis.setRange(yRange);
                    }
                }
            }
            // END HACK

            final CategoryItemRenderer renderer;
            if (graphConfiguration.getGroupReplicates()) {
                switch (graphConfiguration.getGroupedGraphType()) {
                case BOX_PLOT: {
                    BoxAndWhiskerRenderer boxRenderer = new BoxAndWhiskerRenderer();
                    boxRenderer.setMaximumBarWidth(0.03);
                    renderer = boxRenderer;
                }
                    break;

                case SCATTER_PLOT: {
                    renderer = new ScatterRenderer();
                }
                    break;

                default:
                    throw new IllegalArgumentException(
                            "don't know how to deal with plot type: " + graphConfiguration.getGroupedGraphType());
                }
            } else {
                renderer = new LineAndShapeRenderer();
            }
            Plot plot = new CategoryPlot(categoryDataset, xAxis, yAxis, renderer);

            return new JFreeChart("Intensity Values", plot);
        } catch (SQLException ex) {
            LOG.log(Level.SEVERE, "failed to generate image", ex);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private class ComparableContainer<E, C extends Comparable> implements Comparable<ComparableContainer<E, C>> {
        private final E element;
        private final C comparable;
        private final boolean compareOnElementToo;

        public ComparableContainer(E element, C comparable, boolean compareOnElementToo) {
            this.compareOnElementToo = compareOnElementToo;
            this.element = element;
            this.comparable = comparable;
        }

        public E getElement() {
            return this.element;
        }

        public C getComparable() {
            return this.comparable;
        }

        /**
         * {@inheritDoc}
         */
        public int compareTo(ComparableContainer<E, C> otherCompCont) {
            int topComp = this.getComparable().compareTo(otherCompCont.getComparable());
            if (topComp == 0 && this.compareOnElementToo) {
                if (this.getElement() instanceof Comparable && otherCompCont.getElement() instanceof Comparable) {
                    Comparable thisElemComp = (Comparable) this.getElement();
                    Comparable otherElemComp = (Comparable) otherCompCont.getElement();

                    return thisElemComp.compareTo(otherElemComp);
                } else {
                    return 0;
                }
            } else {
                return topComp;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object otherObj) {
            if (otherObj instanceof ComparableContainer) {
                ComparableContainer<E, C> otherCompCont = (ComparableContainer<E, C>) otherObj;

                if (this.compareOnElementToo) {
                    return this.getComparable().equals(otherCompCont.getComparable())
                            && this.getElement().equals(otherCompCont.getElement());
                } else {
                    return this.getComparable().equals(otherCompCont.getComparable());
                }
            } else {
                return false;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            if (this.compareOnElementToo) {
                return this.getElement().toString();
            } else {
                return this.getComparable().toString();
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return this.getComparable().hashCode();
        }
    }

    /**
     * Get the probe intensity graph configuration for the given key
     * @param graphImageKey
     *          the key
     * @return
     *          the configuration
     */
    private ProbeIntensityGraphConfiguration getProbeIntensityGraphConfiguration(String graphImageKey) {
        HttpSession session = this.request.getSession();
        return (ProbeIntensityGraphConfiguration) session.getAttribute(graphImageKey);
    }

    /**
     * TODO this class is also at: org/jax/haplotype/restful/StreamingImageOutput.java
     * This duplication should be resolved
     * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
     */
    private class StreamingPNGOutput implements StreamingOutput {
        private final BufferedImage image;

        /**
         * Constructor
         * @param image
         *          the image to stream
         */
        public StreamingPNGOutput(BufferedImage image) {
            this.image = image;
        }

        /**
         * {@inheritDoc}
         */
        public void write(OutputStream outputStream) throws IOException, WebApplicationException {
            ImageIO.write(this.image, "png", outputStream);
        }
    }

    /**
     * A helper function to get the DB connection
     * @return
     *          the DB connection
     */
    private Connection getConnection() {
        return (Connection) this.context.getAttribute(DatabaseServletContextListener.CONNECTION_ATTRIBUTE);
    }
}