plotting
This commit is contained in:
parent
3b1575a7cc
commit
170b118c1f
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user