adapters.XYChartAdapter.java Source code

Java tutorial

Introduction

Here is the source code for adapters.XYChartAdapter.java

Source

package adapters;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYLineAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.ui.TextAnchor;
import umontreal.iro.lecuyer.charts.Axis;
import umontreal.iro.lecuyer.charts.SSJXYSeriesCollection;

import javax.swing.*;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * Project: DCDMC
 * Package: adapters
 * Date: 14/Apr/2015
 * Time: 08:55
 * System Time: 8:55 AM
 */

/*
 * Class:        XYChartAdapter
 * Description:
 * Environment:  Java
 * Software:     SSJ
 * Copyright (C) 2001  Pierre L'Ecuyer and Universite de Montreal
 * Organization: DIRO, Universite de Montreal
 * @author
 * @since
    
 * SSJ is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License (GPL) as published by the
 * Free Software Foundation, either version 3 of the License, or
 * any later version.
    
 * SSJ 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.
    
 * A copy of the GNU General Public License is available at
   <a href="http://www.gnu.org/licenses">GPL licence site</a>.
 */

/**
 * This class provides tools to create charts from data in a simple way. Its main
 * feature is to produce
 *  TikZ/PGF (see WWW link <TT><A NAME="tex2html1"
 *   HREF="http://sourceforge.net/projects/pgf/">http://sourceforge.net/projects/pgf/</A></TT>)
 *  compatible source code which can be included
 * in <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> documents, but it can also produce charts in other formats.
 * One can easily create a new chart, and customize its appearance using methods
 * of this class, with the encapsulated
 * {@link umontreal.iro.lecuyer.charts.SSJXYSeriesCollection SSJXYSeriesCollection} object
 * representing the data, and the two
 * {@link Axis Axis} objects representing the axes.
 * All these classes depend on the <TT>JFreeChart</TT> API (see WWW link
 * <TT><A NAME="tex2html2"
 *   HREF="http://www.jfree.org/jfreechart/">http://www.jfree.org/jfreechart/</A></TT>) which provides tools to build charts with
 * Java, to draw them, and export them to files. However, only basic features are
 * used here.
 *
 * <P>
 * Moreover, <TT>XYChartAdapter</TT> provides methods to plot data using a MATLAB friendly
 * syntax. None of these methods provides new features; they just propose a
 * different syntax to create charts. Therefore some features are unavailable
 * when using these methods only.
 *
 */
public abstract class XYChartAdapter {
    protected AxisAdapter XAxis;
    protected AxisAdapter YAxis;

    protected SSJXYSeriesCollection dataset;
    protected JFreeChart chart;
    protected boolean latexDocFlag = true;

    protected boolean autoRange;
    protected double[] manualRange;

    protected boolean grid = false;
    protected double xstepGrid;
    protected double ystepGrid;

    // this flag is set true when plotting probabilities. In that case,
    // y is always >= 0.
    protected boolean probFlag = false;

    protected double chartMargin = 0.02; // margin around the chart

    /**
     * Modified method : return value changed from Axis to AxisAdapter
     *
     * Returns the chart's domain axis (<SPAN CLASS="MATH"><I>x</I></SPAN>-axis) object.
     *
     * @return chart's domain axis (<SPAN CLASS="MATH"><I>x</I></SPAN>-axis) object.
     *
     */
    public AxisAdapter getXAxis() {
        return XAxis;
    }

    /**
     *  Modified method : return value changed from Axis to AxisAdapter
     *
     * Returns the chart's range axis (<SPAN CLASS="MATH"><I>y</I></SPAN>-axis) object.
     *
     * @return chart's range axis (<SPAN CLASS="MATH"><I>y</I></SPAN>-axis) object.
     *
     */
    public AxisAdapter getYAxis() {
        return YAxis;
    }

    public abstract JFrame view(int width, int height);

    /**
     * Returns the <TT>JFreeChart</TT> object associated with this chart.
     *
     * @return the associated JFreeChart object.
     *
     */
    public JFreeChart getJFreeChart() {
        return chart;
    }

    /**
     * Gets the current chart title.
     *
     * @return Chart title.
     *
     */
    public String getTitle() {
        return chart.getTitle().getText();
    }

    /**
     * Sets a title to this chart. This title will appear on the chart displayed
     *  by method {@link #view view}.
     *
     * @param title chart title.
     *
     *
     */
    public void setTitle(String title) {
        chart.setTitle(title);
    }

