new plotter

This commit is contained in:
Sergey Solovyev
2012-12-31 00:37:14 +04:00
parent 3dab118a1b
commit 5f7ee1e64e
35 changed files with 3447 additions and 460 deletions

View File

@@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
public class AndroidCalculatorLogger implements CalculatorLogger {
@NotNull
private static final String TAG = AndroidCalculatorLogger.class.getSimpleName();
private static final String TAG = "Calculatorpp";
@Override
public void debug(@Nullable String tag, @NotNull String message) {
@@ -21,7 +21,7 @@ public class AndroidCalculatorLogger implements CalculatorLogger {
@NotNull
private String getTag(@Nullable String tag) {
return tag != null ? tag : TAG;
return tag != null ? TAG + "/" + tag : TAG;
}
@Override

View File

@@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
import org.solovyev.android.AndroidUtils;
import org.solovyev.android.calculator.about.CalculatorFragmentType;
import org.solovyev.android.calculator.about.CalculatorReleaseNotesFragment;
import org.solovyev.android.calculator.plot.CalculatorPlotActivity;
import org.solovyev.android.fragments.FragmentUtils;
import org.solovyev.android.prefs.Preference;
import org.solovyev.common.equals.EqualsTool;
@@ -63,7 +64,7 @@ public class CalculatorActivity extends SherlockFragmentActivity implements Shar
activityHelper.addTab(this, CalculatorFragmentType.variables, null, R.id.main_second_pane);
activityHelper.addTab(this, CalculatorFragmentType.functions, null, R.id.main_second_pane);
activityHelper.addTab(this, CalculatorFragmentType.operators, null, R.id.main_second_pane);
activityHelper.addTab(this, CalculatorFragmentType.plotter, null, R.id.main_second_pane);
activityHelper.addTab(this, CalculatorPlotActivity.getPlotterFragmentType(), null, R.id.main_second_pane);
activityHelper.addTab(this, CalculatorFragmentType.faq, null, R.id.main_second_pane);
} else {
getSupportActionBar().hide();

View File

@@ -23,6 +23,7 @@ import org.solovyev.android.calculator.help.CalculatorHelpActivity;
import org.solovyev.android.calculator.history.CalculatorHistoryActivity;
import org.solovyev.android.calculator.math.edit.*;
import org.solovyev.android.calculator.matrix.CalculatorMatrixActivity;
import org.solovyev.android.calculator.plot.AbstractCalculatorPlotFragment;
import org.solovyev.android.calculator.plot.CalculatorPlotActivity;
import org.solovyev.android.calculator.plot.CalculatorPlotFragment;
import org.solovyev.android.calculator.plot.PlotInput;
@@ -99,10 +100,14 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener
context.startActivity(intent);
}
public static void plotGraph(@NotNull final Context context, @NotNull Generic generic, @NotNull Constant constant){
public static void plotGraph(@NotNull final Context context,
@NotNull Generic generic,
@NotNull Constant xVariable,
@Nullable Constant yVariable){
final Intent intent = new Intent();
intent.putExtra(ChartFactory.TITLE, context.getString(R.string.c_graph));
intent.putExtra(CalculatorPlotFragment.INPUT, new CalculatorPlotFragment.Input(generic.toString(), constant.getName()));
final AbstractCalculatorPlotFragment.Input input = new CalculatorPlotFragment.Input(generic.toString(), xVariable.getName(), yVariable == null ? null : yVariable.getName());
intent.putExtra(CalculatorPlotFragment.INPUT, input);
intent.setClass(context, CalculatorPlotActivity.class);
AndroidUtils2.addFlags(intent, false, context);
context.startActivity(intent);
@@ -214,7 +219,7 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener
App.getInstance().getUiThreadExecutor().execute(new Runnable() {
@Override
public void run() {
plotGraph(context, plotInput.getFunction(), plotInput.getConstant());
plotGraph(context, plotInput.getFunction(), plotInput.getXVariable(), plotInput.getYVariable());
}
});
break;

View File

@@ -3,6 +3,7 @@ package org.solovyev.android.calculator;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import net.robotmedia.billing.BillingController;
@@ -65,8 +66,12 @@ public class CalculatorApplication extends android.app.Application implements Sh
**********************************************************************
*/
@NotNull
private final List<CalculatorEventListener> listeners = new ArrayList<CalculatorEventListener>();
@NotNull
protected final Handler uiHandler = new Handler();
/*
**********************************************************************
*
@@ -186,6 +191,11 @@ public class CalculatorApplication extends android.app.Application implements Sh
return new CalculatorFragmentHelperImpl(layoutId, titleResId, listenersOnCreate);
}
@NotNull
public Handler getUiHandler() {
return uiHandler;
}
/*
**********************************************************************
*

View File

@@ -13,6 +13,7 @@ import org.solovyev.android.calculator.math.edit.CalculatorFunctionsFragment;
import org.solovyev.android.calculator.math.edit.CalculatorOperatorsFragment;
import org.solovyev.android.calculator.math.edit.CalculatorVarsFragment;
import org.solovyev.android.calculator.matrix.CalculatorMatrixEditFragment;
import org.solovyev.android.calculator.plot.CalculatorArityPlotFragment;
import org.solovyev.android.calculator.plot.CalculatorPlotFragment;
/**
@@ -31,6 +32,7 @@ public enum CalculatorFragmentType {
functions(CalculatorFunctionsFragment.class, R.layout.math_entities_fragment, R.string.c_functions),
operators(CalculatorOperatorsFragment.class, R.layout.math_entities_fragment, R.string.c_operators),
plotter(CalculatorPlotFragment.class, R.layout.plot_fragment, R.string.c_graph),
plotter_2(CalculatorArityPlotFragment.class, R.layout.plot_fragment, R.string.c_graph),
about(CalculatorAboutFragment.class, R.layout.about_fragment, R.string.c_about),
faq(CalculatorHelpFaqFragment.class, R.layout.help_faq_fragment, R.string.c_faq),
hints(CalculatorHelpHintsFragment.class, R.layout.help_hints_fragment, R.string.c_hints),

View File

@@ -0,0 +1,525 @@
package org.solovyev.android.calculator.plot;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import jscl.math.Expression;
import jscl.math.Generic;
import jscl.math.function.Constant;
import jscl.text.ParseException;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.*;
import org.solovyev.android.menu.ActivityMenu;
import org.solovyev.android.menu.IdentifiableMenuItem;
import org.solovyev.android.menu.ListActivityMenu;
import org.solovyev.android.sherlock.menu.SherlockMenuHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* User: serso
* Date: 12/30/12
* Time: 3:09 PM
*/
public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment implements CalculatorEventListener {
/*
**********************************************************************
*
* CONSTANTS
*
**********************************************************************
*/
protected static final String TAG = "CalculatorPlotFragment";
public static final String INPUT = "plotter_input";
protected static final String PLOT_BOUNDARIES = "plot_boundaries";
private static final int DEFAULT_MIN_NUMBER = -10;
private static final int DEFAULT_MAX_NUMBER = 10;
/*
**********************************************************************
*
* FIELDS
*
**********************************************************************
*/
@Nullable
private Input input;
private int bgColor;
// thread for applying UI changes
@NotNull
private final Handler uiHandler = new Handler();
@NotNull
private PreparedInput preparedInput;
@NotNull
private ActivityMenu<Menu, MenuItem> fragmentMenu = ListActivityMenu.fromResource(R.menu.plot_menu, PlotMenu.class, SherlockMenuHelper.getInstance());
// thread which calculated data for graph view
@NotNull
private final Executor plotExecutor = Executors.newSingleThreadExecutor();
@NotNull
private final CalculatorEventHolder lastEventHolder = new CalculatorEventHolder(CalculatorUtils.createFirstEventDataId());
public AbstractCalculatorPlotFragment() {
super(CalculatorApplication.getInstance().createFragmentHelper(R.layout.plot_fragment, R.string.c_graph, false));
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle arguments = getArguments();
if (arguments != null) {
input = (CalculatorPlotFragment.Input) arguments.getSerializable(INPUT);
}
if (input == null) {
this.bgColor = getResources().getColor(R.color.cpp_pane_background);
} else {
this.bgColor = getResources().getColor(android.R.color.transparent);
}
setHasOptionsMenu(true);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (input == null) {
this.preparedInput = prepareInputFromDisplay(Locator.getInstance().getDisplay().getViewState(), savedInstanceState);
} else {
this.preparedInput = prepareInput(input, true, savedInstanceState);
}
}
@Override
public void onSaveInstanceState(Bundle out) {
super.onSaveInstanceState(out);
final PlotBoundaries plotBoundaries = getPlotBoundaries();
if (plotBoundaries != null) {
out.putSerializable(PLOT_BOUNDARIES, plotBoundaries);
}
}
@Nullable
protected abstract PlotBoundaries getPlotBoundaries();
@Override
public void onResume() {
super.onResume();
createChart(preparedInput);
createGraphicalView(getView(), preparedInput);
}
@Override
public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable final Object data) {
if (calculatorEventType.isOfType(CalculatorEventType.display_state_changed)) {
PreparedInput preparedInput = getPreparedInput();
if (!preparedInput.isFromInputArgs()) {
final CalculatorEventHolder.Result result = this.lastEventHolder.apply(calculatorEventData);
if (result.isNewAfter()) {
preparedInput = prepareInputFromDisplay(((CalculatorDisplayChangeEventData) data).getNewValue(), null);
this.preparedInput = preparedInput;
final PreparedInput finalPreparedInput = preparedInput;
getUiHandler().post(new Runnable() {
@Override
public void run() {
if (!finalPreparedInput.isError()) {
createChart(finalPreparedInput);
final View view = getView();
if (view != null) {
createGraphicalView(view, finalPreparedInput);
}
} else {
onError();
}
}
});
}
}
}
}
protected abstract void onError();
protected abstract void createGraphicalView(@NotNull View view, @NotNull PreparedInput preparedInput);
protected abstract void createChart(@NotNull PreparedInput preparedInput);
protected double getMaxValue(@Nullable PlotBoundaries plotBoundaries) {
return plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.getXMax();
}
protected double getMinValue(@Nullable PlotBoundaries plotBoundaries) {
return plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.getXMin();
}
/*
**********************************************************************
*
* GETTERS
*
**********************************************************************
*/
@NotNull
public Handler getUiHandler() {
return uiHandler;
}
@NotNull
public PreparedInput getPreparedInput() {
return preparedInput;
}
public int getBgColor() {
return bgColor;
}
@NotNull
public Executor getPlotExecutor() {
return plotExecutor;
}
/*
**********************************************************************
*
* MENU
*
**********************************************************************
*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
final FragmentActivity activity = this.getActivity();
if (activity != null) {
fragmentMenu.onCreateOptionsMenu(activity, menu);
}
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
final FragmentActivity activity = this.getActivity();
if (activity != null) {
fragmentMenu.onPrepareOptionsMenu(activity, menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item) || fragmentMenu.onOptionsItemSelected(this.getActivity(), item);
}
/*
**********************************************************************
*
* STATIC
*
**********************************************************************
*/
@NotNull
protected static PreparedInput prepareInputFromDisplay(@NotNull CalculatorDisplayViewState displayState, @Nullable Bundle savedInstanceState) {
try {
if (displayState.isValid() && displayState.getResult() != null) {
final Generic expression = displayState.getResult();
if (CalculatorUtils.isPlotPossible(expression, displayState.getOperation())) {
final List<Constant> variables = new ArrayList<Constant>(CalculatorUtils.getNotSystemConstants(expression));
final Constant xVariable = variables.get(0);
final Constant yVariable;
if ( variables.size() > 1 ) {
yVariable = variables.get(1);
} else {
yVariable = null;
}
final Input input = new Input(expression.toString(), xVariable.getName(), yVariable == null ? null : yVariable.getName());
return prepareInput(input, false, savedInstanceState);
}
}
} catch (RuntimeException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return PreparedInput.newErrorInstance(false);
}
@NotNull
private static PreparedInput prepareInput(@NotNull Input input, boolean fromInputArgs, @Nullable Bundle savedInstanceState) {
PreparedInput result;
try {
final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression());
final Generic expression = Expression.valueOf(preparedExpression.getExpression());
final Constant xVar = new Constant(input.getXVariableName());
final Constant yVar;
if (input.getYVariableName() != null) {
yVar = new Constant(input.getYVariableName());
} else {
yVar = null;
}
PlotBoundaries plotBoundaries = null;
if (savedInstanceState != null) {
plotBoundaries = (PlotBoundaries) savedInstanceState.getSerializable(PLOT_BOUNDARIES);
}
if ( plotBoundaries == null ) {
plotBoundaries = PlotBoundaries.newDefaultInstance();
}
result = PreparedInput.newInstance(input, expression, xVar, yVar, fromInputArgs, plotBoundaries);
} catch (ParseException e) {
result = PreparedInput.newErrorInstance(fromInputArgs);
Locator.getInstance().getNotifier().showMessage(e);
} catch (CalculatorParseException e) {
result = PreparedInput.newErrorInstance(fromInputArgs);
Locator.getInstance().getNotifier().showMessage(e);
}
return result;
}
private static enum PlotMenu implements IdentifiableMenuItem<MenuItem> {
preferences(R.id.menu_plot_settings) {
@Override
public void onClick(@NotNull MenuItem data, @NotNull Context context) {
context.startActivity(new Intent(context, CalculatorPlotPreferenceActivity.class));
}
};
private final int itemId;
private PlotMenu(int itemId) {
this.itemId = itemId;
}
@NotNull
@Override
public Integer getItemId() {
return itemId;
}
}
public static final class PlotBoundaries implements Serializable {
private double xMin;
private double xMax;
private double yMin;
private double yMax;
public PlotBoundaries() {
}
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
this.xMin = renderer.getXAxisMin();
this.yMin = renderer.getYAxisMin();
this.xMax = renderer.getXAxisMax();
this.yMax = renderer.getYAxisMax();
}
public double getXMin() {
return xMin;
}
public double getXMax() {
return xMax;
}
public double getYMin() {
return yMin;
}
public double getYMax() {
return yMax;
}
@Override
public String toString() {
return "PlotBoundaries{" +
"yMax=" + yMax +
", yMin=" + yMin +
", xMax=" + xMax +
", xMin=" + xMin +
'}';
}
@NotNull
public static PlotBoundaries newDefaultInstance() {
PlotBoundaries plotBoundaries = new PlotBoundaries();
plotBoundaries.xMin = DEFAULT_MIN_NUMBER;
plotBoundaries.yMin = DEFAULT_MIN_NUMBER;
plotBoundaries.xMax = DEFAULT_MAX_NUMBER;
plotBoundaries.yMax = DEFAULT_MAX_NUMBER;
return plotBoundaries;
}
}
public static class PreparedInput {
@Nullable
private Input input;
@Nullable
private Generic expression;
@Nullable
private Constant xVariable;
@Nullable
private Constant yVariable;
private boolean fromInputArgs;
@NotNull
private PlotBoundaries plotBoundaries = PlotBoundaries.newDefaultInstance();
private PreparedInput() {
}
@NotNull
public static PreparedInput newInstance(@NotNull Input input,
@NotNull Generic expression,
@NotNull Constant xVariable,
@Nullable Constant yVariable,
boolean fromInputArgs,
@NotNull PlotBoundaries plotBoundaries) {
final PreparedInput result = new PreparedInput();
result.input = input;
result.expression = expression;
result.xVariable = xVariable;
result.yVariable = yVariable;
result.fromInputArgs = fromInputArgs;
result.plotBoundaries = plotBoundaries;
return result;
}
@NotNull
public static PreparedInput newErrorInstance(boolean fromInputArgs) {
final PreparedInput result = new PreparedInput();
result.input = null;
result.expression = null;
result.xVariable = null;
result.yVariable = null;
result.fromInputArgs = fromInputArgs;
return result;
}
public boolean isFromInputArgs() {
return fromInputArgs;
}
@Nullable
public Input getInput() {
return input;
}
@Nullable
public Generic getExpression() {
return expression;
}
@NotNull
public PlotBoundaries getPlotBoundaries() {
return plotBoundaries;
}
@Nullable
public Constant getXVariable() {
return xVariable;
}
@Nullable
public Constant getYVariable() {
return yVariable;
}
public boolean isError() {
return input == null || expression == null || xVariable == null;
}
}
public static class Input implements Serializable {
@NotNull
private String expression;
@NotNull
private String xVariableName;
@Nullable
private String yVariableName;
public Input(@NotNull String expression,
@NotNull String xVariableName,
@Nullable String yVariableName) {
this.expression = expression;
this.xVariableName = xVariableName;
this.yVariableName = yVariableName;
}
@NotNull
public String getExpression() {
return expression;
}
@NotNull
public String getXVariableName() {
return xVariableName;
}
@Nullable
public String getYVariableName() {
return yVariableName;
}
}
}

View File

@@ -0,0 +1,131 @@
package org.solovyev.android.calculator.plot;
import android.view.View;
import android.view.ViewGroup;
import arity.calculator.Graph2dView;
import arity.calculator.Graph3dView;
import arity.calculator.GraphView;
import jscl.math.Generic;
import jscl.math.function.Constant;
import org.javia.arity.Complex;
import org.javia.arity.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.R;
import java.util.ArrayList;
import java.util.List;
/**
* User: serso
* Date: 12/30/12
* Time: 4:43 PM
*/
public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment {
@Nullable
private GraphView graphView;
@Nullable
@Override
protected PlotBoundaries getPlotBoundaries() {
if ( graphView != null ) {
// todo serso: return plot boundaries
return null;
} else {
return null;
}
}
@Override
protected void createGraphicalView(@NotNull View root, @NotNull PreparedInput preparedInput) {
// remove old
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout);
if (graphView instanceof View) {
graphContainer.removeView((View) graphView);
}
if (!preparedInput.isError()) {
final Generic expression = preparedInput.getExpression();
final Constant xVariable = preparedInput.getXVariable();
final Constant yVariable = preparedInput.getYVariable();
final int arity = yVariable == null ? 1 : 2;
final List<Function> functions = new ArrayList<Function>();
functions.add(new Function() {
@Override
public int arity() {
return arity;
}
@Override
public double eval(double x) {
return PlotUtils.calculatorExpression(expression, xVariable, x).realPart();
}
@Override
public double eval(double x, double y) {
return PlotUtils.calculatorExpression(expression, xVariable, x, yVariable, y).realPart();
}
@Override
public Complex eval(Complex x) {
jscl.math.numeric.Complex result = PlotUtils.calculatorExpression(expression, xVariable, x.re);
return new Complex(result.realPart(), result.imaginaryPart());
}
@Override
public Complex eval(Complex x, Complex y) {
jscl.math.numeric.Complex result = PlotUtils.calculatorExpression(expression, xVariable, x.re, yVariable, y.re);
return new Complex(result.realPart(), result.imaginaryPart());
}
});
if (functions.size() == 1) {
final Function f = functions.get(0);
graphView = f.arity() == 1 ? new Graph2dView(getActivity()) : new Graph3dView(getActivity());
graphView.setFunction(f);
} else {
graphView = new Graph2dView(this.getActivity());
((Graph2dView) graphView).setFunctions(functions);
}
graphContainer.addView((View) graphView);
} else {
onError();
}
}
@Override
protected void createChart(@NotNull PreparedInput preparedInput) {
}
@Override
public void onResume() {
super.onResume();
if (this.graphView != null) {
this.graphView.onResume();
}
}
@Override
protected void onError() {
final View root = getView();
if (root != null && graphView instanceof View) {
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout);
graphContainer.removeView((View) graphView);
}
this.graphView = null;
}
@Override
public void onPause() {
super.onPause();
if (this.graphView != null) {
this.graphView.onPause();
}
}
}

