umontreal.iro.lecuyer.charts.MultipleDatasetChart.java Source code

Java tutorial

Introduction

Here is the source code for umontreal.iro.lecuyer.charts.MultipleDatasetChart.java

Source

/*
 * Class:        MultipleDatasetChart
 * Description:  
 * Environment:  Java
 * Software:     SSJ 
 * Copyright (C) 2001  Pierre L'Ecuyer and Universit de Montral
 * Organization: DIRO, Universit de Montral
 * @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>.
 */

package umontreal.iro.lecuyer.charts;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.plot.PlotOrientation;

import java.util.Locale;
import java.util.Formatter;
import java.util.ArrayList;
import javax.swing.JFrame;

/**
 * Provides tools to plot many datasets on the same chart.
 * This class is mainly used to draw plots with different styles.
 * Class {@link umontreal.iro.lecuyer.charts.XYChart XYChart} and
 * its subclasses are to be preferred to draw simple charts with one style.
 * Datasets are stored in an <TT>ArrayList</TT>. The first dataset is called
 * as the <SPAN  CLASS="textit">primary dataset</SPAN>.
 * 
 */
public class MultipleDatasetChart {

    protected ArrayList<SSJXYSeriesCollection> datasetList;
    protected Axis XAxis;
    protected Axis YAxis;
    protected JFreeChart chart;
    protected boolean latexDocFlag = true;

    protected boolean autoRange = true;
    protected double[] manualRange;

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

    /**
     * Initializes a new <TT>MultipleDatasetChart</TT>.
     * 
     */
    public MultipleDatasetChart() {
        super();

        // create the chart...
        chart = ChartFactory.createXYLineChart(null, // chart title
                null, // x axis label
                null, // y axis label
                null, // data
                PlotOrientation.VERTICAL, true, // include legend
                true, // tool tips
                false // urls
        );

        datasetList = new ArrayList<SSJXYSeriesCollection>();
        // Initialize axis variables
        XAxis = new Axis((NumberAxis) ((XYPlot) chart.getPlot()).getDomainAxis(), Axis.ORIENTATION_HORIZONTAL);
        YAxis = new Axis((NumberAxis) ((XYPlot) chart.getPlot()).getRangeAxis(), Axis.ORIENTATION_VERTICAL);
    }

    /**
     * Initializes a new <TT>MultipleDatasetChart</TT> instance.
     *    <TT>title</TT> sets a title, <TT>XLabel</TT> is a short description of
     *    the <SPAN CLASS="MATH"><I>x</I></SPAN>-axis, and <TT>YLabel</TT> is a short description of the <SPAN CLASS="MATH"><I>y</I></SPAN>-axis.
     * 
     * @param title chart title.
     * 
     *    @param XLabel Label on <SPAN CLASS="MATH"><I>x</I></SPAN>-axis.
     * 
     *    @param YLabel Label on <SPAN CLASS="MATH"><I>y</I></SPAN>-axis.
     * 
     */
    public MultipleDatasetChart(String title, String XLabel, String YLabel) {
        // create the chart...
        chart = ChartFactory.createXYLineChart(title, // chart title
                XLabel, // x axis label
                YLabel, // y axis label
                null, // data
                PlotOrientation.VERTICAL, true, // include legend
                true, // tool tips
                false // urls
        );

        datasetList = new ArrayList<SSJXYSeriesCollection>();
        //Initialize axis variables
        XAxis = new Axis((NumberAxis) ((XYPlot) chart.getPlot()).getDomainAxis(), Axis.ORIENTATION_HORIZONTAL);
        YAxis = new Axis((NumberAxis) ((XYPlot) chart.getPlot()).getRangeAxis(), Axis.ORIENTATION_VERTICAL);
    }

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

    /**
     * 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 Axis getXAxis() {
        return XAxis;
    }

    /**
     * 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 Axis getYAxis() {
        return YAxis;
    }

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

    /**
     * Sets a title to the 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);
    }

    /**
     * Sets chart range to automatic values.
     * 
     */
    public void setAutoRange() {
        autoRange = true;
        double[][] temp = new double[2][datasetList.size()];
        for (int i = 0; i < datasetList.size(); i++) {
            temp[0][i] = (datasetList.get(i).getDomainBounds())[0];
            temp[1][i] = (datasetList.get(i).getDomainBounds())[1];
        }
        XAxis.getAxis().setLowerBound(min(temp[0]));
        XAxis.getAxis().setUpperBound(max(temp[1]));

        for (int i = 0; i < datasetList.size(); i++) {
            temp[0][i] = (datasetList.get(i).getRangeBounds())[0];
            temp[1][i] = (datasetList.get(i).getRangeBounds())[1];
        }
        YAxis.getAxis().setLowerBound(min(temp[0]));
        YAxis.getAxis().setUpperBound(max(temp[1]));
    }

