Plotter improvements

This commit is contained in:
serso 2012-10-04 17:58:08 +04:00
parent a49ca358af
commit 600b454eef
2 changed files with 856 additions and 672 deletions

View File

@ -406,6 +406,7 @@ public class CalculatorPlotFragment extends SherlockFragment implements Calculat
}
return new CubicLineChart(data, renderer, 0.1f);
//return new ScatterChart(data, renderer);
}
private static XYSeriesRenderer createImagRenderer() {
@ -473,7 +474,7 @@ public class CalculatorPlotFragment extends SherlockFragment implements Calculat
private static XYSeriesRenderer createCommonRenderer() {
final XYSeriesRenderer renderer = new XYSeriesRenderer();
renderer.setFillPoints(true);
renderer.setPointStyle(PointStyle.POINT);
renderer.setPointStyle(PointStyle.CIRCLE);
renderer.setLineWidth(3);
renderer.setColor(Color.WHITE);
renderer.setStroke(BasicStroke.SOLID);

View File

@ -6,6 +6,7 @@
package org.solovyev.android.calculator.plot;
import android.util.Log;
import jscl.math.Expression;
import jscl.math.Generic;
import jscl.math.JsclInteger;
@ -25,7 +26,8 @@ import org.jetbrains.annotations.Nullable;
*/
public final class PlotUtils {
private static final double MAX_Y_DIFF = Math.pow(10, 6);
private static final double MAX_Y_DIFF = 1;
private static final double MAX_X_DIFF = 1;
// not intended for instantiation
private PlotUtils() {
@ -51,80 +53,261 @@ public final class PlotUtils {
max = max + dist;
}
final double step = Math.max( dist / numberOfSteps, 0.000000001);
final double eps = 0.000000001;
Double prevRealY = null;
Double prevX = null;
Double prevImagY = null;
final double defaultStep = Math.max(dist / numberOfSteps, eps);
double step = defaultStep;
final Point real = new Point();
final Point imag = new Point();
double x = min;
while (x <= max) {
boolean needToCalculateRealY = realSeries.needToAdd(step, x);
while (x <= max) {
boolean needToCalculateRealY = realSeries.needToAdd(eps, x);
if (needToCalculateRealY) {
final Complex c = calculatorExpression(expression, variable, x);
Double y = prepareY(c.realPart());
if (y != null) {
addSingularityPoint(realSeries, prevX, x, prevRealY, y);
if (y != null) {
real.moveToNextPoint(x, y);
addSingularityPoint(realSeries, real);
realSeries.add(x, y);
prevRealY = y;
prevX = x;
}
boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(step, x);
boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(eps, x);
if (needToCalculateImagY) {
y = prepareY(c.imaginaryPart());
if (y != null) {
addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
imag.moveToNextPoint(x, y);
addSingularityPoint(imagSeries, imag);
imagSeries.add(x, y);
prevImagY = y;
prevX = x;
}
}
if (c.imaginaryPart() != 0d) {
imagExists = true;
}
}
} else {
boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(step, x);
boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(eps, x);
if (needToCalculateImagY) {
final Complex c = calculatorExpression(expression, variable, x);
Double y = prepareY(c.imaginaryPart());
if (y != null) {
addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
imag.moveToNextPoint(x, y);
addSingularityPoint(imagSeries, imag);
imagSeries.add(x, y);
prevImagY = y;
prevX = x;
}
}
if (c.imaginaryPart() != 0d) {
imagExists = true;
}
}
}
x += step;
}
step = updateStep(real, step, defaultStep / 2);
if (real.isX2Defined()) {
x = real.getX2() + step;
} else {
x += step;
}
}
return imagExists;
}
@NotNull
private static class Point {
private static final double DEFAULT = Double.MIN_VALUE;
private double x0 = DEFAULT;
private double x1 = DEFAULT;
private double x2 = DEFAULT;
private double y0 = DEFAULT;
private double y1 = DEFAULT;
private double y2 = DEFAULT;
private Point() {
}
public void moveToNextPoint(double x, double y) {
if ( this.x2 == x ) {
return;
}
this.x0 = this.x1;
this.x1 = this.x2;
this.x2 = x;
this.y0 = this.y1;
this.y1 = this.y2;
this.y2 = y;
}
public boolean isFullyDefined() {
return x0 != DEFAULT && x1 != DEFAULT && x2 != DEFAULT && y0 != DEFAULT && y1 != DEFAULT && y2 != DEFAULT;
}
public double getDx2() {
return x2 - x1;
}
public double getAbsDx2() {
if ( x2 > x1 ) {
return Math.abs(x2 - x1);
} else {
return Math.abs(x1 - x2);
}
}
public double getAbsDx1() {
if ( x1 > x0 ) {
return Math.abs(x1 - x0);
} else {
return Math.abs(x0 - x1);
}
}
public double getAbsDy1() {
if ( y1 > y0 ) {
return Math.abs(y1 - y0);
} else {
return Math.abs(y0 - y1);
}
}
public double getAbsDy2() {
if ( y2 > y1 ) {
return Math.abs(y2 - y1);
} else {
return Math.abs(y1 - y2);
}
}
public double getX0() {
return x0;
}
public double getX1() {
return x1;
}
public double getX2() {
return x2;
}
public boolean isX2Defined() {
return x2 != DEFAULT;
}
public double getY0() {
return y0;
}
public double getY1() {
return y1;
}
public double getY2() {
return y2;
}
public void clearHistory () {
this.x0 = DEFAULT;
this.x1 = DEFAULT;
this.y0 = DEFAULT;
this.y1 = DEFAULT;
}
public double getAbsDyDx2() {
double dx2 = this.getAbsDx2();
double dy2 = this.getAbsDy2();
return dy2 / dx2;
}
public double getAbsDyDx1() {
double dx1 = this.getAbsDx1();
double dy1 = this.getAbsDy1();
return dy1 / dx1;
}
public double getDyDx1() {
double result = getAbsDyDx1();
return y1 > y0 ? result : -result;
}
public double getDyDx2() {
double result = getAbsDyDx2();
return y2 > y1 ? result : -result;
}
@Override
public String toString() {
return "Point{" +
"x0=" + x0 +
", x1=" + x1 +
", x2=" + x2 +
", y0=" + y0 +
", y1=" + y1 +
", y2=" + y2 +
'}';
}
}
private static double updateStep(@NotNull Point real,
double step,
double eps) {
if ( !real.isFullyDefined() ) {
return step;
} else {
double dydx2 = real.getAbsDyDx2();
double dydx1 = real.getAbsDyDx1();
double k = dydx2 / dydx1;
if ( k > 1 ) {
step = step / k;
} else if ( k > 0 ) {
step = step * k;
}
return Math.max(step, eps);
}
}
@NotNull
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant variable, double x) {
return unwrap(expression.substitute(variable, Expression.valueOf(x)).numeric());
}
public static void addSingularityPoint(@NotNull MyXYSeries series, @Nullable Double prevX, @NotNull Double x, @Nullable Double prevY, @NotNull Double y) {
if (prevX != null && prevY != null) {
public static void addSingularityPoint(@NotNull MyXYSeries series,
@NotNull Point point) {
if (point.isFullyDefined()) {
// y or prevY should be more than 1d because if they are too small false singularity may occur (e.g., 1/0.000000000000000001)
if ( (Math.abs(y) >= 1d && Math.abs(prevY / y) > MAX_Y_DIFF) || (Math.abs(prevY) >= 1d && Math.abs(y / prevY) > MAX_Y_DIFF)) {
//Log.d(CalculatorPlotActivity.class.getName(), "Singularity! Prev point: (" + prevX + ", " + prevY + "), current point: (" +x+ ", " + y +")" );
// double dy0 = y1 - y0;
// double dx0 = x1 - x0;
// double dydx0 = dy0 / dx0;
double dy2 = point.getAbsDy2();
double dx2 = point.getAbsDx2();
//double dx1 = x2 - x1;
// double dydx1 = dy2 / dx1;
if ( dy2 > MAX_Y_DIFF && dx2 < MAX_X_DIFF && isDifferentSign(point.getY2(), point.getY1()) && isDifferentSign(point.getDyDx1(), point.getDyDx2())) {
Log.d(CalculatorPlotActivity.class.getName(), "Singularity: " + point);
//Log.d(CalculatorPlotActivity.class.getName(), String.valueOf(prevX + Math.abs(x - prevX) / 2) + ", null");
series.add(prevX + Math.abs(x - prevX) / 2, MathHelper.NULL_VALUE);
series.add(point.getX1() + point.getAbsDx2() / 2, MathHelper.NULL_VALUE);
point.clearHistory();
}
}
}
@Nullable
private static boolean isDifferentSign(@NotNull Double y0, @NotNull Double y1) {
return (y0 >= 0 && y1 < 0) || (y1 >= 0 && y0 < 0);
}
@Nullable
public static Double prepareY(double y) {
if (Double.isNaN(y)) {
return null;