    /**
     * Must be set <TT>true</TT> when plotting probabilities,
     *   <TT>false</TT> otherwise.
     *
     * @param flag <TT>true</TT> for plotting probabilities
     *
     *
     */
    public void setprobFlag(boolean flag) {
        probFlag = flag;
    }

    /**
     * The <SPAN CLASS="MATH"><I>x</I></SPAN> and the <SPAN CLASS="MATH"><I>y</I></SPAN> ranges of the chart are set automatically.
     *
     */
    public void setAutoRange() {
        setAutoRange(false, false, true, true);
    }

    /**
     * The <SPAN CLASS="MATH"><I>x</I></SPAN> and the <SPAN CLASS="MATH"><I>y</I></SPAN> ranges of the chart are set automatically.
     *  If <TT>right</TT> is <TT>true</TT>, the vertical axis will be on the left of
     *  the points, otherwise on the right. If <TT>top</TT> is <TT>true</TT>,
     *  the horizontal axis  will be under the points, otherwise above the points.
     *
     * @param right true if the x-values on the right of axis.
     *
     *    @param top true if the y-values on the top of axis.
     *
     *
     */
    public void setAutoRange(boolean right, boolean top) {
        setAutoRange(false, false, right, top);
    }

    private double[] adjustRangeBounds(double bmin, double bmax) {
        // resets chart lower and upper bounds to round values.
        // Returns corrected [lowerBound, upperBound]

        double del = (bmax - bmin) / 20.0; // Choose 20 intervals to round
        int a = (int) Math.floor(0.5 + Math.log10(del));
        double d = Math.pow(10.0, (double) a); // power of 10
        double lower = d * Math.ceil((bmin - del) / d);
        if (lower > bmin)
            lower -= d;
        if (0 == Math.abs(bmin))
            lower = 0;
        double upper = d * Math.floor((bmax + del) / d);
        if (upper < bmax)
            upper += d;
        double[] range = new double[2];
        range[0] = lower;
        range[1] = upper;
        return range;
    }

    protected void setAutoRange(boolean xZero, boolean yZero, boolean right, boolean top) {
        // see description of setAxesZero
        autoRange = true;
        double BorneMin = (dataset.getDomainBounds())[0];
        double BorneMax = (dataset.getDomainBounds())[1];
        double del;
        if (BorneMax - BorneMin < 1)
            del = (BorneMax - BorneMin) * chartMargin;
        else
            del = chartMargin;
        if (BorneMin < 0.0)
            BorneMin *= 1.0 + del;
        else
            BorneMin *= 1.0 - del;
        if (BorneMax < 0.0)
            BorneMax *= 1.0 - del;
        else
            BorneMax *= 1.0 + del;
        double[] newRange = new double[2];
        newRange = adjustRangeBounds(BorneMin, BorneMax);
        if (probFlag && (BorneMin == 0.0))
            newRange[0] = 0.0;
        XAxis.getAxis().setLowerBound(newRange[0]);
        XAxis.getAxis().setUpperBound(newRange[1]);

        BorneMin = (dataset.getRangeBounds())[0];
        BorneMax = (dataset.getRangeBounds())[1];
        if (BorneMax - BorneMin < 1)
            del = (BorneMax - BorneMin) * chartMargin;
        else
            del = chartMargin;
        if (BorneMin < 0.0)
            BorneMin *= 1.0 + del;
        else
            BorneMin *= 1.0 - del;
        if (BorneMax < 0.0)
            BorneMax *= 1.0 - del;
        else
            BorneMax *= 1.0 + del;
        newRange = adjustRangeBounds(BorneMin, BorneMax);
        if (probFlag && (newRange[0] <= 0.0)) // probabilities are always >= 0
            newRange[0] = 0.0;
        YAxis.getAxis().setLowerBound(newRange[0]);
        YAxis.getAxis().setUpperBound(newRange[1]);

        if (xZero)
            XAxis.setTwinAxisPosition(0);
        else {
            if (right)
                XAxis.setTwinAxisPosition(XAxis.getAxis().getLowerBound());
            else
                XAxis.setTwinAxisPosition(XAxis.getAxis().getUpperBound());
        }

        if (yZero)
            YAxis.setTwinAxisPosition(0);
        else {
            if (top)
                YAxis.setTwinAxisPosition(YAxis.getAxis().getLowerBound());
            else
                YAxis.setTwinAxisPosition(YAxis.getAxis().getUpperBound());
        }
    }

