This commit is contained in:
serso 2011-12-05 23:25:42 +04:00
parent 3b1575a7cc
commit 170b118c1f
8 changed files with 572 additions and 270 deletions

View File

@ -32,7 +32,8 @@
на итальянский - Gabriele Ravanetti\n\n
Это приложение использует следующие открытые библиотеки:\n
<a href="http://simple.sourceforge.net">Simple (XML serialization)</a>\n
<a href="http://meditorworld.appspot.com/meditor.txt">JSCL</a>
<a href="http://meditorworld.appspot.com/meditor.txt">JSCL</a>\n
<a href="http://www.achartengine.org/">AChartEngine</a>
</string>
<string name="c_undo">назад</string>
@ -40,6 +41,10 @@
<string name="c_paste">вставить</string>
<string name="c_vars">переменные</string>
<string name="c_copy">Копировать</string>
<string name="c_plot">Построить график</string>
<string name="c_graph">График</string>
<string name="c_calc_color_display_title">Подсветка выражений</string>
<string name="c_calc_round_result_title">Округление результата</string>
<string name="c_calc_round_result_summary">Включает/выключает округление результата</string>
@ -235,10 +240,9 @@ deg(4.67748) = 268\n
(2i + 1) ^ = -3 + 4i\n
e ^ i = 0.5403 + 0.84147i\n
\n
<b>Умеет ли К++ рисовать графики функций?</b>\n
\n
Нет.\n
<b>Умеет ли К++ строить графики функций?</b>\n
\n
Да, введите выражение с 1 неизвестной переменной (например, cos(t)) и нажмите на результат. В контекстном меню выберите \'Построить график\'\n
<b>Поддерживает ли К++ матричные вычисления?</b>\n
\n
Нет.\n

View File

@ -33,7 +33,8 @@
Italian - Gabriele Ravanetti\n\n
This application uses next open source libraries:\n
<a href="http://simple.sourceforge.net">Simple (XML serialization)</a>\n
<a href="http://meditorworld.appspot.com/meditor.txt">JSCL</a>
<a href="http://meditorworld.appspot.com/meditor.txt">JSCL</a>\n
<a href="http://www.achartengine.org/">AChartEngine</a>
</string>
<string name="c_undo">undo</string>
@ -43,6 +44,10 @@
<string name="c_paste">paste</string>
<string name="c_vars">vars</string>
<string name="c_copy">Copy</string>
<string name="c_plot">Plot graph</string>
<string name="c_graph">Graph</string>
<string name="c_calc_color_display_title">Highlight expressions</string>
<string name="c_calc_round_result_title">Round result</string>
<string name="c_calc_round_result_summary">Toggles rounding of the result</string>
@ -240,7 +245,7 @@ e ^ i = 0.5403 + 0.84147i\n
\n
<b>Can C++ plot graph of the function?</b>\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
<b>Does C++ support matrix calculations?</b>\n
\n

View File

@ -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);

View File

@ -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<CalculatorH
this.editor.setHighlightText(preferences.getBoolean(activity.getString(R.string.p_calc_color_display_key), colorExpressionsInBracketsDefault));
this.display = (CalculatorDisplay) activity.findViewById(R.id.calculatorDisplay);
this.display.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v instanceof CalculatorDisplay) {
final CalculatorDisplay cd = (CalculatorDisplay)v;
if (cd.isValid()) {
switch (cd.getJsclOperation()) {
case simplify:
Generic genericResult = cd.getGenericResult();
if ( genericResult != null ) {
final Set<Constant> notSystemConstants = new HashSet<Constant>();
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<CalculatorH
public CalculatorDisplay getDisplay() {
return display;
}
private static class CalculatorDisplayOnClickListener implements View.OnClickListener {
@NotNull
private final Activity activity;
public CalculatorDisplayOnClickListener(@NotNull Activity activity) {
this.activity = activity;
}
@Override
public void onClick(View v) {
if (v instanceof CalculatorDisplay) {
final CalculatorDisplay cd = (CalculatorDisplay)v;
if (cd.isValid()) {
switch (cd.getJsclOperation()) {
case simplify:
final Generic genericResult = cd.getGenericResult();
if ( genericResult != null ) {
final Set<Constant> notSystemConstants = new HashSet<Constant>();
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);
}
}
}
}
}
}

View File

@ -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<Runnable> pendingOperation = new MutableObject<Runnable>();
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<Point> values = new ArrayList<Point>(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<Point>() {
@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 {

View File

@ -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<Double> mX = new ArrayList<Double>();
/**
* A list to contain the values for the Y axis.
*/
private List<Double> mY = new ArrayList<Double>();
/**
* 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<Double>(initialCapacity);
this.mY = new ArrayList<Double>(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;
}
}

View File

@ -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();
}
}
}

View File

@ -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));
}
}
}
}