plotting added

This commit is contained in:
serso 2011-12-02 01:52:13 +04:00
parent def00244a5
commit d2e161cfa0
4 changed files with 200 additions and 184 deletions

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/plot_view_container"
a:orientation="horizontal"
a:layout_width="fill_parent"
a:layout_height="fill_parent">
<LinearLayout
a:id="@+id/plot_graph_container"
a:orientation="vertical"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="3">
</LinearLayout>
<LinearLayout
a:orientation="vertical"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="1">
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_min_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_min_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_max_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_max_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/plot_view_container"
a:orientation="vertical"
a:layout_width="fill_parent"
a:layout_height="fill_parent">
<LinearLayout
a:id="@+id/plot_graph_container"
a:orientation="horizontal"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="3">
</LinearLayout>
<LinearLayout
a:orientation="horizontal"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="1">
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_min_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_min_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_max_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_max_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/plot_view_container"
a:orientation="vertical"
a:layout_width="fill_parent"
a:layout_height="fill_parent">
</LinearLayout>

View File

@ -10,6 +10,7 @@ import android.app.Activity;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.widget.Toast; import android.widget.Toast;
@ -24,19 +25,25 @@ import jscl.math.numeric.Real;
import jscl.text.ParseException; import jscl.text.ParseException;
import org.achartengine.ChartFactory; import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView; import org.achartengine.GraphicalView;
import org.achartengine.chart.AbstractChart;
import org.achartengine.chart.LineChart; import org.achartengine.chart.LineChart;
import org.achartengine.model.Point;
import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries; import org.achartengine.model.XYSeries;
import org.achartengine.renderer.BasicStroke; import org.achartengine.renderer.BasicStroke;
import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer;
import org.achartengine.tools.PanListener;
import org.achartengine.tools.ZoomEvent;
import org.achartengine.tools.ZoomListener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.solovyev.android.view.widgets.NumberPicker;
import org.solovyev.common.utils.MutableObject; import org.solovyev.common.utils.MutableObject;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/** /**
* User: serso * User: serso
@ -45,7 +52,7 @@ import java.io.Serializable;
*/ */
public class CalculatorPlotActivity extends Activity { public class CalculatorPlotActivity extends Activity {
private static final int DEFAULT_NUMBER_OF_STEPS = 100; private static final int DEFAULT_NUMBER_OF_STEPS = 200;
private static final int DEFAULT_MIN_NUMBER = -10; private static final int DEFAULT_MIN_NUMBER = -10;
@ -53,7 +60,7 @@ public class CalculatorPlotActivity extends Activity {
public static final String INPUT = "org.solovyev.android.calculator.CalculatorPlotActivity_input"; public static final String INPUT = "org.solovyev.android.calculator.CalculatorPlotActivity_input";
public static final long EVAL_DELAY_MILLIS = 1000; public static final long EVAL_DELAY_MILLIS = 300;
/** /**
* The encapsulated graphical view. * The encapsulated graphical view.
@ -66,10 +73,6 @@ public class CalculatorPlotActivity extends Activity {
@NotNull @NotNull
private Constant variable; private Constant variable;
private double minValue = DEFAULT_MIN_NUMBER;
private double maxValue = DEFAULT_MAX_NUMBER;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -90,19 +93,7 @@ public class CalculatorPlotActivity extends Activity {
setContentView(R.layout.calc_plot_view); setContentView(R.layout.calc_plot_view);
setGraphicalView(minValue, maxValue); setGraphicalView(DEFAULT_MIN_NUMBER, DEFAULT_MAX_NUMBER);
final NumberPicker minXNumberPicker = (NumberPicker)findViewById(R.id.plot_x_min_value);
final NumberPicker maxXNumberPicker = (NumberPicker)findViewById(R.id.plot_x_max_value);
minXNumberPicker.setRange(Integer.MIN_VALUE, Integer.MAX_VALUE);
minXNumberPicker.setCurrent(DEFAULT_MIN_NUMBER);
maxXNumberPicker.setRange(Integer.MIN_VALUE, Integer.MAX_VALUE);
maxXNumberPicker.setCurrent(DEFAULT_MAX_NUMBER);
minXNumberPicker.setOnChangeListener(new BoundariesChangeListener(true));
maxXNumberPicker.setOnChangeListener(new BoundariesChangeListener(false));
} catch (ParseException e) { } catch (ParseException e) {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
@ -111,75 +102,90 @@ public class CalculatorPlotActivity extends Activity {
} }
private void setGraphicalView(final double minValue, final double maxValue) { private void setGraphicalView(final double minValue, final double maxValue) {
final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_graph_container); final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_view_container);
if (graphicalView != null) { if (graphicalView != null) {
graphContainer.removeView(graphicalView); graphContainer.removeView(graphicalView);
} }
graphicalView = new GraphicalView(this, prepareChart(minValue, maxValue, expression, variable)); final LineChart chart = prepareChart(minValue, maxValue, expression, variable);
graphicalView = new GraphicalView(this, chart);
graphicalView.addZoomListener(new ZoomListener() {
@Override
public void zoomApplied(ZoomEvent e) {
updateDataSets(chart);
}
@Override
public void zoomReset() {
updateDataSets(chart);
}
}, true, true);
graphicalView.addPanListener(new PanListener() {
@Override
public void panApplied() {
updateDataSets(chart);
}
});
graphContainer.addView(graphicalView); graphContainer.addView(graphicalView);
} }
private void updateDataSets(@NotNull final LineChart chart) {
pendingOperation.setObject(new Runnable() {
@Override
public void run() {
// allow only one runner at one time
synchronized (pendingOperation) {
//lock all operations with history
if (pendingOperation.getObject() == this) {
final XYMultipleSeriesRenderer dr = chart.getRenderer();
Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]");
final XYSeries realSeries = chart.getDataset().getSeriesAt(0);
final XYSeries imagSeries;
if (chart.getDataset().getSeriesCount() > 1) {
imagSeries = chart.getDataset().getSeriesAt(1);
} else {
imagSeries = new XYSeries(getImagFunctionName(CalculatorPlotActivity.this.expression, CalculatorPlotActivity.this.variable));
}
if (addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries)) {
if (chart.getDataset().getSeriesCount() <= 1) {
chart.getDataset().addSeries(imagSeries);
chart.getRenderer().addSeriesRenderer(createImagRenderer());
}
}
graphicalView.repaint();
}
}
}
});
new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
}
@NotNull
private static String getImagFunctionName(@NotNull Generic expression, @NotNull Constant variable) {
return "g(" + variable.getName() +")" + " = " + "Im(" + expression.toString() + ")";
}
@NotNull
private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) {
return "ƒ(" + variable.getName() +")" + " = " + expression.toString();
}
@NotNull @NotNull
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>(); private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
private class BoundariesChangeListener implements NumberPicker.OnChangedListener { private static LineChart 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(expression, variable));
private boolean min; boolean imagExists = addXY(minValue, maxValue, expression, variable, realSeries, imagSeries);
private BoundariesChangeListener(boolean min) {
this.min = min;
}
@Override
public void onChanged(NumberPicker picker, int oldVal, final int newVal) {
if (min) {
minValue = newVal;
} else {
maxValue = newVal;
}
pendingOperation.setObject(new Runnable() {
@Override
public void run() {
// allow only one runner at one time
synchronized (pendingOperation) {
//lock all operations with history
if (pendingOperation.getObject() == this) {
// actually nothing shall be logged while text operations are done
setGraphicalView(CalculatorPlotActivity.this.minValue, CalculatorPlotActivity.this.maxValue);
}
}
}
});
new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
}
}
private static AbstractChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, @NotNull final Constant variable) {
final XYSeries realSeries = new XYSeries(expression.toString());
final XYSeries imagSeries = new XYSeries("Im(" + expression.toString() + ")");
boolean imagExists = false;
final double min = Math.min(minValue, maxValue);
final double max = Math.max(minValue, maxValue);
final int numberOfSteps = DEFAULT_NUMBER_OF_STEPS;
final double step = Math.max((max - min) / numberOfSteps, 0.001);
double x = min;
while (x <= max) {
Generic numeric = expression.substitute(variable, Expression.valueOf(x)).numeric();
final Complex c = unwrap(numeric);
realSeries.add(x, prepareY(c.realPart()));
imagSeries.add(x, prepareY(c.imaginaryPart()));
if (c.imaginaryPart() != 0d) {
imagExists = true;
}
x += step;
}
final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset(); final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
data.addSeries(realSeries); data.addSeries(realSeries);
@ -188,20 +194,109 @@ public class CalculatorPlotActivity extends Activity {
} }
final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
renderer.setZoomEnabled(false);
renderer.setZoomEnabled(false, false);
renderer.addSeriesRenderer(createCommonRenderer()); renderer.addSeriesRenderer(createCommonRenderer());
renderer.setPanEnabled(false); renderer.setShowGrid(true);
renderer.setPanEnabled(false, false); renderer.setXTitle(variable.getName());
renderer.setYTitle("f(" + variable.getName() +")");
if (imagExists) { if (imagExists) {
final XYSeriesRenderer imagRenderer = createCommonRenderer(); renderer.addSeriesRenderer(createImagRenderer());
imagRenderer.setStroke(BasicStroke.DOTTED);
renderer.addSeriesRenderer(imagRenderer);
} }
return new LineChart(data, renderer); return new LineChart(data, renderer);
} }
private static XYSeriesRenderer createImagRenderer() {
final XYSeriesRenderer imagRenderer = createCommonRenderer();
imagRenderer.setStroke(BasicStroke.DOTTED);
return imagRenderer;
}
private static boolean addXY(double minValue, double maxValue, Generic expression, Constant variable, @NotNull XYSeries realSeries, @NotNull XYSeries imagSeries) {
boolean imagExists = false;
final double min = 1.5 * Math.min(minValue, maxValue);
final double max = 1.5 * Math.max(minValue, maxValue);
final int numberOfSteps = DEFAULT_NUMBER_OF_STEPS;
final double step = Math.max((max - min) / numberOfSteps, 0.001);
double x = min;
while (x <= max) {
boolean needToCalculateRealY = needToCalculate(realSeries, step, x);
if (needToCalculateRealY) {
Generic numeric = expression.substitute(variable, Expression.valueOf(x)).numeric();
final Complex c = unwrap(numeric);
Double y = prepareY(c.realPart());
if (y != null) {
realSeries.add(x, y);
}
boolean needToCalculateImagY = needToCalculate(imagSeries, step, x);
if (needToCalculateImagY) {
y = prepareY(c.imaginaryPart());
if (y != null) {
imagSeries.add(x, y);
}
if (c.imaginaryPart() != 0d) {
imagExists = true;
}
}
} else {
boolean needToCalculateImagY = needToCalculate(imagSeries, step, x);
if (needToCalculateImagY) {
Generic numeric = expression.substitute(variable, Expression.valueOf(x)).numeric();
final Complex c = unwrap(numeric);
Double y = prepareY(c.imaginaryPart());
if (y != null) {
imagSeries.add(x, y);
}
if (c.imaginaryPart() != 0d) {
imagExists = true;
}
}
}
x += step;
}
sortSeries(realSeries);
if (imagExists) {
sortSeries(imagSeries);
}
return imagExists;
}
private static boolean needToCalculate(@NotNull XYSeries series, double step, double x) {
boolean needToCalculateY = true;
for ( int i = 0; i < series.getItemCount(); i++ ){
if ( Math.abs(x - series.getX(i)) < step ) {
needToCalculateY = false;
break;
}
}
return needToCalculateY;
}
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((float)series.getX(i), (float)series.getY(i)));
}
Collections.sort(values, new Comparator<Point>() {
@Override
public int compare(Point point, Point point1) {
return Float.compare(point.getX(), point1.getX());
}
});
series.clear();
for (Point value : values) {
series.add(value.getX(), value.getY());
}
}
@NotNull @NotNull
private static XYSeriesRenderer createCommonRenderer() { private static XYSeriesRenderer createCommonRenderer() {
final XYSeriesRenderer renderer = new XYSeriesRenderer(); final XYSeriesRenderer renderer = new XYSeriesRenderer();
@ -210,9 +305,10 @@ public class CalculatorPlotActivity extends Activity {
return renderer; return renderer;
} }
private static double prepareY(double y) { @Nullable
if (Double.isNaN(y) || Double.isInfinite(y)) { private static Double prepareY(double y) {
return 0d; if (Double.isNaN(y)) {
return null;
} else { } else {
return y; return y;
} }