diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 5599619c..2266b115 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -32,7 +32,8 @@
на итальянский - Gabriele Ravanetti\n\n
Это приложение использует следующие открытые библиотеки:\n
Simple (XML serialization)\n
- JSCL
+ JSCL\n
+ AChartEngine
назад
@@ -40,6 +41,10 @@
вставить
переменные
+ Копировать
+ Построить график
+ График
+
Подсветка выражений
Округление результата
Включает/выключает округление результата
@@ -235,10 +240,9 @@ deg(4.67748) = 268\n
(2i + 1) ^ = -3 + 4i\n
e ^ i = 0.5403 + 0.84147i\n
\n
-Умеет ли К++ рисовать графики функций?\n
-\n
-Нет.\n
+Умеет ли К++ строить графики функций?\n
\n
+Да, введите выражение с 1 неизвестной переменной (например, cos(t)) и нажмите на результат. В контекстном меню выберите \'Построить график\'\n
Поддерживает ли К++ матричные вычисления?\n
\n
Нет.\n
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1f3a2be3..85cce047 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -33,7 +33,8 @@
Italian - Gabriele Ravanetti\n\n
This application uses next open source libraries:\n
Simple (XML serialization)\n
- JSCL
+ JSCL\n
+ AChartEngine
undo
@@ -43,6 +44,10 @@
paste
vars
+ Copy
+ Plot graph
+ Graph
+
Highlight expressions
Round result
Toggles rounding of the result
@@ -240,7 +245,7 @@ e ^ i = 0.5403 + 0.84147i\n
\n
Can C++ plot graph of the function?\n
\n
-No, currently C++ cannot plot functions\' graphs.\n
+Yes, type expression which contains 1 undefined variable (e.g. cos(t) and t has no value) and click on the result. In the context menu choose \'Plot graph\'.\n
\n
Does C++ support matrix calculations?\n
\n
diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java b/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java
index 9954faa5..b948e75c 100644
--- a/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java
+++ b/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java
@@ -3,22 +3,10 @@ package org.solovyev.android.calculator;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
-import jscl.math.Expression;
import jscl.math.Generic;
-import jscl.math.JsclInteger;
-import jscl.math.NumericWrapper;
import jscl.math.function.Constant;
-import jscl.math.numeric.Complex;
-import jscl.math.numeric.Numeric;
-import jscl.math.numeric.Real;
import org.achartengine.ChartFactory;
-import org.achartengine.model.XYMultipleSeriesDataset;
-import org.achartengine.model.XYSeries;
-import org.achartengine.renderer.XYMultipleSeriesRenderer;
-import org.achartengine.renderer.XYSeriesRenderer;
-import org.achartengine.util.MathHelper;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.help.HelpActivity;
import org.solovyev.common.utils.StringUtils;
@@ -57,8 +45,9 @@ public class CalculatorActivityLauncher {
context.startActivity(new Intent(context, CalculatorVarsActivity.class));
}
- public static void plotGraph(@NotNull final Context context, @NotNull Generic generic, @NotNull Constant constant) throws ArithmeticException {
+ public static void plotGraph(@NotNull final Context context, @NotNull Generic generic, @NotNull Constant constant){
final Intent intent = new Intent();
+ intent.putExtra(ChartFactory.TITLE, context.getString(R.string.c_graph));
intent.putExtra(CalculatorPlotActivity.INPUT, new CalculatorPlotActivity.Input(generic.toString(), constant.getName()));
intent.setClass(context, CalculatorPlotActivity.class);
context.startActivity(intent);
diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorModel.java b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java
index 823a5af1..e592c2ba 100644
--- a/src/main/java/org/solovyev/android/calculator/CalculatorModel.java
+++ b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java
@@ -8,6 +8,7 @@ package org.solovyev.android.calculator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Handler;
import android.text.ClipboardManager;
@@ -69,58 +70,7 @@ public enum CalculatorModel implements CursorControl, HistoryControl notSystemConstants = new HashSet();
- for (Constant constant : genericResult.getConstants()) {
- Var var = CalculatorEngine.instance.getVarsRegister().get(constant.getName());
- if (var != null && !var.isSystem()) {
- notSystemConstants.add(constant);
- }
- }
-
- if ( notSystemConstants.size() > 0 ) {
- if (notSystemConstants.size() > 1) {
- copyResult(activity, cd);
- } else {
- final Constant constant = CollectionsUtils.getFirstCollectionElement(notSystemConstants);
- assert constant != null;
- try {
- CalculatorActivityLauncher.plotGraph(activity, genericResult, constant);
- } catch (ArithmeticException e) {
- copyResult(activity, cd);
- }
- }
- } else {
- copyResult(activity, cd);
- }
- } else {
- copyResult(activity, cd);
- }
- break;
- case elementary:
- case numeric:
- copyResult(activity, cd);
- break;
- }
- } else {
- final String errorMessage = cd.getErrorMessage();
- if ( errorMessage != null ) {
- showEvaluationError(activity, errorMessage);
- }
- }
- }
- }
- });
-
+ this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity));
final CalculatorHistoryState lastState = CalculatorHistory.instance.getLastHistoryState();
if (lastState == null) {
@@ -439,4 +389,73 @@ public enum CalculatorModel implements CursorControl, HistoryControl notSystemConstants = new HashSet();
+ for (Constant constant : genericResult.getConstants()) {
+ Var var = CalculatorEngine.instance.getVarsRegister().get(constant.getName());
+ if (var != null && !var.isSystem()) {
+ notSystemConstants.add(constant);
+ }
+ }
+
+ if ( notSystemConstants.size() > 0 ) {
+ if (notSystemConstants.size() > 1) {
+ copyResult(activity, cd);
+ } else {
+ final CharSequence[] items = {activity.getText(R.string.c_plot), activity.getText(R.string.c_copy)};
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setItems(items, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ if (item == 0) {
+ final Constant constant = CollectionsUtils.getFirstCollectionElement(notSystemConstants);
+ assert constant != null;
+ CalculatorActivityLauncher.plotGraph(activity, genericResult, constant);
+ } else if ( item == 1 ) {
+ copyResult(activity, cd);
+ }
+ }
+ });
+ builder.create().show();
+
+ }
+ } else {
+ copyResult(activity, cd);
+ }
+ } else {
+ copyResult(activity, cd);
+ }
+ break;
+ case elementary:
+ case numeric:
+ copyResult(activity, cd);
+ break;
+ }
+ } else {
+ final String errorMessage = cd.getErrorMessage();
+ if ( errorMessage != null ) {
+ showEvaluationError(activity, errorMessage);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java
index 9959027f..7aeb3048 100644
--- a/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java
+++ b/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java
@@ -16,13 +16,7 @@ import android.view.Window;
import android.widget.Toast;
import jscl.math.Expression;
import jscl.math.Generic;
-import jscl.math.JsclInteger;
-import jscl.math.NumericWrapper;
import jscl.math.function.Constant;
-import jscl.math.numeric.Complex;
-import jscl.math.numeric.Numeric;
-import jscl.math.numeric.Real;
-import jscl.text.MutableInt;
import jscl.text.ParseException;
import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView;
@@ -37,16 +31,13 @@ import org.achartengine.renderer.XYSeriesRenderer;
import org.achartengine.tools.PanListener;
import org.achartengine.tools.ZoomEvent;
import org.achartengine.tools.ZoomListener;
-import org.achartengine.util.MathHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.solovyev.android.calculator.plot.MyXYSeries;
+import org.solovyev.android.calculator.plot.PlotUtils;
import org.solovyev.common.utils.MutableObject;
import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
/**
* User: serso
@@ -63,7 +54,7 @@ public class CalculatorPlotActivity extends Activity {
public static final String INPUT = "org.solovyev.android.calculator.CalculatorPlotActivity_input";
- public static final long EVAL_DELAY_MILLIS = 400;
+ public static final long EVAL_DELAY_MILLIS = 200;
private XYChart chart;
@@ -78,8 +69,6 @@ public class CalculatorPlotActivity extends Activity {
@NotNull
private Constant variable;
- private static final double MAX_Y_DIFF = Math.pow(10, 6);
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -132,21 +121,10 @@ public class CalculatorPlotActivity extends Activity {
double maxY = Double.MIN_VALUE;
for (XYSeries series : chart.getDataset().getSeries()) {
- if (checkMinMaxValue(series.getMinX())) {
- minX = Math.min(minX, series.getMinX());
- }
-
- if (checkMinMaxValue(series.getMinY())) {
- minY = Math.min(minY, series.getMinY());
- }
-
- if (checkMinMaxValue(series.getMaxX())) {
- maxX = Math.max(maxX, series.getMaxX());
- }
-
- if (checkMinMaxValue(series.getMaxY())) {
- maxY = Math.max(maxY, series.getMaxY());
- }
+ minX = Math.min(minX, series.getMinX());
+ minY = Math.min(minY, series.getMinY());
+ maxX = Math.max(maxX, series.getMaxX());
+ maxY = Math.max(maxY, series.getMaxY());
}
Log.d(CalculatorPlotActivity.class.getName(), "min x: " + minX + ", min y: " + minY + ", max x: " + maxX + ", max y: " + maxY);
@@ -187,14 +165,15 @@ public class CalculatorPlotActivity extends Activity {
});
graphContainer.addView(graphicalView);
- updateDataSets(chart);
+ updateDataSets(chart, 100);
}
- private boolean checkMinMaxValue(@NotNull Double value) {
- return !value.equals(MathHelper.NULL_VALUE);
- }
private void updateDataSets(@NotNull final XYChart chart) {
+ updateDataSets(chart, EVAL_DELAY_MILLIS);
+ }
+
+ private void updateDataSets(@NotNull final XYChart chart, long millisToWait) {
pendingOperation.setObject(new Runnable() {
@Override
public void run() {
@@ -206,17 +185,17 @@ public class CalculatorPlotActivity extends Activity {
//Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]");
- final XYSeries realSeries = chart.getDataset().getSeriesAt(0);
+ final MyXYSeries realSeries = (MyXYSeries)chart.getDataset().getSeriesAt(0);
- final XYSeries imagSeries;
+ final MyXYSeries imagSeries;
if (chart.getDataset().getSeriesCount() > 1) {
- imagSeries = chart.getDataset().getSeriesAt(1);
+ imagSeries = (MyXYSeries)chart.getDataset().getSeriesAt(1);
} else {
- imagSeries = new XYSeries(getImagFunctionName(CalculatorPlotActivity.this.variable));
+ imagSeries = new MyXYSeries(getImagFunctionName(CalculatorPlotActivity.this.variable), DEFAULT_NUMBER_OF_STEPS * 2);
}
try {
- if (addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true)) {
+ if (PlotUtils.addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true, DEFAULT_NUMBER_OF_STEPS)) {
if (chart.getDataset().getSeriesCount() <= 1) {
chart.getDataset().addSeries(imagSeries);
chart.getRenderer().addSeriesRenderer(createImagRenderer());
@@ -237,7 +216,7 @@ public class CalculatorPlotActivity extends Activity {
});
- new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
+ new Handler().postDelayed(pendingOperation.getObject(), millisToWait);
}
@NotNull
@@ -254,10 +233,10 @@ public class CalculatorPlotActivity extends Activity {
private final static MutableObject pendingOperation = new MutableObject();
private static XYChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, @NotNull final Constant variable) {
- final XYSeries realSeries = new XYSeries(getRealFunctionName(expression, variable));
- final XYSeries imagSeries = new XYSeries(getImagFunctionName(variable));
+ final MyXYSeries realSeries = new MyXYSeries(getRealFunctionName(expression, variable), DEFAULT_NUMBER_OF_STEPS * 2);
+ final MyXYSeries imagSeries = new MyXYSeries(getImagFunctionName(variable), DEFAULT_NUMBER_OF_STEPS * 2);
- boolean imagExists = addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false);
+ boolean imagExists = PlotUtils.addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false, DEFAULT_NUMBER_OF_STEPS);
final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
data.addSeries(realSeries);
@@ -286,111 +265,6 @@ public class CalculatorPlotActivity extends Activity {
return imagRenderer;
}
- private static boolean addXY(double minValue, double maxValue, Generic expression, Constant variable, @NotNull XYSeries realSeries, @NotNull XYSeries imagSeries, boolean addExtra) {
- boolean imagExists = false;
-
- double min = Math.min(minValue, maxValue);
- double max = Math.max(minValue, maxValue);
- double dist = max - min;
- if (addExtra) {
- min = min - dist;
- max = max + dist;
- }
-
- final int numberOfSteps = DEFAULT_NUMBER_OF_STEPS;
- final double step = Math.max( dist / numberOfSteps, 0.000000001);
-
- Double prevRealY = null;
- Double prevX = null;
- Double prevImagY = null;
-
- final MutableInt realSeriesI = new MutableInt(0);
- final MutableInt imagSeriesI = new MutableInt(0);
-
- double x = min;
- while (x <= max) {
-
- boolean needToCalculateRealY = needToCalculate(realSeries, step, x, realSeriesI);
-
- if (needToCalculateRealY) {
- final Complex c = calculatorExpression(expression, variable, x);
- Double y = prepareY(c.realPart());
- if (y != null) {
- addSingularityPoint(realSeries, prevX, x, prevRealY, y);
- realSeries.add(x, y);
- prevRealY = y;
- prevX = x;
- }
-
- boolean needToCalculateImagY = needToCalculate(imagSeries, step, x, imagSeriesI);
- if (needToCalculateImagY) {
- y = prepareY(c.imaginaryPart());
- if (y != null) {
- addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
- imagSeries.add(x, y);
- prevImagY = y;
- prevX = x;
- }
- if (c.imaginaryPart() != 0d) {
- imagExists = true;
- }
- }
- } else {
- boolean needToCalculateImagY = needToCalculate(imagSeries, step, x, imagSeriesI);
- if (needToCalculateImagY) {
- final Complex c = calculatorExpression(expression, variable, x);
- Double y = prepareY(c.imaginaryPart());
- if (y != null) {
- addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
- imagSeries.add(x, y);
- prevImagY = y;
- prevX = x;
- }
- if (c.imaginaryPart() != 0d) {
- imagExists = true;
- }
- }
- }
-
- x += step;
- }
-
- sortSeries(realSeries);
- if (imagExists) {
- sortSeries(imagSeries);
- }
-
- return imagExists;
- }
-
- @NotNull
- private static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant variable, double x) {
- return unwrap(expression.substitute(variable, Expression.valueOf(x)).numeric());
- }
-
- // todo serso: UNABLE TO PLOT i/ln(t)!!!
-
- private static void addSingularityPoint(@NotNull XYSeries series, @Nullable Double prevX, @NotNull Double x, @Nullable Double prevY, @NotNull Double y) {
- if (prevX != null && prevY != null) {
- if ( (Math.abs(y) > 0d && Math.abs(prevY / y) > MAX_Y_DIFF) || (Math.abs(prevY) > 0d && Math.abs(y / prevY) > MAX_Y_DIFF)) {
- //Log.d(CalculatorPlotActivity.class.getName(), "Singularity! Prev point: (" + prevX + ", " + prevY + "), current point: (" +x+ ", " + y +")" );
- //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);
- }
- }
- }
-
- private static boolean needToCalculate(@NotNull XYSeries series, double step, double x, @NotNull MutableInt i) {
- boolean needToCalculateY = true;
- for ( ; i.intValue() < series.getItemCount(); i.increment() ){
- if ( Math.abs(x - series.getX(i.intValue())) < step ) {
- needToCalculateY = false;
- break;
- }
- }
- return needToCalculateY;
- }
-
@Override
public Object onRetainNonConfigurationInstance() {
return new PlotBoundaries(chart.getRenderer());
@@ -464,27 +338,6 @@ public class CalculatorPlotActivity extends Activity {
}
}
- private static void sortSeries(@NotNull XYSeries series) {
- final List values = new ArrayList(series.getItemCount());
-
- for (int i = 0; i < series.getItemCount(); i++) {
- values.add(new Point(series.getX(i), series.getY(i)));
- }
-
- Collections.sort(values, new Comparator() {
- @Override
- public int compare(Point point, Point point1) {
- return Double.compare(point.getX(), point1.getX());
- }
- });
-
- Log.d(CalculatorPlotActivity.class.getName(), "Points for " + series.getTitle());
- series.clear();
- for (Point value : values) {
- series.add(value.getX(), value.getY());
- Log.d(CalculatorPlotActivity.class.getName(), "x = " + value.getX() + ", y = " + value.getY());
- }
- }
@NotNull
private static XYSeriesRenderer createCommonRenderer() {
@@ -497,37 +350,6 @@ public class CalculatorPlotActivity extends Activity {
return renderer;
}
- @Nullable
- private static Double prepareY(double y) {
- if (Double.isNaN(y)) {
- return null;
- } else {
- return y;
- }
- }
-
- @NotNull
- private static Complex unwrap(@Nullable Generic numeric) {
- if (numeric instanceof JsclInteger) {
- return Complex.valueOf(((JsclInteger) numeric).intValue(), 0d);
- } else if (numeric instanceof NumericWrapper) {
- return unwrap(((NumericWrapper) numeric).content());
- } else {
- throw new ArithmeticException();
- }
- }
-
- @NotNull
- private static Complex unwrap(@Nullable Numeric content) {
- if (content instanceof Real) {
- return Complex.valueOf(((Real) content).doubleValue(), 0d);
- } else if (content instanceof Complex) {
- return ((Complex) content);
- } else {
- throw new ArithmeticException();
- }
- }
-
public static class Input implements Serializable {
diff --git a/src/main/java/org/solovyev/android/calculator/plot/MyXYSeries.java b/src/main/java/org/solovyev/android/calculator/plot/MyXYSeries.java
new file mode 100644
index 00000000..e9d3f2df
--- /dev/null
+++ b/src/main/java/org/solovyev/android/calculator/plot/MyXYSeries.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2009-2011. Created by serso aka se.solovyev.
+ * For more information, please, contact se.solovyev@gmail.com
+ * or visit http://se.solovyev.org
+ */
+
+package org.solovyev.android.calculator.plot;
+
+import org.achartengine.model.XYSeries;
+import org.achartengine.util.MathHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * User: serso
+ * Date: 12/5/11
+ * Time: 8:43 PM
+ */
+
+/**
+ * BEST SOLUTION IS TO MODIFY LIBRARY CLASS
+ * NOTE: this class is a copy of XYSeries with some modifications:
+ * 1. Possibility to insert point in th emiddle og the range
+ */
+
+public class MyXYSeries extends XYSeries {
+ /**
+ * The series title.
+ */
+ private String mTitle;
+ /**
+ * A list to contain the values for the X axis.
+ */
+ private List mX = new ArrayList();
+ /**
+ * A list to contain the values for the Y axis.
+ */
+ private List mY = new ArrayList();
+ /**
+ * The minimum value for the X axis.
+ */
+ private double mMinX = MathHelper.NULL_VALUE;
+ /**
+ * The maximum value for the X axis.
+ */
+ private double mMaxX = -MathHelper.NULL_VALUE;
+ /**
+ * The minimum value for the Y axis.
+ */
+ private double mMinY = MathHelper.NULL_VALUE;
+ /**
+ * The maximum value for the Y axis.
+ */
+ private double mMaxY = -MathHelper.NULL_VALUE;
+ /**
+ * The scale number for this series.
+ */
+ private int mScaleNumber;
+
+ /**
+ * Builds a new XY series.
+ *
+ * @param title the series title.
+ */
+ public MyXYSeries(String title) {
+ this(title, 10);
+ }
+
+ public MyXYSeries(String title, int initialCapacity) {
+ super(title, 0);
+
+ this.mX = new ArrayList(initialCapacity);
+ this.mY = new ArrayList(initialCapacity);
+
+ mTitle = title;
+ mScaleNumber = 0;
+
+ initRange();
+ }
+
+ public int getScaleNumber() {
+ return mScaleNumber;
+ }
+
+ /**
+ * Initializes the range for both axes.
+ */
+ private void initRange() {
+ mMinX = MathHelper.NULL_VALUE;
+ mMaxX = -MathHelper.NULL_VALUE;
+ mMinY = MathHelper.NULL_VALUE;
+ mMaxY = -MathHelper.NULL_VALUE;
+ int length = getItemCount();
+ for (int k = 0; k < length; k++) {
+ double x = getX(k);
+ double y = getY(k);
+ updateRange(x, y);
+ }
+ }
+
+ /**
+ * Updates the range on both axes.
+ *
+ * @param x the new x value
+ * @param y the new y value
+ */
+ private void updateRange(double x, double y) {
+ mMinX = Math.min(mMinX, x);
+ mMaxX = Math.max(mMaxX, x);
+ mMinY = Math.min(mMinY, y);
+ mMaxY = Math.max(mMaxY, y);
+ }
+
+ /**
+ * Returns the series title.
+ *
+ * @return the series title
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the series title.
+ *
+ * @param title the series title
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ /**
+ * Adds a new value to the series.
+ *
+ * @param x the value for the X axis
+ * @param y the value for the Y axis
+ */
+ public synchronized void add(double x, double y) {
+ boolean added = false;
+ for (int i = 0; i < mX.size(); i++ ) {
+ if ( mX.get(i) > x ) {
+ mX.add(i, x);
+ mY.add(i, y);
+ added = true;
+ break;
+ }
+ }
+
+ if ( !added ) {
+ mX.add(x);
+ mY.add(y);
+ }
+
+ updateRange(x, y);
+ }
+
+
+ public boolean needToAdd(double density, double x) {
+ boolean result = true;
+
+ for (Double x1 : mX) {
+ if (Math.abs(x - x1) < density) {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Removes an existing value from the series.
+ *
+ * @param index the index in the series of the value to remove
+ */
+ public synchronized void remove(int index) {
+ double removedX = mX.remove(index);
+ double removedY = mY.remove(index);
+ if (removedX == mMinX || removedX == mMaxX || removedY == mMinY || removedY == mMaxY) {
+ initRange();
+ }
+ }
+
+ /**
+ * Removes all the existing values from the series.
+ */
+ public synchronized void clear() {
+ mX.clear();
+ mY.clear();
+ initRange();
+ }
+
+ /**
+ * Returns the X axis value at the specified index.
+ *
+ * @param index the index
+ * @return the X value
+ */
+ public synchronized double getX(int index) {
+ return mX.get(index);
+ }
+
+ /**
+ * Returns the Y axis value at the specified index.
+ *
+ * @param index the index
+ * @return the Y value
+ */
+ public synchronized double getY(int index) {
+ return mY.get(index);
+ }
+
+ /**
+ * Returns the series item count.
+ *
+ * @return the series item count
+ */
+ public synchronized int getItemCount() {
+ return mX == null ? 0 : mX.size();
+ }
+
+ /**
+ * Returns the minimum value on the X axis.
+ *
+ * @return the X axis minimum value
+ */
+ public double getMinX() {
+ return mMinX;
+ }
+
+ /**
+ * Returns the minimum value on the Y axis.
+ *
+ * @return the Y axis minimum value
+ */
+ public double getMinY() {
+ return mMinY;
+ }
+
+ /**
+ * Returns the maximum value on the X axis.
+ *
+ * @return the X axis maximum value
+ */
+ public double getMaxX() {
+ return mMaxX;
+ }
+
+ /**
+ * Returns the maximum value on the Y axis.
+ *
+ * @return the Y axis maximum value
+ */
+ public double getMaxY() {
+ return mMaxY;
+ }
+}
+
diff --git a/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java b/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java
new file mode 100644
index 00000000..b3b9e2f8
--- /dev/null
+++ b/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2009-2011. Created by serso aka se.solovyev.
+ * For more information, please, contact se.solovyev@gmail.com
+ * or visit http://se.solovyev.org
+ */
+
+package org.solovyev.android.calculator.plot;
+
+import jscl.math.Expression;
+import jscl.math.Generic;
+import jscl.math.JsclInteger;
+import jscl.math.NumericWrapper;
+import jscl.math.function.Constant;
+import jscl.math.numeric.Complex;
+import jscl.math.numeric.Numeric;
+import jscl.math.numeric.Real;
+import org.achartengine.util.MathHelper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * User: serso
+ * Date: 12/5/11
+ * Time: 8:58 PM
+ */
+public final class PlotUtils {
+
+ private static final double MAX_Y_DIFF = Math.pow(10, 6);
+
+ // not intended for instantiation
+ private PlotUtils() {
+ throw new AssertionError();
+ }
+
+ public static boolean addXY(double minValue,
+ double maxValue,
+ @NotNull Generic expression,
+ @NotNull Constant variable,
+ @NotNull MyXYSeries realSeries,
+ @NotNull MyXYSeries imagSeries,
+ boolean addExtra,
+ int numberOfSteps) {
+
+ boolean imagExists = false;
+
+ double min = Math.min(minValue, maxValue);
+ double max = Math.max(minValue, maxValue);
+ double dist = max - min;
+ if (addExtra) {
+ min = min - dist;
+ max = max + dist;
+ }
+
+ final double step = Math.max( dist / numberOfSteps, 0.000000001);
+
+ Double prevRealY = null;
+ Double prevX = null;
+ Double prevImagY = null;
+
+ double x = min;
+ while (x <= max) {
+
+ boolean needToCalculateRealY = realSeries.needToAdd(step, x);
+
+ if (needToCalculateRealY) {
+ final Complex c = calculatorExpression(expression, variable, x);
+ Double y = prepareY(c.realPart());
+ if (y != null) {
+ addSingularityPoint(realSeries, prevX, x, prevRealY, y);
+ realSeries.add(x, y);
+ prevRealY = y;
+ prevX = x;
+ }
+
+ boolean needToCalculateImagY = imagSeries.needToAdd(step, x);
+ if (needToCalculateImagY) {
+ y = prepareY(c.imaginaryPart());
+ if (y != null) {
+ addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
+ imagSeries.add(x, y);
+ prevImagY = y;
+ prevX = x;
+ }
+ if (c.imaginaryPart() != 0d) {
+ imagExists = true;
+ }
+ }
+ } else {
+ boolean needToCalculateImagY = imagSeries.needToAdd(step, x);
+ if (needToCalculateImagY) {
+ final Complex c = calculatorExpression(expression, variable, x);
+ Double y = prepareY(c.imaginaryPart());
+ if (y != null) {
+ addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
+ imagSeries.add(x, y);
+ prevImagY = y;
+ prevX = x;
+ }
+ if (c.imaginaryPart() != 0d) {
+ imagExists = true;
+ }
+ }
+ }
+
+ x += step;
+ }
+
+ return imagExists;
+ }
+
+ @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) {
+ // 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 +")" );
+ //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);
+ }
+ }
+ }
+
+ @Nullable
+ public static Double prepareY(double y) {
+ if (Double.isNaN(y)) {
+ return null;
+ } else {
+ return y;
+ }
+ }
+
+ @NotNull
+ public static Complex unwrap(@Nullable Generic numeric) {
+ if (numeric instanceof JsclInteger) {
+ return Complex.valueOf(((JsclInteger) numeric).intValue(), 0d);
+ } else if (numeric instanceof NumericWrapper) {
+ return unwrap(((NumericWrapper) numeric).content());
+ } else {
+ throw new ArithmeticException();
+ }
+ }
+
+ @NotNull
+ public static Complex unwrap(@Nullable Numeric content) {
+ if (content instanceof Real) {
+ return Complex.valueOf(((Real) content).doubleValue(), 0d);
+ } else if (content instanceof Complex) {
+ return ((Complex) content);
+ } else {
+ throw new ArithmeticException();
+ }
+ }
+}
diff --git a/src/test/java/org/solovyev/android/calculator/plot/PlotUtilsTest.java b/src/test/java/org/solovyev/android/calculator/plot/PlotUtilsTest.java
new file mode 100644
index 00000000..7314ce9b
--- /dev/null
+++ b/src/test/java/org/solovyev/android/calculator/plot/PlotUtilsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2009-2011. Created by serso aka se.solovyev.
+ * For more information, please, contact se.solovyev@gmail.com
+ * or visit http://se.solovyev.org
+ */
+
+package org.solovyev.android.calculator.plot;
+
+import jscl.math.Expression;
+import jscl.math.function.Constant;
+import junit.framework.Assert;
+import org.achartengine.model.XYSeries;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+/**
+ * User: serso
+ * Date: 12/5/11
+ * Time: 9:07 PM
+ */
+
+public class PlotUtilsTest {
+
+ @Test
+ public void testAddXY() throws Exception {
+ MyXYSeries series = new MyXYSeries("test_01", 100);
+ PlotUtils.addXY(-10, 10, Expression.valueOf("asin(t)"), new Constant("t"), series, new MyXYSeries("test_01_imag"), false, 200);
+ testAscSeries(series);
+ PlotUtils.addXY(-1, 1, Expression.valueOf("asin(t)"), new Constant("t"), series, new MyXYSeries("test_01_imag"), true, 200);
+ testAscSeries(series);
+
+ series = new MyXYSeries("test_02", 1000);
+ PlotUtils.addXY(-10, 10, Expression.valueOf("1/t"), new Constant("t"), series, new MyXYSeries("test_01_imag"), false, 1000);
+ testAscSeries(series);
+ PlotUtils.addXY(-1, 1, Expression.valueOf("1/t"), new Constant("t"), series, new MyXYSeries("test_01_imag"), true, 1000);
+ testAscSeries(series);
+
+ }
+
+ public void testAscSeries(@NotNull XYSeries series) {
+ for ( int i = 0; i < series.getItemCount(); i++ ) {
+ if (i > 1) {
+ Assert.assertTrue(series.getX(i - 1) + " > " +series.getX(i) + " at " + i + " of " + series.getItemCount(), series.getX(i - 1) <= series.getX(i));
+ }
+ }
+ }
+}