umontreal.iro.lecuyer.charts.XYChart Maven / Gradle / Ivy
Show all versions of ssj Show documentation
/*
* Class: XYChart
* Description:
* Environment: Java
* Software: SSJ
* Copyright (C) 2001 Pierre L'Ecuyer and Université de Montréal
* Organization: DIRO, Université de Montréal
* @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
GPL licence site.
*/
package umontreal.iro.lecuyer.charts;
import java.io.*;
import org.jfree.chart.JFreeChart;
import javax.swing.JFrame;
/**
* This class provides tools to create charts from data in a simple way. Its main
* feature is to produce
* TikZ/PGF (see WWW link http://sourceforge.net/projects/pgf/)
* compatible source code which can be included
* in LATEX 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 umontreal.iro.lecuyer.charts.Axis Axis} objects representing the axes.
* All these classes depend on the JFreeChart API (see WWW link
* http://www.jfree.org/jfreechart/) which provides tools to build charts with
* Java, to draw them, and export them to files. However, only basic features are
* used here.
*
*
* Moreover, XYChart 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 XYChart {
protected Axis XAxis;
protected Axis 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
/**
* Returns the JFreeChart object associated with this chart.
*
* @return the associated JFreeChart object.
*
*/
public JFreeChart getJFreeChart() {
return chart;
}
/**
* Returns the chart's domain axis (x-axis) object.
*
* @return chart's domain axis (x-axis) object.
*
*/
public Axis getXAxis() {
return XAxis;
}
/**
* Returns the chart's range axis (y-axis) object.
*
* @return chart's range axis (y-axis) object.
*
*/
public Axis getYAxis() {
return YAxis;
}
public abstract JFrame view (int width, int height);
/**
* 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 true when plotting probabilities,
* false otherwise.
*
* @param flag true for plotting probabilities
*
*
*/
public void setprobFlag (boolean flag) {
probFlag = flag;
}
/**
* The x and the y ranges of the chart are set automatically.
*
*/
public void setAutoRange() {
setAutoRange (false, false, true, true);
}
/**
* The x and the y ranges of the chart are set automatically.
* If right is true, the vertical axis will be on the left of
* the points, otherwise on the right. If top is true,
* 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 x and the y ranges of the chart are set automatically.
* If xZero is true, the vertical axis will pass through the
* point (0, y). If yZero is true, the horizontal axis
* will pass through the point (x, 0).
*
* @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 x and y ranges of the chart using the format: range =
* [xmin, xmax, ymin, ymax].
* @param range new axis ranges.
*
*
*/
public void setManualRange (double[] range) {
setManualRange (range, false, false, true, true);
}
/**
* Sets the x and y ranges of the chart using the format: range =
* [xmin, xmax, ymin, ymax].
* If right is true, the vertical axis will be on the left of
* the points, otherwise on the right. If top is true,
* 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 x and y ranges of the chart using the format: range =
* [xmin, xmax, ymin, ymax].
* If xZero is true, the vertical axis will pass through the
* point (0, y). If yZero is true, the horizontal axis
* will pass through the point (x, 0).
*
* @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 0.02.
*
*/
public double getChartMargin() {
return chartMargin;
}
/**
* Sets the chart margin to margin. It is the fraction by
* which the chart is enlarged on its borders.
* Restriction:
* margin >= 0.
*
* @param margin margin percentage amount.
*
*
*/
public void setChartMargin (double margin) {
if (margin < 0.0)
throw new IllegalArgumentException ("margin < 0");
chartMargin = margin;
}
/**
* Synchronizes x-axis ticks to the s-th series x-values.
*
* @param s series.
*
*/
public abstract void setTicksSynchro (int s);
/**
* 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: xstep
* and ystep 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 LATEX source code using PGF/TikZ.
* This method constructs and returns a string that can be written to
* a LATEX document to render the plot. width and height
* represents the width and the height of the produced chart. These dimensions
* do not take into account the axes and labels extra space. The width
* and the height 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 LATEX form and writes it in file fileName.
* The chart's width and height (in centimeters) are width and height.
*
*/
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 \documentclass
(and other) commands in the
* created LATEX files.
* If flag is true, then when charts are translated into
* LATEX form, it will be as a self-contained file that can be directly
* compiled with LATEX. However, in this form, the file cannot be included in
* another LATEX file without causing compilation errors because of the multiple
* instructions \documentclass
and \begin{document}
.
* By setting flag to false, these instructions will be
* removed from the LATEX chart files, which can then be included in a master
* LATEX file. By default, the flag is true.
*
*/
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);
}
}