    /**
     * Sets new <SPAN CLASS="MATH"><I>x</I></SPAN>-axis and <SPAN CLASS="MATH"><I>y</I></SPAN>-axis bounds, with format: <TT>axisRange</TT> = [xmin, xmax, ymin, ymax].
     * 
     * @param axisRange new axis ranges.
     * 
     * 
     */
    public void setManualRange(double[] axisRange) {
        if (axisRange.length != 4)
            throw new IllegalArgumentException("axisRange must share the format: [xmin, xmax, ymin, ymax]");
        autoRange = false;
        XAxis.getAxis().setLowerBound(axisRange[0]);
        XAxis.getAxis().setUpperBound(axisRange[1]);
        YAxis.getAxis().setLowerBound(axisRange[2]);
        YAxis.getAxis().setUpperBound(axisRange[3]);
    }

    /**
     * Adds a new dataset to the chart at the end of the list and returns its position.
     * 
     * @param dataset dataset to add.
     * 
     *    @return the dataset position in the list.
     * 
     */
    public int add(SSJXYSeriesCollection dataset) {
        ((XYPlot) chart.getPlot()).setDataset(datasetList.size(), dataset.getSeriesCollection());
        ((XYPlot) chart.getPlot()).setRenderer(datasetList.size(), dataset.getRenderer());
        datasetList.add(dataset);
        if (datasetList.size() == 1) {
            XAxis.setLabelsAuto();
            YAxis.setLabelsAuto();
        }
        return datasetList.size() - 1;
    }

    /**
     * Gets the primary dataset.
     * 
     * @return dataset.
     * 
     */
    public SSJXYSeriesCollection get() {
        return datasetList.get(0);
    }

    /**
     * Sets the primary dataset for the plot, replacing the existing dataset if there is one.
     * 
     * @param dataset the new primary dataset.
     * 
     * 
     */
    public void set(SSJXYSeriesCollection dataset) {
        ((XYPlot) chart.getPlot()).setDataset(dataset.getSeriesCollection());
        ((XYPlot) chart.getPlot()).setRenderer(dataset.getRenderer());
        datasetList.set(0, dataset);
    }

    /**
     * Gets the element at the specified position in the dataset list.
     * 
     * @param datasetNum position in the dataset list.
     * 
     *    @return dataset.
     * 
     */
    public SSJXYSeriesCollection get(int datasetNum) {
        return datasetList.get(datasetNum);
    }

    /**
     * Replaces the element at the specified position in the dataset list with the specified element.
     * 
     * @param datasetNum position in the dataset list.
     * 
     *    @param dataset dataset list.
     * 
     * 
     */
    public void set(int datasetNum, SSJXYSeriesCollection dataset) {
        ((XYPlot) chart.getPlot()).setDataset(datasetNum, dataset.getSeriesCollection());
        ((XYPlot) chart.getPlot()).setRenderer(datasetNum, dataset.getRenderer());
        datasetList.add(datasetNum, dataset);
    }

    /**
     * Returns the dataset list.
     * 
     * @return dataset list.
     * 
     */
    public ArrayList<SSJXYSeriesCollection> getList() {
        return datasetList;
    }

