package umontreal.iro.lecuyer.charts;

import   umontreal.iro.lecuyer.functions.MathFunction;
import   umontreal.iro.lecuyer.functionfit.SmoothingCubicSpline;
import   umontreal.iro.lecuyer.util.RootFinder;

import   org.jfree.data.xy.*;
import   org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;

import   cern.colt.list.DoubleArrayList;

import   java.util.Locale;
import   java.util.Formatter;
import   java.awt.Color;

/**
 * This class extends
 * {@link umontreal.iro.lecuyer.charts.SSJXYSeriesCollection SSJXYSeriesCollection}.
 * It stores data used in a XYLineChart or in other related charts.
 * XYListSeriesCollection provides complementary tools to draw
 *  simple curves; for example, one may
 * add or remove plots series and modify plot style.
 * This class is linked with the JFreeChart XYSeriesCollection class to
 * store data plots,
 * and linked with the JFreeChart XYLineAndShapeRenderer to render the plot.
 * Each series must contain enough points to plot a nice curve.
 * It is recommended to use about 30 points. However, some rapidly
 * varying functions may require many more points. This class can be used to draw scatter plots.
 * 
 */
public class XYListSeriesCollection  extends SSJXYSeriesCollection  {
   protected String[] marksType;   // marks on points (+, x, *...)
   protected String[] dashPattern; // line dashing (solid, dotted, densely dotted, loosely dotted,
                                 //               dashed, densely dashed, loosely dashed, only marks)
   protected String[] plotStyle;   // plot style (lines, curves...)
   private boolean autoCompletion = false;


   /**
    * Creates a new XYListSeriesCollection instance with an empty dataset.
    * 
    */
   public XYListSeriesCollection()  {
      renderer = new XYLineAndShapeRenderer(true, false);
     // ((XYLineAndShapeRenderer)renderer).setShapesVisible(false);
      seriesCollection = new XYSeriesCollection();
   }


