edu.fullerton.viewerplugin.PluginSupport.java Source code

Java tutorial

Introduction

Here is the source code for edu.fullerton.viewerplugin.PluginSupport.java

Source

/*
 * Copyright (C) 2012 Joseph Areeda <joseph.areeda at ligo.org>
 *
 * This program 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 program 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 program.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.fullerton.viewerplugin;

import com.areeda.jaDatabaseSupport.Database;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.DefaultFontMapper;
import com.itextpdf.text.pdf.FontMapper;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import edu.fullerton.jspWebUtils.Page;
import edu.fullerton.jspWebUtils.WebUtilException;
import edu.fullerton.ldvjutils.LdvTableException;
import edu.fullerton.ldvjutils.TimeAndDate;
import edu.fullerton.ldvtables.ImageTable;
import edu.fullerton.ldvtables.ViewUser;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.TreeSet;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.TimeSeriesDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

/**
 *
 * @author Joseph Areeda <joseph.areeda at ligo.org>
 */
public class PluginSupport {
    protected Database db = null;
    protected Page vpage = null;
    protected ViewUser vuser = null;
    protected int width = 640;
    protected int height = 480;
    protected Map<String, String[]> parameterMap; // all request parameters for a plot request
    private String xAxisLabel;
    protected String enableKey;

    public PluginSupport() {
        enableKey = "no way should the parameter map contain this";
    }

    public boolean isSelected() {
        boolean ret = false;
        if (parameterMap != null) {
            ret = parameterMap.containsKey(enableKey);
        }
        return ret;
    }

    public String getEnableKey() {
        return enableKey;
    }

    public void setup(Database db, Page vpage, ViewUser vuser) {
        this.db = db;
        this.vpage = vpage;
        this.vuser = vuser;
    }

    /**
     * Use information from the buffer to create a title for the plot
     * @param dbufs - data being plotted
     * @param compact - if it's a small plot don't tell them too much
     * @return
     * @throws LdvTableException
     */
    public String getTitle(ArrayList<ChanDataBuffer> dbufs, boolean compact) throws LdvTableException {
        String ret;
        if (dbufs.size() == 1) {
            ret = getLegend(dbufs.get(0), compact);
        } else if (dbufs.size() > 1) {
            TreeSet<String> chans = new TreeSet<>();
            for (ChanDataBuffer dbuf : dbufs) {
                chans.add(dbuf.getChanInfo().getChanName());
            }
            ret = "";
            for (String cname : chans) {
                if (ret.length() > 0) {
                    ret += ", ";
                }
                ret += cname;
            }
        } else {
            ret = "Looks like we don't have any data??";
        }
        return ret;
    }

    /**
     * From the single time series create text for a legend
     * @param dbuf
     * @param compact
     * @return
     * @throws LdvTableException
     */
    public String getLegend(ChanDataBuffer dbuf, boolean compact) throws LdvTableException {
        String ret;
        String chName = dbuf.getChanInfo().getChanName();
        float fs = dbuf.getChanInfo().getRate();
        String fsStr;
        if (fs > 0.999) {
            fsStr = String.format("%1$.0f", fs);
        } else {
            fsStr = String.format("%1$.3f", fs);
        }
        long startGps = dbuf.getTimeInterval().getStartGps();
        String startUTCstr = TimeAndDate.gpsAsUtcString(startGps);
        long stopGps = dbuf.getTimeInterval().getStopGps();
        int duration = (int) (stopGps - startGps);
        String durationStr = TimeAndDate.hrTime(duration);
        if (compact) {
            ret = String.format("%1$s %2$d (%3$s)", chName, startGps, durationStr);
        } else {
            ret = String.format("%1$s t=%3$s at %2$sHz \n%4$s UTC (%5$d)", chName, fsStr, durationStr, startUTCstr,
                    startGps);
        }
        return ret;
    }

    /**
     * Create an image from the Chart Panel and add it the database
     * @param cp input plot
     * @return image ID of newly added row
     * @throws IOException
     * @throws SQLException
     * @throws NoSuchAlgorithmException 
     */
    public int saveImageAsPNG(ChartPanel cp) throws IOException, SQLException, NoSuchAlgorithmException {
        JFreeChart chart = cp.getChart();
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ChartUtilities.writeChartAsPNG(bos, chart, width, height);

        ImageTable itbl = new ImageTable(db);

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        int imgId = itbl.addImg(vuser.getCn(), bis, "image/png");
        return imgId;
    }

