diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java b/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java index aa40fcae..2668e998 100644 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java +++ b/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorDisplayMenuItem.java @@ -2,17 +2,12 @@ package org.solovyev.android.calculator; import android.content.Context; import jscl.math.Generic; -import jscl.math.function.Constant; import org.jetbrains.annotations.NotNull; import org.solovyev.android.calculator.core.R; import org.solovyev.android.calculator.jscl.JsclOperation; -import org.solovyev.android.calculator.plot.PlotInput; import org.solovyev.android.calculator.view.NumeralBaseConverterDialog; import org.solovyev.android.menu.LabeledMenuItem; -import java.util.ArrayList; -import java.util.List; - /** * User: Solovyev_S * Date: 21.09.12 @@ -81,31 +76,17 @@ public enum CalculatorDisplayMenuItem implements LabeledMenuItem variables = new ArrayList(CalculatorUtils.getNotSystemConstants(generic)); - - final Constant xVariable; - if ( variables.size() > 0 ) { - xVariable = variables.get(0); - } else { - xVariable = null; - } - - final Constant yVariable; - if ( variables.size() > 1 ) { - yVariable = variables.get(1); - } else { - yVariable = null; - } - - Locator.getInstance().getCalculator().fireCalculatorEvent(CalculatorEventType.plot_graph, PlotInput.newInstance(generic, xVariable, yVariable), context); + Locator.getInstance().getPlotter().removeAllUnpinned(); + Locator.getInstance().getPlotter().addFunction(expression); + Locator.getInstance().getPlotter().plot(); } @Override protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) { - return CalculatorUtils.isPlotPossible(generic, operation); + return Locator.getInstance().getPlotter().isPlotPossible(generic); } }; diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java b/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java index 1f643e84..0aee0bcf 100644 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java +++ b/android-app-core/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java @@ -8,7 +8,6 @@ import org.solovyev.android.AndroidUtils; import org.solovyev.android.calculator.math.MathType; import org.solovyev.android.calculator.model.AndroidCalculatorEngine; import org.solovyev.android.calculator.plot.GraphLineColor; -import org.solovyev.android.calculator.plot.ParcelablePlotInput; import org.solovyev.android.prefs.BooleanPreference; import org.solovyev.android.prefs.IntegerPreference; import org.solovyev.android.prefs.LongPreference; @@ -16,10 +15,8 @@ import org.solovyev.android.prefs.Preference; import org.solovyev.android.prefs.R; import org.solovyev.android.prefs.StringPreference; import org.solovyev.android.view.VibratorContainer; -import org.solovyev.common.ListMapper; import java.text.DecimalFormatSymbols; -import java.util.List; import java.util.Locale; /** diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/AndroidCalculatorPlotter.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/AndroidCalculatorPlotter.java new file mode 100644 index 00000000..e5f7d53f --- /dev/null +++ b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/AndroidCalculatorPlotter.java @@ -0,0 +1,164 @@ +package org.solovyev.android.calculator.plot; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import jscl.math.Generic; +import org.jetbrains.annotations.NotNull; +import org.solovyev.android.calculator.CalculatorPreferences; + +import java.util.List; + +/** + * User: serso + * Date: 1/12/13 + * Time: 11:03 PM + */ +public class AndroidCalculatorPlotter implements CalculatorPlotter, SharedPreferences.OnSharedPreferenceChangeListener { + + @NotNull + private final CalculatorPlotter plotter; + + public AndroidCalculatorPlotter(@NotNull Context context, + @NotNull CalculatorPlotter plotter) { + this.plotter = plotter; + + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + preferences.registerOnSharedPreferenceChangeListener(this); + + onSharedPreferenceChanged(preferences, CalculatorPreferences.Graph.plotImag.getKey()); + onSharedPreferenceChanged(preferences, CalculatorPreferences.Graph.lineColorReal.getKey()); + onSharedPreferenceChanged(preferences, CalculatorPreferences.Graph.lineColorImag.getKey()); + } + + @Override + @NotNull + public PlotData getPlotData() { + return plotter.getPlotData(); + } + + @Override + public boolean addFunction(@NotNull Generic expression) { + return plotter.addFunction(expression); + } + + @Override + public boolean addFunction(@NotNull PlotFunction plotFunction) { + return plotter.addFunction(plotFunction); + } + + @Override + public boolean addFunction(@NotNull XyFunction xyFunction) { + return plotter.addFunction(xyFunction); + } + + @Override + public boolean addFunction(@NotNull XyFunction xyFunction, @NotNull PlotFunctionLineDef functionLineDef) { + return plotter.addFunction(xyFunction, functionLineDef); + } + + @Override + public boolean updateFunction(@NotNull PlotFunction newFunction) { + return plotter.updateFunction(newFunction); + } + + @Override + public boolean updateFunction(@NotNull XyFunction xyFunction, @NotNull PlotFunctionLineDef functionLineDef) { + return plotter.updateFunction(xyFunction, functionLineDef); + } + + @Override + public boolean removeFunction(@NotNull PlotFunction plotFunction) { + return plotter.removeFunction(plotFunction); + } + + @Override + public boolean removeFunction(@NotNull XyFunction xyFunction) { + return plotter.removeFunction(xyFunction); + } + + @Override + public void pin(@NotNull PlotFunction plotFunction) { + plotter.pin(plotFunction); + } + + @Override + public void unpin(@NotNull PlotFunction plotFunction) { + plotter.unpin(plotFunction); + } + + @Override + public void show(@NotNull PlotFunction plotFunction) { + plotter.show(plotFunction); + } + + @Override + public void hide(@NotNull PlotFunction plotFunction) { + plotter.hide(plotFunction); + } + + @Override + public void clearAllFunctions() { + plotter.clearAllFunctions(); + } + + @Override + @NotNull + public List getFunctions() { + return plotter.getFunctions(); + } + + @Override + @NotNull + public List getVisibleFunctions() { + return plotter.getVisibleFunctions(); + } + + @Override + public void plot() { + plotter.plot(); + } + + @Override + public boolean isPlotPossible(@NotNull Generic expression) { + return plotter.isPlotPossible(expression); + } + + @Override + public void setPlot3d(boolean plot3d) { + plotter.setPlot3d(plot3d); + } + + @Override + public void removeAllUnpinned() { + plotter.removeAllUnpinned(); + } + + @Override + public void setPlotImag(boolean plotImag) { + plotter.setPlotImag(plotImag); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { + if (CalculatorPreferences.Graph.plotImag.getKey().equals(key)) { + setPlotImag(CalculatorPreferences.Graph.plotImag.getPreference(preferences)); + } + + if (CalculatorPreferences.Graph.lineColorReal.getKey().equals(key)) { + setRealLineColor(CalculatorPreferences.Graph.lineColorReal.getPreference(preferences)); + } + + if (CalculatorPreferences.Graph.lineColorImag.getKey().equals(key)) { + setImagLineColor(CalculatorPreferences.Graph.lineColorImag.getPreference(preferences)); + } + } + + public void setImagLineColor(@NotNull GraphLineColor imagLineColor) { + plotter.setImagLineColor(imagLineColor); + } + + public void setRealLineColor(@NotNull GraphLineColor realLineColor) { + plotter.setRealLineColor(realLineColor); + } +} diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/AndroidPlotLineStyle.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/AndroidPlotLineStyle.java new file mode 100644 index 00000000..c6a9b403 --- /dev/null +++ b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/AndroidPlotLineStyle.java @@ -0,0 +1,63 @@ +package org.solovyev.android.calculator.plot; + +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: serso + * Date: 1/5/13 + * Time: 7:37 PM + */ +public enum AndroidPlotLineStyle { + + solid(PlotLineStyle.solid) { + @Override + public void applyToPaint(@NotNull Paint paint) { + paint.setPathEffect(null); + } + }, + + dashed(PlotLineStyle.dashed) { + @Override + public void applyToPaint(@NotNull Paint paint) { + paint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); + } + }, + + dotted(PlotLineStyle.dotted) { + @Override + public void applyToPaint(@NotNull Paint paint) { + paint.setPathEffect(new DashPathEffect(new float[]{5, 1}, 0)); + } + }, + + dash_dotted(PlotLineStyle.dash_dotted) { + @Override + public void applyToPaint(@NotNull Paint paint) { + paint.setPathEffect(new DashPathEffect(new float[]{10, 20, 5, 1}, 0)); + } + }; + + @NotNull + private final PlotLineStyle plotLineStyle; + + AndroidPlotLineStyle(@NotNull PlotLineStyle plotLineStyle) { + this.plotLineStyle = plotLineStyle; + } + + public abstract void applyToPaint(@NotNull Paint paint); + + @Nullable + public static AndroidPlotLineStyle valueOf(@NotNull PlotLineStyle plotLineStyle) { + for (AndroidPlotLineStyle androidPlotLineStyle : values()) { + if ( androidPlotLineStyle.plotLineStyle == plotLineStyle ) { + return androidPlotLineStyle; + } + } + + return null; + } + +} diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineDef.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineDef.java deleted file mode 100644 index 94f010b4..00000000 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineDef.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.solovyev.android.calculator.plot; - -import android.graphics.Color; -import android.graphics.Paint; -import android.os.Parcel; -import android.os.Parcelable; -import org.jetbrains.annotations.NotNull; - -/** - * User: serso - * Date: 1/5/13 - * Time: 7:41 PM - */ -public class FunctionLineDef implements Parcelable { - - /* - ********************************************************************** - * - * CONSTANTS - * - ********************************************************************** - */ - - @NotNull - private static final Float DEFAULT_LINE_WIDTH = -1f; - - /* - ********************************************************************** - * - * STATIC - * - ********************************************************************** - */ - - private static final Creator CREATOR = new Creator() { - @Override - public FunctionLineDef createFromParcel(@NotNull Parcel in) { - return fromParcel(in); - } - - @Override - public FunctionLineDef[] newArray(int size) { - return new FunctionLineDef[size]; - } - }; - - /* - ********************************************************************** - * - * FIELDS - * - ********************************************************************** - */ - - @NotNull - private FunctionLineColorType lineColorType = FunctionLineColorType.solid; - - private int lineColor = Color.WHITE; - - @NotNull - private FunctionLineStyle lineStyle = FunctionLineStyle.solid; - - private float lineWidth = -DEFAULT_LINE_WIDTH; - - private FunctionLineDef() { - } - - @NotNull - public static FunctionLineDef newInstance(int lineColor, @NotNull FunctionLineStyle lineStyle) { - final FunctionLineDef result = new FunctionLineDef(); - result.lineColor = lineColor; - result.lineStyle = lineStyle; - return result; - } - - @NotNull - public static FunctionLineDef newInstance(int lineColor, @NotNull FunctionLineStyle lineStyle, float lineWidth) { - final FunctionLineDef result = new FunctionLineDef(); - result.lineColor = lineColor; - result.lineStyle = lineStyle; - result.lineWidth = lineWidth; - return result; - } - - @NotNull - public static FunctionLineDef newInstance(int lineColor, @NotNull FunctionLineStyle lineStyle, float lineWidth, @NotNull FunctionLineColorType lineColorType) { - final FunctionLineDef result = new FunctionLineDef(); - result.lineColor = lineColor; - result.lineColorType = lineColorType; - result.lineStyle = lineStyle; - result.lineWidth = lineWidth; - return result; - } - - public static FunctionLineDef fromParcel(@NotNull Parcel in) { - final FunctionLineDef result = new FunctionLineDef(); - - result.lineColorType = (FunctionLineColorType) in.readSerializable(); - result.lineColor = in.readInt(); - result.lineStyle = (FunctionLineStyle) in.readSerializable(); - result.lineWidth = in.readFloat(); - - return result; - } - - - @NotNull - public static FunctionLineDef newDefaultInstance() { - return new FunctionLineDef(); - } - - - public int getLineColor() { - return lineColor; - } - - @NotNull - public FunctionLineStyle getLineStyle() { - return lineStyle; - } - - public float getLineWidth() { - return lineWidth; - } - - @NotNull - public FunctionLineColorType getLineColorType() { - return lineColorType; - } - - public void applyToPaint(@NotNull Paint paint) { - paint.setColor(lineColor); - paint.setStyle(Paint.Style.STROKE); - - if ( lineWidth == DEFAULT_LINE_WIDTH ) { - paint.setStrokeWidth(0); - } else { - paint.setStrokeWidth(lineWidth); - } - - lineStyle.applyToPaint(paint); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NotNull Parcel out, int flags) { - out.writeSerializable(lineColorType); - out.writeInt(lineColor); - out.writeSerializable(lineStyle); - out.writeFloat(lineWidth); - } -} diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineStyle.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineStyle.java deleted file mode 100644 index ecd73a77..00000000 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineStyle.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.solovyev.android.calculator.plot; - -import android.graphics.DashPathEffect; -import android.graphics.Paint; -import org.jetbrains.annotations.NotNull; - -/** - * User: serso - * Date: 1/5/13 - * Time: 7:37 PM - */ -public enum FunctionLineStyle { - - solid { - @Override - public void applyToPaint(@NotNull Paint paint) { - paint.setPathEffect(null); - } - }, - - dashed { - @Override - public void applyToPaint(@NotNull Paint paint) { - paint.setPathEffect(new DashPathEffect(new float[] {10, 20}, 0)); - } - }, - - dotted { - @Override - public void applyToPaint(@NotNull Paint paint) { - paint.setPathEffect(new DashPathEffect(new float[] {5, 1}, 0)); - } - }, - - dash_dotted { - @Override - public void applyToPaint(@NotNull Paint paint) { - paint.setPathEffect(new DashPathEffect(new float[] {10, 20, 5, 1}, 0)); - } - }; - - public abstract void applyToPaint(@NotNull Paint paint); - -} diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/ParcelablePlotInput.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/ParcelablePlotInput.java deleted file mode 100644 index b33deab5..00000000 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/ParcelablePlotInput.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.solovyev.android.calculator.plot; - -import android.os.Parcel; -import android.os.Parcelable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class ParcelablePlotInput implements Parcelable { - - /* - ********************************************************************** - * - * STATIC - * - ********************************************************************** - */ - @NotNull - public static Creator CREATOR = new Creator() { - @Override - public ParcelablePlotInput createFromParcel(@NotNull Parcel in) { - return fromParcel(in); - } - - @Override - public ParcelablePlotInput[] newArray(int size) { - return new ParcelablePlotInput[size]; - } - }; - - /* - ********************************************************************** - * - * FIELDS - * - ********************************************************************** - */ - - @NotNull - private String expression; - - @Nullable - private String xVariableName; - - @Nullable - private String yVariableName; - - @Nullable - private FunctionLineDef lineDef; - - public ParcelablePlotInput(@NotNull String expression, - @Nullable String xVariableName, - @Nullable String yVariableName) { - this(expression, xVariableName, yVariableName, null); - } - - public ParcelablePlotInput(@NotNull String expression, - @Nullable String xVariableName, - @Nullable String yVariableName, - @Nullable FunctionLineDef lineDef) { - this.expression = expression; - this.xVariableName = xVariableName; - this.yVariableName = yVariableName; - this.lineDef = lineDef; - } - - @NotNull - public static ParcelablePlotInput fromParcel(@NotNull Parcel in) { - final String expression = in.readString(); - final String xVariableName = in.readString(); - final String yVariableName = in.readString(); - final FunctionLineDef lineDef = in.readParcelable(Thread.currentThread().getContextClassLoader()); - return new ParcelablePlotInput(expression, xVariableName, yVariableName, lineDef); - } - - @NotNull - public String getExpression() { - return expression; - } - - @Nullable - public String getXVariableName() { - return xVariableName; - } - - @Nullable - public String getYVariableName() { - return yVariableName; - } - - @Nullable - public FunctionLineDef getLineDef() { - return lineDef; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NotNull Parcel out, int flags) { - out.writeString(expression); - out.writeString(xVariableName); - out.writeString(yVariableName); - out.writeParcelable(lineDef, 0); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ParcelablePlotInput)) return false; - - final ParcelablePlotInput that = (ParcelablePlotInput) o; - - if (!expression.equals(that.expression)) return false; - if (xVariableName != null ? !xVariableName.equals(that.xVariableName) : that.xVariableName != null) - return false; - if (yVariableName != null ? !yVariableName.equals(that.yVariableName) : that.yVariableName != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = expression.hashCode(); - result = 31 * result + (xVariableName != null ? xVariableName.hashCode() : 0); - result = 31 * result + (yVariableName != null ? yVariableName.hashCode() : 0); - return result; - } -} diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/ParcelablePlotInputListItem.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/ParcelablePlotInputListItem.java index 86d74b3c..71a07ca3 100644 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/ParcelablePlotInputListItem.java +++ b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/ParcelablePlotInputListItem.java @@ -13,12 +13,12 @@ import org.solovyev.android.view.UpdatableViewBuilder; public class ParcelablePlotInputListItem implements ListItem { @NotNull - private ParcelablePlotInput plotInput; + private XyFunction plotInput; @NotNull private UpdatableViewBuilder viewBuilder; - public ParcelablePlotInputListItem(@NotNull ParcelablePlotInput plotInput) { + public ParcelablePlotInputListItem(@NotNull XyFunction plotInput) { this.plotInput = plotInput; // todo serso: use correct tag this.viewBuilder = TextViewBuilder.newInstance(R.layout.plot_functions_fragment_list_item, null); @@ -52,6 +52,6 @@ public class ParcelablePlotInputListItem implements ListItem { } private void fill(@NotNull TextView textView) { - textView.setText(plotInput.getExpression()); + textView.setText(plotInput.getExpressionString()); } } diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/PlotInput.java b/android-app-core/src/main/java/org/solovyev/android/calculator/plot/PlotInput.java deleted file mode 100644 index 6a5084b8..00000000 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/PlotInput.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.solovyev.android.calculator.plot; - -import jscl.math.Generic; -import jscl.math.function.Constant; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * User: serso - * Date: 12/1/12 - * Time: 5:09 PM - */ -public class PlotInput { - - @NotNull - private Generic function; - - @Nullable - private Constant xVariable; - - @Nullable - private Constant yVariable; - - public PlotInput() { - } - - @NotNull - public static PlotInput newInstance(@NotNull Generic function, - @Nullable Constant xVariable, - @Nullable Constant yVariable) { - PlotInput result = new PlotInput(); - - result.function = function; - result.xVariable = xVariable; - result.yVariable = yVariable; - - return result; - } - - @NotNull - public Generic getFunction() { - return function; - } - - @Nullable - public Constant getXVariable() { - return xVariable; - } - - @Nullable - public Constant getYVariable() { - return yVariable; - } -} diff --git a/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java b/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java index 45ec9bc5..3a9fef58 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java @@ -11,8 +11,6 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragmentActivity; -import jscl.math.Generic; -import jscl.math.function.Constant; import org.achartengine.ChartFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,16 +19,9 @@ import org.solovyev.android.calculator.about.CalculatorAboutActivity; import org.solovyev.android.calculator.function.FunctionEditDialogFragment; import org.solovyev.android.calculator.help.CalculatorHelpActivity; import org.solovyev.android.calculator.history.CalculatorHistoryActivity; -import org.solovyev.android.calculator.math.edit.CalculatorFunctionsActivity; -import org.solovyev.android.calculator.math.edit.CalculatorOperatorsActivity; -import org.solovyev.android.calculator.math.edit.CalculatorVarsActivity; -import org.solovyev.android.calculator.math.edit.CalculatorVarsFragment; -import org.solovyev.android.calculator.math.edit.VarEditDialogFragment; +import org.solovyev.android.calculator.math.edit.*; import org.solovyev.android.calculator.matrix.CalculatorMatrixActivity; import org.solovyev.android.calculator.plot.CalculatorPlotActivity; -import org.solovyev.android.calculator.plot.CalculatorPlotFragment; -import org.solovyev.android.calculator.plot.ParcelablePlotInput; -import org.solovyev.android.calculator.plot.PlotInput; import org.solovyev.common.msg.Message; import org.solovyev.common.msg.MessageType; import org.solovyev.common.text.StringUtils; @@ -104,14 +95,9 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener context.startActivity(intent); } - public static void plotGraph(@NotNull final Context context, - @NotNull Generic generic, - @Nullable Constant xVariable, - @Nullable Constant yVariable){ + public static void plotGraph(@NotNull final Context context){ final Intent intent = new Intent(); intent.putExtra(ChartFactory.TITLE, context.getString(R.string.c_graph)); - final ParcelablePlotInput input = new ParcelablePlotInput(generic.toString(), xVariable == null ? null : 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); @@ -218,12 +204,10 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener }); break; case plot_graph: - final PlotInput plotInput = (PlotInput) data; - assert plotInput != null; App.getInstance().getUiThreadExecutor().execute(new Runnable() { @Override public void run() { - plotGraph(context, plotInput.getFunction(), plotInput.getXVariable(), plotInput.getYVariable()); + plotGraph(context); } }); break; diff --git a/android-app/src/main/java/org/solovyev/android/calculator/CalculatorApplication.java b/android-app/src/main/java/org/solovyev/android/calculator/CalculatorApplication.java index 80282a87..e0955f4e 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/CalculatorApplication.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/CalculatorApplication.java @@ -19,6 +19,8 @@ import org.solovyev.android.calculator.external.AndroidExternalListenersContaine import org.solovyev.android.calculator.history.AndroidCalculatorHistory; import org.solovyev.android.calculator.model.AndroidCalculatorEngine; import org.solovyev.android.calculator.onscreen.CalculatorOnscreenStartActivity; +import org.solovyev.android.calculator.plot.AndroidCalculatorPlotter; +import org.solovyev.android.calculator.plot.CalculatorPlotterImpl; import org.solovyev.common.msg.MessageType; import java.util.ArrayList; @@ -118,7 +120,8 @@ public class CalculatorApplication extends android.app.Application implements Sh new AndroidCalculatorLogger(), new AndroidCalculatorPreferenceService(this), new AndroidCalculatorKeyboard(this, new CalculatorKeyboardImpl(calculator)), - new AndroidExternalListenersContainer(calculator)); + new AndroidExternalListenersContainer(calculator), + new AndroidCalculatorPlotter(this, new CalculatorPlotterImpl(calculator))); Locator.getInstance().getCalculator().init(); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/about/CalculatorFragmentType.java b/android-app/src/main/java/org/solovyev/android/calculator/about/CalculatorFragmentType.java index 8cbe9267..ac0b70ad 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/about/CalculatorFragmentType.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/about/CalculatorFragmentType.java @@ -14,7 +14,6 @@ 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; import org.solovyev.android.calculator.plot.CalculatorPlotFunctionsFragment; /** @@ -32,8 +31,7 @@ public enum CalculatorFragmentType { variables(CalculatorVarsFragment.class, R.layout.vars_fragment, R.string.c_vars), 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), + plotter(CalculatorArityPlotFragment.class, R.layout.plot_fragment, R.string.c_graph), // todo serso: strings plotter_functions(CalculatorPlotFunctionsFragment.class, R.layout.plot_functions_fragment, R.string.c_graph), diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java index b7840d33..c3dc2214 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/AbstractCalculatorPlotFragment.java @@ -2,35 +2,18 @@ package org.solovyev.android.calculator.plot; import android.content.Context; import android.content.Intent; +import android.graphics.Paint; 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.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.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.calculator.*; import org.solovyev.android.menu.ActivityMenu; import org.solovyev.android.menu.IdentifiableMenuItem; import org.solovyev.android.menu.ListActivityMenu; @@ -38,6 +21,7 @@ import org.solovyev.android.sherlock.menu.SherlockMenuHelper; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -59,8 +43,6 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment protected static final String TAG = "CalculatorPlotFragment"; - public static final String INPUT = "plot_input"; - protected static final String PLOT_BOUNDARIES = "plot_boundaries"; private static final int DEFAULT_MIN_NUMBER = -10; @@ -75,9 +57,6 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment ********************************************************************** */ - @Nullable - private ParcelablePlotInput input; - private int bgColor; // thread for applying UI changes @@ -85,7 +64,7 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment private final Handler uiHandler = new Handler(); @NotNull - private PreparedInput preparedInput; + private PlotData plotData = new PlotData(Collections.emptyList(), false); @NotNull private ActivityMenu fragmentMenu; @@ -107,13 +86,9 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final Bundle arguments = getArguments(); - - if (arguments != null) { - input = (ParcelablePlotInput) arguments.getParcelable(INPUT); - } - - if (input == null) { + // todo serso: init variable properly + boolean paneFragment = true; + if (paneFragment) { this.bgColor = getResources().getColor(R.color.cpp_pane_background); } else { this.bgColor = getResources().getColor(android.R.color.transparent); @@ -126,11 +101,7 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment 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); - } + this.plotData = Locator.getInstance().getPlotter().getPlotData(); } @Override @@ -150,42 +121,31 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment public void onResume() { super.onResume(); - createChart(preparedInput); - createGraphicalView(getView(), preparedInput); + createChart(plotData); + createGraphicalView(getView(), plotData); } @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); - onNewPreparedInput(preparedInput); - } + if (calculatorEventType.isOfType(CalculatorEventType.plot_data_changed)) { + final CalculatorEventHolder.Result result = this.lastEventHolder.apply(calculatorEventData); + if (result.isNewAfter()) { + onNewPlotData((PlotData) data); } } } - private void onNewPreparedInput(@NotNull PreparedInput preparedInput) { - this.preparedInput = preparedInput; + private void onNewPlotData(@NotNull final PlotData plotData) { + this.plotData = plotData; - final PreparedInput finalPreparedInput = preparedInput; getUiHandler().post(new Runnable() { @Override public void run() { + createChart(plotData); - if (!finalPreparedInput.isError()) { - createChart(finalPreparedInput); - - final View view = getView(); - if (view != null) { - createGraphicalView(view, finalPreparedInput); - } - } else { - onError(); + final View view = getView(); + if (view != null) { + createGraphicalView(view, plotData); } } }); @@ -193,9 +153,9 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment protected abstract void onError(); - protected abstract void createGraphicalView(@NotNull View view, @NotNull PreparedInput preparedInput); + protected abstract void createGraphicalView(@NotNull View view, @NotNull PlotData plotData); - protected abstract void createChart(@NotNull PreparedInput preparedInput); + protected abstract void createChart(@NotNull PlotData plotData); protected double getMaxValue(@Nullable PlotBoundaries plotBoundaries) { @@ -219,11 +179,6 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment return uiHandler; } - @NotNull - public PreparedInput getPreparedInput() { - return preparedInput; - } - public int getBgColor() { return bgColor; } @@ -257,7 +212,7 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment @Override public void onClick(@NotNull MenuItem data, @NotNull Context context) { - onNewPreparedInput(PreparedInput.force3dInstance(preparedInput)); + Locator.getInstance().getPlotter().setPlot3d(true); } }); } @@ -294,82 +249,6 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment ********************************************************************** */ - @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 variables = new ArrayList(CalculatorUtils.getNotSystemConstants(expression)); - - final Constant xVariable; - if ( variables.size() > 0 ) { - xVariable = variables.get(0); - } else { - xVariable = null; - } - - final Constant yVariable; - if ( variables.size() > 1 ) { - yVariable = variables.get(1); - } else { - yVariable = null; - } - - final ParcelablePlotInput input = new ParcelablePlotInput(expression.toString(), xVariable == null ? null : 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 ParcelablePlotInput 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; - if (input.getXVariableName() != null) { - xVar = new Constant(input.getXVariableName()); - } else { - xVar = null; - } - - 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 { preferences(R.id.menu_plot_settings) { @@ -447,108 +326,19 @@ public abstract class AbstractCalculatorPlotFragment extends CalculatorFragment } } - public static class PreparedInput { + public static void applyToPaint(@NotNull PlotFunctionLineDef plotFunctionLineDef, @NotNull Paint paint) { + paint.setColor(plotFunctionLineDef.getLineColor()); + paint.setStyle(Paint.Style.STROKE); - @Nullable - private ParcelablePlotInput input; - - @Nullable - private Generic expression; - - @Nullable - private Constant xVariable; - - @Nullable - private Constant yVariable; - - private boolean fromInputArgs; - - private boolean force3d = false; - - @NotNull - private PlotBoundaries plotBoundaries = PlotBoundaries.newDefaultInstance(); - - private PreparedInput() { + if ( plotFunctionLineDef.getLineWidth() == PlotFunctionLineDef.DEFAULT_LINE_WIDTH ) { + paint.setStrokeWidth(0); + } else { + paint.setStrokeWidth(plotFunctionLineDef.getLineWidth()); } - @NotNull - public static PreparedInput newInstance(@NotNull ParcelablePlotInput input, - @NotNull Generic expression, - @Nullable 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; - } - - @NotNull - public static PreparedInput force3dInstance(final PreparedInput that) { - if (!that.isError()) { - final PreparedInput result = PreparedInput.newInstance(that.input, that.expression, that.xVariable, that.yVariable, that.fromInputArgs, that.plotBoundaries); - result.force3d = true; - return result; - } else { - return that; - } - } - - public boolean isFromInputArgs() { - return fromInputArgs; - } - - @Nullable - public ParcelablePlotInput 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 isForce3d() { - return force3d; - } - - public boolean isError() { - return input == null || expression == null; + final AndroidPlotLineStyle androidPlotLineStyle = AndroidPlotLineStyle.valueOf(plotFunctionLineDef.getLineStyle()); + if (androidPlotLineStyle != null) { + androidPlotLineStyle.applyToPaint(paint); } } diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionPlotDef.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/ArityPlotFunction.java similarity index 50% rename from android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionPlotDef.java rename to android-app/src/main/java/org/solovyev/android/calculator/plot/ArityPlotFunction.java index c3e747cb..2734dff5 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/FunctionPlotDef.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/ArityPlotFunction.java @@ -8,25 +8,25 @@ import org.jetbrains.annotations.NotNull; * Date: 1/5/13 * Time: 7:35 PM */ -public class FunctionPlotDef { +public class ArityPlotFunction { @NotNull private Function function; @NotNull - private FunctionLineDef lineDef; + private PlotFunctionLineDef lineDef; - private FunctionPlotDef() { + private ArityPlotFunction() { } @NotNull - public static FunctionPlotDef newInstance(@NotNull Function function) { - return newInstance(function, FunctionLineDef.newDefaultInstance()); + public static ArityPlotFunction newInstance(@NotNull Function function) { + return newInstance(function, PlotFunctionLineDef.newDefaultInstance()); } @NotNull - public static FunctionPlotDef newInstance(@NotNull Function function, @NotNull FunctionLineDef lineDef) { - final FunctionPlotDef result = new FunctionPlotDef(); + public static ArityPlotFunction newInstance(@NotNull Function function, @NotNull PlotFunctionLineDef lineDef) { + final ArityPlotFunction result = new ArityPlotFunction(); result.function = function; result.lineDef = lineDef; @@ -40,7 +40,7 @@ public class FunctionPlotDef { } @NotNull - public FunctionLineDef getLineDef() { + public PlotFunctionLineDef getLineDef() { return lineDef; } } diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java index 521d4bd5..61d27dfb 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorArityPlotFragment.java @@ -1,8 +1,6 @@ package org.solovyev.android.calculator.plot; -import android.content.SharedPreferences; import android.graphics.Color; -import android.preference.PreferenceManager; import android.view.View; import android.view.ViewGroup; import jscl.math.Generic; @@ -10,7 +8,6 @@ import jscl.math.function.Constant; import org.javia.arity.Function; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.CalculatorPreferences; import org.solovyev.android.calculator.R; import java.util.ArrayList; @@ -38,13 +35,7 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment } @Override - protected void createGraphicalView(@NotNull View root, @NotNull PreparedInput preparedInput) { - - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity()); - - final GraphLineColor realLineColor = CalculatorPreferences.Graph.lineColorReal.getPreference(preferences); - final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences); - final boolean plotImag = CalculatorPreferences.Graph.plotImag.getPreference(preferences); + protected void createGraphicalView(@NotNull View root, @NotNull PlotData plotData) { // remove old final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout); @@ -53,47 +44,42 @@ public class CalculatorArityPlotFragment extends AbstractCalculatorPlotFragment graphContainer.removeView((View) graphView); } - if (!preparedInput.isError()) { - final Generic expression = preparedInput.getExpression(); - final Constant xVariable = preparedInput.getXVariable(); - final Constant yVariable = preparedInput.getYVariable(); + final List arityFunctions = new ArrayList(); + + for (PlotFunction plotFunction : plotData.getFunctions()) { + + final XyFunction xyFunction = plotFunction.getXyFunction(); + + final Generic expression = xyFunction.getExpression(); + final Constant xVariable = xyFunction.getXVariable(); + final Constant yVariable = xyFunction.getYVariable(); final int arity = xVariable == null ? 0 : (yVariable == null ? 1 : 2); - final List functions = new ArrayList(); - - functions.add(FunctionPlotDef.newInstance(new RealArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(realLineColor.getColor(), FunctionLineStyle.solid, 3f, FunctionLineColorType.color_map))); - if (plotImag) { - functions.add(FunctionPlotDef.newInstance(new ImaginaryArityFunction(arity, expression, xVariable, yVariable), FunctionLineDef.newInstance(imagLineColor.getColor(), FunctionLineStyle.solid, 3f, FunctionLineColorType.color_map))); + final Function arityFunction; + if (xyFunction.isImag()) { + arityFunction = new ImaginaryArityFunction(arity, expression, xVariable, yVariable); + } else { + arityFunction = new RealArityFunction(arity, expression, xVariable, yVariable); } - switch (arity) { - case 0: - case 1: - if (preparedInput.isForce3d()) { - graphView = new Graph3dView(getActivity()); - } else { - graphView = new Graph2dView(getActivity()); - } - break; - case 2: - graphView = new Graph3dView(getActivity()); - break; - default: - throw new IllegalArgumentException("Unsupported arity: " + arity); - } - - graphView.init(FunctionViewDef.newInstance(Color.WHITE, Color.WHITE, Color.DKGRAY, getBgColor())); - graphView.setFunctionPlotDefs(functions); - - graphContainer.addView((View) graphView); - } else { - onError(); + arityFunctions.add(ArityPlotFunction.newInstance(arityFunction, plotFunction.getPlotFunctionLineDef())); } + + if ( plotData.isPlot3d() ) { + graphView = new Graph3dView(getActivity()); + } else { + graphView = new Graph2dView(getActivity()); + } + + graphView.init(FunctionViewDef.newInstance(Color.WHITE, Color.WHITE, Color.DKGRAY, getBgColor())); + graphView.setFunctionPlotDefs(arityFunctions); + + graphContainer.addView((View) graphView); } @Override - protected void createChart(@NotNull PreparedInput preparedInput) { + protected void createChart(@NotNull PlotData plotData) { } @Override diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java index ceaaadd4..cc638b55 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java @@ -35,6 +35,6 @@ public class CalculatorPlotActivity extends CalculatorFragmentActivity { @NotNull public static CalculatorFragmentType getPlotterFragmentType() { - return CalculatorFragmentType.plotter_2; + return CalculatorFragmentType.plotter; } } diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java deleted file mode 100644 index 39c2a2ed..00000000 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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 android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.view.View; -import android.view.ViewGroup; -import jscl.math.Generic; -import jscl.math.function.Constant; -import org.achartengine.chart.XYChart; -import org.achartengine.model.XYMultipleSeriesDataset; -import org.achartengine.model.XYSeries; -import org.achartengine.renderer.XYMultipleSeriesRenderer; -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.calculator.CalculatorPreferences; -import org.solovyev.android.calculator.R; -import org.solovyev.common.MutableObject; - -/** - * User: serso - * Date: 12/1/11 - * Time: 12:40 AM - */ -public class CalculatorPlotFragment extends AbstractCalculatorPlotFragment { - - public static final long EVAL_DELAY_MILLIS = 200; - - @Nullable - private XYChart chart; - - /** - * The encapsulated graphical view. - */ - @Nullable - private MyGraphicalView graphicalView; - - 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); - - //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); - } - } - - @Override - protected boolean is3dPlotSupported() { - return false; - } - - @Nullable - @Override - protected PlotBoundaries getPlotBoundaries() { - if (chart != null) { - return new PlotBoundaries(chart.getRenderer()); - } else { - return null; - } - } - - 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 (!getPreparedInput().isError()) { - final XYChart chart = this.chart; - assert chart != null; - - final PlotBoundaries plotBoundaries = preparedInput.getPlotBoundaries(); - double minValue = getMinValue(plotBoundaries); - double maxValue = getMaxValue(plotBoundaries); - - // 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()); - } - - 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.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.getBgColor()); - - 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); - - updateDataSets(chart, 50); - } else { - graphicalView = null; - } - - } - - - private void updateDataSets(@NotNull final XYChart chart) { - updateDataSets(chart, EVAL_DELAY_MILLIS); - } - - private void updateDataSets(@NotNull final XYChart chart, long millisToWait) { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity()); - final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences); - - final PreparedInput preparedInput = getPreparedInput(); - - 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() { - // allow only one runner at one time - synchronized (pendingOperation) { - //lock all operations with history - if (pendingOperation.getObject() == this) { - - getPlotExecutor().execute(new Runnable() { - @Override - public void run() { - final XYMultipleSeriesRenderer dr = chart.getRenderer(); - - 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); - } - - 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); - } - - getUiHandler().post(new Runnable() { - @Override - public void run() { - graphicalView.repaint(); - } - }); - } - } - } - }); - } - } - } - }); - } - - - getUiHandler().postDelayed(pendingOperation.getObject(), millisToWait); - } - - @NotNull - private final MutableObject pendingOperation = new MutableObject(); - - -/* public void zoomInClickHandler(@NotNull View v) { - this.graphicalView.zoomIn(); - } - - public void zoomOutClickHandler(@NotNull View v) { - this.graphicalView.zoomOut(); - }*/ - - - public void onError() { - this.chart = null; - } -} diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsController.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsController.java index 3825f721..48d3e8cd 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsController.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsController.java @@ -12,7 +12,7 @@ final class CalculatorPlotFunctionsController { private static final CalculatorPlotFunctionsController instance = new CalculatorPlotFunctionsController(); @NotNull - private final List functions = new ArrayList(); + private final List functions = new ArrayList(); private CalculatorPlotFunctionsController() { } @@ -23,11 +23,11 @@ final class CalculatorPlotFunctionsController { } @NotNull - public List getFunctions() { + public List getFunctions() { return Collections.unmodifiableList(functions); } - public boolean addFunction(@NotNull ParcelablePlotInput function) { + public boolean addFunction(@NotNull XyFunction function) { if (!functions.contains(function)) { return functions.add(function); } else { diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsFragment.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsFragment.java index 9e395bb7..a82064e6 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsFragment.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFunctionsFragment.java @@ -23,9 +23,9 @@ public class CalculatorPlotFunctionsFragment extends CalculatorListFragment { public void onResume() { super.onResume(); - final List items = Lists.transform(CalculatorPlotFunctionsController.getInstance().getFunctions(), new Function() { + final List items = Lists.transform(CalculatorPlotFunctionsController.getInstance().getFunctions(), new Function() { @Override - public ParcelablePlotInputListItem apply(@Nullable ParcelablePlotInput input) { + public ParcelablePlotInputListItem apply(@Nullable XyFunction input) { return new ParcelablePlotInputListItem(input); } }); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dView.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dView.java index 8715e3ee..dab68d0e 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dView.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dView.java @@ -86,12 +86,12 @@ public class Graph2dView extends View implements GraphView { @Override public void init(@NotNull FunctionViewDef functionViewDef) { - this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.emptyList()); + this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.emptyList()); } - public void setFunctionPlotDefs(@NotNull List functionPlotDefs) { + public void setFunctionPlotDefs(@NotNull List functionPlotDefs) { - for (FunctionPlotDef functionPlotDef: functionPlotDefs) { + for (ArityPlotFunction functionPlotDef: functionPlotDefs) { final int arity = functionPlotDef.getFunction().arity(); if (arity != 0 && arity != 1) { throw new IllegalArgumentException("Function must have arity 0 or 1 for 2d plot!"); @@ -348,15 +348,16 @@ public class Graph2dView extends View implements GraphView { private static StringBuilder b = new StringBuilder(); private static char[] buf = new char[20]; - private static StringBuilder format(float fv) { + private static StringBuilder format(final float value) { int pos = 0; boolean addDot = false; - int v = Math.round(fv * 100); - boolean isNeg = v < 0; - v = isNeg ? -v : v; + + final boolean negative = value < 0; + + int absValue = Math.round(Math.abs(value) * 100); for (int i = 0; i < 2; ++i) { - int digit = v % 10; - v /= 10; + int digit = absValue % 10; + absValue /= 10; if (digit != 0 || addDot) { buf[pos++] = (char) ('0' + digit); addDot = true; @@ -365,14 +366,14 @@ public class Graph2dView extends View implements GraphView { if (addDot) { buf[pos++] = '.'; } - if (v == 0) { + if (absValue == 0) { buf[pos++] = '0'; } - while (v != 0) { - buf[pos++] = (char) ('0' + (v % 10)); - v /= 10; + while (absValue != 0) { + buf[pos++] = (char) ('0' + (absValue % 10)); + absValue /= 10; } - if (isNeg) { + if (negative) { buf[pos++] = '-'; } b.setLength(0); @@ -459,13 +460,16 @@ public class Graph2dView extends View implements GraphView { paint.setPathEffect(null); } - // AXIS + { + // AXIS - paint.setColor(graphViewHelper.getFunctionViewDef().getAxisColor()); - if (drawYAxis) { - canvas.drawLine(x0, 0, x0, height, paint); + paint.setColor(graphViewHelper.getFunctionViewDef().getAxisColor()); + if (drawYAxis) { + canvas.drawLine(x0, 0, x0, height, paint); + } + canvas.drawLine(0, y0, width, y0, paint); } - canvas.drawLine(0, y0, width, y0, paint); + matrix.reset(); matrix.preTranslate(-currentX, -currentY); @@ -474,23 +478,29 @@ public class Graph2dView extends View implements GraphView { paint.setAntiAlias(false); - final List functionPlotDefs = graphViewHelper.getFunctionPlotDefs(); + { + //GRAPH - // create path once - final Path path = new Path(); + final List functionPlotDefs = graphViewHelper.getFunctionPlotDefs(); - for (int i = 0; i < functionPlotDefs.size(); i++) { - final FunctionPlotDef fpd = functionPlotDefs.get(i); - computeGraph(fpd.getFunction(), minX, maxX, boundMinY, boundMaxY, graphs.get(i)); + // create path once + final Path path = new Path(); - graphToPath(graphs.get(i), path); + for (int i = 0; i < functionPlotDefs.size(); i++) { + final ArityPlotFunction fpd = functionPlotDefs.get(i); + computeGraph(fpd.getFunction(), minX, maxX, boundMinY, boundMaxY, graphs.get(i)); - path.transform(matrix); + graphToPath(graphs.get(i), path); - fpd.getLineDef().applyToPaint(paint); + path.transform(matrix); - canvas.drawPath(path, paint); + AbstractCalculatorPlotFragment.applyToPaint(fpd.getLineDef(), paint); + + canvas.drawPath(path, paint); + } } + + lastMinX = minX; } diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dViewNew.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dViewNew.java new file mode 100644 index 00000000..15cb4d1d --- /dev/null +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph2dViewNew.java @@ -0,0 +1,637 @@ +// Copyright (C) 2009-2010 Mihai Preda + +package org.solovyev.android.calculator.plot; + + +import android.content.Context; +import android.graphics.*; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Scroller; +import android.widget.ZoomButtonsController; +import org.javia.arity.Function; +import org.jetbrains.annotations.NotNull; +import org.solovyev.android.AndroidUtils2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Graph2dViewNew extends View implements GraphView { + + // view width and height + private int width; + private int height; + + @NotNull + private final Matrix matrix = new Matrix(); + + // paints + + @NotNull + private final Paint paint = new Paint(); + + @NotNull + private final Paint textPaint = new Paint(); + + @NotNull + private final Paint fillPaint = new Paint(); + + @NotNull + private GraphViewHelper graphViewHelper = GraphViewHelper.newDefaultInstance(); + + private final GraphData next = GraphData.newEmptyInstance(); + + private final GraphData endGraph = GraphData.newEmptyInstance(); + + @NotNull + private List graphs = new ArrayList(graphViewHelper.getFunctionPlotDefs().size()); + + private float x0; + private float y0; + private float graphWidth = 20; + + private float lastXMin; + + private float lastYMin; + private float lastYMax; + + private float lastTouchX, lastTouchY; + + + @NotNull + private TouchHandler touchHandler; + + @NotNull + protected ZoomButtonsController zoomController = new ZoomButtonsController(this); + + @NotNull + private ZoomTracker zoomTracker = new ZoomTracker(); + + @NotNull + private Scroller scroller; + + public Graph2dViewNew(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public Graph2dViewNew(Context context) { + super(context); + init(context); + } + + private void init(Context context) { + touchHandler = new TouchHandler(this); + zoomController.setOnZoomListener(this); + scroller = new Scroller(context); + + paint.setAntiAlias(false); + textPaint.setAntiAlias(true); + + width = this.getWidth(); + height = this.getHeight(); + } + + public String captureScreenshot() { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + Canvas canvas = new Canvas(bitmap); + onDraw(canvas); + return AndroidUtils2.saveBitmap(bitmap, GraphView.SCREENSHOT_DIR, "calculator"); + } + + private void clearAllGraphs() { + for (GraphData graph : graphs) { + graph.clear(); + } + + while ( graphViewHelper.getFunctionPlotDefs().size() > graphs.size() ) { + graphs.add(GraphData.newEmptyInstance()); + } + } + + @Override + public void init(@NotNull FunctionViewDef functionViewDef) { + this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.emptyList()); + } + + public void setFunctionPlotDefs(@NotNull List functionPlotDefs) { + + for (ArityPlotFunction functionPlotDef: functionPlotDefs) { + final int arity = functionPlotDef.getFunction().arity(); + if (arity != 0 && arity != 1) { + throw new IllegalArgumentException("Function must have arity 0 or 1 for 2d plot!"); + } + } + + this.graphViewHelper = this.graphViewHelper.copy(functionPlotDefs); + clearAllGraphs(); + invalidate(); + } + + public void onVisibilityChanged(boolean visible) { + } + + public void onZoom(boolean zoomIn) { + if (zoomIn) { + if (canZoomIn()) { + graphWidth /= 2; + invalidateGraphs(); + } + } else { + if (canZoomOut()) { + graphWidth *= 2; + invalidateGraphs(); + } + } + zoomController.setZoomInEnabled(canZoomIn()); + zoomController.setZoomOutEnabled(canZoomOut()); + } + + public void onResume() { + } + + public void onPause() { + } + + public void onDetachedFromWindow() { + zoomController.setVisible(false); + super.onDetachedFromWindow(); + } + + protected void onSizeChanged(int w, int h, int ow, int oh) { + width = w; + height = h; + clearAllGraphs(); + // points = new float[w+w]; + } + + protected void onDraw(Canvas canvas) { + if (graphViewHelper.getFunctionPlotDefs().size() == 0) { + return; + } + if (scroller.computeScrollOffset()) { + final float scale = graphWidth / width; + x0 = scroller.getCurrX() * scale; + y0 = scroller.getCurrY() * scale; + if (!scroller.isFinished()) { + invalidate(); + } + } + drawGraph(canvas); + } + + private float eval(Function f, float x) { + float v = (float) f.eval(x); + // Calculator.log("eval " + x + "; " + v); + if (v < -10000f) { + return -10000f; + } + if (v > 10000f) { + return 10000f; + } + return v; + } + + // distance from (x,y) to the line (x1,y1) to (x2,y2), squared, multiplied by 4 + /* + private float distance(float x1, float y1, float x2, float y2, float x, float y) { + float dx = x2 - x1; + float dy = y2 - y1; + float mx = x - x1; + float my = y - y1; + float up = dx*my - dy*mx; + return up*up*4/(dx*dx + dy*dy); + } + */ + + // distance as above when x==(x1+x2)/2. + private float distance2(float x1, float y1, float x2, float y2, float y) { + final float dx = x2 - x1; + final float dy = y2 - y1; + final float up = dx * (y1 + y2 - y - y); + return up * up / (dx * dx + dy * dy); + } + + private void computeGraph(@NotNull Function function, + float xMin, + float xMax, + float yMin, + float yMax, + @NotNull GraphData graph) { + if (function.arity() == 0) { + float v = (float) function.eval(); + if (v < -10000f) { + v = -10000f; + } + if (v > 10000f) { + v = 10000f; + } + graph.clear(); + graph.push(xMin, v); + graph.push(xMax, v); + return; + } + + // prepare graph + if (!graph.empty()) { + if (xMin >= lastXMin) { + graph.eraseBefore(xMin); + } else { + graph.eraseAfter(xMax); + xMax = Math.min(xMax, graph.firstX()); + graph.swap(endGraph); + } + } + if (graph.empty()) { + graph.push(xMin, eval(function, xMin)); + } + + final float scale = width / graphWidth; + final float maxStep = 15.8976f / scale; + final float minStep = .05f / scale; + float ythresh = 1 / scale; + ythresh = ythresh * ythresh; + + + float leftX, leftY; + float rightX = graph.topX(), rightY = graph.topY(); + int nEval = 1; + while (true) { + leftX = rightX; + leftY = rightY; + if (leftX > xMax) { + break; + } + if (next.empty()) { + float x = leftX + maxStep; + next.push(x, eval(function, x)); + ++nEval; + } + rightX = next.topX(); + rightY = next.topY(); + next.pop(); + + if (leftY != leftY && rightY != rightY) { // NaN + continue; + } + + float dx = rightX - leftX; + float middleX = (leftX + rightX) / 2; + float middleY = eval(function, middleX); + ++nEval; + boolean middleIsOutside = (middleY < leftY && middleY < rightY) || (leftY < middleY && rightY < middleY); + if (dx < minStep) { + // Calculator.log("minStep"); + if (middleIsOutside) { + graph.push(rightX, Float.NaN); + } + graph.push(rightX, rightY); + continue; + } + if (middleIsOutside && ((leftY < yMin && rightY > yMax) || (leftY > yMax && rightY < yMin))) { + graph.push(rightX, Float.NaN); + graph.push(rightX, rightY); + // Calculator.log("+-inf"); + continue; + } + + if (!middleIsOutside) { + /* + float diff = leftY + rightY - middleY - middleY; + float dy = rightY - leftY; + float dx2 = dx*dx; + float distance = dx2*diff*diff/(dx2+dy*dy); + */ + // Calculator.log("" + dx + ' ' + leftY + ' ' + middleY + ' ' + rightY + ' ' + distance + ' ' + ythresh); + if (distance2(leftX, leftY, rightX, rightY, middleY) < ythresh) { + graph.push(rightX, rightY); + continue; + } + } + next.push(rightX, rightY); + next.push(middleX, middleY); + rightX = leftX; + rightY = leftY; + } + if (!endGraph.empty()) { + graph.append(endGraph); + } + long t2 = System.currentTimeMillis(); + // Calculator.log("graph points " + graph.size + " evals " + nEval + " time " + (t2-t1)); + + next.clear(); + endGraph.clear(); + } + + private static void graphToPath(@NotNull GraphData graph, @NotNull Path path) { + + final int size = graph.getSize(); + final float[] xs = graph.getXs(); + final float[] ys = graph.getYs(); + + path.rewind(); + + boolean newCurve = true; + + for (int i = 0; i < size; i++) { + + final float y = ys[i]; + final float x = xs[i]; + + if (y != y) { + newCurve = true; + } else { // !NaN + if (newCurve) { + path.moveTo(x, y); + newCurve = false; + } else { + path.lineTo(x, y); + } + } + } + } + + private static final float NTICKS = 15; + + private static float stepFactor(float w) { + float f = 1; + while (w / f > NTICKS) { + f *= 10; + } + while (w / f < NTICKS / 10) { + f /= 10; + } + float r = w / f; + if (r < NTICKS / 5) { + return f / 5; + } else if (r < NTICKS / 2) { + return f / 2; + } else { + return f; + } + } + + private static StringBuilder b = new StringBuilder(); + private static char[] buf = new char[20]; + + private static StringBuilder format(final float value) { + int pos = 0; + boolean addDot = false; + + final boolean negative = value < 0; + + int absValue = Math.round(Math.abs(value) * 100); + for (int i = 0; i < 2; ++i) { + int digit = absValue % 10; + absValue /= 10; + if (digit != 0 || addDot) { + buf[pos++] = (char) ('0' + digit); + addDot = true; + } + } + if (addDot) { + buf[pos++] = '.'; + } + if (absValue == 0) { + buf[pos++] = '0'; + } + while (absValue != 0) { + buf[pos++] = (char) ('0' + (absValue % 10)); + absValue /= 10; + } + if (negative) { + buf[pos++] = '-'; + } + b.setLength(0); + b.append(buf, 0, pos); + b.reverse(); + return b; + } + + private void drawGraph(Canvas canvas) { + + final float xMin = getXMin(); + final float xMax = getXMax(xMin); + + float graphHeight = graphWidth * height / width; + float yMin = y0 - graphHeight / 2; + float yMax = yMin + graphHeight; + + if (yMin < lastYMin || yMax > lastYMax) { + float halfGraphHeight = graphHeight / 2; + lastYMin = yMin - halfGraphHeight; + lastYMax = yMax + halfGraphHeight; + clearAllGraphs(); + } + + // set background + canvas.drawColor(graphViewHelper.getFunctionViewDef().getBackgroundColor()); + + // prepare paint + paint.setStrokeWidth(0); + paint.setAntiAlias(false); + paint.setStyle(Paint.Style.STROKE); + + final float scale = width / graphWidth; + + float x0 = -xMin * scale; + boolean drawYAxis = true; + if (x0 < 25) { + x0 = 25; + // drawYAxis = false; + } else if (x0 > width - 3) { + x0 = width - 3; + // drawYAxis = false; + } + float y0 = yMax * scale; + if (y0 < 3) { + y0 = 3; + } else if (y0 > height - 15) { + y0 = height - 15; + } + + final float tickSize = 3; + final float y2 = y0 + tickSize; + + + { + // GRID + + paint.setPathEffect(new DashPathEffect(new float[]{5, 10}, 0)); + paint.setColor(graphViewHelper.getFunctionViewDef().getGridColor()); + + float step = stepFactor(graphWidth); + // Calculator.log("width " + gwidth + " step " + step); + float v = ((int) (xMin / step)) * step; + textPaint.setColor(graphViewHelper.getFunctionViewDef().getAxisLabelsColor()); + textPaint.setTextSize(12); + textPaint.setTextAlign(Paint.Align.CENTER); + float stepScale = step * scale; + for (float x = (v - xMin) * scale; x <= width; x += stepScale, v += step) { + canvas.drawLine(x, 0, x, height, paint); + if (!(-.001f < v && v < .001f)) { + StringBuilder b = format(v); + canvas.drawText(b, 0, b.length(), x, y2 + 10, textPaint); + } + } + + final float x1 = x0 - tickSize; + v = ((int) (yMin / step)) * step; + textPaint.setTextAlign(Paint.Align.RIGHT); + for (float y = height - (v - yMin) * scale; y >= 0; y -= stepScale, v += step) { + canvas.drawLine(0, y, width, y, paint); + if (!(-.001f < v && v < .001f)) { + StringBuilder b = format(v); + canvas.drawText(b, 0, b.length(), x1, y + 4, textPaint); + } + } + + paint.setPathEffect(null); + } + + { + // AXIS + + paint.setColor(graphViewHelper.getFunctionViewDef().getAxisColor()); + if (drawYAxis) { + canvas.drawLine(x0, 0, x0, height, paint); + } + canvas.drawLine(0, y0, width, y0, paint); + } + + + matrix.reset(); + matrix.preTranslate(-this.x0, -this.y0); + matrix.postScale(scale, -scale); + matrix.postTranslate(width / 2, height / 2); + + paint.setAntiAlias(false); + + { + //GRAPH + + final List functionPlotDefs = graphViewHelper.getFunctionPlotDefs(); + + // create path once + final Path path = new Path(); + + for (int i = 0; i < functionPlotDefs.size(); i++) { + final ArityPlotFunction fpd = functionPlotDefs.get(i); + computeGraph(fpd.getFunction(), xMin, xMax, lastYMin, lastYMax, graphs.get(i)); + + graphToPath(graphs.get(i), path); + + path.transform(matrix); + + AbstractCalculatorPlotFragment.applyToPaint(fpd.getLineDef(), paint); + + canvas.drawPath(path, paint); + } + } + + + lastXMin = xMin; + } + + private float getXMax(float minX) { + return minX + graphWidth; + } + + private float getXMax() { + return getXMax(getXMin()); + } + + private float getXMin() { + return x0 - graphWidth / 2; + } + + private boolean canZoomIn() { + return true; + } + + private boolean canZoomOut() { + return true; + } + + private void invalidateGraphs() { + clearAllGraphs(); + lastYMin = lastYMax = 0; + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return touchHandler != null ? touchHandler.onTouchEvent(event) : super.onTouchEvent(event); + } + + public void onTouchDown(float x, float y) { + zoomController.setVisible(true); + if (!scroller.isFinished()) { + scroller.abortAnimation(); + } + lastTouchX = x; + lastTouchY = y; + } + + public void onTouchMove(float x, float y) { + float deltaX = x - lastTouchX; + float deltaY = y - lastTouchY; + if (deltaX < -1 || deltaX > 1 || deltaY < -1 || deltaY > 1) { + scroll(-deltaX, deltaY); + lastTouchX = x; + lastTouchY = y; + invalidate(); + } + } + + public void onTouchUp(float x, float y) { + final float scale = width / graphWidth; + float sx = -touchHandler.velocityTracker.getXVelocity(); + float sy = touchHandler.velocityTracker.getYVelocity(); + final float asx = Math.abs(sx); + final float asy = Math.abs(sy); + if (asx < asy / 3) { + sx = 0; + } else if (asy < asx / 3) { + sy = 0; + } + scroller.fling(Math.round(x0 * scale), + Math.round(y0 * scale), + Math.round(sx), Math.round(sy), -10000, 10000, -10000, 10000); + invalidate(); + } + + public void onTouchZoomDown(float x1, float y1, float x2, float y2) { + zoomTracker.start(graphWidth, x1, y1, x2, y2); + } + + public void onTouchZoomMove(float x1, float y1, float x2, float y2) { + if (!zoomTracker.update(x1, y1, x2, y2)) { + return; + } + float targetGwidth = zoomTracker.value; + if (targetGwidth > .25f && targetGwidth < 200) { + graphWidth = targetGwidth; + } + // scroll(-zoomTracker.moveX, zoomTracker.moveY); + invalidateGraphs(); + // Calculator.log("zoom redraw"); + } + + private void scroll(float deltaX, float deltaY) { + final float scale = graphWidth / width; + float dx = deltaX * scale; + float dy = deltaY * scale; + final float adx = Math.abs(dx); + final float ady = Math.abs(dy); + if (adx < ady / 3) { + dx = 0; + } else if (ady < adx / 3) { + dy = 0; + } + x0 += dx; + y0 += dy; + } +} diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java index d7caf179..114c90f4 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3d.java @@ -92,9 +92,9 @@ class Graph3d { return bb; } - public void update(@NotNull GL11 gl, @NotNull FunctionPlotDef fpd, float zoom) { + public void update(@NotNull GL11 gl, @NotNull ArityPlotFunction fpd, float zoom) { final Function function = fpd.getFunction(); - final FunctionLineDef lineDef = fpd.getLineDef(); + final PlotFunctionLineDef lineDef = fpd.getLineDef(); final int NTICK = useHighQuality3d ? 5 : 0; final float size = 4 * zoom; @@ -285,7 +285,7 @@ class Graph3d { return maxAbsZ; } - private byte[] prepareFunctionPolygonColors(FunctionLineDef lineDef, float[] vertices, float maxAbsZ) { + private byte[] prepareFunctionPolygonColors(PlotFunctionLineDef lineDef, float[] vertices, float maxAbsZ) { // 4 color components per polygon (color[i] = red, color[i+1] = green, color[i+2] = blue, color[i+3] = alpha ) final byte colors[] = new byte[polygonsⁿ * COLOR_COMPONENTS_COUNT]; @@ -295,7 +295,7 @@ class Graph3d { final float z = vertices[j]; if (!Float.isNaN(z)) { - if (lineDef.getLineColorType() == FunctionLineColorType.color_map) { + if (lineDef.getLineColorType() == PlotFunctionLineColorType.color_map) { final float color = z / maxAbsZ; final float abs = Math.abs(color); colors[i] = floatToByte(color); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java index 4ab2143c..0001594a 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/Graph3dView.java @@ -174,11 +174,11 @@ public class Graph3dView extends GLView implements GraphView { @Override public void init(@NotNull FunctionViewDef functionViewDef) { - this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.emptyList()); + this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.emptyList()); } - public void setFunctionPlotDefs(@NotNull List functionPlotDefs) { - for (FunctionPlotDef functionPlotDef: functionPlotDefs) { + public void setFunctionPlotDefs(@NotNull List functionPlotDefs) { + for (ArityPlotFunction functionPlotDef: functionPlotDefs) { final int arity = functionPlotDef.getFunction().arity(); if (arity != 0 && arity != 1 && arity != 2) { throw new IllegalArgumentException("Function must have arity 0 or 1 or 2 for 3d plot!"); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphView.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphView.java index d17a148a..473feea2 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphView.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphView.java @@ -13,7 +13,7 @@ public interface GraphView extends ZoomButtonsController.OnZoomListener, TouchHa public void init(@NotNull FunctionViewDef functionViewDef); - public void setFunctionPlotDefs(@NotNull List functionPlotDefs); + public void setFunctionPlotDefs(@NotNull List functionPlotDefs); public void onPause(); public void onResume(); diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphViewHelper.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphViewHelper.java index 9f0a1796..7a5bb33c 100644 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphViewHelper.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/GraphViewHelper.java @@ -16,7 +16,7 @@ public class GraphViewHelper { private FunctionViewDef functionViewDef = FunctionViewDef.newDefaultInstance(); @NotNull - private List functionPlotDefs = Collections.emptyList(); + private List functionPlotDefs = Collections.emptyList(); private GraphViewHelper() { } @@ -28,7 +28,7 @@ public class GraphViewHelper { @NotNull public static GraphViewHelper newInstance(@NotNull FunctionViewDef functionViewDef, - @NotNull List functionPlotDefs) { + @NotNull List functionPlotDefs) { final GraphViewHelper result = new GraphViewHelper(); result.functionViewDef = functionViewDef; @@ -38,7 +38,7 @@ public class GraphViewHelper { } @NotNull - public GraphViewHelper copy(@NotNull List newFunctionPlotDefs) { + public GraphViewHelper copy(@NotNull List newFunctionPlotDefs) { final GraphViewHelper result = new GraphViewHelper(); result.functionViewDef = functionViewDef; @@ -48,7 +48,7 @@ public class GraphViewHelper { } @NotNull - public List getFunctionPlotDefs() { + public List getFunctionPlotDefs() { return functionPlotDefs; } diff --git a/android-app/src/main/java/org/solovyev/android/calculator/plot/ZoomTracker.java b/android-app/src/main/java/org/solovyev/android/calculator/plot/ZoomTracker.java index ec6cc6f8..73e96238 100755 --- a/android-app/src/main/java/org/solovyev/android/calculator/plot/ZoomTracker.java +++ b/android-app/src/main/java/org/solovyev/android/calculator/plot/ZoomTracker.java @@ -36,7 +36,7 @@ class ZoomTracker { return true; } - private float distance(float x1, float y1, float x2, float y2) { + private static float distance(float x1, float y1, float x2, float y2) { final float dx = x1-x2; final float dy = y1-y2; // return (float) Math.sqrt(dx*dx+dy*dy); diff --git a/android-app/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java b/android-app/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java index 739229be..29d7271f 100644 --- a/android-app/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java +++ b/android-app/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; import org.mockito.Mockito; import org.solovyev.android.calculator.external.CalculatorExternalListenersContainer; import org.solovyev.android.calculator.history.CalculatorHistory; +import org.solovyev.android.calculator.plot.CalculatorPlotter; /** * User: serso @@ -16,7 +17,7 @@ import org.solovyev.android.calculator.history.CalculatorHistory; public class CalculatorTestUtils { public static void staticSetUp(@Nullable Context context) throws Exception { - Locator.getInstance().init(new CalculatorImpl(), newCalculatorEngine(), Mockito.mock(CalculatorClipboard.class), Mockito.mock(CalculatorNotifier.class), Mockito.mock(CalculatorHistory.class), new SystemOutCalculatorLogger(), Mockito.mock(CalculatorPreferenceService.class), Mockito.mock(CalculatorKeyboard.class), Mockito.mock(CalculatorExternalListenersContainer.class)); + Locator.getInstance().init(new CalculatorImpl(), newCalculatorEngine(), Mockito.mock(CalculatorClipboard.class), Mockito.mock(CalculatorNotifier.class), Mockito.mock(CalculatorHistory.class), new SystemOutCalculatorLogger(), Mockito.mock(CalculatorPreferenceService.class), Mockito.mock(CalculatorKeyboard.class), Mockito.mock(CalculatorExternalListenersContainer.class), Mockito.mock(CalculatorPlotter.class)); Locator.getInstance().getEngine().init(); if ( context != null ) { diff --git a/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java b/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java index 295aa6da..5af19aed 100644 --- a/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java +++ b/core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java @@ -159,11 +159,8 @@ public enum CalculatorEventType { show_create_matrix_dialog, show_create_function_dialog, - //org.solovyev.android.calculator.plot.PlotInput plot_graph, - - - plot_graph_3d, + plot_data_changed, //String show_evaluation_error; diff --git a/core/src/main/java/org/solovyev/android/calculator/CalculatorLocator.java b/core/src/main/java/org/solovyev/android/calculator/CalculatorLocator.java index 458d0271..c5b01291 100644 --- a/core/src/main/java/org/solovyev/android/calculator/CalculatorLocator.java +++ b/core/src/main/java/org/solovyev/android/calculator/CalculatorLocator.java @@ -3,6 +3,7 @@ package org.solovyev.android.calculator; import org.jetbrains.annotations.NotNull; import org.solovyev.android.calculator.external.CalculatorExternalListenersContainer; import org.solovyev.android.calculator.history.CalculatorHistory; +import org.solovyev.android.calculator.plot.CalculatorPlotter; /** * User: Solovyev_S @@ -19,7 +20,8 @@ public interface CalculatorLocator { @NotNull CalculatorLogger logger, @NotNull CalculatorPreferenceService preferenceService, @NotNull CalculatorKeyboard keyboard, - @NotNull CalculatorExternalListenersContainer externalListenersContainer); + @NotNull CalculatorExternalListenersContainer externalListenersContainer, + @NotNull CalculatorPlotter plotter); @NotNull Calculator getCalculator(); @@ -48,6 +50,9 @@ public interface CalculatorLocator { @NotNull CalculatorLogger getLogger(); + @NotNull + CalculatorPlotter getPlotter(); + @NotNull CalculatorPreferenceService getPreferenceService(); diff --git a/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java b/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java index 19005fb2..fc033def 100644 --- a/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java +++ b/core/src/main/java/org/solovyev/android/calculator/CalculatorUtils.java @@ -4,7 +4,6 @@ import jscl.math.Generic; import jscl.math.function.Constant; import jscl.math.function.IConstant; import org.jetbrains.annotations.NotNull; -import org.solovyev.android.calculator.jscl.JsclOperation; import java.util.HashSet; import java.util.Set; @@ -41,16 +40,4 @@ public final class CalculatorUtils { return notSystemConstants; } - public static boolean isPlotPossible(@NotNull Generic expression, @NotNull JsclOperation operation) { - boolean result = false; - - if (operation == JsclOperation.simplify || operation == JsclOperation.numeric) { - int size = getNotSystemConstants(expression).size(); - if (size == 0 || size == 1 || size == 2) { - result = true; - } - } - - return result; - } } diff --git a/core/src/main/java/org/solovyev/android/calculator/Locator.java b/core/src/main/java/org/solovyev/android/calculator/Locator.java index b75459f2..f296ccd3 100644 --- a/core/src/main/java/org/solovyev/android/calculator/Locator.java +++ b/core/src/main/java/org/solovyev/android/calculator/Locator.java @@ -3,6 +3,7 @@ package org.solovyev.android.calculator; import org.jetbrains.annotations.NotNull; import org.solovyev.android.calculator.external.CalculatorExternalListenersContainer; import org.solovyev.android.calculator.history.CalculatorHistory; +import org.solovyev.android.calculator.plot.CalculatorPlotter; /** * User: Solovyev_S @@ -47,7 +48,10 @@ public class Locator implements CalculatorLocator { @NotNull private CalculatorExternalListenersContainer calculatorExternalListenersContainer; - public Locator() { + @NotNull + private CalculatorPlotter calculatorPlotter; + + public Locator() { } @Override @@ -59,7 +63,8 @@ public class Locator implements CalculatorLocator { @NotNull CalculatorLogger logger, @NotNull CalculatorPreferenceService preferenceService, @NotNull CalculatorKeyboard keyboard, - @NotNull CalculatorExternalListenersContainer externalListenersContainer) { + @NotNull CalculatorExternalListenersContainer externalListenersContainer, + @NotNull CalculatorPlotter plotter) { this.calculator = calculator; this.calculatorEngine = engine; @@ -69,6 +74,7 @@ public class Locator implements CalculatorLocator { this.calculatorLogger = logger; this.calculatorPreferenceService = preferenceService; this.calculatorExternalListenersContainer = externalListenersContainer; + this.calculatorPlotter = plotter; calculatorEditor = new CalculatorEditorImpl(this.calculator); calculatorDisplay = new CalculatorDisplayImpl(this.calculator); @@ -134,6 +140,12 @@ public class Locator implements CalculatorLocator { return calculatorLogger; } + @NotNull + @Override + public CalculatorPlotter getPlotter() { + return calculatorPlotter; + } + @NotNull @Override public CalculatorPreferenceService getPreferenceService() { diff --git a/core/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotter.java b/core/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotter.java new file mode 100644 index 00000000..91133f26 --- /dev/null +++ b/core/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotter.java @@ -0,0 +1,56 @@ +package org.solovyev.android.calculator.plot; + +import jscl.math.Generic; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * User: serso + * Date: 1/12/13 + * Time: 8:23 PM + */ +public interface CalculatorPlotter { + + @NotNull + PlotData getPlotData(); + + boolean addFunction(@NotNull Generic expression); + boolean addFunction(@NotNull PlotFunction plotFunction); + boolean addFunction(@NotNull XyFunction xyFunction); + boolean addFunction(@NotNull XyFunction xyFunction, @NotNull PlotFunctionLineDef functionLineDef); + + boolean updateFunction(@NotNull PlotFunction newFunction); + boolean updateFunction(@NotNull XyFunction xyFunction, @NotNull PlotFunctionLineDef functionLineDef); + + boolean removeFunction(@NotNull PlotFunction plotFunction); + boolean removeFunction(@NotNull XyFunction xyFunction); + + void pin(@NotNull PlotFunction plotFunction); + void unpin(@NotNull PlotFunction plotFunction); + + void show(@NotNull PlotFunction plotFunction); + void hide(@NotNull PlotFunction plotFunction); + + void clearAllFunctions(); + + @NotNull + List getFunctions(); + + @NotNull + List getVisibleFunctions(); + + void plot(); + + boolean isPlotPossible(@NotNull Generic expression); + + void setPlot3d(boolean plot3d); + + void removeAllUnpinned(); + + void setPlotImag(boolean plotImag); + + void setRealLineColor(@NotNull GraphLineColor realLineColor); + + void setImagLineColor(@NotNull GraphLineColor imagLineColor); +} diff --git a/core/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotterImpl.java b/core/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotterImpl.java new file mode 100644 index 00000000..5c286c12 --- /dev/null +++ b/core/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotterImpl.java @@ -0,0 +1,294 @@ +package org.solovyev.android.calculator.plot; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import jscl.math.Generic; +import jscl.math.function.Constant; +import org.jetbrains.annotations.NotNull; +import org.solovyev.android.calculator.Calculator; +import org.solovyev.android.calculator.CalculatorEventType; +import org.solovyev.android.calculator.CalculatorUtils; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** + * User: serso + * Date: 1/12/13 + * Time: 8:42 PM + */ +public class CalculatorPlotterImpl implements CalculatorPlotter { + + @NotNull + private final List functions = new ArrayList(); + + @NotNull + private final Calculator calculator; + + private boolean plot3d = false; + + private boolean plotImag = false; + + @NotNull + private GraphLineColor realLineColor; + + @NotNull + private GraphLineColor imagLineColor; + + public CalculatorPlotterImpl(@NotNull Calculator calculator) { + this.calculator = calculator; + } + + @NotNull + @Override + public PlotData getPlotData() { + return new PlotData(getVisibleFunctions(), plot3d); + } + + @Override + public boolean addFunction(@NotNull Generic expression) { + final List variables = new ArrayList(CalculatorUtils.getNotSystemConstants(expression)); + + assert variables.size() <= 2; + + final Constant xVariable; + if (variables.size() > 0) { + xVariable = variables.get(0); + } else { + xVariable = null; + } + + final Constant yVariable; + if (variables.size() > 1) { + yVariable = variables.get(1); + } else { + yVariable = null; + } + + boolean realAdded = addFunction(new XyFunction(expression, xVariable, yVariable, false)); + + final PlotFunction imagPlotFunction = new PlotFunction(new XyFunction(expression, xVariable, yVariable, true)); + final boolean imagAdded = addFunction(plotImag ? imagPlotFunction : PlotFunction.invisible(imagPlotFunction)); + + return imagAdded || realAdded; + } + + @Override + public boolean addFunction(@NotNull PlotFunction plotFunction) { + synchronized (functions) { + if (!functions.contains(plotFunction)) { + functions.add(plotFunction); + onFunctionsChanged(); + return true; + } else { + return false; + } + } + } + + @Override + public void removeAllUnpinned() { + synchronized (functions) { + boolean changed = Iterables.removeIf(functions, new Predicate() { + @Override + public boolean apply(@Nullable PlotFunction function) { + return function != null && !function.isPinned(); + } + }); + + if (changed) { + onFunctionsChanged(); + } + } + } + + @Override + public boolean removeFunction(@NotNull PlotFunction plotFunction) { + synchronized (functions) { + boolean changed = functions.remove(plotFunction); + if (changed) { + onFunctionsChanged(); + } + return changed; + } + } + + @Override + public boolean addFunction(@NotNull XyFunction xyFunction) { + return addFunction(new PlotFunction(xyFunction)); + } + + @Override + public boolean addFunction(@NotNull XyFunction xyFunction, @NotNull PlotFunctionLineDef functionLineDef) { + return addFunction(new PlotFunction(xyFunction, functionLineDef)); + } + + @Override + public boolean updateFunction(@NotNull XyFunction xyFunction, @NotNull PlotFunctionLineDef functionLineDef) { + final PlotFunction newFunction = new PlotFunction(xyFunction, functionLineDef); + + return updateFunction(newFunction); + } + + @Override + public boolean updateFunction(@NotNull PlotFunction newFunction) { + boolean changed = false; + + synchronized (functions) { + for (int i = 0; i < functions.size(); i++) { + final PlotFunction plotFunction = functions.get(i); + if (plotFunction.equals(newFunction)) { + // update old function + functions.set(i, newFunction); + changed = true; + break; + } + } + } + + return changed; + } + + @Override + public boolean removeFunction(@NotNull XyFunction xyFunction) { + return removeFunction(new PlotFunction(xyFunction)); + } + + @Override + public void pin(@NotNull PlotFunction plotFunction) { + updateFunction(PlotFunction.pin(plotFunction)); + } + + @Override + public void unpin(@NotNull PlotFunction plotFunction) { + updateFunction(PlotFunction.unpin(plotFunction)); + } + + @Override + public void show(@NotNull PlotFunction plotFunction) { + updateFunction(PlotFunction.visible(plotFunction)); + firePlotDataChangedEvent(); + } + + @Override + public void hide(@NotNull PlotFunction plotFunction) { + updateFunction(PlotFunction.invisible(plotFunction)); + firePlotDataChangedEvent(); + } + + @Override + public void clearAllFunctions() { + synchronized (functions) { + functions.clear(); + onFunctionsChanged(); + } + } + + // NOTE: this method must be called from synchronized block + private void onFunctionsChanged() { + assert Thread.holdsLock(functions); + + int maxArity = 0; + for (PlotFunction function : functions) { + final XyFunction xyFunction = function.getXyFunction(); + + maxArity = Math.max(maxArity, xyFunction.getArity()); + } + + if (maxArity > 1) { + plot3d = true; + } else { + plot3d = false; + } + + firePlotDataChangedEvent(); + } + + @NotNull + @Override + public List getFunctions() { + synchronized (functions) { + return new ArrayList(functions); + } + } + + @NotNull + @Override + public List getVisibleFunctions() { + synchronized (functions) { + return Lists.newArrayList(Iterables.filter(functions, new Predicate() { + @Override + public boolean apply(@Nullable PlotFunction function) { + return function != null && function.isVisible(); + } + })); + } + } + + @Override + public void plot() { + calculator.fireCalculatorEvent(CalculatorEventType.plot_graph, null); + } + + @Override + public boolean isPlotPossible(@NotNull Generic expression) { + boolean result = false; + + int size = CalculatorUtils.getNotSystemConstants(expression).size(); + if (size == 0 || size == 1 || size == 2) { + result = true; + } + + return result; + } + + @Override + public void setPlot3d(boolean plot3d) { + if (this.plot3d != plot3d) { + this.plot3d = plot3d; + firePlotDataChangedEvent(); + } + } + + private void firePlotDataChangedEvent() { + calculator.fireCalculatorEvent(CalculatorEventType.plot_data_changed, getPlotData()); + } + + @Override + public void setPlotImag(boolean plotImag) { + if (this.plotImag != plotImag) { + this.plotImag = plotImag; + if (toggleImagFunctions(this.plotImag)) { + firePlotDataChangedEvent(); + } + } + } + + @Override + public void setRealLineColor(@NotNull GraphLineColor realLineColor) { + this.realLineColor = realLineColor; + } + + @Override + public void setImagLineColor(@NotNull GraphLineColor imagLineColor) { + this.imagLineColor = imagLineColor; + } + + private boolean toggleImagFunctions(boolean show) { + boolean changed = false; + + synchronized (functions) { + for (int i = 0; i < functions.size(); i++) { + final PlotFunction plotFunction = functions.get(i); + if (plotFunction.getXyFunction().isImag()) { + functions.set(i, show ? PlotFunction.visible(plotFunction) : PlotFunction.invisible(plotFunction)); + changed = true; + } + } + } + + return changed; + } +} diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java b/core/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java similarity index 62% rename from android-app-core/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java rename to core/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java index 98720842..90f8fa22 100644 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java +++ b/core/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java @@ -1,7 +1,5 @@ package org.solovyev.android.calculator.plot; -import android.graphics.Color; - /** * User: serso * Date: 10/4/12 @@ -9,11 +7,19 @@ import android.graphics.Color; */ public enum GraphLineColor { - white(Color.WHITE), - grey(Color.GRAY), - red(Color.RED), - blue(Color.rgb(16, 100, 140)), - green(Color.GREEN); + // Color.WHITE + white(0xFFFFFFFF), + + // Color.GRAY + grey(0xFF888888), + + // Color.RED + red(0xFFFF0000), + + blue(0xFF10648C), + + // Color.GREEN + green(0xFF00FF00); private final int color; diff --git a/core/src/main/java/org/solovyev/android/calculator/plot/PlotData.java b/core/src/main/java/org/solovyev/android/calculator/plot/PlotData.java new file mode 100644 index 00000000..2ec8c187 --- /dev/null +++ b/core/src/main/java/org/solovyev/android/calculator/plot/PlotData.java @@ -0,0 +1,28 @@ +package org.solovyev.android.calculator.plot; + +import java.util.List; + +/** + * User: serso + * Date: 1/12/13 + * Time: 10:01 PM + */ +public class PlotData { + + private List functions; + + private boolean plot3d; + + public PlotData(List functions, boolean plot3d) { + this.functions = functions; + this.plot3d = plot3d; + } + + public List getFunctions() { + return functions; + } + + public boolean isPlot3d() { + return plot3d; + } +} diff --git a/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunction.java b/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunction.java new file mode 100644 index 00000000..6b41e2f1 --- /dev/null +++ b/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunction.java @@ -0,0 +1,102 @@ +package org.solovyev.android.calculator.plot; + +import org.jetbrains.annotations.NotNull; + +/** + * User: serso + * Date: 1/12/13 + * Time: 8:45 PM + */ +public class PlotFunction { + + @NotNull + private XyFunction xyFunction; + + @NotNull + private PlotFunctionLineDef plotFunctionLineDef; + + private boolean pinned = false; + + private boolean visible = true; + + public PlotFunction(@NotNull XyFunction xyFunction) { + this.xyFunction = xyFunction; + this.plotFunctionLineDef = PlotFunctionLineDef.newDefaultInstance(); + } + + public PlotFunction(@NotNull XyFunction xyFunction, + @NotNull PlotFunctionLineDef plotFunctionLineDef) { + this.xyFunction = xyFunction; + this.plotFunctionLineDef = plotFunctionLineDef; + } + + @NotNull + private PlotFunction copy() { + final PlotFunction copy = new PlotFunction(this.xyFunction); + + copy.plotFunctionLineDef = this.plotFunctionLineDef; + copy.pinned = this.pinned; + copy.visible = this.visible; + + return copy; + } + + public static PlotFunction pin(@NotNull PlotFunction that) { + final PlotFunction copy = that.copy(); + copy.pinned = true; + return copy; + } + + public static PlotFunction unpin(@NotNull PlotFunction that) { + final PlotFunction copy = that.copy(); + copy.pinned = false; + return copy; + } + + public static PlotFunction visible(@NotNull PlotFunction that) { + final PlotFunction copy = that.copy(); + copy.visible = true; + return copy; + } + + public static PlotFunction invisible(@NotNull PlotFunction that) { + final PlotFunction copy = that.copy(); + copy.visible = false; + return copy; + } + + @NotNull + public XyFunction getXyFunction() { + return xyFunction; + } + + @NotNull + public PlotFunctionLineDef getPlotFunctionLineDef() { + return plotFunctionLineDef; + } + + public boolean isPinned() { + return pinned; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PlotFunction)) return false; + + PlotFunction that = (PlotFunction) o; + + if (!xyFunction.equals(that.xyFunction)) return false; + + return true; + } + + @Override + public int hashCode() { + return xyFunction.hashCode(); + } + + public boolean isVisible() { + return visible; + } +} diff --git a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineColorType.java b/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunctionLineColorType.java similarity index 76% rename from android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineColorType.java rename to core/src/main/java/org/solovyev/android/calculator/plot/PlotFunctionLineColorType.java index 9b2adee5..25be77e5 100644 --- a/android-app-core/src/main/java/org/solovyev/android/calculator/plot/FunctionLineColorType.java +++ b/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunctionLineColorType.java @@ -5,7 +5,7 @@ package org.solovyev.android.calculator.plot; * Date: 1/5/13 * Time: 10:45 PM */ -public enum FunctionLineColorType { +public enum PlotFunctionLineColorType { color_map, solid; diff --git a/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunctionLineDef.java b/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunctionLineDef.java new file mode 100644 index 00000000..93016d86 --- /dev/null +++ b/core/src/main/java/org/solovyev/android/calculator/plot/PlotFunctionLineDef.java @@ -0,0 +1,96 @@ +package org.solovyev.android.calculator.plot; + +import org.jetbrains.annotations.NotNull; + +/** + * User: serso + * Date: 1/5/13 + * Time: 7:41 PM + */ +public class PlotFunctionLineDef { + + /* + ********************************************************************** + * + * CONSTANTS + * + ********************************************************************** + */ + + @NotNull + public static final Float DEFAULT_LINE_WIDTH = -1f; + + private static final int WHITE = 0xFFFFFFFF; + + + /* + ********************************************************************** + * + * FIELDS + * + ********************************************************************** + */ + + @NotNull + private PlotFunctionLineColorType lineColorType = PlotFunctionLineColorType.solid; + + private int lineColor = WHITE; + + @NotNull + private PlotLineStyle lineStyle = PlotLineStyle.solid; + + private float lineWidth = -DEFAULT_LINE_WIDTH; + + private PlotFunctionLineDef() { + } + + @NotNull + public static PlotFunctionLineDef newInstance(int lineColor, @NotNull PlotLineStyle lineStyle) { + final PlotFunctionLineDef result = new PlotFunctionLineDef(); + result.lineColor = lineColor; + result.lineStyle = lineStyle; + return result; + } + + @NotNull + public static PlotFunctionLineDef newInstance(int lineColor, @NotNull PlotLineStyle lineStyle, float lineWidth) { + final PlotFunctionLineDef result = new PlotFunctionLineDef(); + result.lineColor = lineColor; + result.lineStyle = lineStyle; + result.lineWidth = lineWidth; + return result; + } + + @NotNull + public static PlotFunctionLineDef newInstance(int lineColor, @NotNull PlotLineStyle lineStyle, float lineWidth, @NotNull PlotFunctionLineColorType lineColorType) { + final PlotFunctionLineDef result = new PlotFunctionLineDef(); + result.lineColor = lineColor; + result.lineColorType = lineColorType; + result.lineStyle = lineStyle; + result.lineWidth = lineWidth; + return result; + } + + @NotNull + public static PlotFunctionLineDef newDefaultInstance() { + return new PlotFunctionLineDef(); + } + + public int getLineColor() { + return lineColor; + } + + @NotNull + public PlotLineStyle getLineStyle() { + return lineStyle; + } + + public float getLineWidth() { + return lineWidth; + } + + @NotNull + public PlotFunctionLineColorType getLineColorType() { + return lineColorType; + } +} diff --git a/core/src/main/java/org/solovyev/android/calculator/plot/PlotLineStyle.java b/core/src/main/java/org/solovyev/android/calculator/plot/PlotLineStyle.java new file mode 100644 index 00000000..e1f6a263 --- /dev/null +++ b/core/src/main/java/org/solovyev/android/calculator/plot/PlotLineStyle.java @@ -0,0 +1,16 @@ +package org.solovyev.android.calculator.plot; + + +/** + * User: serso + * Date: 1/5/13 + * Time: 7:37 PM + */ +public enum PlotLineStyle { + + solid, + dashed, + dotted, + dash_dotted; + +} diff --git a/core/src/main/java/org/solovyev/android/calculator/plot/XyFunction.java b/core/src/main/java/org/solovyev/android/calculator/plot/XyFunction.java new file mode 100644 index 00000000..e2f2a0be --- /dev/null +++ b/core/src/main/java/org/solovyev/android/calculator/plot/XyFunction.java @@ -0,0 +1,128 @@ +package org.solovyev.android.calculator.plot; + +import jscl.math.Generic; +import jscl.math.function.Constant; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class XyFunction { + + /* + ********************************************************************** + * + * FIELDS + * + ********************************************************************** + */ + + @NotNull + private final Generic expression; + + @NotNull + private String expressionString; + + @Nullable + private final Constant xVariable; + + @Nullable + private String xVariableName; + + @Nullable + private final Constant yVariable; + + private final boolean imag; + + @Nullable + private String yVariableName; + + private int arity; + + public XyFunction(@NotNull Generic expression, + @Nullable Constant xVariable, + @Nullable Constant yVariable, + boolean imag) { + this.expression = expression; + this.xVariable = xVariable; + this.yVariable = yVariable; + this.imag = imag; + + if (imag) { + this.expressionString = "Im(" + expression.toString() + ")"; + } else { + this.expressionString = expression.toString(); + } + this.xVariableName = xVariable == null ? null : xVariable.getName(); + this.yVariableName = yVariable == null ? null : yVariable.getName(); + + this.arity = 2; + if ( this.yVariableName == null ) { + this.arity--; + } + if ( this.xVariableName == null ) { + this.arity--; + } + + } + + public boolean isImag() { + return imag; + } + + public int getArity() { + return arity; + } + + @NotNull + public Generic getExpression() { + return expression; + } + + @Nullable + public Constant getXVariable() { + return xVariable; + } + + @Nullable + public Constant getYVariable() { + return yVariable; + } + + @NotNull + public String getExpressionString() { + return expressionString; + } + + @Nullable + public String getXVariableName() { + return xVariableName; + } + + @Nullable + public String getYVariableName() { + return yVariableName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof XyFunction)) return false; + + final XyFunction that = (XyFunction) o; + + if (!expressionString.equals(that.expressionString)) return false; + if (xVariableName != null ? !xVariableName.equals(that.xVariableName) : that.xVariableName != null) + return false; + if (yVariableName != null ? !yVariableName.equals(that.yVariableName) : that.yVariableName != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = expressionString.hashCode(); + result = 31 * result + (xVariableName != null ? xVariableName.hashCode() : 0); + result = 31 * result + (yVariableName != null ? yVariableName.hashCode() : 0); + return result; + } +} diff --git a/core/src/test/java/org/solovyev/android/calculator/AbstractCalculatorTest.java b/core/src/test/java/org/solovyev/android/calculator/AbstractCalculatorTest.java index fbbd7d9f..7bcc1de8 100644 --- a/core/src/test/java/org/solovyev/android/calculator/AbstractCalculatorTest.java +++ b/core/src/test/java/org/solovyev/android/calculator/AbstractCalculatorTest.java @@ -3,6 +3,7 @@ package org.solovyev.android.calculator; import org.mockito.Mockito; import org.solovyev.android.calculator.external.CalculatorExternalListenersContainer; import org.solovyev.android.calculator.history.CalculatorHistory; +import org.solovyev.android.calculator.plot.CalculatorPlotter; /** * User: serso @@ -12,7 +13,7 @@ import org.solovyev.android.calculator.history.CalculatorHistory; public class AbstractCalculatorTest { protected void setUp() throws Exception { - Locator.getInstance().init(new CalculatorImpl(), CalculatorTestUtils.newCalculatorEngine(), Mockito.mock(CalculatorClipboard.class), Mockito.mock(CalculatorNotifier.class), Mockito.mock(CalculatorHistory.class), new SystemOutCalculatorLogger(), Mockito.mock(CalculatorPreferenceService.class), Mockito.mock(CalculatorKeyboard.class), Mockito.mock(CalculatorExternalListenersContainer.class)); + Locator.getInstance().init(new CalculatorImpl(), CalculatorTestUtils.newCalculatorEngine(), Mockito.mock(CalculatorClipboard.class), Mockito.mock(CalculatorNotifier.class), Mockito.mock(CalculatorHistory.class), new SystemOutCalculatorLogger(), Mockito.mock(CalculatorPreferenceService.class), Mockito.mock(CalculatorKeyboard.class), Mockito.mock(CalculatorExternalListenersContainer.class), Mockito.mock(CalculatorPlotter.class)); Locator.getInstance().getEngine().init(); } diff --git a/core/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java b/core/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java index 758eba4d..02f0efda 100644 --- a/core/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java +++ b/core/src/test/java/org/solovyev/android/calculator/CalculatorTestUtils.java @@ -8,6 +8,7 @@ import org.mockito.Mockito; import org.solovyev.android.calculator.external.CalculatorExternalListenersContainer; import org.solovyev.android.calculator.history.CalculatorHistory; import org.solovyev.android.calculator.jscl.JsclOperation; +import org.solovyev.android.calculator.plot.CalculatorPlotter; import java.io.*; import java.util.concurrent.CountDownLatch; @@ -24,7 +25,7 @@ public class CalculatorTestUtils { public static final int TIMEOUT = 3; public static void staticSetUp() throws Exception { - Locator.getInstance().init(new CalculatorImpl(), newCalculatorEngine(), Mockito.mock(CalculatorClipboard.class), Mockito.mock(CalculatorNotifier.class), Mockito.mock(CalculatorHistory.class), new SystemOutCalculatorLogger(), Mockito.mock(CalculatorPreferenceService.class), Mockito.mock(CalculatorKeyboard.class), Mockito.mock(CalculatorExternalListenersContainer.class)); + Locator.getInstance().init(new CalculatorImpl(), newCalculatorEngine(), Mockito.mock(CalculatorClipboard.class), Mockito.mock(CalculatorNotifier.class), Mockito.mock(CalculatorHistory.class), new SystemOutCalculatorLogger(), Mockito.mock(CalculatorPreferenceService.class), Mockito.mock(CalculatorKeyboard.class), Mockito.mock(CalculatorExternalListenersContainer.class), Mockito.mock(CalculatorPlotter.class)); Locator.getInstance().getEngine().init(); }