package umontreal.iro.lecuyer.charts;

import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import java.util.Locale;
import java.util.Formatter;
import java.lang.Math;


/**
 * Represents an axis of a chart encapsulated by
 * an instance of XYChart.
 * Axis uses the JFreeChart class NumberAxis
 * to store some axis properties.
 * This class represents the x-axis or the y-axis of a XYChart
 * and, consequently,  is drawn when calling the {@link #toLatex toLatex} method.
 * It provides tools to customize the axis in modifying labels and description.
 * 
 * 

* *

*
* *

* */ public class Axis { public static final boolean ORIENTATION_VERTICAL = false; public static final boolean ORIENTATION_HORIZONTAL = true; private NumberAxis axis; // Uses JFreeChart API private boolean orientation; // Vertical or horizontal private double twinAxisPosition = 0; private boolean tick0Flag; // if true, label number at origin is shifted // right or above a little to prevent the label from being on the // perpendicular axis. private boolean labelsFlag; // true : enable manual labels private String[] labelsName; // Sets manual labels private double[] labelsValue; // Where putting the labels on the axes private double fixStep (double minA, double maxA, double step) { // reset step for labels on axes if it is too large or too small final double H = 10; final double inter = maxA - minA; while (step > inter) // step is too large, make it smaller step /= H; while (step < inter / H) // step is too small, make it larger step *= H; if (step > inter/2) step /= 2; return step; } /** * Create a new Axis instance from an existing NumberAxis * instance with vertical (y-axis) or horizontal (x-axis) orientation. * * @param inAxis NumberAxis instance associated to the new variable. * * @param orientation axis direction, horizontal or vertical * */ public Axis (NumberAxis inAxis, boolean orientation) { this.axis = inAxis; this.orientation = orientation; this.labelsFlag = false; this.labelsValue = null; this.labelsName = null; inAxis.setAutoRangeIncludesZero(false); inAxis.setLowerMargin(0); inAxis.setUpperMargin(0); axis.setTickUnit(new NumberTickUnit(computeAutoTickValue())); } /** * Returns the NumberAxis instance (from JFreeChart) linked with the current variable. * * @return the NumberAxis instance (from JFreeChart) linked with the current variable. * */ protected NumberAxis getAxis() { return axis; } /** * Returns the axis description. * * @return the axis description. * */ public String getLabel() { return axis.getLabel(); } /** * Sets the axis description. This description will be displayed on the chart, near the axis. * * @param label axis label. * * */ public void setLabel (String label) { axis.setLabel(label); } /** * Sets a periodic label display. Labels will be shown every tick unit. * This tick unit replaces the default unit. * * @param tick tick unit. * * */ public void setLabels (double tick) { axis.setTickUnit(new NumberTickUnit(tick)); labelsFlag = false; } /** * Calculates and sets an automatic tick unit. * */ public void setLabelsAuto() { axis.setTickUnit(new NumberTickUnit(computeAutoTickValue())); labelsFlag = false; } /** * Not used anymore. * */ @Deprecated public void enableCustomLabels() { labelsFlag = true; } /** * Not used anymore. * */ @Deprecated public void disableCustomLabels() { labelsFlag = false; } /** * Sets the position of each label on this axis. This method requires an array * containing an increasing sequence of numbers representing the positions at * which labels will appear on the axis. It is designed to export the * axis to a LaTeX source code; it has no effect on the chart appearance * displayed with XYChart.view(). * * @param position new label positions. * * */ public void setLabels (double[] position) { this.labelsName = null; this.labelsValue = position.clone(); double max = max(position); if (axis.getUpperBound() < max) axis.setUpperBound(max); double min = min(position); if (axis.getLowerBound() > min) axis.setLowerBound(min); labelsFlag = true; } /** * Assigns custom labels to user-defined positions on the axis. * This method requires an array of positions as well as an array of labels. * The label label[i] will be used at position position[i]. * It is designed to export the axis to a LaTeX source code, * and to use LATEX/TikZ commands to write prettier characters; it has no * effect on the chart appearance displayed with XYChart.view(). * * @param position label series position on the axis. * * @param label label series name on the axis. * * */ public void setLabels (double[] position, String[] label) { if (label.length != position.length) throw new IllegalArgumentException ("A label is required for each given position"); this.labelsName = label.clone(); this.labelsValue = position.clone(); double max = max(position); if (axis.getUpperBound() < max) axis.setUpperBound(max); double min = min(position); if (axis.getLowerBound() > min) axis.setLowerBound(min); labelsFlag = true; } /** * Returns the drawing position parameter (default equals 0). * * @return drawing position parameter. * */ public double getTwinAxisPosition() { return this.twinAxisPosition; } /** * Defines where the opposite axis must be drawn on the current axis, * where it should appear, and on which label. * * @param position new drawing position. * * */ public void setTwinAxisPosition (double position) { this.twinAxisPosition = position; } /** * Formats and returns a string containing a LATEX-compatible * source code which represents this axis and its parameters. * * @return LaTeX source code in a String. * @param scale current axis wished scale. * * */ public String toLatex (double scale) { Formatter formatter = new Formatter(Locale.US); double maxAxis = Math.max(axis.getRange().getUpperBound(), twinAxisPosition); //valeur du label le plus grand double minAxis = Math.min(axis.getRange().getLowerBound(), twinAxisPosition); //valeur du label le plus petit String precisionAffichageLabel; //determine combien de decimales seront affichees pour les labels double pas = axis.getTickUnit().getSize(); //step d'affichage des labels pas = fixStep (minAxis, maxAxis, pas); int puissDix; //echelle des valeurs sur l'axe if (Math.log10(pas) < 0) puissDix = (int)Math.log10(pas) - 1; else puissDix = (int)Math.log10(pas); //Placement des fleches, on pourrait facilement les rendre personnalisables... String arrowLeftType = "latex"; String arrowRightType = "latex"; String arrowLeftMargin = "3mm"; String arrowRightMargin = "3mm"; if (twinAxisPosition == minAxis) { arrowLeftType = ""; arrowLeftMargin = "0mm"; } else if (twinAxisPosition == maxAxis) { arrowRightType = ""; arrowRightMargin = "0mm"; } // Label pour l'axe, avec eventuellement l'echelle String label = axis.getLabel(); if (label == null) label = " "; if (puissDix < -2 || puissDix > 2) label = label + " $(10^{" + puissDix + "})$"; else puissDix = 0; if (orientation) { //on est sur l'axe des abscisses //affichage de l'axe formatter.format("\\draw [%s-%s] ([xshift=-%s] %s,0) -- ([xshift=%s] %s,0) node[right] {%s};%n", arrowLeftType, arrowRightType, arrowLeftMargin, (minAxis - twinAxisPosition)*scale, arrowRightMargin, (maxAxis - twinAxisPosition)*scale, label); if (labelsFlag) { //labels manuels String name; double value; double labelTemp; for (int i = 0; i < labelsValue.length; i++) { value = labelsValue[i]; if (labelsName == null) { labelTemp = (value * 100.0 / Math.pow(10, puissDix)); // if(labelTemp == (int)(labelTemp)) name = Integer.toString((int)(labelTemp / 100.0)); // else // name = Double.toString(Math.round(labelTemp)/100.0); } else name = labelsName[i]; if (value == twinAxisPosition && tick0Flag) formatter.format("\\draw (%s,00) -- +(0mm,1mm) -- +(0mm,-1mm) node[below right] {%s};%n", (value - twinAxisPosition)*scale, name); else formatter.format("\\draw (%s,0) -- +(0mm,1mm) -- +(0mm,-1mm) node[below] {%s};%n", (value - twinAxisPosition)*scale, name); } } else { //labels automatiques double labelTemp; double k = twinAxisPosition; labelTemp = (Math.round(k * 100 / Math.pow(10, puissDix))) / 100.0; if (labelTemp == (int)(labelTemp)) precisionAffichageLabel = "0"; else if (labelTemp*10 == (int)(labelTemp*10)) precisionAffichageLabel = "1"; else precisionAffichageLabel = "2"; if (tick0Flag) formatter.format("\\draw (%s,0) -- +(0mm,1mm) -- +(0mm,-1mm) node[below right] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); else formatter.format("\\draw (%s,0) -- +(0mm,1mm) -- +(0mm,-1mm) node[below] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); k += pas; while (k <= maxAxis) { //cote positif labelTemp = (Math.round(k * 100 / Math.pow(10, puissDix))) / 100.0; if (labelTemp == (int)(labelTemp)) precisionAffichageLabel = "0"; else if (labelTemp*10 == (int)(labelTemp*10)) precisionAffichageLabel = "1"; else precisionAffichageLabel = "2"; formatter.format("\\draw (%s,0) -- +(0mm,1mm) -- +(0mm,-1mm) node[below] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); k += pas; } k = twinAxisPosition - pas; while (k >= minAxis) { //cote negatif labelTemp = (Math.round(k * 100 / Math.pow(10, puissDix))) / 100.0; if (labelTemp == (int)(labelTemp)) precisionAffichageLabel = "0"; else if (labelTemp*10 == (int)(labelTemp*10)) precisionAffichageLabel = "1"; else precisionAffichageLabel = "2"; formatter.format("\\draw (%s,0) -- +(0mm,1mm) -- +(0mm,-1mm) node[below] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); k -= pas; } } } else { //On est sur l'axe des ordonnees //affichage de l'axe formatter.format("\\draw [%s-%s] ([yshift=-%s] 0,%s) -- ([yshift=%s] 0, %s) node[above] {%s};%n", arrowLeftType, arrowRightType, arrowLeftMargin, (minAxis - twinAxisPosition)*scale, arrowRightMargin, (maxAxis - twinAxisPosition)*scale, label); if (labelsFlag) { //labels manuels String name; double value; double labelTemp; for (int i = 0; i < labelsValue.length; i++) { value = labelsValue[i]; if (labelsName == null) { labelTemp = (value * scale * 100 / Math.pow(10, puissDix)); if (labelTemp == (int)(labelTemp)) name = Integer.toString((int)(labelTemp / 100.0)); else name = Double.toString(Math.round(labelTemp) / 100.0); } else name = labelsName[i]; if (value == twinAxisPosition && tick0Flag) formatter.format("\\draw (%s,%s) -- +(1mm,0mm) -- +(-1mm,0mm) node[above left] {%s};%n", 0, (value - twinAxisPosition)*scale, name); else formatter.format("\\draw (%s,%s) -- +(1mm,0mm) -- +(-1mm,0mm) node[left] {%s};%n", 0, (value - twinAxisPosition)*scale, name); } } else { //Les labels automatiques double k = twinAxisPosition; double labelTemp; labelTemp = (Math.round(k * 100 / Math.pow(10, puissDix))) / 100.0; if (labelTemp == (int)(labelTemp)) precisionAffichageLabel = "0"; else if (labelTemp*10 == (int)(labelTemp*10)) precisionAffichageLabel = "1"; else precisionAffichageLabel = "2"; if (tick0Flag) formatter.format("\\draw (0,%s) -- +(1mm,0mm) -- +(-1mm,0mm) node[above left] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); else formatter.format("\\draw (0,%s) -- +(1mm,0mm) -- +(-1mm,0mm) node[left] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); k += pas; while (k <= maxAxis) { //cote positif de l'axe labelTemp = (Math.round(k * 100 / Math.pow(10, puissDix))) / 100.0; if (labelTemp == (int)(labelTemp)) precisionAffichageLabel = "0"; else if (labelTemp*10 == (int)(labelTemp*10)) precisionAffichageLabel = "1"; else precisionAffichageLabel = "2"; formatter.format("\\draw (0,%s) -- +(1mm,0mm) -- +(-1mm,0mm) node[left] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); k += pas; } k = twinAxisPosition - pas; while (k >= minAxis) { //cote negatif de l'axe labelTemp = (Math.round(k * 100 / Math.pow(10, puissDix))) / 100.0; if (labelTemp == (int)(labelTemp)) precisionAffichageLabel = "0"; else if (labelTemp*10 == (int)(labelTemp*10)) precisionAffichageLabel = "1"; else precisionAffichageLabel = "2"; formatter.format("\\draw (0,%s) -- +(1mm,0mm) -- +(-1mm,0mm) node[left] {%." + precisionAffichageLabel + "f};%n", (k - twinAxisPosition)*scale, labelTemp); k -= pas; } } } return formatter.toString(); } private double computeAutoTickValue() { // Calcule le pas d'affichage par defaut des labels double pas; double[] bounds = new double[2]; bounds[0] = Math.min(axis.getRange().getLowerBound(), twinAxisPosition); bounds[1] = Math.max(axis.getRange().getUpperBound(), twinAxisPosition); if ( bounds[1] - bounds[0] >= 1) { int nbMaxDeFois = (int)(bounds[1] - bounds[0]); int puissDix = (int)Math.log10(nbMaxDeFois); nbMaxDeFois = (int)(nbMaxDeFois / Math.pow(10, puissDix)) + 1; if (nbMaxDeFois > 5) pas = Math.pow(10, puissDix); else if (nbMaxDeFois > 3) pas = 0.5 * Math.pow(10, puissDix); else if (nbMaxDeFois > 1) pas = 0.25 * Math.pow(10, puissDix); else //nbMaxDeFois==1 pas = 0.1 * Math.pow(10, puissDix); } else { double nbMaxDeFois = bounds[1] - bounds[0]; int puissDix = 0; while (nbMaxDeFois < 1) { nbMaxDeFois *= 10; puissDix++; } if (nbMaxDeFois > 5) pas = 1 / Math.pow(10, puissDix); else if (nbMaxDeFois > 3) pas = 0.5 / Math.pow(10, puissDix); else if (nbMaxDeFois > 1) pas = 0.3 / Math.pow(10, puissDix); else //nbMaxDeFois==1 pas = 0.1 / Math.pow(10, puissDix); } return pas; } private static double max (double[] t) { if (t == null) throw new IllegalArgumentException ("max: null argument."); 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) { if (t == null) throw new IllegalArgumentException ("min: null argument."); double aux = t[0]; for (int i = 1; i < t.length; i++) if (t[i] < aux) aux = t[i]; return aux; } void setTick0Flag(boolean flag) { tick0Flag = flag; } }

Ads help maintain this website.