import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Polyline;
import javafx.collections.ObservableList;
import java.util.function.Function;

/** Example of passing a function as a parameter.
 * @author Jaanus
 */
public class DrawFunction extends Application {

    public static void main (String[] args) {
        launch (args);
    }

    /** Compulsory method for Application.
     * @param myStage JavaFX main stage for this application
     */
    @Override
    public void start (Stage myStage) {
        Function<Double, Double> fn = x->x*(x-3.)*(x+4)*Math.cos(x);
        double from = -5.;
        double to = 5.;
        Pane myPane = new Pane();
        Scene myScene = new Scene (myPane, 319, 159);
        myStage.setScene (myScene);
        myStage.setTitle ("Graph of a function");
        ObservableList<Node> nodes = myPane.getChildren();
        drawFunction (fn, from, to, myScene.getWidth(), myScene.getHeight(), nodes);
        myScene.widthProperty().addListener (
                (obsv, oldv, newv) -> {
                    drawFunction (fn, from, to, myScene.getWidth(), myScene.getHeight(), nodes);
                });
        myScene.heightProperty().addListener (
                (obsv, oldv, newv) -> {
                    drawFunction (fn, from, to, myScene.getWidth(), myScene.getHeight(), nodes);
                });
        myStage.show();
    }

    /** Method to draw the graph of a function.
     * @param f function
     * @param start from argument value
     * @param end to argument value
     * @param w width of the pane
     * @param h height of the pane
     * @param nl contents of the pane
     */
    public static void drawFunction (Function<Double,Double> f, double start, double end,
      double w, double h, ObservableList<Node> nl) {
        int iw = (int)w;
        Double[] points = new Double[2*iw];
        double fmax = Double.MIN_VALUE;
        double fmin = Double.MAX_VALUE;
        for (int i=0; i < iw; i++) {
            double arg = start + ((double)i)*(end-start)/w;
            double value = f.apply (arg);
            points[2*i] = (double)i;
            points[2*i+1] = value; // to be scaled later
            if (value > fmax) fmax = value;
            if (value < fmin) fmin = value;
        }
        fmax += 0.01*(end-start); // padding
        fmin -= 0.01*(end-start);
        double limit = (end-start)*h*50./w;
        for (int i=0; i < iw; i++) {
            double value = points[2*i+1];
            double cutvalue = value; // cut extreme values
            if (value > limit) 
               cutvalue = limit;
            if (value < -limit)
               cutvalue = -limit;
            points[2*i+1] = (fmax-cutvalue)*h/(fmax-fmin); // scale
        }
        Polyline graph = new Polyline();
        graph.getPoints().addAll (points);
        nl.clear();
        nl.add (graph);
    }
}