    /**
     * The <SPAN CLASS="MATH"><I>x</I></SPAN> and the <SPAN CLASS="MATH"><I>y</I></SPAN> ranges of the chart are set automatically.
     * If <TT>xZero</TT> is <TT>true</TT>, the vertical axis will pass through the
     * point <SPAN CLASS="MATH">(0, <I>y</I>)</SPAN>. If <TT>yZero</TT> is <TT>true</TT>, the horizontal axis
     * will pass through the point <SPAN CLASS="MATH">(<I>x</I>, 0)</SPAN>.
     *
     * @param xZero true if vertical axis passes through point 0
     *
     *   @param yZero true if horizontal axis passes through point 0
     *
     *
     */
    public void setAutoRange00(boolean xZero, boolean yZero) {
        setAutoRange(xZero, yZero, true, true);
    }

    /**
     * Sets the <SPAN CLASS="MATH"><I>x</I></SPAN> and <SPAN CLASS="MATH"><I>y</I></SPAN> ranges of the chart  using the format: <TT>range =
     *   [xmin, xmax, ymin, ymax]</TT>.
     * @param range new axis ranges.
     *
     *
     */
    public void setManualRange(double[] range) {
        setManualRange(range, false, false, true, true);
    }

    /**
     * Sets the <SPAN CLASS="MATH"><I>x</I></SPAN> and <SPAN CLASS="MATH"><I>y</I></SPAN> ranges of the chart  using the format: <TT>range =
     *   [xmin, xmax, ymin, ymax]</TT>.
     *   If <TT>right</TT> is <TT>true</TT>, the vertical axis will be on the left of
     *  the points, otherwise on the right. If <TT>top</TT> is <TT>true</TT>,
     *  the horizontal axis  will be under the points, otherwise above the points.
     *
     * @param range new axis ranges.
     *
     *    @param right true if the x-values on the right.
     *
     *    @param top true if the y-values on the top.
     *
     *
     */
    public void setManualRange(double[] range, boolean right, boolean top) {
        setManualRange(range, false, false, right, top);
    }

    private void setManualRange(double[] range, boolean xZero, boolean yZero, boolean right, boolean top) {
        if (range.length != 4)
            throw new IllegalArgumentException("range must have the format: [xmin, xmax, ymin, ymax]");
        autoRange = false;
        XAxis.getAxis().setLowerBound(Math.min(range[0], range[1]));
        XAxis.getAxis().setUpperBound(Math.max(range[0], range[1]));
        YAxis.getAxis().setLowerBound(Math.min(range[2], range[3]));
        YAxis.getAxis().setUpperBound(Math.max(range[2], range[3]));

        if (xZero)
            XAxis.setTwinAxisPosition(0);
        else {
            if (right)
                XAxis.setTwinAxisPosition(XAxis.getAxis().getLowerBound());
            else
                XAxis.setTwinAxisPosition(XAxis.getAxis().getUpperBound());
        }

        if (yZero)
            YAxis.setTwinAxisPosition(0);
        else {
            if (top)
                YAxis.setTwinAxisPosition(YAxis.getAxis().getLowerBound());
            else
                YAxis.setTwinAxisPosition(YAxis.getAxis().getUpperBound());
        }
    }

    /**
     * Sets the <SPAN CLASS="MATH"><I>x</I></SPAN> and <SPAN CLASS="MATH"><I>y</I></SPAN> ranges of the chart using the format: <TT>range =
     *   [xmin, xmax, ymin, ymax]</TT>.
     *  If <TT>xZero</TT> is <TT>true</TT>, the vertical axis will pass through the
     * point <SPAN CLASS="MATH">(0, <I>y</I>)</SPAN>. If <TT>yZero</TT> is <TT>true</TT>, the horizontal axis
     * will pass through the point <SPAN CLASS="MATH">(<I>x</I>, 0)</SPAN>.
     *
     * @param xZero true if vertical axis passes through point 0
     *
     *   @param yZero true if horizontal axis passes through point 0
     *
     *
     */
    public void setManualRange00(double[] range, boolean xZero, boolean yZero) {
        setManualRange(range, xZero, yZero, true, true);
    }