View File

@@ -3,6 +3,7 @@ package org.solovyev.android.calculator.plot;
import android.app.ActionBar;
import android.content.Intent;
import android.os.Bundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.CalculatorFragmentActivity;
import org.solovyev.android.calculator.R;
@@ -29,6 +30,11 @@ public class CalculatorPlotActivity extends CalculatorFragmentActivity {
}
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
getActivityHelper().setFragment(this, CalculatorFragmentType.plotter, arguments, R.id.main_layout);
getActivityHelper().setFragment(this, getPlotterFragmentType(), arguments, R.id.main_layout);
}
@NotNull
public static CalculatorFragmentType getPlotterFragmentType() {
return CalculatorFragmentType.plotter_2;
}
}

View File

@@ -6,23 +6,12 @@
package org.solovyev.android.calculator.plot;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import jscl.math.Expression;
import jscl.math.Generic;
import jscl.math.function.Constant;
import jscl.text.ParseException;
import org.achartengine.chart.XYChart;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
@@ -32,47 +21,16 @@ import org.achartengine.tools.ZoomEvent;
import org.achartengine.tools.ZoomListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.CalculatorApplication;
import org.solovyev.android.calculator.CalculatorDisplayChangeEventData;
import org.solovyev.android.calculator.CalculatorDisplayViewState;
import org.solovyev.android.calculator.CalculatorEventData;
import org.solovyev.android.calculator.CalculatorEventHolder;
import org.solovyev.android.calculator.CalculatorEventListener;
import org.solovyev.android.calculator.CalculatorEventType;
import org.solovyev.android.calculator.CalculatorFragment;
import org.solovyev.android.calculator.CalculatorParseException;
import org.solovyev.android.calculator.CalculatorPreferences;
import org.solovyev.android.calculator.CalculatorUtils;
import org.solovyev.android.calculator.Locator;
import org.solovyev.android.calculator.PreparedExpression;
import org.solovyev.android.calculator.R;
import org.solovyev.android.calculator.ToJsclTextProcessor;
import org.solovyev.android.menu.ActivityMenu;
import org.solovyev.android.menu.IdentifiableMenuItem;
import org.solovyev.android.menu.ListActivityMenu;
import org.solovyev.android.sherlock.menu.SherlockMenuHelper;
import org.solovyev.common.MutableObject;
import org.solovyev.common.collections.CollectionsUtils;
import java.io.Serializable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* User: serso
* Date: 12/1/11
* Time: 12:40 AM
*/
public class CalculatorPlotFragment extends CalculatorFragment implements CalculatorEventListener {
private static final String TAG = CalculatorPlotFragment.class.getSimpleName();
private static final int DEFAULT_MIN_NUMBER = -10;
private static final int DEFAULT_MAX_NUMBER = 10;
public static final String INPUT = "plotter_input";
private static final String PLOT_BOUNDARIES = "plot_boundaries";
public class CalculatorPlotFragment extends AbstractCalculatorPlotFragment {
public static final long EVAL_DELAY_MILLIS = 200;
@@ -85,153 +43,42 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
@Nullable
private MyGraphicalView graphicalView;
// thread which calculated data for graph view
@NotNull
private final Executor plotExecutor = Executors.newSingleThreadExecutor();
protected void createChart(@NotNull PreparedInput preparedInput) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
final Boolean interpolate = CalculatorPreferences.Graph.interpolate.getPreference(preferences);
final GraphLineColor realLineColor = CalculatorPreferences.Graph.lineColorReal.getPreference(preferences);
final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences);
// thread for applying UI changes
@NotNull
private final Handler uiHandler = new Handler();
@NotNull
private PreparedInput preparedInput;
//noinspection ConstantConditions
try {
this.chart = PlotUtils.prepareChart(getMinValue(null), getMaxValue(null), preparedInput.getExpression(), preparedInput.getXVariable(), getBgColor(), interpolate, realLineColor.getColor(), imagLineColor.getColor());
} catch (ArithmeticException e) {
PlotUtils.handleArithmeticException(e, CalculatorPlotFragment.this);
}
}
@Nullable
private Input input;
@NotNull
private final CalculatorEventHolder lastEventHolder = new CalculatorEventHolder(CalculatorUtils.createFirstEventDataId());
private int bgColor;
@NotNull
private ActivityMenu<Menu, MenuItem> fragmentMenu = ListActivityMenu.fromResource(R.menu.plot_menu, PlotMenu.class, SherlockMenuHelper.getInstance());
public CalculatorPlotFragment() {
super(CalculatorApplication.getInstance().createFragmentHelper(R.layout.plot_fragment, R.string.c_graph, false));
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle arguments = getArguments();
if (arguments != null) {
input = (Input) arguments.getSerializable(INPUT);
}
if (input == null) {
this.bgColor = getResources().getColor(R.color.cpp_pane_background);
} else {
this.bgColor = getResources().getColor(android.R.color.transparent);
}
setHasOptionsMenu(true);
}
@NotNull
private static PreparedInput prepareInputFromDisplay(@NotNull CalculatorDisplayViewState displayState, @Nullable Bundle savedInstanceState) {
try {
if (displayState.isValid() && displayState.getResult() != null) {
final Generic expression = displayState.getResult();
if (CalculatorUtils.isPlotPossible(expression, displayState.getOperation())) {
final Constant constant = CollectionsUtils.getFirstCollectionElement(CalculatorUtils.getNotSystemConstants(expression));
final Input input = new Input(expression.toString(), constant.getName());
return prepareInput(input, false, savedInstanceState);
}
}
} catch (RuntimeException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return PreparedInput.newErrorInstance(false);
}
@NotNull
private static PreparedInput prepareInput(@NotNull Input input, boolean fromInputArgs, @Nullable Bundle savedInstanceState) {
PreparedInput result;
try {
final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression());
final Generic expression = Expression.valueOf(preparedExpression.getExpression());
final Constant variable = new Constant(input.getVariableName());
PlotBoundaries plotBoundaries = null;
if ( savedInstanceState != null ) {
plotBoundaries = (PlotBoundaries)savedInstanceState.getSerializable(PLOT_BOUNDARIES);
}
result = PreparedInput.newInstance(input, expression, variable, fromInputArgs, plotBoundaries);
} catch (ParseException e) {
result = PreparedInput.newErrorInstance(fromInputArgs);
Locator.getInstance().getNotifier().showMessage(e);
} catch (CalculatorParseException e) {
result = PreparedInput.newErrorInstance(fromInputArgs);
Locator.getInstance().getNotifier().showMessage(e);
}
return result;
}
private void createChart() {
if (!preparedInput.isError()) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
final Boolean interpolate = CalculatorPreferences.Graph.interpolate.getPreference(preferences);
final GraphLineColor realLineColor = CalculatorPreferences.Graph.lineColorReal.getPreference(preferences);
final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences);
//noinspection ConstantConditions
try {
this.chart = PlotUtils.prepareChart(getMinValue(null), getMaxValue(null), preparedInput.getExpression(), preparedInput.getVariable(), bgColor, interpolate, realLineColor.getColor(), imagLineColor.getColor());
} catch (ArithmeticException e) {
PlotUtils.handleArithmeticException(e, CalculatorPlotFragment.this);
}
} else {
onError();
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (input == null) {
this.preparedInput = prepareInputFromDisplay(Locator.getInstance().getDisplay().getViewState(), savedInstanceState);
} else {
this.preparedInput = prepareInput(input, true, savedInstanceState);
}
}
@Override
public void onSaveInstanceState(Bundle out) {
super.onSaveInstanceState(out);
protected PlotBoundaries getPlotBoundaries() {
if (chart != null) {
out.putSerializable(PLOT_BOUNDARIES, new PlotBoundaries(chart.getRenderer()));
return new PlotBoundaries(chart.getRenderer());
} else {
return null;
}
}
@Override
public void onResume() {
super.onResume();
createChart();
createGraphicalView(getView(), this.preparedInput.getPlotBoundaries());
}
private void createGraphicalView(@NotNull View root, @Nullable PlotBoundaries plotBoundaries) {
protected void createGraphicalView(@NotNull View root, @Nullable PreparedInput preparedInput) {
final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout);
if (graphicalView != null) {
graphContainer.removeView(graphicalView);
}
if (!preparedInput.isError()) {
if (!getPreparedInput().isError()) {
final XYChart chart = this.chart;
assert chart != null;
final PlotBoundaries plotBoundaries = preparedInput.getPlotBoundaries();
double minValue = getMinValue(plotBoundaries);
double maxValue = getMaxValue(plotBoundaries);
@@ -249,20 +96,20 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
maxY = Math.max(maxY, series.getMaxY());
}
if (plotBoundaries == null) {
if (preparedInput == 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 {
chart.getRenderer().setXAxisMin(plotBoundaries.xMin);
chart.getRenderer().setYAxisMin(plotBoundaries.yMin);
chart.getRenderer().setXAxisMax(plotBoundaries.xMax);
chart.getRenderer().setYAxisMax(plotBoundaries.yMax);
chart.getRenderer().setXAxisMin(plotBoundaries.getXMin());
chart.getRenderer().setYAxisMin(plotBoundaries.getYMin());
chart.getRenderer().setXAxisMax(plotBoundaries.getXMax());
chart.getRenderer().setYAxisMax(plotBoundaries.getYMax());
}
graphicalView = new MyGraphicalView(this.getActivity(), chart);
graphicalView.setBackgroundColor(this.bgColor);
graphicalView.setBackgroundColor(this.getBgColor());
graphicalView.addZoomListener(new ZoomListener() {
@Override
@@ -292,14 +139,6 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
}
private double getMaxValue(@Nullable PlotBoundaries plotBoundaries) {
return plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax;
}
private double getMinValue(@Nullable PlotBoundaries plotBoundaries) {
return plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin;
}
private void updateDataSets(@NotNull final XYChart chart) {
updateDataSets(chart, EVAL_DELAY_MILLIS);
@@ -309,10 +148,13 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences);
final Generic expression = preparedInput.getExpression();
final Constant variable = preparedInput.getVariable();
final PreparedInput preparedInput = getPreparedInput();
if (expression != null && variable != null) {
final Generic expression = preparedInput.getExpression();
final Constant variable = preparedInput.getXVariable();
final MyGraphicalView graphicalView = this.graphicalView;
if (expression != null && variable != null && graphicalView != null) {
pendingOperation.setObject(new Runnable() {
@Override
public void run() {
@@ -321,43 +163,43 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
//lock all operations with history
if (pendingOperation.getObject() == this) {
plotExecutor.execute(new Runnable() {
getPlotExecutor().execute(new Runnable() {
@Override
public void run() {
final XYMultipleSeriesRenderer dr = chart.getRenderer();
final XYMultipleSeriesRenderer dr = chart.getRenderer();
final XYMultipleSeriesDataset dataset = chart.getDataset();
if (dataset != null && dr != null) {
final MyXYSeries realSeries = (MyXYSeries) dataset.getSeriesAt(0);
final XYMultipleSeriesDataset dataset = chart.getDataset();
if (dataset != null && dr != null) {
final MyXYSeries realSeries = (MyXYSeries) dataset.getSeriesAt(0);
if (realSeries != null) {
final MyXYSeries imagSeries;
if (dataset.getSeriesCount() > 1) {
imagSeries = (MyXYSeries) dataset.getSeriesAt(1);
} else {
imagSeries = new MyXYSeries(PlotUtils.getImagFunctionName(variable), PlotUtils.DEFAULT_NUMBER_OF_STEPS * 2);
}
if (realSeries != null) {
final MyXYSeries imagSeries;
if (dataset.getSeriesCount() > 1) {
imagSeries = (MyXYSeries) dataset.getSeriesAt(1);
} else {
imagSeries = new MyXYSeries(PlotUtils.getImagFunctionName(variable), PlotUtils.DEFAULT_NUMBER_OF_STEPS * 2);
}
try {
if (PlotUtils.addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true, PlotUtils.DEFAULT_NUMBER_OF_STEPS)) {
if (dataset.getSeriesCount() <= 1) {
dataset.addSeries(imagSeries);
dr.addSeriesRenderer(PlotUtils.createImagRenderer(imagLineColor.getColor()));
}
}
} catch (ArithmeticException e) {
PlotUtils.handleArithmeticException(e, CalculatorPlotFragment.this);
}
try {
if (PlotUtils.addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true, PlotUtils.DEFAULT_NUMBER_OF_STEPS)) {
if (dataset.getSeriesCount() <= 1) {
dataset.addSeries(imagSeries);
dr.addSeriesRenderer(PlotUtils.createImagRenderer(imagLineColor.getColor()));
}
}
} catch (ArithmeticException e) {
PlotUtils.handleArithmeticException(e, CalculatorPlotFragment.this);
}
uiHandler.post(new Runnable() {
@Override
public void run() {
graphicalView.repaint();
}
});
}
}
}
getUiHandler().post(new Runnable() {
@Override
public void run() {
graphicalView.repaint();
}
});
}
}
}
});
}
}
@@ -366,37 +208,12 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
}
uiHandler.postDelayed(pendingOperation.getObject(), millisToWait);
getUiHandler().postDelayed(pendingOperation.getObject(), millisToWait);
}
@NotNull
private final MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
@Override
public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable final Object data) {
if (calculatorEventType.isOfType(CalculatorEventType.display_state_changed)) {
if (!preparedInput.isFromInputArgs()) {
final CalculatorEventHolder.Result result = this.lastEventHolder.apply(calculatorEventData);
if (result.isNewAfter()) {
this.preparedInput = prepareInputFromDisplay(((CalculatorDisplayChangeEventData) data).getNewValue(), null);
createChart();
uiHandler.post(new Runnable() {
@Override
public void run() {
final View view = getView();
if (view != null) {
createGraphicalView(view, preparedInput.getPlotBoundaries());
}
}
});
}
}
}
}
/* public void zoomInClickHandler(@NotNull View v) {
this.graphicalView.zoomIn();
@@ -406,196 +223,8 @@ public class CalculatorPlotFragment extends CalculatorFragment implements Calcul
this.graphicalView.zoomOut();
}*/
/*
**********************************************************************
*
* MENU
*
**********************************************************************
*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
final FragmentActivity activity = this.getActivity();
if (activity != null) {
fragmentMenu.onCreateOptionsMenu(activity, menu);
}
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
final FragmentActivity activity = this.getActivity();
if (activity != null) {
fragmentMenu.onPrepareOptionsMenu(activity, menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item) || fragmentMenu.onOptionsItemSelected(this.getActivity(), item);
}
public void onError() {
this.chart = null;
}
/*
**********************************************************************
*
* STATIC
*
**********************************************************************
*/
private static enum PlotMenu implements IdentifiableMenuItem<MenuItem> {
preferences(R.id.menu_plot_settings) {
@Override
public void onClick(@NotNull MenuItem data, @NotNull Context context) {
context.startActivity(new Intent(context, CalculatorPlotPreferenceActivity.class));
}
};
private final int itemId;
private PlotMenu(int itemId) {
this.itemId = itemId;
}
@NotNull
@Override
public Integer getItemId() {
return itemId;
}
}
public static final class PlotBoundaries implements Serializable {
private double xMin;
private double xMax;
private double yMin;
private double yMax;
public PlotBoundaries() {
}
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
this.xMin = renderer.getXAxisMin();
this.yMin = renderer.getYAxisMin();
this.xMax = renderer.getXAxisMax();
this.yMax = renderer.getYAxisMax();
}
@Override
public String toString() {
return "PlotBoundaries{" +
"yMax=" + yMax +
", yMin=" + yMin +
", xMax=" + xMax +
", xMin=" + xMin +
'}';
}
}
public static class PreparedInput {
@Nullable
private Input input;
@Nullable
private Generic expression;
@Nullable
private Constant variable;
private boolean fromInputArgs;
@Nullable
private PlotBoundaries plotBoundaries = null;
private PreparedInput() {
}
@NotNull
public static PreparedInput newInstance(@NotNull Input input, @NotNull Generic expression, @NotNull Constant variable, boolean fromInputArgs, @Nullable PlotBoundaries plotBoundaries) {
final PreparedInput result = new PreparedInput();
result.input = input;
result.expression = expression;
result.variable = variable;
result.fromInputArgs = fromInputArgs;
result.plotBoundaries = plotBoundaries;
return result;
}
@NotNull
public static PreparedInput newErrorInstance(boolean fromInputArgs) {
final PreparedInput result = new PreparedInput();
result.input = null;
result.expression = null;
result.variable = null;
result.fromInputArgs = fromInputArgs;
return result;
}
public boolean isFromInputArgs() {
return fromInputArgs;
}
@Nullable
public Input getInput() {
return input;
}
@Nullable
public Generic getExpression() {
return expression;
}
@Nullable
public PlotBoundaries getPlotBoundaries() {
return plotBoundaries;
}
@Nullable
public Constant getVariable() {
return variable;
}
public boolean isError() {
return input == null || expression == null || variable == null;
}
}
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;
}
}
}

View File

@@ -202,7 +202,7 @@ public final class PlotUtils {
return renderer;
}
static void handleArithmeticException(@NotNull ArithmeticException e, @NotNull CalculatorPlotFragment calculatorPlotFragment) {
static void handleArithmeticException(@NotNull ArithmeticException e, @NotNull AbstractCalculatorPlotFragment calculatorPlotFragment) {
String message = e.getLocalizedMessage();
if (StringUtils.isEmpty(message)) {
message = e.getMessage();
@@ -371,9 +371,20 @@ public final class PlotUtils {
}
@NotNull
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant variable, double x) {
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant xVar, double x) {
try {
return unwrap(expression.substitute(variable, Expression.valueOf(x)).numeric());
return unwrap(expression.substitute(xVar, Expression.valueOf(x)).numeric());
} catch (RuntimeException e) {
return NaN;
}
}
@NotNull
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant xVar, double x, @NotNull Constant yVar, double y) {
try {
Generic tmp = expression.substitute(xVar, Expression.valueOf(x));
tmp = tmp.substitute(yVar, Expression.valueOf(y));
return unwrap(tmp.numeric());
} catch (RuntimeException e) {
return NaN;
}