    /**
     * Displays chart on the screen using Swing.
     *    This method creates an application containing a chart panel displaying
     *    the chart. The created frame is positioned on-screen, and displayed before
     *    it is returned. The <TT>width</TT> and the <TT>height</TT>
     *    of the chart are measured in pixels.
     * 
     * @param width frame width in pixels.
     * 
     *    @param height frame height in pixels.
     * 
     * 
     */
    public JFrame view(int width, int height) {
        JFrame myFrame;
        if (chart.getTitle() != null)
            myFrame = new JFrame("MultipleDatasetChart from SSJ: " + chart.getTitle().getText());
        else
            myFrame = new JFrame("MultipleDatasetChart from SSJ");
        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(width, height));
        myFrame.setContentPane(chartPanel);
        myFrame.pack();
        myFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        myFrame.setLocationRelativeTo(null);
        myFrame.setVisible(true);
        return myFrame;
    }

    /**
     * Puts grid on the background. It is important to note that the grid is
     * always placed 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 stepping. <TT>xstep</TT>
     *    and <TT>ystep</TT> sets the stepping in each direction.
     * 
     * @param xstep sets the stepping in the <SPAN CLASS="MATH"><I>x</I></SPAN>-direction.
     * 
     *    @param ystep sets the stepping in the <SPAN CLASS="MATH"><I>y</I></SPAN>-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;
    }

    /**
     * Same as in {@link XYChart}.
     * 
     * @param width Chart's width in centimeters.
     * 
     *    @param height Chart's height in centimeters.
     * 
     * 
     */
    public String toLatex(double width, double height) {
        double xunit, yunit;
        double[] save = new double[4];

        if (datasetList.size() == 0)
            throw new IllegalArgumentException("Empty chart");

        //Calcul des parametres d'echelle et de decalage
        double XScale = computeXScale(XAxis.getTwinAxisPosition());
        double YScale = computeYScale(YAxis.getTwinAxisPosition());

        xunit = width / ((Math.max(XAxis.getAxis().getRange().getUpperBound(), XAxis.getTwinAxisPosition())
                * XScale)
                - (Math.min(XAxis.getAxis().getRange().getLowerBound(), XAxis.getTwinAxisPosition()) * XScale));
        //taille d'une unite en x et en cm dans l'objet "tikzpicture"
        yunit = height / ((Math.max(YAxis.getAxis().getRange().getUpperBound(), YAxis.getTwinAxisPosition())
                * YScale)
                - (Math.min(YAxis.getAxis().getRange().getLowerBound(), YAxis.getTwinAxisPosition()) * YScale));
        //taille d'une unite en y et en cm dans l'objet "tikzpicture"

        Formatter formatter = new Formatter(Locale.US);

        /*Entete du document*/
        if (latexDocFlag) {
            formatter.format("\\documentclass[12pt]{article}%n%n");
            formatter.format("\\usepackage{tikz}%n\\usetikzlibrary{plotmarks}%n\\begin{document}%n%n");
        }
        if (chart.getTitle() != null)
            formatter.format("%% PGF/TikZ picture from SSJ : %s%n", chart.getTitle().getText());
        else
            formatter.format("%% PGF/TikZ picture from SSJ %n");
        formatter.format("%% XScale = %s,  YScale = %s,  XShift = %s,  YShift = %s%n", XScale, YScale,
                XAxis.getTwinAxisPosition(), YAxis.getTwinAxisPosition());
        formatter.format("%% Therefore, thisFileXValue = (originalSeriesXValue+XShift)*XScale%n");
        formatter.format("%%        and thisFileYValue = (originalSeriesYValue+YShift)*YScale%n%n");
        if (chart.getTitle() != null)
            formatter.format("\\begin{figure}%n");
        formatter.format("\\begin{center}%n");
        formatter.format("\\begin{tikzpicture}[x=%scm, y=%scm]%n", xunit, yunit);
        formatter.format("\\footnotesize%n");
        if (grid)
            formatter.format("\\draw[color=lightgray] (%s, %s) grid[xstep = %s, ystep=%s] (%s, %s);%n",
                    (Math.min(XAxis.getAxis().getRange().getLowerBound(), XAxis.getTwinAxisPosition())
                            - XAxis.getTwinAxisPosition()) * XScale,
                    (Math.min(YAxis.getAxis().getRange().getLowerBound(), YAxis.getTwinAxisPosition())
                            - YAxis.getTwinAxisPosition()) * YScale,
                    xstepGrid * XScale, ystepGrid * YScale,
                    (Math.max(XAxis.getAxis().getRange().getUpperBound(), XAxis.getTwinAxisPosition())
                            - XAxis.getTwinAxisPosition()) * XScale,
                    (Math.max(YAxis.getAxis().getRange().getUpperBound(), YAxis.getTwinAxisPosition())
                            - YAxis.getTwinAxisPosition()) * YScale);

        formatter.format("%s", XAxis.toLatex(XScale));
        formatter.format("%s", YAxis.toLatex(YScale));

        for (int i = 0; i < datasetList.size(); i++)
            formatter.format("%s", datasetList.get(i).toLatex(XScale, YScale, XAxis.getTwinAxisPosition(),
                    YAxis.getTwinAxisPosition(), XAxis.getAxis().getLowerBound(), XAxis.getAxis().getUpperBound(),
                    YAxis.getAxis().getLowerBound(), YAxis.getAxis().getUpperBound()));

        formatter.format("\\end{tikzpicture}%n");
        formatter.format("\\end{center}%n");
        if (chart.getTitle() != null) {
            formatter.format("\\caption{");
            formatter.format(chart.getTitle().getText());
            formatter.format("}%n\\end{figure}%n");
        }
        if (latexDocFlag)
            formatter.format("\\end{document}%n");
        return formatter.toString();
    }

    /**
     * Same as in {@link XYChart}.
     * 
     */
    public void setLatexDocFlag(boolean flag) {
        latexDocFlag = flag;
    }

    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);
    }

    private static double max(double[] t) {
        double aux = t[0];
        for (int i = 1; i < t.length; i++)
            if (t[i] > aux)
                aux = t[i];
        return aux;
    }

    private static double min(double[] t) {
        double aux = t[0];
        for (int i = 1; i < t.length; i++)
            if (t[i] < aux)
                aux = t[i];
        return aux;
    }
}