    /**
     * Returns the chart margin, which is the fraction by which the chart
     *     is enlarged on its borders. The default value is <SPAN CLASS="MATH">0.02</SPAN>.
     *
     */
    public double getChartMargin() {
        return chartMargin;
    }

    /**
     * Sets the chart margin to <TT>margin</TT>. It is the fraction by
     *     which the chart is enlarged on its borders.
     *    Restriction:
     * <SPAN CLASS="MATH"><texttt>margin</texttt>&nbsp;&gt;=&nbsp; 0</SPAN>.
     *
     * @param margin margin percentage amount.
     *
     *
     */
    public void setChartMargin(double margin) {
        if (margin < 0.0)
            throw new IllegalArgumentException("margin < 0");
        chartMargin = margin;
    }

    /**
     * Synchronizes <SPAN CLASS="MATH"><I>x</I></SPAN>-axis ticks to the <SPAN CLASS="MATH"><I>s</I></SPAN>-th series <SPAN CLASS="MATH"><I>x</I></SPAN>-values.
     *
     * @param s series.
     *
     *
     */
    public abstract void setTicksSynchro(int s);

    /**
     * Draws a vertical line on the chart at <SPAN CLASS="MATH"><I>x</I></SPAN>-coordinate <TT>x</TT>.
     * <TT>name</TT> is written near the line at <SPAN CLASS="MATH"><I>y</I></SPAN> position <TT>yfrac</TT>
     * (a fraction of the <SPAN CLASS="MATH"><I>y</I></SPAN>-size of the chart, 0 is the bottom, 1 is the top);
     * if <TT>right</TT> is <TT>true</TT>, <TT>name</TT> is written on the right
     * of the line, else on the left.
     *
     * @param x <SPAN CLASS="MATH"><I>x</I></SPAN>-coordinate of the line
     *
     *    @param name description of the line
     *
     *    @param yfrac <SPAN CLASS="MATH"><I>y</I></SPAN>-position of name
     *
     *    @param right <SPAN CLASS="MATH"><I>x</I></SPAN>-position of name
     *
     */
    public void drawVerticalLine(double x, String name, double yfrac, boolean right) {
        double ybottom = YAxis.getAxis().getLowerBound();
        final Object o = this;
        if (this instanceof HistogramChartAdapter)
            ybottom = 0;
        double ytop = YAxis.getAxis().getUpperBound();
        XYLineAnnotation line = new XYLineAnnotation(x, ybottom, x, ytop);
        XYTextAnnotation text = new XYTextAnnotation(name, x, ytop * yfrac);
        if (!right)
            text.setTextAnchor(TextAnchor.HALF_ASCENT_RIGHT);
        else
            text.setTextAnchor(TextAnchor.HALF_ASCENT_LEFT);
        XYPlot plot = getJFreeChart().getXYPlot();
        plot.addAnnotation(line);
        plot.addAnnotation(text);
    }

    /**
     * Puts a grid on the background. It is important to note that the grid is
     *    always shifted in such a way that it contains the axes. Thus, the grid does
     *    not always have an intersection at the corner points; this occurs
     *    only if the corner points are multiples of the steps: <TT>xstep</TT>
     *    and <TT>ystep</TT> sets the step in each direction.
     *
     * @param xstep sets the step in the x-direction.
     *
     *    @param ystep sets the step in the y-direction.
     *
     *
     */
    public void enableGrid(double xstep, double ystep) {
        this.grid = true;
        this.xstepGrid = xstep;
        this.ystepGrid = ystep;
    }

    /**
     * Disables the background grid.
     *
     */
    public void disableGrid() {
        this.grid = false;
    }

    /**
     * Exports the chart to a <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> source code using PGF/TikZ.
     *    This method constructs and returns a string that can be written to
     *    a <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> document to render the plot. <TT>width</TT> and <TT>height</TT>
     *    represents the width and the height of the produced chart. These dimensions
     *    do not take into account the axes and labels extra space. The <TT>width</TT>
     *    and the <TT>height</TT> of the chart are measured in centimeters.
     *
     * @param width Chart's width in centimeters.
     *
     *    @param height Chart's height in centimeters.
     *
     *    @return LaTeX source code.
     *
     */
    public abstract String toLatex(double width, double height);

