Plotter
This commit is contained in:
parent
abebb25e53
commit
043576f5e8
@ -1,360 +1,62 @@
|
|||||||
/*
|
|
||||||
* 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;
|
package org.solovyev.android.calculator.plot;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.Window;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import jscl.math.Expression;
|
|
||||||
import jscl.math.Generic;
|
|
||||||
import jscl.math.function.Constant;
|
|
||||||
import jscl.text.ParseException;
|
|
||||||
import org.achartengine.ChartFactory;
|
|
||||||
import org.achartengine.GraphicalView;
|
|
||||||
import org.achartengine.chart.CubicLineChart;
|
|
||||||
import org.achartengine.chart.PointStyle;
|
|
||||||
import org.achartengine.chart.XYChart;
|
|
||||||
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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.solovyev.android.calculator.CalculatorActivityHelper;
|
||||||
|
import org.solovyev.android.calculator.CalculatorApplication;
|
||||||
import org.solovyev.android.calculator.R;
|
import org.solovyev.android.calculator.R;
|
||||||
import org.solovyev.android.calculator.CalculatorParseException;
|
|
||||||
import org.solovyev.android.calculator.PreparedExpression;
|
|
||||||
import org.solovyev.android.calculator.ToJsclTextProcessor;
|
|
||||||
import org.solovyev.common.MutableObject;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User: serso
|
* User: serso
|
||||||
* Date: 12/1/11
|
* Date: 9/30/12
|
||||||
* Time: 12:40 AM
|
* Time: 4:56 PM
|
||||||
*/
|
*/
|
||||||
public class CalculatorPlotActivity extends Activity {
|
public class CalculatorPlotActivity extends SherlockFragmentActivity {
|
||||||
|
|
||||||
private static final String TAG = CalculatorPlotActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final int DEFAULT_NUMBER_OF_STEPS = 100;
|
|
||||||
|
|
||||||
private static final int DEFAULT_MIN_NUMBER = -10;
|
|
||||||
|
|
||||||
private static final int DEFAULT_MAX_NUMBER = 10;
|
|
||||||
|
|
||||||
public static final String INPUT = "org.solovyev.android.calculator.CalculatorPlotActivity_input";
|
|
||||||
|
|
||||||
public static final long EVAL_DELAY_MILLIS = 200;
|
|
||||||
|
|
||||||
private XYChart chart;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The encapsulated graphical view.
|
|
||||||
*/
|
|
||||||
private GraphicalView graphicalView;
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Generic expression;
|
private final CalculatorActivityHelper activityHelper = CalculatorApplication.getInstance().createActivityHelper(R.layout.main_empty, CalculatorPlotActivity.class.getSimpleName());
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Constant variable;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Bundle extras = getIntent().getExtras();
|
|
||||||
|
|
||||||
final Input input = (Input) extras.getSerializable(INPUT);
|
activityHelper.onCreate(this, savedInstanceState);
|
||||||
|
|
||||||
try {
|
final Intent intent = getIntent();
|
||||||
final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression());
|
|
||||||
this.expression = Expression.valueOf(preparedExpression.getExpression());
|
|
||||||
this.variable = new Constant(input.getVariableName());
|
|
||||||
|
|
||||||
String title = extras.getString(ChartFactory.TITLE);
|
final Bundle arguments;
|
||||||
if (title == null) {
|
if (intent != null) {
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
arguments = intent.getExtras();
|
||||||
} else if (title.length() > 0) {
|
|
||||||
setTitle(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.calc_plot_view);
|
|
||||||
|
|
||||||
final Object lastNonConfigurationInstance = getLastNonConfigurationInstance();
|
|
||||||
setGraphicalView(lastNonConfigurationInstance instanceof PlotBoundaries ? (PlotBoundaries)lastNonConfigurationInstance : null);
|
|
||||||
|
|
||||||
} catch (ParseException e) {
|
|
||||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
} catch (ArithmeticException e) {
|
|
||||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
} catch (CalculatorParseException e) {
|
|
||||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setGraphicalView(@Nullable PlotBoundaries plotBoundaries) {
|
|
||||||
double minValue = plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin;
|
|
||||||
double maxValue = plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax;
|
|
||||||
|
|
||||||
final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_view_container);
|
|
||||||
|
|
||||||
if (graphicalView != null) {
|
|
||||||
graphContainer.removeView(graphicalView);
|
|
||||||
}
|
|
||||||
|
|
||||||
chart = prepareChart(minValue, maxValue, expression, variable);
|
|
||||||
|
|
||||||
// reverting boundaries (as in prepareChart() we add some cached values )
|
|
||||||
double minX = Double.MAX_VALUE;
|
|
||||||
double minY = Double.MAX_VALUE;
|
|
||||||
|
|
||||||
double maxX = Double.MIN_VALUE;
|
|
||||||
double maxY = Double.MIN_VALUE;
|
|
||||||
|
|
||||||
for (XYSeries series : chart.getDataset().getSeries()) {
|
|
||||||
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);
|
|
||||||
Log.d(CalculatorPlotActivity.class.getName(), "Plot boundaries are " + plotBoundaries);
|
|
||||||
|
|
||||||
|
|
||||||
if (plotBoundaries == null) {
|
|
||||||
chart.getRenderer().setXAxisMin(Math.max(minX, minValue));
|
|
||||||
chart.getRenderer().setYAxisMin(Math.max(minY, minValue));
|
|
||||||
chart.getRenderer().setXAxisMax(Math.min(maxX, maxValue));
|
|
||||||
chart.getRenderer().setYAxisMax(Math.min(maxY, maxValue));
|
|
||||||
} else {
|
} else {
|
||||||
chart.getRenderer().setXAxisMin(plotBoundaries.xMin);
|
arguments = null;
|
||||||
chart.getRenderer().setYAxisMin(plotBoundaries.yMin);
|
|
||||||
chart.getRenderer().setXAxisMax(plotBoundaries.xMax);
|
|
||||||
chart.getRenderer().setYAxisMax(plotBoundaries.yMax);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
graphicalView = new GraphicalView(this, chart);
|
activityHelper.addTab(this, "plot", CalculatorPlotFragment.class, arguments, R.string.c_plot, R.id.main_layout);
|
||||||
|
|
||||||
graphicalView.addZoomListener(new ZoomListener() {
|
|
||||||
@Override
|
|
||||||
public void zoomApplied(ZoomEvent e) {
|
|
||||||
updateDataSets(chart);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void zoomReset() {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
updateDataSets(chart);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
|
||||||
}, true, true);
|
|
||||||
|
|
||||||
graphicalView.addPanListener(new PanListener() {
|
activityHelper.onSaveInstanceState(this, outState);
|
||||||
@Override
|
|
||||||
public void panApplied() {
|
|
||||||
Log.d(TAG, "org.achartengine.tools.PanListener.panApplied");
|
|
||||||
updateDataSets(chart);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
graphContainer.addView(graphicalView);
|
|
||||||
|
|
||||||
updateDataSets(chart, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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() {
|
|
||||||
// allow only one runner at one time
|
|
||||||
synchronized (pendingOperation) {
|
|
||||||
//lock all operations with history
|
|
||||||
if (pendingOperation.getObject() == this) {
|
|
||||||
|
|
||||||
Log.d(TAG, "org.solovyev.android.calculator.plot.CalculatorPlotActivity.updateDataSets");
|
|
||||||
|
|
||||||
final XYMultipleSeriesRenderer dr = chart.getRenderer();
|
|
||||||
|
|
||||||
//Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]");
|
|
||||||
|
|
||||||
final MyXYSeries realSeries = (MyXYSeries)chart.getDataset().getSeriesAt(0);
|
|
||||||
|
|
||||||
final MyXYSeries imagSeries;
|
|
||||||
if (chart.getDataset().getSeriesCount() > 1) {
|
|
||||||
imagSeries = (MyXYSeries)chart.getDataset().getSeriesAt(1);
|
|
||||||
} else {
|
|
||||||
imagSeries = new MyXYSeries(getImagFunctionName(CalculatorPlotActivity.this.variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ArithmeticException e) {
|
|
||||||
// todo serso: translate
|
|
||||||
Toast.makeText(CalculatorPlotActivity.this, "Arithmetic error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
|
||||||
CalculatorPlotActivity.this.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingOperation.getObject() == this) {
|
|
||||||
graphicalView.repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
new Handler().postDelayed(pendingOperation.getObject(), millisToWait);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static String getImagFunctionName(@NotNull Constant variable) {
|
|
||||||
return "g(" + variable.getName() +")" + " = " + "Im(ƒ(" + variable.getName() +"))";
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) {
|
|
||||||
return "ƒ(" + variable.getName() +")" + " = " + expression.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
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 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 = PlotUtils.addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false, DEFAULT_NUMBER_OF_STEPS);
|
|
||||||
|
|
||||||
final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
|
|
||||||
data.addSeries(realSeries);
|
|
||||||
if (imagExists) {
|
|
||||||
data.addSeries(imagSeries);
|
|
||||||
}
|
|
||||||
|
|
||||||
final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
|
|
||||||
renderer.setShowGrid(true);
|
|
||||||
renderer.setXTitle(variable.getName());
|
|
||||||
renderer.setYTitle("f(" + variable.getName() + ")");
|
|
||||||
renderer.setChartTitleTextSize(20);
|
|
||||||
|
|
||||||
renderer.setZoomEnabled(true);
|
|
||||||
renderer.setZoomButtonsVisible(true);
|
|
||||||
|
|
||||||
renderer.addSeriesRenderer(createCommonRenderer());
|
|
||||||
if (imagExists) {
|
|
||||||
renderer.addSeriesRenderer(createImagRenderer());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CubicLineChart(data, renderer, 0.1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static XYSeriesRenderer createImagRenderer() {
|
|
||||||
final XYSeriesRenderer imagRenderer = createCommonRenderer();
|
|
||||||
imagRenderer.setStroke(BasicStroke.DASHED);
|
|
||||||
imagRenderer.setColor(Color.LTGRAY);
|
|
||||||
return imagRenderer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object onRetainNonConfigurationInstance() {
|
protected void onResume() {
|
||||||
return new PlotBoundaries(chart.getRenderer());
|
super.onResume();
|
||||||
}
|
|
||||||
|
|
||||||
private static final class PlotBoundaries implements Serializable {
|
activityHelper.onResume(this);
|
||||||
|
|
||||||
private final double xMin;
|
|
||||||
private final double xMax;
|
|
||||||
private final double yMin;
|
|
||||||
private final double yMax;
|
|
||||||
|
|
||||||
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
|
|
||||||
this.xMin = renderer.getXAxisMin();
|
|
||||||
this.yMin = renderer.getYAxisMin();
|
|
||||||
this.xMax = renderer.getXAxisMax();
|
|
||||||
this.yMax = renderer.getYAxisMax();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
protected void onDestroy() {
|
||||||
return "PlotBoundaries{" +
|
super.onDestroy();
|
||||||
"yMax=" + yMax +
|
|
||||||
", yMin=" + yMin +
|
activityHelper.onDestroy(this);
|
||||||
", xMax=" + xMax +
|
|
||||||
", xMin=" + xMin +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static XYSeriesRenderer createCommonRenderer() {
|
|
||||||
final XYSeriesRenderer renderer = new XYSeriesRenderer();
|
|
||||||
renderer.setFillPoints(true);
|
|
||||||
renderer.setPointStyle(PointStyle.POINT);
|
|
||||||
renderer.setLineWidth(3);
|
|
||||||
renderer.setColor(Color.WHITE);
|
|
||||||
renderer.setStroke(BasicStroke.SOLID);
|
|
||||||
return renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void zoomInClickHandler(@NotNull View v) {
|
|
||||||
this.graphicalView.zoomIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void zoomOutClickHandler(@NotNull View v) {
|
|
||||||
this.graphicalView.zoomOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class Input implements Serializable {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String expression;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String variableName;
|
|
||||||
|
|
||||||
public Input(@NotNull String expression, @NotNull String variableName) {
|
|
||||||
this.expression = expression;
|
|
||||||
this.variableName = variableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getExpression() {
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getVariableName() {
|
|
||||||
return variableName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user