    public void saveImageAsPNGFile(ChartPanel cp, String fname) throws WebUtilException {
        FileOutputStream fos = null;
        try {
            JFreeChart chart = cp.getChart();

            fos = new FileOutputStream(fname);
            ChartUtilities.writeChartAsPNG(fos, chart, width, height);
            fos.close();
            fos = null;
        } catch (Exception ex) {
            throw new WebUtilException("Saving image: " + ex.getClass() + " - " + ex.getLocalizedMessage());
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (Exception ex) {
                throw new WebUtilException("Saving image: " + ex.getClass() + " - " + ex.getLocalizedMessage());
            }
        }

    }

    /**
     * Create a Scalable Vector Graphics (SVG) file from a JFree Chart
     * @param chart the plot ready for saving
     * @param filename the output filename
     * @throws WebUtilException 
     */
    public void saveImageAsSvgFile(JFreeChart chart, String filename) throws WebUtilException {
        // THE FOLLOWING CODE BASED ON THE EXAMPLE IN THE BATIK DOCUMENTATION...
        // Get a DOMImplementation
        DOMImplementation domImpl;
        domImpl = SVGDOMImplementation.getDOMImplementation();

        // Create an instance of org.w3c.dom.Document
        Document document = domImpl.createDocument(null, "svg", null);
        // Create an instance of the SVG Generator
        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
        // set the precision to avoid a null pointer exception in Batik 1.5
        svgGenerator.getGeneratorContext().setPrecision(6);
        // Ask the chart to render into the SVG Graphics2D implementation
        chart.draw(svgGenerator, new Rectangle2D.Double(0, 0, width, height), null);
        // Finally, stream out SVG to a file using UTF-8 character to
        // byte encoding
        boolean useCSS = true;
        Writer out;
        try {
            out = new OutputStreamWriter(new FileOutputStream(new File(filename)), "UTF-8");
            svgGenerator.stream(out, useCSS);
            out.close();
        } catch (IOException ex) {
            throw new WebUtilException("Writing SVG image", ex);
        }
    }