    /**
     * Transforms the chart to <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> form and writes it in file <TT>fileName</TT>.
     *    The chart's width and height (in centimeters) are <TT>width</TT> and <TT>height</TT>.
     *
     */
    public void toLatexFile(String fileName, double width, double height) {
        String output = toLatex(width, height);
        Writer file = null;
        try {
            file = new FileWriter(fileName);
            file.write(output);
            file.close();
        } catch (IOException e) {
            System.err.println("   toLatexFile:  cannot write to  " + fileName);
            e.printStackTrace();
            try {
                if (file != null)
                    file.close();
            } catch (IOException ioe) {
            }
        }
    }

    /**
     * Flag to remove the <code>\documentclass</code> (and other) commands in the
     * created <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> files.
     * If <TT>flag</TT> is <TT>true</TT>,  then when charts are translated into
     * <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> form, it will be as a self-contained file that can be directly
     * compiled with <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN>. However, in this form, the file cannot be included in
     * another <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> file without causing compilation errors because of the multiple
     * instructions <code>\documentclass</code> and <code>\begin{document}</code>.
     * By setting <TT>flag</TT> to <TT>false</TT>, these instructions will be
     * removed from the <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> chart files, which can then be included in a master
     * <SPAN CLASS="logo,LaTeX">L<SUP><SMALL>A</SMALL></SUP>T<SMALL>E</SMALL>X</SPAN> file. By default, the flag is <TT>true</TT>.
     *
     */
    public void setLatexDocFlag(boolean flag) {
        latexDocFlag = flag;
    }

    protected void setTick0Flags() {
        // Set flag true if first or last label is on perpendicular axis.
        // The label will be moved a little to the right (x-label), or above
        // (y-label) to prevent it from being on the perpendicular axis.
        // But it is unnecessary when graph begins or ends where label is;
        // in this case, flag is false.
        // We cannot put this method in Axis because it depends on the
        // other axis.
        double minAxis = Math.min(XAxis.getAxis().getRange().getLowerBound(), XAxis.getTwinAxisPosition());
        double maxAxis = Math.max(XAxis.getAxis().getRange().getUpperBound(), XAxis.getTwinAxisPosition());
        if (XAxis.getTwinAxisPosition() == minAxis || XAxis.getTwinAxisPosition() == maxAxis)
            YAxis.setTick0Flag(false);
        else
            YAxis.setTick0Flag(true);

        minAxis = Math.min(YAxis.getAxis().getRange().getLowerBound(), YAxis.getTwinAxisPosition());
        maxAxis = Math.max(YAxis.getAxis().getRange().getUpperBound(), YAxis.getTwinAxisPosition());
        if (YAxis.getTwinAxisPosition() == minAxis || YAxis.getTwinAxisPosition() == maxAxis)
            XAxis.setTick0Flag(false);
        else
            XAxis.setTick0Flag(true);
    }

    protected double computeXScale(double position) {
        double[] bounds = new double[2];
        bounds[0] = XAxis.getAxis().getLowerBound();
        bounds[1] = XAxis.getAxis().getUpperBound();

        if (position < bounds[0])
            bounds[0] = position;
        if (position > bounds[1])
            bounds[1] = position;
        bounds[0] -= position;
        bounds[1] -= position;
        return computeScale(bounds);
    }

    protected double computeYScale(double position) {
        double[] bounds = new double[2];
        bounds[0] = YAxis.getAxis().getLowerBound();
        bounds[1] = YAxis.getAxis().getUpperBound();

        if (position < bounds[0])
            bounds[0] = position;
        if (position > bounds[1])
            bounds[1] = position;
        bounds[0] -= position;
        bounds[1] -= position;
        return computeScale(bounds);
    }

    protected double computeScale(double[] bounds) {
        int tenPowerRatio = 0;
        // echelle < 1 si les valeurs sont grandes
        while (bounds[1] > 1000 || bounds[0] < -1000) {
            bounds[1] /= 10;
            bounds[0] /= 10;
            tenPowerRatio++;
        }
        // echelle > 1 si les valeurs sont petites
        while (bounds[1] < 100 && bounds[0] > -100) {
            bounds[1] *= 10;
            bounds[0] *= 10;
            tenPowerRatio--;
        }
        return 1 / Math.pow(10, tenPowerRatio);
    }
}