   /**
    * Creates a new XYListSeriesCollection instance with default
    *    parameters and given data series. The input parameter data
    *    represents a set of plotting data.
    * 
    * 

* For example, if one n-row matrix data1 is given as argument, * then the first row data1[0] represents the * x-coordinate vector, and every other row data1 * [i], i = 1,…, n - 1, represents a y-coordinate set for the points. * Therefore matrix data1[i][j], * i = 0,…, n - 1, corresponds * to n - 1 curves, all with the same x-coordinates. * *

* However, one may want to plot several curves with different x-coordinates. * In that case, one should give the curves as matrices with two rows. * For examples, if the argument data is made of three 2-row matrices * data1, data2 and data3, then they represents * three different curves, data*[0] being the x-coordinates, * and data*[1] the y-coordinates of the curves. * *

* However, we may also consider the sets of points above not as part of curves, * but rather as several list of points. * * @param data series of point sets. * * */ public XYListSeriesCollection (double[][]... data) { renderer = new XYLineAndShapeRenderer(true, false); // ((XYLineAndShapeRenderer)renderer).setShapesVisible(false); seriesCollection = new XYSeriesCollection(); XYSeriesCollection tempSeriesCollection = (XYSeriesCollection)seriesCollection; for (int i = 0; i < data.length; i ++) { if (data[i].length < 2) throw new IllegalArgumentException ( "Unable to render the plot. data["+ i +"] contains less than two rows"); for (int j = 0; j < data[i].length-1; j++) if (data[i][j].length != data[i][j+1].length) throw new IllegalArgumentException( "data["+ i +"][" + j + "] and data["+ i +"]["+ (j+1) +"] must share the same length"); for (int j = 1; j < data[i].length; j++) { XYSeries serie = new XYSeries(" "); for (int k = 0; k < data[i][0].length; k++) serie.add(data[i][0][k], data[i][j][k]); tempSeriesCollection.addSeries(serie); } } // set default colors for(int i = 0; i < tempSeriesCollection.getSeriesCount(); i++) renderer.setSeriesPaint(i, getDefaultColor(i)); // set default plot style plotStyle = new String[tempSeriesCollection.getSeriesCount()]; marksType = new String[tempSeriesCollection.getSeriesCount()]; dashPattern = new String[tempSeriesCollection.getSeriesCount()]; for(int i = 0; i < tempSeriesCollection.getSeriesCount(); i++) { marksType[i] = " "; plotStyle[i] = "smooth"; dashPattern[i] = "solid"; } } /** * Creates a new XYListSeriesCollection instance with default * parameters and given points data. * If data is a n-row matrix, * then the first row data[0] represents the * x-coordinate vector, and every other row data * [i], i = 1,…, n - 1, represents a y-coordinate set of points. * Therefore, if the points represents curves to be plotted, * data[i][ ], * i = 0,…, n - 1, corresponds * to n - 1 curves, all with the same x-coordinates. * Only the first numPoints of data will be considered * for each of the set of points. * * @param data series of point sets. * * @param numPoints Number of points to plot * * */ public XYListSeriesCollection (double[][] data, int numPoints) { renderer = new XYLineAndShapeRenderer(true, false); // ((XYLineAndShapeRenderer)renderer).setShapesVisible(false); seriesCollection = new XYSeriesCollection(); XYSeriesCollection tempSeriesCollection = (XYSeriesCollection)seriesCollection; if (data.length < 2) throw new IllegalArgumentException ( "Unable to render the plot. data contains less than two rows"); // n-1 curves: data[0] is x; data[i] is y for each curve for (int j = 1; j < data.length; j++) { XYSeries serie = new XYSeries(" "); for (int k = 0; k < numPoints; k++) serie.add(data[0][k], data[j][k]); tempSeriesCollection.addSeries(serie); } // set default colors for (int i = 0; i < tempSeriesCollection.getSeriesCount(); i++) renderer.setSeriesPaint(i, getDefaultColor(i)); // set default plot style plotStyle = new String[tempSeriesCollection.getSeriesCount()]; marksType = new String[tempSeriesCollection.getSeriesCount()]; dashPattern = new String[tempSeriesCollection.getSeriesCount()]; for (int i = 0; i < tempSeriesCollection.getSeriesCount(); i++) { marksType[i] = " "; plotStyle[i] = "smooth"; dashPattern[i] = "solid"; } } /** * Creates a new XYListSeriesCollection instance with default parameters and given data. * The input parameter represents a set of data plots, the constructor will count the occurrence number * Y of each value X in the DoubleArrayList, and plot the point (X, Y). * Each {@link cern.colt.list.DoubleArrayList DoubleArrayList} variable corresponds to a curve on the chart. * * @param data series of point sets. * * */ public XYListSeriesCollection (DoubleArrayList... data) { renderer = new XYLineAndShapeRenderer(true, false); // ((XYLineAndShapeRenderer)renderer).setShapesVisible(false); seriesCollection = new XYSeriesCollection (); XYSeriesCollection tempSeriesCollection = (XYSeriesCollection )seriesCollection; XYSeries serie; double[] elements; int count = 0; DoubleArrayList temp; for(int i = 0; i < data.length; i++) { serie = new XYSeries(" "); temp = data[i].copy(); // deep copy temp.trimToSize(); // set capacity to the current size temp.quickSortFromTo(0, temp.size()-1); // sort list in increasing order, simplify the next processings elements = temp.elements(); int j = 0; int l = 0; while(j < elements.length) { while(j < elements.length && elements[j] == elements[l]) { j++; count++; } serie.add(elements[l], count); count = 0; l = j; } tempSeriesCollection.addSeries(serie); } // set default colors for(int i = 0; i < tempSeriesCollection.getSeriesCount(); i++) { renderer.setSeriesPaint(i, getDefaultColor(i)); } // set default plot style plotStyle = new String[tempSeriesCollection.getSeriesCount()]; marksType = new String[tempSeriesCollection.getSeriesCount()]; dashPattern = new String[tempSeriesCollection.getSeriesCount()]; for(int i = 0; i < tempSeriesCollection.getSeriesCount(); i++) { marksType[i] = " "; plotStyle[i] = "smooth"; dashPattern[i] = "solid"; } } /** * Creates a new XYListSeriesCollection instance with default parameters and given data series. * The input parameter represents a set of plotting data. * Each series of the given collection corresponds to a curve on the plot. * * @param data series of point sets. * */ public XYListSeriesCollection (XYSeriesCollection data) { renderer = new XYLineAndShapeRenderer(true, false); // ((XYLineAndShapeRenderer)renderer).setShapesVisible(false); seriesCollection = data; for(int i = 0; i < data.getSeriesCount(); i++) { XYSeries serie = data.getSeries(i); } // set default colors for(int i = 0; i < data.getSeriesCount(); i++) { renderer.setSeriesPaint(i, getDefaultColor(i)); } // set default plot style plotStyle = new String[data.getSeriesCount()]; marksType = new String[data.getSeriesCount()]; dashPattern = new String[data.getSeriesCount()]; for(int i = 0; i < data.getSeriesCount(); i++) { marksType[i] = " "; plotStyle[i] = "smooth"; dashPattern[i] = "solid"; } } /** * Adds a data series into the series collection. Vector x represents * the x-coordinates and vector y represents the y-coordinates of * the series. * * @param x xi coordinates. * * @param y yi coordinates. * * @return Integer that represent the new point set's position in the JFreeChart XYSeriesCollection object. * */ public int add (double[] x, double[] y) { if (x.length != y.length) throw new IllegalArgumentException("x and y must have the same length"); return add (x, y, x.length); } /** * Adds a data series into the series collection. Vector x represents * the x-coordinates and vector y represents the y-coordinates of * the series. Only the first numPoints of x * and y will be added to the new series. * * @param x xi coordinates. * * @param y yi coordinates. * * @param numPoints Number of points to add * * @return Integer that represent the new point set's position in the JFreeChart XYSeriesCollection object. * */ public int add (double[] x, double[] y, int numPoints) { XYSeries serie = new XYSeries(" "); XYSeriesCollection tempSeriesCollection = (XYSeriesCollection )seriesCollection; serie.setNotify(true); if ((x.length < numPoints) ||(y.length < numPoints)) throw new IllegalArgumentException("numPoints > length of x or y"); for (int i = 0; i < numPoints; i++) serie.add(x[i], y[i]); tempSeriesCollection.addSeries(serie); // color int j = tempSeriesCollection.getSeriesCount()-1; renderer.setSeriesPaint(j, getDefaultColor(j)); int co = tempSeriesCollection.getSeriesCount(); String[] newPlotStyle = new String[co]; String[] newMarksType = new String[co]; String[] newDashPattern = new String[co]; for(j = 0; j < co - 1; j++) { newPlotStyle[j] = plotStyle[j]; newMarksType[j] = marksType[j]; newDashPattern[j] = dashPattern[j]; } newPlotStyle[j] = "smooth"; newMarksType[j] = " "; newDashPattern[j] = "solid"; plotStyle = newPlotStyle; marksType = newMarksType; dashPattern = newDashPattern; return tempSeriesCollection.getSeriesCount()-1; } /** * Adds a data series into the series collection. The input format of * data is described in constructor * XYListSeriesCollection(double[][] data). * * @param data input data. * * @return Integer that represent the number of point sets added to the current dataset. * */ public int add (double[][] data) { return add (data, data[0].length); } /** * Adds data series into the series collection. The input format of * data is described in constructor * XYListSeriesCollection(double[][] data). * Only the first numPoints of data * (the first numPoints columns of the matrix) * will be added to each new series. * * @param data input data. * * @param numPoints Number of points to add for each new series * * @return Integer that represent the number of point sets added to the current dataset. * */ public int add (double[][] data, int numPoints) { XYSeriesCollection tempSeriesCollection = (XYSeriesCollection) seriesCollection; int n = tempSeriesCollection.getSeriesCount(); if (data.length < 2) throw new IllegalArgumentException( "Unable to render the plot. data contains less than two rows"); for (int j = 0; j < data.length; j++) if (data[j].length < numPoints) throw new IllegalArgumentException( "data[" + j + "] has not enough points"); for (int j = 1; j < data.length; j++) { XYSeries serie = new XYSeries(" "); serie.setNotify(true); for (int k = 0; k < numPoints; k++) serie.add(data[0][k], data[j][k]); tempSeriesCollection.addSeries(serie); } // color for(int j = n; j < tempSeriesCollection.getSeriesCount(); j++) renderer.setSeriesPaint(j, getDefaultColor(j)); String[] newPlotStyle = new String[tempSeriesCollection.getSeriesCount()]; String[] newMarksType = new String[tempSeriesCollection.getSeriesCount()]; String[] newDashPattern = new String[tempSeriesCollection.getSeriesCount()]; for (int j = 0; j < n; j++) { newPlotStyle[j] = plotStyle[j]; newMarksType[j] = marksType[j]; newDashPattern[j] = dashPattern[j]; } for(int j = n; j < tempSeriesCollection.getSeriesCount(); j++) { newPlotStyle[j] = "smooth"; newMarksType[j] = " "; newDashPattern[j] = "solid"; } plotStyle = newPlotStyle; marksType = newMarksType; dashPattern = newDashPattern; return (tempSeriesCollection.getSeriesCount()-n); } /** * Adds a data series into the series collection. The input format of * data is described in constructor * XYListSeriesCollection (DoubleArrayList... data). * * @param data data series. * * @return Integer that represent the new point set's position in the JFreeChart XYSeriesCollection object. * */ public int add (DoubleArrayList data) { XYSeries serie = new XYSeries(" "); DoubleArrayList temp = data.copy(); // deep copy XYSeriesCollection tempSeriesCollection = (XYSeriesCollection) seriesCollection; temp.trimToSize(); // set capacity to the current size temp.quickSortFromTo(0, temp.size()-1); // sort list in increasing order, simplify the next processings double[] elements = temp.elements(); int count = 0; int j = 0; int l = 0; while(j < elements.length) { while(j < elements.length && elements[j] == elements[l]) { j++; count++; } serie.add(elements[l], count); count = 0; l = j; } tempSeriesCollection.addSeries(serie); // color j = tempSeriesCollection.getSeriesCount()-1; renderer.setSeriesPaint(j, getDefaultColor(j)); String[] newPlotStyle = new String[tempSeriesCollection.getSeriesCount()]; String[] newMarksType = new String[tempSeriesCollection.getSeriesCount()]; String[] newDashPattern = new String[tempSeriesCollection.getSeriesCount()]; for(j = 0; j < tempSeriesCollection.getSeriesCount()-1; j++) { newPlotStyle[j] = plotStyle[j]; newMarksType[j] = marksType[j]; newDashPattern[j] = dashPattern[j]; } newPlotStyle[j] = "smooth"; newMarksType[j] = " "; newDashPattern[j] = "solid"; plotStyle = newPlotStyle; marksType = newMarksType; dashPattern = newDashPattern; return tempSeriesCollection.getSeriesCount()-1; } /** * Gets the current name of the selected series. * * @param series series index. * * @return current name of the series. * */ public String getName (int series) { return (String)((XYSeriesCollection )seriesCollection).getSeries(series).getKey(); } /** * Sets the name of the selected series. * * @param series series index. * * @param name point set new name. * */ public void setName (int series, String name) { if(name == null) name = " "; ((XYSeriesCollection)seriesCollection).getSeries(series).setKey(name); } /** * Enables the auto completion option. When this parameter * is enabled, straight lines are used to approximate points on the * chart bounds if the method isn't able to display all points, * because the user defined bounds are smaller than the * most significant data point coordinate, for instance. * It does not extrapolate the point sets, but simply estimates * point coordinates on the curve at bound positions for a better visual rendering. * */ public void enableAutoCompletion() { this.autoCompletion = true; } /** * Disables auto completion option. Default status is disabled. * */ public void disableAutoCompletion() { this.autoCompletion = false; } /** * Returns the mark type associated with the seriesth data series. * * @param series series index. * * @return mark type. * */ public String getMarksType (int series) { return marksType[series]; } /** * Adds marks on points to a data series. * It is possible to use all the marks provided by the TikZ package, * some of which are *, + and x. * A blank character, used by default, will disable marks. * The PGF/TikZ documentation provides more information about placing marks on plots. * * @param series series index. * * @param marksType mark type. * * */ public void setMarksType (int series, String marksType) { this.marksType[series] = marksType; } /** * Returns the dash pattern associated with the seriesth data series. * * @param series series index. * * @return mark type. * */ public String getDashPattern (int series) { return dashPattern[series]; } /** * Selects dash pattern for a data series. * It is possible to use all the dash options provided by * the TikZ package: solid, dotted, densely dotted, * loosely dotted, dashed, densely dashed and loosely dashed. * * @param series series index. * * @param dashPattern dash style. * * */ public void setDashPattern (int series, String dashPattern) { this.dashPattern[series] = dashPattern; } /** * Gets the current plot style for the selected series. * * @param series series index. * * @return current plot style. * */ public String getPlotStyle (int series) { return plotStyle[series]; } /** * Selects the plot style for a given series. It is possible to use all the * plot options provided by the TikZ package. Some of which are: * sharp plot, which joins points with straight lines, * smooth, which joins points with a smoothing curve, * only marks, which does not join points, etc. * The PGF/TikZ documentation provides more information about smooth plots, * sharp plots and comb plots. * * @param series series index. * * @param plotStyle plot style. * * */ public void setPlotStyle (int series, String plotStyle) { this.plotStyle[series] = plotStyle; } public String toLatex (double XScale, double YScale, double XShift, double YShift, double xmin, double xmax, double ymin, double ymax) { // Calcule les bornes reelles du graphique, en prenant en compte la position des axes xmin = Math.min(XShift, xmin); xmax = Math.max(XShift, xmax); ymin = Math.min(YShift, ymin); ymax = Math.max(YShift, ymax); Formatter formatter = new Formatter(Locale.US); XYSeriesCollection tempSeriesCollection = (XYSeriesCollection) seriesCollection; double XEPSILON = (1.0E-4/XScale)+XShift; double YEPSILON = (1.0E-4/YScale)+YShift; boolean outOfBounds = false; MathFunction[] spline = null; double[] xBounds = getRangeBounds(); double[] yBounds = getDomainBounds(); double x, y; // Smoothing splines, consulter ref: QA278.2 G74, QA278.2 T35, QA278.2 E87 // if(xBounds[0] < xmin || xBounds[1] > xmax || yBounds[0] < ymin || yBounds[1] > ymax) { // // on sait qu'il y a des points qui vont sortir du chart // // initialisation d'une spline pour chaque courbe // spline = new SmoothingCubicSpline[seriesCollection.getSeriesCount()]; // for(int i = 0; i= 0; i--) { XYSeries temp = tempSeriesCollection.getSeries(i); if (temp.getItemCount() < 2) throw new IllegalArgumentException("Unable to plot series " + i + ": this series must have two points at least"); Color color = (Color)renderer.getSeriesPaint(i); String colorString = detectXColorClassic(color); if( colorString == null) { colorString = "color"+i; formatter.format( "\\definecolor{%s}{rgb}{%.2f, %.2f, %.2f}%n", colorString, color.getRed()/255.0, color.getGreen()/255.0, color.getBlue()/255.0); } // Cas particulier pour le premier point, on doit savoir si il est dans le chart ou pas if ( temp.getX(0).doubleValue() >= xmin && temp.getX(0).doubleValue() <= xmax && temp.getY(0).doubleValue() >= ymin && temp.getY(0).doubleValue() <= ymax) { outOfBounds = false; formatter.format( "\\draw [%s, color=%s, mark=%s, style=%s] plot coordinates {%%%n", plotStyle[i], colorString, marksType[i], dashPattern[i]); } else { outOfBounds = true; formatter.format("%% "); } formatter.format("(%.2f,%.4f)", (temp.getX(0).doubleValue()-XShift)*XScale, (temp.getY(0).doubleValue()-YShift)*YScale); formatter.format(" %% (%f, %f)%n", temp.getX(0).doubleValue(), temp.getY(0).doubleValue()); // Cas general for(int j = 1; j < temp.getItemCount(); j++) { double[] result; if (!outOfBounds) { //on est dans le chart result = evalLimitValues(xmin, xmax, ymin, ymax, XEPSILON, YEPSILON, spline[i], temp, j, false); // on regarde si on ne sort pas du chart, si c'est le cas on evalue le point en limite if (result != null) { // le point courant n'est pas dans le chart, on sort donc du chart outOfBounds = true; if (autoCompletion) formatter.format("(%.2f,%.4f) %%%n", (result[0]-XShift)*XScale, (result[1]-YShift)*YScale); formatter.format("}%%%n%% "); } } else { // le point precedent etait hors du chart if ( temp.getX(j).doubleValue() >= xmin && temp.getX(j).doubleValue() <= xmax && temp.getY(j).doubleValue() >= ymin && temp.getY(j).doubleValue() <= ymax) { // on rentre dans le chart, il faut evaluer le point en limite j = j-1; result = evalLimitValues(xmin, xmax, ymin, ymax, XEPSILON, YEPSILON, spline[i], temp, j, true); // ici result ne peut pas etre null formatter.format( ";%%%n\\draw [%s, color=%s, mark=%s, style=%s] plot coordinates {%%%n", plotStyle[i], colorString, marksType[i], dashPattern[i]); if (autoCompletion) formatter.format("(%.2f,%.4f) %%%n ", (result[0]-XShift)*XScale, (result[1]-YShift)*YScale); formatter.format("%% "); outOfBounds = false; } else { formatter.format("%% "); // on les donnees sont toujours hors du chart } } /* on affiche les coordonnees du point quoiqu'il arrive, si celui ci est hors du chart alors la balise de commentaire a ete deja place */ formatter.format("(%.2f,%.4f)", (temp.getX(j).doubleValue()-XShift)*XScale, (temp.getY(j).doubleValue()-YShift)*YScale); if(j == temp.getItemCount()-1) formatter.format("}"); formatter.format(" %% (%f, %f)%n", temp.getX(j).doubleValue(), temp.getY(j).doubleValue()); // formatter.format(" %%%n"); } formatter.format(" node[right] {%s};%n", (String)temp.getKey()); } return formatter.toString(); } /* * * Compute x and y to chart limit bounds for extra-bounded points * * @param xmin lower bound for x coordinates * @param xmax upper bound for x coordinates * @param ymin lower bound for y coordinates * @param ymax upper bound for y coordinates * @param XEPSILON increment step size for x coordinates * @param YEPSILON increment step size for y coordinates * @param spline sline used to approximate points * @param temp current series * @param numPoint point index in the current series * @param sens direction of the in chart last point * true : point numPoint+1 is in the chart * false : point numPoint-1 is in the chart * * @return x and y coordinates on the chart bounds. */ private static double[] evalLimitValues(double xmin, double xmax, double ymin, double ymax, double XEPSILON, double YEPSILON, MathFunction spline, XYSeries temp, int numPoint, boolean sens) { int j = numPoint; int k = 0; double x, y; if(sens) k = j+1; else k = j-1; if(temp.getX(j).doubleValue() < xmin) {// Hors du chart mais on etait dans le chart au point precedent x = xmin; y = spline.evaluate(xmin); // spline puis evaluer en xmin while(yxmin tantque y ymax) { x += XEPSILON; y = spline.evaluate(x); } // evaluer un x en ymax avec x > xmin } else if(temp.getX(j).doubleValue() > xmax) { x = xmax; y = spline.evaluate(xmax); while(y ymax) { x -= XEPSILON; y = spline.evaluate(x); } // evaluer un xymax } else if(temp.getY(j).doubleValue() < ymin) { y = ymin; x = evaluateX(spline, y, temp.getX(j).doubleValue(), temp.getX(k).doubleValue());// spline puis evaluer en ymin avec x ente xValue(ptCourant) et xValue(ptCourant-1) while(x < xmin) { y += YEPSILON; x = evaluateX(spline, y, x, temp.getX(k).doubleValue()); } while(x > xmax) { y += YEPSILON; x = evaluateX(spline, y, x, temp.getX(k).doubleValue()); } } else if(temp.getY(j).doubleValue() > ymax) { y = ymax; x = evaluateX(spline, y, temp.getX(j).doubleValue(), temp.getX(k).doubleValue());// spline puis evaluer en ymax avec x ente xValue(ptCourant) et xValue(ptCourant-1) while(x < xmin) { y -= YEPSILON; x = evaluateX(spline, y, x, temp.getX(k).doubleValue()); } while(x > xmax) { y -= YEPSILON; x = evaluateX(spline, y, x, temp.getX(k).doubleValue()); } } else return null; double[] retour = new double[2]; retour[0] = x; retour[1] = y; return retour; } private static double evaluateX (final MathFunction spline, final double y, double xPrincipal, double xAnnexe) { final MathFunction xFunction = new MathFunction () { public double evaluate (double t) { return spline.evaluate(t) - y; } }; return RootFinder.brentDekker (xPrincipal, xAnnexe-1.0E-6, xFunction, 1e-6); } private class AffineFit implements MathFunction{ double[] x; double[] y; public AffineFit(double[] x, double[] y) { this.x =x; this.y =y; } public double evaluate(double t) { int i = 0; if (t <= x[0]) return y[0]; while (i < x.length && t > x[i]) i++; i--; if (i == x.length) return x[x.length-1]; return y[i] + ((t - x[i]) / (x[i+1] - x[i])) * (y[i+1] - y[i]); } } }

Ads help maintain this website.