    public void saveImageAsPdfFile(JFreeChart chart, String filename) throws WebUtilException {
        try {
            OutputStream out = new BufferedOutputStream(new FileOutputStream(filename));
            Rectangle pagesize = new Rectangle(width, height);
            com.itextpdf.text.Document document;
            document = new com.itextpdf.text.Document(pagesize, 50, 50, 50, 50);
            try {
                PdfWriter writer = PdfWriter.getInstance(document, out);
                FontMapper mapper = new DefaultFontMapper();
                document.addAuthor("JFreeChart");
                document.addSubject("Demonstration");
                document.open();
                PdfContentByte cb = writer.getDirectContent();
                PdfTemplate tp = cb.createTemplate(width, height);
                Graphics2D g2 = tp.createGraphics(width, height, mapper);
                Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height);
                chart.draw(g2, r2D);
                g2.dispose();
                cb.addTemplate(tp, 0, 0);
            } catch (DocumentException de) {
                throw new WebUtilException("Saving as pdf", de);
            }
            document.close();
        } catch (FileNotFoundException ex) {
            throw new WebUtilException("Saving plot as pdf: ", ex);
        }

    }

    public void setSize(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public void setParameters(Map<String, String[]> parameterMap) {
        this.parameterMap = parameterMap;
    }

    public static void getRangeLimits(TimeSeriesCollection mtds, Double rng[]) {
        Double minx, miny, maxx, maxy;

        minx = miny = Double.MAX_VALUE;

        maxx = maxy = -Double.MAX_VALUE;
        for (Iterator it = mtds.getSeries().iterator(); it.hasNext();) {
            TimeSeries ds = (TimeSeries) it.next();
            for (int item = 1; item < ds.getItemCount() - 1; item++) {
                TimeSeriesDataItem dataItem = ds.getDataItem(item);
                RegularTimePeriod period = dataItem.getPeriod();

                double y = dataItem.getValue().doubleValue();
                double x = period.getFirstMillisecond();

                minx = Math.min(minx, x);
                miny = Math.min(miny, y);
                maxx = Math.max(maxx, x);
                maxy = Math.max(maxy, y);
            }
        }
        rng[0] = minx;
        rng[1] = miny;
        rng[2] = maxx;
        rng[3] = maxy;
    }

    public static void getRangeLimits(XYSeriesCollection mtds, Double[] rng, int skip) {
        getRangeLimits(mtds, rng, skip, -Float.MAX_VALUE, Float.MAX_VALUE);
    }

    public static void getRangeLimits(XYSeriesCollection mtds, Double[] rng, int skip, float xmin, float xmax) {
        Double minx, miny, maxx, maxy;

        minx = miny = Double.MAX_VALUE;

        maxx = maxy = -Double.MAX_VALUE;
        for (Iterator it = mtds.getSeries().iterator(); it.hasNext();) {

            XYSeries ds = (XYSeries) it.next();
            for (int item = skip; item < ds.getItemCount() - skip; item++) {
                double x = ds.getX(item).doubleValue();
                double y = ds.getY(item).doubleValue();

                if (x >= xmin && x <= xmax) {
                    minx = Math.min(minx, x);
                    miny = Math.min(miny, y);
                    maxx = Math.max(maxx, x);
                    maxy = Math.max(maxy, y);
                }
            }
        }
        rng[0] = minx;
        rng[1] = miny;
        rng[2] = maxx;
        rng[3] = maxy;
    }

    public static int scaleRange(TimeSeriesCollection mtds, Double miny, Double maxy) {
        int exp = PluginSupport.getExp(miny, maxy);
        double scale = Math.pow(10, exp);
        for (Iterator it = mtds.getSeries().iterator(); it.hasNext();) {
            TimeSeries ds = (TimeSeries) it.next();
            for (int item = 0; item < ds.getItemCount(); item++) {
                TimeSeriesDataItem dataItem = ds.getDataItem(item);
                RegularTimePeriod period = dataItem.getPeriod();

                double y = dataItem.getValue().doubleValue();
                y *= scale;
                ds.update(period, y);
            }
        }
        return exp;
    }

    static int getExp(Double miny, Double maxy) {
        int mine = miny == 0 ? 0 : (int) Math.floor(Math.log10(Math.abs(miny)));
        int maxe = maxy == 0 ? 0 : (int) Math.ceil(Math.log10(Math.abs(maxy)));
        int rng = maxe - mine;
        int exp = -(maxe + mine) / 2;

        double min = 0;
        if (miny != 0) {
            min = Math.log10(Math.abs(miny));
        }
        double max = 0;
        if (maxy != 0) {
            max = Math.log10(Math.abs(maxy));
        }
        if (Math.abs(max) > Math.abs(min)) {
            exp = -(int) Math.round(max);
        } else {
            exp = -(int) Math.round(min);
        }
        return exp;
    }

    public static int scaleRange(XYSeriesCollection mtds, Double miny, Double maxy) {
        int exp = PluginSupport.getExp(miny, maxy);
        if (exp > 0 && exp < 100) {
            int nseries = mtds.getSeriesCount();
            XYSeries[] newSeries = new XYSeries[nseries];

            double scale = Math.pow(10, exp);
            for (int s = 0; s < nseries; s++) {
                XYSeries ds = (XYSeries) mtds.getSeries(s);
                Comparable skey = mtds.getSeriesKey(s);
                XYSeries nds = new XYSeries(skey, true);
                for (int item = 0; item < ds.getItemCount(); item++) {
                    double x = ds.getX(item).doubleValue();
                    double y = ds.getY(item).doubleValue();

                    y *= scale;
                    nds.add(x, y);
                }
                newSeries[s] = nds;
            }
            mtds.removeAllSeries();
            for (int s = 0; s < nseries; s++) {
                mtds.addSeries(newSeries[s]);
            }
        } else {
            exp = 0;
        }
        return exp;
    }

    /**
     * Assume that the product wants the PlotManager to do the data transfer. Override for special cases
     * @return always true
     */
    public boolean needsDataXfer() {
        return true;
    }

    /**
     * Generate a JFreeChart TimeSeries object from a ChanDataBuffer
     * Note:  the time axis is in UTC.  For GPS or delta T use XY series.
     * @param dbuf - ldvw data buffer
     * @param legend - plot legend for this series
     * @return JFreeChart time series for adding to a plot
     */
    public TimeSeries getTimeSeries(ChanDataBuffer dbuf, String legend, int sum) throws LdvTableException {
        sum = sum < 1 ? 1 : sum;
        TimeSeries ts;
        ts = new TimeSeries(legend, Millisecond.class);
        SimpleTimeZone utctz = new SimpleTimeZone(0, "UTC");

        float rate = dbuf.getChanInfo().getRate();
        double msPerSample = 1000 / rate;
        long startMs = TimeAndDate.gps2utc(dbuf.getTimeInterval().getStartGps()) * 1000;
        float[] data = dbuf.getData();
        for (int i = 0; i < dbuf.getDataLength(); i += sum) {
            float td = 0.f;
            int nsum = 0;
            for (int j = 0; j < sum && i + j < dbuf.getDataLength(); j++) {
                td += data[i + j];
                nsum++;
            }
            td /= nsum;

            long curMs = Math.round(msPerSample * i + startMs);
            Date t = new Date(curMs);
            ts.addOrUpdate(new Millisecond(t, utctz), td);
            if (msPerSample >= 1000) {
                // this plots trend data as stair steps
                long endMs = Math.round(curMs + msPerSample - 1);
                Date t1 = new Date(endMs);
                ts.addOrUpdate(new Millisecond(t1, utctz), td);
            }
        }
        return ts;
    }

    /**
     * Convert a ldvw data buffer to JFreeChart XY-series
     * 
     * @param dbuf - ldvw data buf
     * @param legend - String to identify series
     * @param dt if true X axis is 0-t in [hopefully] intelligent units else it's gps time
     * @param sum
     * @param xAxisLabel this argument is returned set to a label for the X-axis
     * @return A series to be added to a JFreeChart plot
     */
    public XYSeries addXySeries(ChanDataBuffer dbuf, String legend, boolean dt, int sum) throws LdvTableException {
        sum = sum < 1 ? 1 : sum;
        XYSeries xys = new XYSeries(legend, false);

        float rate = dbuf.getChanInfo().getRate();

        long startSec = dbuf.getTimeInterval().getStartGps();
        long durSec = dbuf.getTimeInterval().getStopGps() - dbuf.getTimeInterval().getStartGps();
        double scale = 1;
        double t0 = startSec;
        xAxisLabel = "GPS time";
        String units;
        if (dt) {
            t0 = 0;
            // put dt plots into reasonable units
            if (durSec < 1800) {
                scale = 1.;
                units = "sec";
            } else if (durSec < 3 * 3600) {
                scale = 1 / 60.;
                units = "min";
            } else if (durSec < 48 * 3600) {
                scale = 1 / 3600.;
                units = "hrs";
            } else {
                scale = 1.0 / (24 * 3600);
                units = "days";
            }
            xAxisLabel = String.format("dt from %1$,d (%2$s)", startSec, units);
        }

        float[] data = dbuf.getData();
        for (int i = 0; i < dbuf.getDataLength(); i += sum) {
            double x = (i / rate + t0) * scale; // handle gps vs dt and dt units
            double y = 0;
            int nsum = 0;
            for (int j = 0; j < sum && i + j < dbuf.getDataLength(); j++) {
                y += data[i + j];
                nsum++;
            }
            y /= nsum;
            xys.add(x, y);
        }

        return xys;
    }

    public String getxAxisLabel() {
        return xAxisLabel;
    }

    /**
     * As part of remembering where we came from, form values are passed back and forth to select
     * more. Here we use the previous value or default for the specified key
     *
     * @param key - Parameter name for this field
     * @param idx - Index into value array, 0 if only 1 value allowed
     * @param def - default value if no parameter or parameter is empty
     * @return
     */
    public String getPrevValue(String key, int idx, String def) {
        String ret = def;
        String[] prev = parameterMap.get(key);
        if (prev != null && prev.length > idx && !prev[0].isEmpty()) {
            ret = prev[idx];
        }
        return ret;
    }

    /**
     * Checkboxes are a bit difficult because their key only gets sent if it's checked.  So we 
     * don't really know if it's the first time thru with no values for anything or they unchecked
     * it.
     * 
     * @param key - parameter name
     * @return true if parameter is available
     */
    public boolean getPrevValue(String key) {
        boolean ret = parameterMap.containsKey(key);
        return ret;
    }
}