package org.geogebra.common.kernel.algos; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.FunctionNVar; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunctionNVar; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.implicit.GeoImplicitCurve; import org.geogebra.common.main.MyError; import org.geogebra.common.util.debug.Log; /** * Contour lines of a given function */ public class AlgoContourPlot extends AlgoElement { private GeoFunctionNVar func; // input expression private double xmin, xmax, ymin, ymax; // the definition domain where the // contour plot is defined private GeoNumeric contourStep; private GeoList list; // output private Equation equ; private ExpressionNode en; private GeoImplicitCurve implicitPoly; private double min, max, step, xstep, ystep; private int divisionPoints; private double calcmin, calcmax, calcxmin, calcxmax, calcymin, calcymax, minadded, maxadded; private boolean fixed; private static final int minContours = 7; private static final int maxContours = 25; /** * Creates a new algorithm to create a list of implicit functions that form * the contour plot of a function * * @param c * construction * @param label * label * @param func * function * @param xmin * lower bound of x * @param xmax * upper bound of x * @param ymin * lower bound of y * @param ymax * upper bound of y */ public AlgoContourPlot(Construction c, String label, GeoFunctionNVar func, double xmin, double xmax, double ymin, double ymax) { super(c); c.registerEuclidianViewCE(this); step = 0; this.xmin = xmin; this.xmax = xmax; this.ymin = ymin; this.ymax = ymax; this.func = func; this.divisionPoints = 5; this.fixed = false; list = new GeoList(cons); setInputOutput(); compute(); list.setLabel(label); } /** * Creates a new algorithm to create a list of implicit functions that form * the contour plot of a function * * @param c * construction * @param label * label * @param func * function * @param xmin * lower bound of x * @param xmax * upper bound of x * @param ymin * lower bound of y * @param ymax * upper bound of y * @param contourStep * the value of the contour line height multiplier */ public AlgoContourPlot(Construction c, String label, GeoFunctionNVar func, double xmin, double xmax, double ymin, double ymax, double contourStep) { super(c); c.registerEuclidianViewCE(this); step = contourStep; this.xmin = xmin; this.xmax = xmax; this.ymin = ymin; this.ymax = ymax; this.func = func; this.divisionPoints = 5; this.fixed = true; list = new GeoList(cons); setInputOutput(); compute(); list.setLabel(label); } @Override protected void setInputOutput() { list.setTypeStringForXML("implicitpoly"); contourStep = new GeoNumeric(cons, step); if (this.fixed) { input = new GeoElement[2]; input[1] = contourStep; } else { input = new GeoElement[1]; } input[0] = func; setOutputLength(1); setOutput(0, list); setDependencies(); // done by AlgoElement } private void addToList(GeoList list1, double value) { equ = new Equation(kernel, en, new MyDouble(kernel, value)); equ.initEquation(); implicitPoly.fromEquation(equ, null); list1.add(new GeoImplicitCurve(implicitPoly)); } private double checkPolyValue(int i, int j) { double x = xmin + xstep * i; double y = ymin + ystep * j; return implicitPoly.evaluateImplicitCurve(x, y); } private int calculateBoundary(int order) { double val; int newContours = 0; for (int i = order - 1; i < divisionPoints + order - 1; i++) { val = checkPolyValue(i, -order); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } val = checkPolyValue(i, divisionPoints + order - 1); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } val = checkPolyValue(-order, i); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } val = checkPolyValue(divisionPoints + order - 1, i); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } } // add the 4 edges val = checkPolyValue(-order, -order); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } val = checkPolyValue(-order, divisionPoints + order); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } val = checkPolyValue(-order, divisionPoints + order); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } val = checkPolyValue(divisionPoints + order, divisionPoints + order); if (val < min) { calcmin = val; } if (val > max) { calcmax = val; } newContours += minadded > calcmin ? Math.ceil(Math.abs(minadded - calcmin) / step) : 0; newContours += maxadded < calcmax ? Math.ceil(Math.abs(calcmax - maxadded) / step) : 0; calcxmin -= xstep; calcxmax += xstep; calcymin -= ystep; calcymax += ystep; return newContours; } private void addAdditionalElements(GeoList list1) { calcmin = min; calcmax = max; // add boundaries calculateBoundary(1); calculateBoundary(2); for (double i = minadded - step; i > calcmin - step; i -= step) { addToList(list1, i); minadded = i; } for (double i = maxadded + step; i < calcmax + step; i += step) { addToList(list1, i); maxadded = i; } } @Override public void compute() { calcxmin = xmin; calcxmax = xmax; calcymin = ymin; calcymax = ymax; min = Double.MAX_VALUE; max = -Double.MAX_VALUE; implicitPoly = new GeoImplicitCurve(cons); implicitPoly.setDefined(); FunctionNVar f = func.getFunction(); FunctionVariable[] fvars = f.getFunctionVariables(); xstep = (xmax - xmin) / (divisionPoints - 1.0); ystep = (ymax - ymin) / (divisionPoints - 1.0); if (Kernel.isEqual(xstep, 0) || Kernel.isEqual(ystep, 0)) { list.setUndefined(); return; } if (fvars.length != 2) { implicitPoly.setUndefined(); return; } try { en = f.getExpression().getCopy(kernel); FunctionVariable xVar = new FunctionVariable(kernel, "x"); FunctionVariable yVar = new FunctionVariable(kernel, "y"); en.replace(fvars[0], xVar); en.replace(fvars[1], yVar); equ = new Equation(kernel, en, new MyDouble(kernel)); implicitPoly.fromEquation(equ, null); for (int i = 0; i < divisionPoints; i++) { for (int j = 0; j < divisionPoints; j++) { double val = checkPolyValue(i, j); if (val < min) { min = val; } if (val > max) { max = val; } } } if (Kernel.isEqual(max, min)) { list.setUndefined(); return; } double freeTerm = 0; if (step == 0 && !fixed) { freeTerm = implicitPoly.evaluateImplicitCurve(0, 0); step = Math.abs((max - min) / 10.0); contourStep.setValue(step); } if ((min <= freeTerm) && (max >= freeTerm)) { for (double i = freeTerm; i > min - step; i -= step) { addToList(list, i); minadded = i; } for (double i = freeTerm + step; i < max + step; i += step) { addToList(list, i); maxadded = i; } } else { minadded = step * Math.floor((min - freeTerm) / step); for (double i = minadded; i < max + step; i += step) { addToList(list, i); maxadded = i; } } addAdditionalElements(list); } catch (MyError e) { Log.debug(e.getMessage()); implicitPoly.setUndefined(); list.add(new GeoImplicitCurve(implicitPoly)); } } private boolean movedOut() { return xmin < calcxmin || xmax > calcxmax || ymin < calcymin || ymax > calcymax; } // TODO implement isOnScreen for implicit curves private int getVisibleContourCount() { int count = 0; for (int i = 0; i < list.size(); i++) { /* if (((GeoImplicitCurve) (list.get(i))).isOnScreen()) { */ count++; // } } return count; } @Override public void update() { xmin = cons.getApplication().getActiveEuclidianView().getXmin(); xmax = cons.getApplication().getActiveEuclidianView().getXmax(); ymin = cons.getApplication().getActiveEuclidianView().getYmin(); ymax = cons.getApplication().getActiveEuclidianView().getYmax(); int visible = getVisibleContourCount(); if (movedOut()) { list.clear(); compute(); } if (visible < minContours && !fixed) { step = step / 2; contourStep.setValue(step); list.clear(); compute(); } if (visible > maxContours && !fixed) { step = step * 2; contourStep.setValue(step); list.clear(); compute(); } getOutput(0).update(); } @Override public GetCommand getClassName() { return Commands.ContourPlot; } }