From d2e161cfa0ba5b45951e4ad9221031b6db31513c Mon Sep 17 00:00:00 2001 From: serso Date: Fri, 2 Dec 2011 01:52:13 +0400 Subject: [PATCH] plotting added --- res/layout-land/calc_plot_view.xml | 48 ---- res/layout-port/calc_plot_view.xml | 48 ---- res/layout/calc_plot_view.xml | 16 ++ .../calculator/CalculatorPlotActivity.java | 272 ++++++++++++------ 4 files changed, 200 insertions(+), 184 deletions(-) delete mode 100644 res/layout-land/calc_plot_view.xml delete mode 100644 res/layout-port/calc_plot_view.xml create mode 100644 res/layout/calc_plot_view.xml diff --git a/res/layout-land/calc_plot_view.xml b/res/layout-land/calc_plot_view.xml deleted file mode 100644 index a9380099..00000000 --- a/res/layout-land/calc_plot_view.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout-port/calc_plot_view.xml b/res/layout-port/calc_plot_view.xml deleted file mode 100644 index 1ca5792f..00000000 --- a/res/layout-port/calc_plot_view.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/calc_plot_view.xml b/res/layout/calc_plot_view.xml new file mode 100644 index 00000000..bf69b88b --- /dev/null +++ b/res/layout/calc_plot_view.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java index ff0f0564..12fa3392 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java @@ -10,6 +10,7 @@ import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; +import android.util.Log; import android.view.ViewGroup; import android.view.Window; import android.widget.Toast; @@ -24,19 +25,25 @@ import jscl.math.numeric.Real; import jscl.text.ParseException; import org.achartengine.ChartFactory; import org.achartengine.GraphicalView; -import org.achartengine.chart.AbstractChart; import org.achartengine.chart.LineChart; +import org.achartengine.model.Point; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.BasicStroke; import org.achartengine.renderer.XYMultipleSeriesRenderer; 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.Nullable; -import org.solovyev.android.view.widgets.NumberPicker; 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 @@ -45,7 +52,7 @@ import java.io.Serializable; */ 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; @@ -53,7 +60,7 @@ public class CalculatorPlotActivity extends Activity { 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. @@ -66,10 +73,6 @@ public class CalculatorPlotActivity extends Activity { @NotNull private Constant variable; - private double minValue = DEFAULT_MIN_NUMBER; - - private double maxValue = DEFAULT_MAX_NUMBER; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -90,19 +93,7 @@ public class CalculatorPlotActivity extends Activity { setContentView(R.layout.calc_plot_view); - setGraphicalView(minValue, maxValue); - - 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)); + setGraphicalView(DEFAULT_MIN_NUMBER, DEFAULT_MAX_NUMBER); } catch (ParseException e) { 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) { - final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_graph_container); + final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_view_container); if (graphicalView != null) { 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); } + 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 private final static MutableObject pendingOperation = new MutableObject(); - 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; - - 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; - } + boolean imagExists = addXY(minValue, maxValue, expression, variable, realSeries, imagSeries); final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset(); data.addSeries(realSeries); @@ -188,20 +194,109 @@ public class CalculatorPlotActivity extends Activity { } final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); - renderer.setZoomEnabled(false); - renderer.setZoomEnabled(false, false); renderer.addSeriesRenderer(createCommonRenderer()); - renderer.setPanEnabled(false); - renderer.setPanEnabled(false, false); + renderer.setShowGrid(true); + renderer.setXTitle(variable.getName()); + renderer.setYTitle("f(" + variable.getName() +")"); if (imagExists) { - final XYSeriesRenderer imagRenderer = createCommonRenderer(); - imagRenderer.setStroke(BasicStroke.DOTTED); - renderer.addSeriesRenderer(imagRenderer); + renderer.addSeriesRenderer(createImagRenderer()); } 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 values = new ArrayList(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() { + @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 private static XYSeriesRenderer createCommonRenderer() { final XYSeriesRenderer renderer = new XYSeriesRenderer(); @@ -210,9 +305,10 @@ public class CalculatorPlotActivity extends Activity { return renderer; } - private static double prepareY(double y) { - if (Double.isNaN(y) || Double.isInfinite(y)) { - return 0d; + @Nullable + private static Double prepareY(double y) { + if (Double.isNaN(y)) { + return null; } else { return y; }