diff --git a/calculatorpp/AndroidManifest.xml b/calculatorpp/AndroidManifest.xml index be0b0f9e..48a7312b 100644 --- a/calculatorpp/AndroidManifest.xml +++ b/calculatorpp/AndroidManifest.xml @@ -1,57 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/calculatorpp/res/values/arrays.xml b/calculatorpp/res/values/arrays.xml index acd061ab..e7b77d69 100644 --- a/calculatorpp/res/values/arrays.xml +++ b/calculatorpp/res/values/arrays.xml @@ -87,4 +87,19 @@ bin + + White + Grey + Red + Blue + Green + + + white + grey + red + blue + green + + \ No newline at end of file diff --git a/calculatorpp/res/values/theme_default.xml b/calculatorpp/res/values/theme_default.xml index d2b54117..8ddc41f3 100644 --- a/calculatorpp/res/values/theme_default.xml +++ b/calculatorpp/res/values/theme_default.xml @@ -108,6 +108,8 @@ @style/default_actionbar_tab_style + @style/default_actionbar_tab_style @style/default_actionbar_style + @style/default_actionbar_style \ No newline at end of file diff --git a/calculatorpp/res/values/theme_metro_blue.xml b/calculatorpp/res/values/theme_metro_blue.xml index 024decf0..ddfdf61f 100644 --- a/calculatorpp/res/values/theme_metro_blue.xml +++ b/calculatorpp/res/values/theme_metro_blue.xml @@ -38,7 +38,9 @@ @style/metro_blue_actionbar_tab_style + @style/metro_blue_actionbar_tab_style @style/metro_blue_actionbar_style + @style/metro_blue_actionbar_style \ No newline at end of file diff --git a/calculatorpp/res/xml/plot_preferences.xml b/calculatorpp/res/xml/plot_preferences.xml new file mode 100644 index 00000000..896b5838 --- /dev/null +++ b/calculatorpp/res/xml/plot_preferences.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelper.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelper.java index ebe5d92d..41ca265c 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelper.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelper.java @@ -1,56 +1,61 @@ -package org.solovyev.android.calculator; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.View; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.about.CalculatorFragmentType; - -/** - * User: serso - * Date: 9/25/12 - * Time: 10:31 PM - */ -public interface CalculatorActivityHelper { - - void onCreate(@NotNull SherlockFragmentActivity activity, @Nullable Bundle savedInstanceState); - void onCreate(@NotNull Activity activity, @Nullable Bundle savedInstanceState); - - void onSaveInstanceState(@NotNull SherlockFragmentActivity activity, @NotNull Bundle outState); - void onSaveInstanceState(@NotNull Activity activity, @NotNull Bundle outState); - - int getLayoutId(); - - @NotNull - CalculatorPreferences.Gui.Theme getTheme(); - - void onResume(@NotNull SherlockFragmentActivity activity); - void onResume(@NotNull Activity activity); - - void onPause(@NotNull Activity activity); - void onPause(@NotNull SherlockFragmentActivity activity); - - void onDestroy(@NotNull SherlockFragmentActivity activity); - void onDestroy(@NotNull Activity activity); - - void addTab(@NotNull SherlockFragmentActivity activity, - @NotNull String tag, - @NotNull Class fragmentClass, - @Nullable Bundle fragmentArgs, - int captionResId, - int parentViewId); - - void addTab(@NotNull SherlockFragmentActivity activity, - @NotNull CalculatorFragmentType fragmentType, - @Nullable Bundle fragmentArgs, - int parentViewId); - - - void logDebug(@NotNull String message); - - void processButtons(@NotNull Activity activity, @NotNull View root); - -} +package org.solovyev.android.calculator; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.View; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.solovyev.android.calculator.about.CalculatorFragmentType; + +/** + * User: serso + * Date: 9/25/12 + * Time: 10:31 PM + */ +public interface CalculatorActivityHelper { + + void onCreate(@NotNull SherlockFragmentActivity activity, @Nullable Bundle savedInstanceState); + void onCreate(@NotNull Activity activity, @Nullable Bundle savedInstanceState); + + void onSaveInstanceState(@NotNull SherlockFragmentActivity activity, @NotNull Bundle outState); + void onSaveInstanceState(@NotNull Activity activity, @NotNull Bundle outState); + + int getLayoutId(); + + @NotNull + CalculatorPreferences.Gui.Theme getTheme(); + + void onResume(@NotNull SherlockFragmentActivity activity); + void onResume(@NotNull Activity activity); + + void onPause(@NotNull Activity activity); + void onPause(@NotNull SherlockFragmentActivity activity); + + void onDestroy(@NotNull SherlockFragmentActivity activity); + void onDestroy(@NotNull Activity activity); + + void addTab(@NotNull SherlockFragmentActivity activity, + @NotNull String tag, + @NotNull Class fragmentClass, + @Nullable Bundle fragmentArgs, + int captionResId, + int parentViewId); + + void addTab(@NotNull SherlockFragmentActivity activity, + @NotNull CalculatorFragmentType fragmentType, + @Nullable Bundle fragmentArgs, + int parentViewId); + + void setFragment(@NotNull SherlockFragmentActivity activity, + @NotNull CalculatorFragmentType fragmentType, + @Nullable Bundle fragmentArgs, + int parentViewId); + + + void logDebug(@NotNull String message); + + void processButtons(@NotNull Activity activity, @NotNull View root); + +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelperImpl.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelperImpl.java index dcddbfe0..db7183dc 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelperImpl.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivityHelperImpl.java @@ -1,219 +1,230 @@ -package org.solovyev.android.calculator; - -import android.app.Activity; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v4.app.Fragment; -import android.util.Log; -import android.view.View; -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.AndroidUtils; -import org.solovyev.android.calculator.about.CalculatorFragmentType; -import org.solovyev.android.sherlock.tabs.ActionBarFragmentTabListener; - -/** - * User: serso - * Date: 9/25/12 - * Time: 10:32 PM - */ -public class CalculatorActivityHelperImpl extends AbstractCalculatorHelper implements CalculatorActivityHelper { - - /* - ********************************************************************** - * - * CONSTANTS - * - ********************************************************************** - */ - - /* - ********************************************************************** - * - * FIELDS - * - ********************************************************************** - */ - - private int layoutId; - - private boolean homeIcon = false; - - @NotNull - private CalculatorPreferences.Gui.Theme theme; - - private int selectedNavigationIndex = 0; - - public CalculatorActivityHelperImpl(int layoutId, @NotNull String logTag) { - super(logTag); - this.layoutId = layoutId; - } - - public CalculatorActivityHelperImpl(int layoutId, boolean homeIcon) { - this.layoutId = layoutId; - this.homeIcon = homeIcon; - } - - @Override - public void onCreate(@NotNull Activity activity, @Nullable Bundle savedInstanceState) { - super.onCreate(activity); - - if (activity instanceof CalculatorEventListener) { - CalculatorLocatorImpl.getInstance().getCalculator().addCalculatorEventListener((CalculatorEventListener) activity); - } - - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - - this.theme = CalculatorPreferences.Gui.getTheme(preferences); - activity.setTheme(this.theme.getThemeId()); - - activity.setContentView(layoutId); - - final View root = activity.findViewById(R.id.main_layout); - if (root != null) { - processButtons(activity, root); - } else { - Log.e(CalculatorActivityHelperImpl.class.getSimpleName(), "Root is null for " + activity.getClass().getName()); - } - } - - @Override - public void onCreate(@NotNull final SherlockFragmentActivity activity, @Nullable Bundle savedInstanceState) { - this.onCreate((Activity) activity, savedInstanceState); - - final ActionBar actionBar = activity.getSupportActionBar(); - actionBar.setDisplayUseLogoEnabled(false); - actionBar.setDisplayHomeAsUpEnabled(homeIcon); - actionBar.setHomeButtonEnabled(false); - actionBar.setDisplayShowHomeEnabled(true); - - toggleTitle(activity, true); - - actionBar.setIcon(R.drawable.icon_action_bar); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - } - - private void toggleTitle(@NotNull SherlockFragmentActivity activity, boolean showTitle) { - final ActionBar actionBar = activity.getSupportActionBar(); - - if (activity instanceof CalculatorActivity) { - if (AndroidUtils.getScreenOrientation(activity) == Configuration.ORIENTATION_PORTRAIT) { - actionBar.setDisplayShowTitleEnabled(true); - } else { - actionBar.setDisplayShowTitleEnabled(false); - } - } else { - actionBar.setDisplayShowTitleEnabled(showTitle); - } - } - - public void restoreSavedTab(@NotNull SherlockFragmentActivity activity) { - final ActionBar actionBar = activity.getSupportActionBar(); - if (selectedNavigationIndex >= 0 && selectedNavigationIndex < actionBar.getTabCount()) { - actionBar.setSelectedNavigationItem(selectedNavigationIndex); - } - } - - @Override - public void onSaveInstanceState(@NotNull SherlockFragmentActivity activity, @NotNull Bundle outState) { - onSaveInstanceState((Activity) activity, outState); - } - - @Override - public void onSaveInstanceState(@NotNull Activity activity, @NotNull Bundle outState) { - } - - @Override - public void onResume(@NotNull Activity activity) { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - - final CalculatorPreferences.Gui.Theme newTheme = CalculatorPreferences.Gui.theme.getPreference(preferences); - if (!theme.equals(newTheme)) { - AndroidUtils.restartActivity(activity); - } - } - - @Override - public void onPause(@NotNull Activity activity) { - } - - @Override - public void onPause(@NotNull SherlockFragmentActivity activity) { - onPause((Activity) activity); - - final int selectedNavigationIndex = activity.getSupportActionBar().getSelectedNavigationIndex(); - if (selectedNavigationIndex >= 0) { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - final SharedPreferences.Editor editor = preferences.edit(); - editor.putInt(getSavedTabPreferenceName(activity), selectedNavigationIndex); - editor.commit(); - } - - } - - @NotNull - private String getSavedTabPreferenceName(@NotNull Activity activity) { - return "tab_" + activity.getClass().getSimpleName(); - } - - @Override - public void onDestroy(@NotNull Activity activity) { - super.onDestroy(activity); - - if (activity instanceof CalculatorEventListener) { - CalculatorLocatorImpl.getInstance().getCalculator().removeCalculatorEventListener((CalculatorEventListener) activity); - } - } - - @Override - public void onDestroy(@NotNull SherlockFragmentActivity activity) { - this.onDestroy((Activity) activity); - } - - @Override - public void addTab(@NotNull SherlockFragmentActivity activity, - @NotNull String tag, - @NotNull Class fragmentClass, - @Nullable Bundle fragmentArgs, - int captionResId, - int parentViewId) { - final ActionBar actionBar = activity.getSupportActionBar(); - - final ActionBar.Tab tab = actionBar.newTab(); - tab.setTag(tag); - tab.setText(captionResId); - - final ActionBarFragmentTabListener listener = new ActionBarFragmentTabListener(activity, tag, fragmentClass, fragmentArgs, parentViewId); - tab.setTabListener(listener); - actionBar.addTab(tab); - } - - @Override - public void addTab(@NotNull SherlockFragmentActivity activity, @NotNull CalculatorFragmentType fragmentType, @Nullable Bundle fragmentArgs, int parentViewId) { - addTab(activity, fragmentType.getFragmentTag(), fragmentType.getFragmentClass(), fragmentArgs, fragmentType.getDefaultTitleResId(), parentViewId); - } - - @Override - public int getLayoutId() { - return layoutId; - } - - @Override - @NotNull - public CalculatorPreferences.Gui.Theme getTheme() { - return theme; - } - - @Override - public void onResume(@NotNull SherlockFragmentActivity activity) { - onResume((Activity) activity); - - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - selectedNavigationIndex = preferences.getInt(getSavedTabPreferenceName(activity), -1); - restoreSavedTab(activity); - } -} +package org.solovyev.android.calculator; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.View; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.solovyev.android.AndroidUtils; +import org.solovyev.android.calculator.about.CalculatorFragmentType; +import org.solovyev.android.sherlock.tabs.ActionBarFragmentTabListener; + +/** + * User: serso + * Date: 9/25/12 + * Time: 10:32 PM + */ +public class CalculatorActivityHelperImpl extends AbstractCalculatorHelper implements CalculatorActivityHelper { + + /* + ********************************************************************** + * + * CONSTANTS + * + ********************************************************************** + */ + + /* + ********************************************************************** + * + * FIELDS + * + ********************************************************************** + */ + + private int layoutId; + + private boolean homeIcon = false; + + @NotNull + private CalculatorPreferences.Gui.Theme theme; + + private int selectedNavigationIndex = 0; + + public CalculatorActivityHelperImpl(int layoutId, @NotNull String logTag) { + super(logTag); + this.layoutId = layoutId; + } + + public CalculatorActivityHelperImpl(int layoutId, boolean homeIcon) { + this.layoutId = layoutId; + this.homeIcon = homeIcon; + } + + @Override + public void onCreate(@NotNull Activity activity, @Nullable Bundle savedInstanceState) { + super.onCreate(activity); + + if (activity instanceof CalculatorEventListener) { + CalculatorLocatorImpl.getInstance().getCalculator().addCalculatorEventListener((CalculatorEventListener) activity); + } + + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + + this.theme = CalculatorPreferences.Gui.getTheme(preferences); + activity.setTheme(this.theme.getThemeId()); + + activity.setContentView(layoutId); + + final View root = activity.findViewById(R.id.main_layout); + if (root != null) { + processButtons(activity, root); + } else { + Log.e(CalculatorActivityHelperImpl.class.getSimpleName(), "Root is null for " + activity.getClass().getName()); + } + } + + @Override + public void onCreate(@NotNull final SherlockFragmentActivity activity, @Nullable Bundle savedInstanceState) { + this.onCreate((Activity) activity, savedInstanceState); + + final ActionBar actionBar = activity.getSupportActionBar(); + actionBar.setDisplayUseLogoEnabled(false); + actionBar.setDisplayHomeAsUpEnabled(homeIcon); + actionBar.setHomeButtonEnabled(false); + actionBar.setDisplayShowHomeEnabled(true); + + toggleTitle(activity, true); + + actionBar.setIcon(R.drawable.icon_action_bar); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + } + + private void toggleTitle(@NotNull SherlockFragmentActivity activity, boolean showTitle) { + final ActionBar actionBar = activity.getSupportActionBar(); + + if (activity instanceof CalculatorActivity) { + if (AndroidUtils.getScreenOrientation(activity) == Configuration.ORIENTATION_PORTRAIT) { + actionBar.setDisplayShowTitleEnabled(true); + } else { + actionBar.setDisplayShowTitleEnabled(false); + } + } else { + actionBar.setDisplayShowTitleEnabled(showTitle); + } + } + + public void restoreSavedTab(@NotNull SherlockFragmentActivity activity) { + final ActionBar actionBar = activity.getSupportActionBar(); + if (selectedNavigationIndex >= 0 && selectedNavigationIndex < actionBar.getTabCount()) { + actionBar.setSelectedNavigationItem(selectedNavigationIndex); + } + } + + @Override + public void onSaveInstanceState(@NotNull SherlockFragmentActivity activity, @NotNull Bundle outState) { + onSaveInstanceState((Activity) activity, outState); + } + + @Override + public void onSaveInstanceState(@NotNull Activity activity, @NotNull Bundle outState) { + } + + @Override + public void onResume(@NotNull Activity activity) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + + final CalculatorPreferences.Gui.Theme newTheme = CalculatorPreferences.Gui.theme.getPreference(preferences); + if (!theme.equals(newTheme)) { + AndroidUtils.restartActivity(activity); + } + } + + @Override + public void onPause(@NotNull Activity activity) { + } + + @Override + public void onPause(@NotNull SherlockFragmentActivity activity) { + onPause((Activity) activity); + + final int selectedNavigationIndex = activity.getSupportActionBar().getSelectedNavigationIndex(); + if (selectedNavigationIndex >= 0) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + final SharedPreferences.Editor editor = preferences.edit(); + editor.putInt(getSavedTabPreferenceName(activity), selectedNavigationIndex); + editor.commit(); + } + + } + + @NotNull + private String getSavedTabPreferenceName(@NotNull Activity activity) { + return "tab_" + activity.getClass().getSimpleName(); + } + + @Override + public void onDestroy(@NotNull Activity activity) { + super.onDestroy(activity); + + if (activity instanceof CalculatorEventListener) { + CalculatorLocatorImpl.getInstance().getCalculator().removeCalculatorEventListener((CalculatorEventListener) activity); + } + } + + @Override + public void onDestroy(@NotNull SherlockFragmentActivity activity) { + this.onDestroy((Activity) activity); + } + + @Override + public void addTab(@NotNull SherlockFragmentActivity activity, + @NotNull String tag, + @NotNull Class fragmentClass, + @Nullable Bundle fragmentArgs, + int captionResId, + int parentViewId) { + final ActionBar actionBar = activity.getSupportActionBar(); + + final ActionBar.Tab tab = actionBar.newTab(); + tab.setTag(tag); + tab.setText(captionResId); + + final ActionBarFragmentTabListener listener = new ActionBarFragmentTabListener(activity, tag, fragmentClass, fragmentArgs, parentViewId); + tab.setTabListener(listener); + actionBar.addTab(tab); + } + + @Override + public void addTab(@NotNull SherlockFragmentActivity activity, @NotNull CalculatorFragmentType fragmentType, @Nullable Bundle fragmentArgs, int parentViewId) { + addTab(activity, fragmentType.getFragmentTag(), fragmentType.getFragmentClass(), fragmentArgs, fragmentType.getDefaultTitleResId(), parentViewId); + } + + @Override + public void setFragment(@NotNull SherlockFragmentActivity activity, @NotNull CalculatorFragmentType fragmentType, @Nullable Bundle fragmentArgs, int parentViewId) { + final Fragment fragment = Fragment.instantiate(activity, fragmentType.getFragmentClass().getName(), fragmentArgs); + final FragmentManager fm = activity.getSupportFragmentManager(); + final FragmentTransaction ft = fm.beginTransaction(); + ft.add(parentViewId, fragment); + ft.commit(); + } + + @Override + public int getLayoutId() { + return layoutId; + } + + @Override + @NotNull + public CalculatorPreferences.Gui.Theme getTheme() { + return theme; + } + + @Override + public void onResume(@NotNull SherlockFragmentActivity activity) { + onResume((Activity) activity); + + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + selectedNavigationIndex = preferences.getInt(getSavedTabPreferenceName(activity), -1); + restoreSavedTab(activity); + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java index dd199437..587e4d04 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferences.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.NotNull; 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.prefs.BooleanPreference; import org.solovyev.android.prefs.IntegerPreference; import org.solovyev.android.prefs.Preference; @@ -98,7 +99,9 @@ public final class CalculatorPreferences { } public static class Graph { - public static final Preference showComplexGraph = new BooleanPreference("show_complex_graph", false); + public static final Preference interpolate = new BooleanPreference("graph_interpolate", true); + public static final Preference lineColorReal = StringPreference.newInstance("graph_line_color_real", GraphLineColor.white, GraphLineColor.class); + public static final Preference lineColorImag = StringPreference.newInstance("graph_line_color_imag", GraphLineColor.blue, GraphLineColor.class); } diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java index a3212e24..98926673 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorPreferencesActivity.java @@ -39,7 +39,10 @@ public class CalculatorPreferencesActivity extends SherlockPreferenceActivity im protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.preferences); + //noinspection deprecation + addPreferencesFromResource(R.xml.preferences); + //noinspection deprecation + addPreferencesFromResource(R.xml.plot_preferences); final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY); adFreePreference.setEnabled(false); diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java index 078806b0..439f2543 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotActivity.java @@ -1,32 +1,34 @@ -package org.solovyev.android.calculator.plot; - -import android.content.Intent; -import android.os.Bundle; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.CalculatorFragmentActivity; -import org.solovyev.android.calculator.R; -import org.solovyev.android.calculator.about.CalculatorFragmentType; - -/** - * User: serso - * Date: 9/30/12 - * Time: 4:56 PM - */ -public class CalculatorPlotActivity extends CalculatorFragmentActivity { - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final Intent intent = getIntent(); - - final Bundle arguments; - if (intent != null) { - arguments = intent.getExtras(); - } else { - arguments = null; - } - - getActivityHelper().addTab(this, CalculatorFragmentType.plotter, arguments, R.id.main_layout); - } -} +package org.solovyev.android.calculator.plot; + +import android.app.ActionBar; +import android.content.Intent; +import android.os.Bundle; +import org.jetbrains.annotations.Nullable; +import org.solovyev.android.calculator.CalculatorFragmentActivity; +import org.solovyev.android.calculator.R; +import org.solovyev.android.calculator.about.CalculatorFragmentType; + +/** + * User: serso + * Date: 9/30/12 + * Time: 4:56 PM + */ +public class CalculatorPlotActivity extends CalculatorFragmentActivity { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Intent intent = getIntent(); + + final Bundle arguments; + if (intent != null) { + arguments = intent.getExtras(); + } else { + arguments = null; + } + + getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + getActivityHelper().setFragment(this, CalculatorFragmentType.plotter, arguments, R.id.main_layout); + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java index 2636a2fa..0c1f675d 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotFragment.java @@ -1,516 +1,543 @@ -/* - * 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.graphics.Color; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; -import com.actionbarsherlock.app.SherlockFragment; -import jscl.math.Expression; -import jscl.math.Generic; -import jscl.math.function.Constant; -import jscl.text.ParseException; -import org.achartengine.GraphicalView; -import org.achartengine.chart.CubicLineChart; -import org.achartengine.chart.PointStyle; -import org.achartengine.chart.XYChart; -import org.achartengine.model.XYMultipleSeriesDataset; -import org.achartengine.model.XYSeries; -import org.achartengine.renderer.BasicStroke; -import org.achartengine.renderer.XYMultipleSeriesRenderer; -import org.achartengine.renderer.XYSeriesRenderer; -import org.achartengine.tools.PanListener; -import org.achartengine.tools.ZoomEvent; -import org.achartengine.tools.ZoomListener; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.*; -import org.solovyev.common.MutableObject; -import org.solovyev.common.collections.CollectionsUtils; - -import java.io.Serializable; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * User: serso - * Date: 12/1/11 - * Time: 12:40 AM - */ -public class CalculatorPlotFragment extends SherlockFragment implements CalculatorEventListener { - - private static final String TAG = CalculatorPlotFragment.class.getSimpleName(); - - private static final int DEFAULT_NUMBER_OF_STEPS = 100; - - private static final int DEFAULT_MIN_NUMBER = -10; - - private static final int DEFAULT_MAX_NUMBER = 10; - - public static final String INPUT = "plotter_input"; - private static final String PLOT_BOUNDARIES = "plot_boundaries"; - - public static final long EVAL_DELAY_MILLIS = 200; - - private XYChart chart; - - /** - * The encapsulated graphical view. - */ - private GraphicalView graphicalView; - - @NotNull - private Generic expression; - - @NotNull - private Constant variable; - - @NotNull - private final CalculatorFragmentHelper fragmentHelper = CalculatorApplication.getInstance().createFragmentHelper(R.layout.plot_fragment, R.string.c_graph, false); - - @NotNull - private final Executor plotExecutor = Executors.newSingleThreadExecutor(); - - @NotNull - private final Handler uiHandler = new Handler(); - - @Nullable - private Input input = null; - - private boolean inputFromArgs = true; - - @NotNull - private CalculatorEventData lastCalculatorEventData = CalculatorUtils.createFirstEventDataId(); - - private int bgColor; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - this.fragmentHelper.onCreate(this); - - - final Bundle arguments = getArguments(); - - if (arguments != null) { - input = (Input) arguments.getSerializable(INPUT); - } - - if (input == null) { - inputFromArgs = false; - createInputFromDisplayState(CalculatorLocatorImpl.getInstance().getDisplay().getViewState()); - this.bgColor = getResources().getColor(R.color.pane_background); - } else { - this.bgColor = getResources().getColor(android.R.color.transparent); - prepareData(); - } - - setRetainInstance(true); - } - - private void createInputFromDisplayState(@NotNull CalculatorDisplayViewState displayState) { - try { - if (displayState.isValid() && displayState.getResult() != null) { - final Generic expression = displayState.getResult(); - if (CalculatorUtils.isPlotPossible(expression, displayState.getOperation())) { - final Constant constant = CollectionsUtils.getFirstCollectionElement(CalculatorUtils.getNotSystemConstants(expression)); - input = new Input(expression.toString(), constant.getName()); - - prepareData(); - } - } - } catch (RuntimeException e) { - this.input = null; - Log.e(TAG, e.getLocalizedMessage(), e); - } - } - - private void prepareData(){ - try { - if (input != null) { - final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression()); - this.expression = Expression.valueOf(preparedExpression.getExpression()); - this.variable = new Constant(input.getVariableName()); - - this.chart = prepareChart(getMinValue(null), getMaxValue(null), this.expression, variable, bgColor); - } - } catch (ParseException e) { - this.input = null; - Toast.makeText(this.getActivity(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } catch (CalculatorParseException e) { - this.input = null; - Toast.makeText(this.getActivity(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return this.fragmentHelper.onCreateView(this, inflater, container); - } - - @Override - public void onViewCreated(View root, Bundle savedInstanceState) { - super.onViewCreated(root, savedInstanceState); - - this.fragmentHelper.onViewCreated(this, root); - - - PlotBoundaries plotBoundaries = null; - /*if ( savedInstanceState != null ) { - final Object object = savedInstanceState.getSerializable(PLOT_BOUNDARIES); - if ( object instanceof PlotBoundaries) { - plotBoundaries = ((PlotBoundaries) object); - } - }*/ - - updateGraphicalView(root, plotBoundaries); - } - - @Override - public void onSaveInstanceState(Bundle out) { - super.onSaveInstanceState(out); - - /*if (chart != null) { - out.putSerializable(PLOT_BOUNDARIES, new PlotBoundaries(chart.getRenderer())); - }*/ - } - - @Override - public void onResume() { - super.onResume(); - - this.fragmentHelper.onResume(this); - - if ( !inputFromArgs ) { - createInputFromDisplayState(CalculatorLocatorImpl.getInstance().getDisplay().getViewState()); - updateGraphicalView(getView(), null); - } - } - - @Override - public void onPause() { - this.fragmentHelper.onPause(this); - - super.onPause(); - } - - private void updateGraphicalView(@NotNull View root, @Nullable PlotBoundaries plotBoundaries) { - if (input != null) { - setGraphicalView(root, plotBoundaries); - } else { - Toast.makeText(this.getActivity(), "Plot is not possible!", Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onDestroy() { - this.fragmentHelper.onDestroy(this); - - super.onDestroy(); - } - - private void setGraphicalView(@NotNull View root, @Nullable PlotBoundaries plotBoundaries) { - double minValue = getMinValue(plotBoundaries); - double maxValue = getMaxValue(plotBoundaries); - - final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout); - - if (graphicalView != null) { - graphContainer.removeView(graphicalView); - } - - // reverting boundaries (as in prepareChart() we add some cached values ) - double minX = Double.MAX_VALUE; - double minY = Double.MAX_VALUE; - - double maxX = Double.MIN_VALUE; - double maxY = Double.MIN_VALUE; - - for (XYSeries series : chart.getDataset().getSeries()) { - minX = Math.min(minX, series.getMinX()); - minY = Math.min(minY, series.getMinY()); - maxX = Math.max(maxX, series.getMaxX()); - maxY = Math.max(maxY, series.getMaxY()); - } - - Log.d(CalculatorPlotFragment.class.getName(), "min x: " + minX + ", min y: " + minY + ", max x: " + maxX + ", max y: " + maxY); - Log.d(CalculatorPlotFragment.class.getName(), "Plot boundaries are " + plotBoundaries); - - - if (plotBoundaries == null) { - chart.getRenderer().setXAxisMin(Math.max(minX, minValue)); - chart.getRenderer().setYAxisMin(Math.max(minY, minValue)); - chart.getRenderer().setXAxisMax(Math.min(maxX, maxValue)); - chart.getRenderer().setYAxisMax(Math.min(maxY, maxValue)); - } else { - chart.getRenderer().setXAxisMin(plotBoundaries.xMin); - chart.getRenderer().setYAxisMin(plotBoundaries.yMin); - chart.getRenderer().setXAxisMax(plotBoundaries.xMax); - chart.getRenderer().setYAxisMax(plotBoundaries.yMax); - } - - graphicalView = new GraphicalView(this.getActivity(), chart); - graphicalView.setBackgroundColor(this.bgColor); - - 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() { - Log.d(TAG, "org.achartengine.tools.PanListener.panApplied"); - updateDataSets(chart); - } - - }); - graphContainer.addView(graphicalView); - - updateDataSets(chart, 50); - } - - private double getMaxValue(@Nullable PlotBoundaries plotBoundaries) { - return plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax; - } - - private double getMinValue(@Nullable PlotBoundaries plotBoundaries) { - return plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin; - } - - - private void updateDataSets(@NotNull final XYChart chart) { - updateDataSets(chart, EVAL_DELAY_MILLIS); - } - - private void updateDataSets(@NotNull final XYChart chart, long millisToWait) { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity()); - final boolean showComplexGraph = CalculatorPreferences.Graph.showComplexGraph.getPreference(preferences); - - 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) { - - plotExecutor.execute(new Runnable() { - @Override - public void run() { - Log.d(TAG, "org.solovyev.android.calculator.plot.CalculatorPlotActivity.updateDataSets"); - - final XYMultipleSeriesRenderer dr = chart.getRenderer(); - - //Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]"); - - final MyXYSeries realSeries = (MyXYSeries) chart.getDataset().getSeriesAt(0); - - final MyXYSeries imagSeries; - if (chart.getDataset().getSeriesCount() > 1) { - imagSeries = (MyXYSeries) chart.getDataset().getSeriesAt(1); - } else { - imagSeries = new MyXYSeries(getImagFunctionName(CalculatorPlotFragment.this.variable), DEFAULT_NUMBER_OF_STEPS * 2); - } - - try { - if (PlotUtils.addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true, DEFAULT_NUMBER_OF_STEPS)) { - if (chart.getDataset().getSeriesCount() <= 1) { - chart.getDataset().addSeries(imagSeries); - chart.getRenderer().addSeriesRenderer(createImagRenderer()); - } - } - } catch (ArithmeticException e) { - // todo serso: translate - Toast.makeText(CalculatorPlotFragment.this.getActivity(), "Arithmetic error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } - - if (pendingOperation.getObject() == this) { - uiHandler.post(new Runnable() { - @Override - public void run() { - graphicalView.repaint(); - } - }); - } - } - }); - } - } - } - }); - - - uiHandler.postDelayed(pendingOperation.getObject(), millisToWait); - } - - @NotNull - private static String getImagFunctionName(@NotNull Constant variable) { - return "g(" + variable.getName() + ")" + " = " + "Im(ƒ(" + variable.getName() + "))"; - } - - @NotNull - private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) { - return "ƒ(" + variable.getName() + ")" + " = " + expression.toString(); - } - - @NotNull - private final MutableObject pendingOperation = new MutableObject(); - - private static XYChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, @NotNull final Constant variable, int bgColor) { - final MyXYSeries realSeries = new MyXYSeries(getRealFunctionName(expression, variable), DEFAULT_NUMBER_OF_STEPS * 2); - final MyXYSeries imagSeries = new MyXYSeries(getImagFunctionName(variable), DEFAULT_NUMBER_OF_STEPS * 2); - - boolean imagExists = PlotUtils.addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false, DEFAULT_NUMBER_OF_STEPS); - - final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset(); - data.addSeries(realSeries); - if (imagExists) { - data.addSeries(imagSeries); - } - - final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); - renderer.setShowGrid(true); - renderer.setXTitle(variable.getName()); - renderer.setYTitle("f(" + variable.getName() + ")"); - renderer.setChartTitleTextSize(20); - renderer.setApplyBackgroundColor(true); - renderer.setBackgroundColor(bgColor); - renderer.setMarginsColor(bgColor); - - renderer.setZoomEnabled(true); - renderer.setZoomButtonsVisible(true); - - renderer.addSeriesRenderer(createCommonRenderer()); - if (imagExists) { - renderer.addSeriesRenderer(createImagRenderer()); - } - - return new CubicLineChart(data, renderer, 0.1f); - //return new ScatterChart(data, renderer); - } - - private static XYSeriesRenderer createImagRenderer() { - final XYSeriesRenderer imagRenderer = createCommonRenderer(); - imagRenderer.setStroke(BasicStroke.DASHED); - imagRenderer.setColor(Color.LTGRAY); - return imagRenderer; - } - - @Override - public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable final Object data) { - if ( calculatorEventType.isOfType(CalculatorEventType.display_state_changed) ) { - if ( !inputFromArgs ) { - if ( calculatorEventData.isAfter(this.lastCalculatorEventData) ) { - this.lastCalculatorEventData = calculatorEventData; - - createInputFromDisplayState(((CalculatorDisplayChangeEventData) data).getNewValue()); - - uiHandler.post(new Runnable() { - @Override - public void run() { - final View view = getView(); - if (view != null) { - updateGraphicalView(view, null); - } - } - }); - } - } - } - } - - /*@Override - public Object onRetainNonConfigurationInstance() { - return new PlotBoundaries(chart.getRenderer()); - }*/ - - private static final class PlotBoundaries implements Serializable { - - private final double xMin; - private final double xMax; - private final double yMin; - private final double yMax; - - public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) { - this.xMin = renderer.getXAxisMin(); - this.yMin = renderer.getYAxisMin(); - this.xMax = renderer.getXAxisMax(); - this.yMax = renderer.getYAxisMax(); - } - - @Override - public String toString() { - return "PlotBoundaries{" + - "yMax=" + yMax + - ", yMin=" + yMin + - ", xMax=" + xMax + - ", xMin=" + xMin + - '}'; - } - } - - - @NotNull - private static XYSeriesRenderer createCommonRenderer() { - final XYSeriesRenderer renderer = new XYSeriesRenderer(); - renderer.setFillPoints(true); - renderer.setPointStyle(PointStyle.CIRCLE); - renderer.setLineWidth(3); - renderer.setColor(Color.WHITE); - renderer.setStroke(BasicStroke.SOLID); - return renderer; - } - - public void zoomInClickHandler(@NotNull View v) { - this.graphicalView.zoomIn(); - } - - public void zoomOutClickHandler(@NotNull View v) { - this.graphicalView.zoomOut(); - } - - - public static class Input implements Serializable { - - @NotNull - private String expression; - - @NotNull - private String variableName; - - public Input(@NotNull String expression, @NotNull String variableName) { - this.expression = expression; - this.variableName = variableName; - } - - @NotNull - public String getExpression() { - return expression; - } - - @NotNull - public String getVariableName() { - return variableName; - } - } -} +/* + * 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.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; +import com.actionbarsherlock.app.SherlockFragment; +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.GraphicalView; +import org.achartengine.chart.XYChart; +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.*; +import org.solovyev.android.menu.ActivityMenu; +import org.solovyev.android.menu.LabeledMenuItem; +import org.solovyev.android.menu.ListActivityMenu; +import org.solovyev.android.sherlock.menu.SherlockMenuHelper; +import org.solovyev.common.MutableObject; +import org.solovyev.common.collections.CollectionsUtils; + +import java.io.Serializable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * User: serso + * Date: 12/1/11 + * Time: 12:40 AM + */ +public class CalculatorPlotFragment extends SherlockFragment implements CalculatorEventListener, SharedPreferences.OnSharedPreferenceChangeListener { + + private static final String TAG = CalculatorPlotFragment.class.getSimpleName(); + + private static final int DEFAULT_MIN_NUMBER = -10; + + private static final int DEFAULT_MAX_NUMBER = 10; + + public static final String INPUT = "plotter_input"; + private static final String PLOT_BOUNDARIES = "plot_boundaries"; + + public static final long EVAL_DELAY_MILLIS = 200; + + private XYChart chart; + + /** + * The encapsulated graphical view. + */ + private GraphicalView graphicalView; + + @NotNull + private Generic expression; + + @NotNull + private Constant variable; + + @NotNull + private final CalculatorFragmentHelper fragmentHelper = CalculatorApplication.getInstance().createFragmentHelper(R.layout.plot_fragment, R.string.c_graph, false); + + @NotNull + private final Executor plotExecutor = Executors.newSingleThreadExecutor(); + + @NotNull + private final Handler uiHandler = new Handler(); + + @Nullable + private Input input = null; + + private boolean inputFromArgs = true; + + @NotNull + private CalculatorEventData lastCalculatorEventData = CalculatorUtils.createFirstEventDataId(); + + private int bgColor; + + @Nullable + private PlotBoundaries plotBoundaries = null; + + @NotNull + private ActivityMenu fragmentMenu = ListActivityMenu.fromList(PlotMenu.class, SherlockMenuHelper.getInstance()); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + this.fragmentHelper.onCreate(this); + + + final Bundle arguments = getArguments(); + + if (arguments != null) { + input = (Input) arguments.getSerializable(INPUT); + } + + if (input == null) { + inputFromArgs = false; + createInputFromDisplayState(CalculatorLocatorImpl.getInstance().getDisplay().getViewState()); + this.bgColor = getResources().getColor(R.color.pane_background); + } else { + this.bgColor = getResources().getColor(android.R.color.transparent); + prepareData(); + } + + PreferenceManager.getDefaultSharedPreferences(this.getActivity()).registerOnSharedPreferenceChangeListener(this); + + setRetainInstance(true); + setHasOptionsMenu(true); + } + + private void createInputFromDisplayState(@NotNull CalculatorDisplayViewState displayState) { + try { + if (displayState.isValid() && displayState.getResult() != null) { + final Generic expression = displayState.getResult(); + if (CalculatorUtils.isPlotPossible(expression, displayState.getOperation())) { + final Constant constant = CollectionsUtils.getFirstCollectionElement(CalculatorUtils.getNotSystemConstants(expression)); + input = new Input(expression.toString(), constant.getName()); + + prepareData(); + } + } + } catch (RuntimeException e) { + this.input = null; + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + private void prepareData(){ + try { + if (input != null) { + final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression()); + this.expression = Expression.valueOf(preparedExpression.getExpression()); + this.variable = new Constant(input.getVariableName()); + + initChart(); + } + } catch (ParseException e) { + this.input = null; + Toast.makeText(this.getActivity(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } catch (CalculatorParseException e) { + this.input = null; + Toast.makeText(this.getActivity(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + } + + private void initChart() { + if (input != null) { + 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); + + this.chart = PlotUtils.prepareChart(getMinValue(null), getMaxValue(null), this.expression, variable, bgColor, interpolate, realLineColor.getColor(), imagLineColor.getColor()); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return this.fragmentHelper.onCreateView(this, inflater, container); + } + + @Override + public void onViewCreated(View root, Bundle savedInstanceState) { + super.onViewCreated(root, savedInstanceState); + + this.fragmentHelper.onViewCreated(this, root); + + + /*if ( savedInstanceState != null ) { + final Object object = savedInstanceState.getSerializable(PLOT_BOUNDARIES); + if ( object instanceof PlotBoundaries) { + plotBoundaries = ((PlotBoundaries) object); + } + }*/ + + updateGraphicalView(root, plotBoundaries); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + /*if (chart != null) { + out.putSerializable(PLOT_BOUNDARIES, new PlotBoundaries(chart.getRenderer())); + }*/ + } + + @Override + public void onResume() { + super.onResume(); + + this.fragmentHelper.onResume(this); + + if ( !inputFromArgs ) { + createInputFromDisplayState(CalculatorLocatorImpl.getInstance().getDisplay().getViewState()); + updateGraphicalView(getView(), null); + } + } + + @Override + public void onPause() { + this.fragmentHelper.onPause(this); + + super.onPause(); + } + + private void updateGraphicalView(@NotNull View root, @Nullable PlotBoundaries plotBoundaries) { + if (input != null) { + setGraphicalView(root, plotBoundaries); + } else { + Toast.makeText(this.getActivity(), "Plot is not possible!", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onDestroy() { + this.fragmentHelper.onDestroy(this); + + PreferenceManager.getDefaultSharedPreferences(this.getActivity()).unregisterOnSharedPreferenceChangeListener(this); + + super.onDestroy(); + } + + private void setGraphicalView(@NotNull View root, @Nullable PlotBoundaries plotBoundaries) { + double minValue = getMinValue(plotBoundaries); + double maxValue = getMaxValue(plotBoundaries); + + final ViewGroup graphContainer = (ViewGroup) root.findViewById(R.id.main_fragment_layout); + + if (graphicalView != null) { + graphContainer.removeView(graphicalView); + } + + // reverting boundaries (as in prepareChart() we add some cached values ) + double minX = Double.MAX_VALUE; + double minY = Double.MAX_VALUE; + + double maxX = Double.MIN_VALUE; + double maxY = Double.MIN_VALUE; + + for (XYSeries series : chart.getDataset().getSeries()) { + minX = Math.min(minX, series.getMinX()); + minY = Math.min(minY, series.getMinY()); + maxX = Math.max(maxX, series.getMaxX()); + maxY = Math.max(maxY, series.getMaxY()); + } + + Log.d(CalculatorPlotFragment.class.getName(), "min x: " + minX + ", min y: " + minY + ", max x: " + maxX + ", max y: " + maxY); + Log.d(CalculatorPlotFragment.class.getName(), "Plot boundaries are " + plotBoundaries); + + + if (plotBoundaries == null) { + chart.getRenderer().setXAxisMin(Math.max(minX, minValue)); + chart.getRenderer().setYAxisMin(Math.max(minY, minValue)); + chart.getRenderer().setXAxisMax(Math.min(maxX, maxValue)); + chart.getRenderer().setYAxisMax(Math.min(maxY, maxValue)); + } else { + chart.getRenderer().setXAxisMin(plotBoundaries.xMin); + chart.getRenderer().setYAxisMin(plotBoundaries.yMin); + chart.getRenderer().setXAxisMax(plotBoundaries.xMax); + chart.getRenderer().setYAxisMax(plotBoundaries.yMax); + } + + graphicalView = new GraphicalView(this.getActivity(), chart); + graphicalView.setBackgroundColor(this.bgColor); + + 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() { + Log.d(TAG, "org.achartengine.tools.PanListener.panApplied"); + updateDataSets(chart); + } + + }); + graphContainer.addView(graphicalView); + + updateDataSets(chart, 50); + } + + private double getMaxValue(@Nullable PlotBoundaries plotBoundaries) { + return plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax; + } + + private double getMinValue(@Nullable PlotBoundaries plotBoundaries) { + return plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin; + } + + + private void updateDataSets(@NotNull final XYChart chart) { + updateDataSets(chart, EVAL_DELAY_MILLIS); + } + + private void updateDataSets(@NotNull final XYChart chart, long millisToWait) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getActivity()); + final GraphLineColor imagLineColor = CalculatorPreferences.Graph.lineColorImag.getPreference(preferences); + + 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) { + + plotExecutor.execute(new Runnable() { + @Override + public void run() { + Log.d(TAG, "org.solovyev.android.calculator.plot.CalculatorPlotActivity.updateDataSets"); + + final XYMultipleSeriesRenderer dr = chart.getRenderer(); + + //Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]"); + + final MyXYSeries realSeries = (MyXYSeries) chart.getDataset().getSeriesAt(0); + + final MyXYSeries imagSeries; + if (chart.getDataset().getSeriesCount() > 1) { + imagSeries = (MyXYSeries) chart.getDataset().getSeriesAt(1); + } else { + imagSeries = new MyXYSeries(PlotUtils.getImagFunctionName(CalculatorPlotFragment.this.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 (chart.getDataset().getSeriesCount() <= 1) { + chart.getDataset().addSeries(imagSeries); + chart.getRenderer().addSeriesRenderer(PlotUtils.createImagRenderer(imagLineColor.getColor())); + } + } + } catch (ArithmeticException e) { + // todo serso: translate + Toast.makeText(CalculatorPlotFragment.this.getActivity(), "Arithmetic error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + + if (pendingOperation.getObject() == this) { + uiHandler.post(new Runnable() { + @Override + public void run() { + graphicalView.repaint(); + } + }); + } + } + }); + } + } + } + }); + + + uiHandler.postDelayed(pendingOperation.getObject(), millisToWait); + } + + @NotNull + private final MutableObject pendingOperation = new MutableObject(); + + @Override + public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable final Object data) { + if ( calculatorEventType.isOfType(CalculatorEventType.display_state_changed) ) { + if ( !inputFromArgs ) { + if ( calculatorEventData.isAfter(this.lastCalculatorEventData) ) { + this.lastCalculatorEventData = calculatorEventData; + + createInputFromDisplayState(((CalculatorDisplayChangeEventData) data).getNewValue()); + + uiHandler.post(new Runnable() { + @Override + public void run() { + final View view = getView(); + if (view != null) { + updateGraphicalView(view, null); + } + } + }); + } + } + } + } + + @Override + public void onSharedPreferenceChanged(@NotNull SharedPreferences preferences, @NotNull String key) { + if ( CalculatorPreferences.Graph.interpolate.getKey().equals(key) || + CalculatorPreferences.Graph.lineColorReal.getKey().equals(key) || + CalculatorPreferences.Graph.lineColorImag.getKey().equals(key)) { + initChart(); + updateGraphicalView(getView(), plotBoundaries); + } + } + + /*@Override + public Object onRetainNonConfigurationInstance() { + return new PlotBoundaries(chart.getRenderer()); + }*/ + + + public void zoomInClickHandler(@NotNull View v) { + this.graphicalView.zoomIn(); + } + + public void zoomOutClickHandler(@NotNull View v) { + this.graphicalView.zoomOut(); + } + + /* + ********************************************************************** + * + * MENU + * + ********************************************************************** + */ + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + + fragmentMenu.onCreateOptionsMenu(this.getActivity(), menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + fragmentMenu.onPrepareOptionsMenu(this.getActivity(), menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return super.onOptionsItemSelected(item) || fragmentMenu.onOptionsItemSelected(this.getActivity(), item); + } + + /* + ********************************************************************** + * + * STATIC + * + ********************************************************************** + */ + + private static enum PlotMenu implements LabeledMenuItem { + + preferences(R.string.c_settings) { + @Override + public void onClick(@NotNull MenuItem data, @NotNull Context context) { + context.startActivity(new Intent(context, CalculatorPlotPreferenceActivity.class)); + } + }; + + private final int captionResId; + + private PlotMenu(int captionResId) { + this.captionResId = captionResId; + } + + @NotNull + @Override + public String getCaption(@NotNull Context context) { + return context.getString(captionResId); + } + } + + private static final class PlotBoundaries implements Serializable { + + private final double xMin; + private final double xMax; + private final double yMin; + private final double yMax; + + public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) { + this.xMin = renderer.getXAxisMin(); + this.yMin = renderer.getYAxisMin(); + this.xMax = renderer.getXAxisMax(); + this.yMax = renderer.getYAxisMax(); + } + + @Override + public String toString() { + return "PlotBoundaries{" + + "yMax=" + yMax + + ", yMin=" + yMin + + ", xMax=" + xMax + + ", xMin=" + xMin + + '}'; + } + } + + public static class Input implements Serializable { + + @NotNull + private String expression; + + @NotNull + private String variableName; + + public Input(@NotNull String expression, @NotNull String variableName) { + this.expression = expression; + this.variableName = variableName; + } + + @NotNull + public String getExpression() { + return expression; + } + + @NotNull + public String getVariableName() { + return variableName; + } + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotPreferenceActivity.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotPreferenceActivity.java new file mode 100644 index 00000000..300b756b --- /dev/null +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/CalculatorPlotPreferenceActivity.java @@ -0,0 +1,21 @@ +package org.solovyev.android.calculator.plot; + +import android.os.Bundle; +import com.actionbarsherlock.app.SherlockPreferenceActivity; +import org.solovyev.android.calculator.R; + +/** + * User: serso + * Date: 10/4/12 + * Time: 9:01 PM + */ +public class CalculatorPlotPreferenceActivity extends SherlockPreferenceActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + //noinspection deprecation + addPreferencesFromResource(R.xml.plot_preferences); + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java new file mode 100644 index 00000000..98720842 --- /dev/null +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/GraphLineColor.java @@ -0,0 +1,28 @@ +package org.solovyev.android.calculator.plot; + +import android.graphics.Color; + +/** + * User: serso + * Date: 10/4/12 + * Time: 10:08 PM + */ +public enum GraphLineColor { + + white(Color.WHITE), + grey(Color.GRAY), + red(Color.RED), + blue(Color.rgb(16, 100, 140)), + green(Color.GREEN); + + private final int color; + + private GraphLineColor(int color) { + this.color = color; + } + + + public int getColor() { + return this.color; + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java index 8d605725..807eaa9c 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/plot/PlotUtils.java @@ -1,336 +1,420 @@ -/* - * 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.util.Log; -import jscl.math.Expression; -import jscl.math.Generic; -import jscl.math.JsclInteger; -import jscl.math.NumericWrapper; -import jscl.math.function.Constant; -import jscl.math.numeric.Complex; -import jscl.math.numeric.Numeric; -import jscl.math.numeric.Real; -import org.achartengine.util.MathHelper; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * User: serso - * Date: 12/5/11 - * Time: 8:58 PM - */ -public final class PlotUtils { - - private static final double MAX_Y_DIFF = 1; - private static final double MAX_X_DIFF = 1; - - // not intended for instantiation - private PlotUtils() { - throw new AssertionError(); - } - - public static boolean addXY(double minValue, - double maxValue, - @NotNull Generic expression, - @NotNull Constant variable, - @NotNull MyXYSeries realSeries, - @Nullable MyXYSeries imagSeries, - boolean addExtra, - int numberOfSteps) throws ArithmeticException { - - boolean imagExists = false; - - double min = Math.min(minValue, maxValue); - double max = Math.max(minValue, maxValue); - double dist = max - min; - if (addExtra) { - min = min - dist; - max = max + dist; - } - - final double eps = 0.000000001; - - final double defaultStep = Math.max(dist / numberOfSteps, eps); - double step = defaultStep; - - final Point real = new Point(); - final Point imag = new Point(); - - double x = min; - - while (x <= max) { - - boolean needToCalculateRealY = realSeries.needToAdd(step, x); - - if (needToCalculateRealY) { - final Complex c = calculatorExpression(expression, variable, x); - Double y = prepareY(c.realPart()); - - if (y != null) { - real.moveToNextPoint(x, y); - addSingularityPoint(realSeries, real); - realSeries.add(x, y); - } - - boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(step, x); - if (needToCalculateImagY) { - y = prepareY(c.imaginaryPart()); - if (y != null) { - imag.moveToNextPoint(x, y); - addSingularityPoint(imagSeries, imag); - imagSeries.add(x, y); - } - if (c.imaginaryPart() != 0d) { - imagExists = true; - } - } - } else { - boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(step, x); - if (needToCalculateImagY) { - final Complex c = calculatorExpression(expression, variable, x); - Double y = prepareY(c.imaginaryPart()); - if (y != null) { - imag.moveToNextPoint(x, y); - addSingularityPoint(imagSeries, imag); - imagSeries.add(x, y); - } - if (c.imaginaryPart() != 0d) { - imagExists = true; - } - } - } - - step = updateStep(real, step, defaultStep / 2); - - x += step; - } - - return imagExists; - } - - private static class Point { - private static final double DEFAULT = Double.MIN_VALUE; - - private double x0 = DEFAULT; - private double x1 = DEFAULT; - private double x2 = DEFAULT; - - private double y0 = DEFAULT; - private double y1 = DEFAULT; - private double y2 = DEFAULT; - - private Point() { - } - - public void moveToNextPoint(double x, double y) { - if ( this.x2 == x ) { - return; - } - - this.x0 = this.x1; - this.x1 = this.x2; - this.x2 = x; - - this.y0 = this.y1; - this.y1 = this.y2; - this.y2 = y; - } - - public boolean isFullyDefined() { - return x0 != DEFAULT && x1 != DEFAULT && x2 != DEFAULT && y0 != DEFAULT && y1 != DEFAULT && y2 != DEFAULT; - } - - public double getDx2() { - return x2 - x1; - } - - public double getAbsDx2() { - if ( x2 > x1 ) { - return Math.abs(x2 - x1); - } else { - return Math.abs(x1 - x2); - } - } - - public double getAbsDx1() { - if ( x1 > x0 ) { - return Math.abs(x1 - x0); - } else { - return Math.abs(x0 - x1); - } - } - - public double getAbsDy1() { - if ( y1 > y0 ) { - return Math.abs(y1 - y0); - } else { - return Math.abs(y0 - y1); - } - } - - public double getAbsDy2() { - if ( y2 > y1 ) { - return Math.abs(y2 - y1); - } else { - return Math.abs(y1 - y2); - } - } - - public double getX0() { - return x0; - } - - public double getX1() { - return x1; - } - - public double getX2() { - return x2; - } - - public boolean isX2Defined() { - return x2 != DEFAULT; - } - - public double getY0() { - return y0; - } - - public double getY1() { - return y1; - } - - public double getY2() { - return y2; - } - - public void clearHistory () { - this.x0 = DEFAULT; - this.x1 = DEFAULT; - this.y0 = DEFAULT; - this.y1 = DEFAULT; - } - - public double getAbsDyDx2() { - double dx2 = this.getAbsDx2(); - double dy2 = this.getAbsDy2(); - return dy2 / dx2; - } - - public double getAbsDyDx1() { - double dx1 = this.getAbsDx1(); - double dy1 = this.getAbsDy1(); - return dy1 / dx1; - } - - public double getDyDx1() { - double result = getAbsDyDx1(); - return y1 > y0 ? result : -result; - } - - public double getDyDx2() { - double result = getAbsDyDx2(); - return y2 > y1 ? result : -result; - } - - @Override - public String toString() { - return "Point{" + - "x0=" + x0 + - ", x1=" + x1 + - ", x2=" + x2 + - ", y0=" + y0 + - ", y1=" + y1 + - ", y2=" + y2 + - '}'; - } - } - - private static double updateStep(@NotNull Point real, - double step, - double eps) { - if ( !real.isFullyDefined() ) { - return step; - } else { - double dydx2 = real.getAbsDyDx2(); - double dydx1 = real.getAbsDyDx1(); - - double k = dydx2 / dydx1; - - if ( k > 1 ) { - step = step / k; - } else if ( k > 0 ) { - step = step * k; - } - - return Math.max(step, eps); - } - } - - @NotNull - public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant variable, double x) { - return unwrap(expression.substitute(variable, Expression.valueOf(x)).numeric()); - } - - public static void addSingularityPoint(@NotNull MyXYSeries series, - @NotNull Point point) { - if (point.isFullyDefined()) { - // y or prevY should be more than 1d because if they are too small false singularity may occur (e.g., 1/0.000000000000000001) - // double dy0 = y1 - y0; - // double dx0 = x1 - x0; - // double dydx0 = dy0 / dx0; - - double dy2 = point.getAbsDy2(); - double dx2 = point.getAbsDx2(); - //double dx1 = x2 - x1; - // double dydx1 = dy2 / dx1; - - if ( dy2 > MAX_Y_DIFF && dx2 < MAX_X_DIFF && isDifferentSign(point.getY2(), point.getY1()) && isDifferentSign(point.getDyDx1(), point.getDyDx2())) { - Log.d(CalculatorPlotActivity.class.getName(), "Singularity: " + point); - //Log.d(CalculatorPlotActivity.class.getName(), String.valueOf(prevX + Math.abs(x - prevX) / 2) + ", null"); - series.add(point.getX1() + point.getAbsDx2() / 2, MathHelper.NULL_VALUE); - point.clearHistory(); - } - } - } - - private static boolean isDifferentSign(@NotNull Double y0, @NotNull Double y1) { - return (y0 >= 0 && y1 < 0) || (y1 >= 0 && y0 < 0); - } - - @Nullable - public static Double prepareY(double y) { - if (Double.isNaN(y)) { - return null; - } else { - return y; - } - } - - @NotNull - public static Complex unwrap(@Nullable Generic numeric) { - if (numeric instanceof JsclInteger) { - return Complex.valueOf(((JsclInteger) numeric).intValue(), 0d); - } else if (numeric instanceof NumericWrapper) { - return unwrap(((NumericWrapper) numeric).content()); - } else { - throw new ArithmeticException(); - } - } - - @NotNull - public static Complex unwrap(@Nullable Numeric content) { - if (content instanceof Real) { - return Complex.valueOf(((Real) content).doubleValue(), 0d); - } else if (content instanceof Complex) { - return ((Complex) content); - } else { - throw new ArithmeticException(); - } - } -} +/* + * 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.util.Log; +import jscl.math.Expression; +import jscl.math.Generic; +import jscl.math.JsclInteger; +import jscl.math.NumericWrapper; +import jscl.math.function.Constant; +import jscl.math.numeric.Complex; +import jscl.math.numeric.Numeric; +import jscl.math.numeric.Real; +import org.achartengine.chart.CubicLineChart; +import org.achartengine.chart.PointStyle; +import org.achartengine.chart.ScatterChart; +import org.achartengine.chart.XYChart; +import org.achartengine.model.XYMultipleSeriesDataset; +import org.achartengine.renderer.BasicStroke; +import org.achartengine.renderer.XYMultipleSeriesRenderer; +import org.achartengine.renderer.XYSeriesRenderer; +import org.achartengine.util.MathHelper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: serso + * Date: 12/5/11 + * Time: 8:58 PM + */ +public final class PlotUtils { + + private static final double MAX_Y_DIFF = 1; + private static final double MAX_X_DIFF = 1; + static final int DEFAULT_NUMBER_OF_STEPS = 100; + + // not intended for instantiation + private PlotUtils() { + throw new AssertionError(); + } + + public static boolean addXY(double minValue, + double maxValue, + @NotNull Generic expression, + @NotNull Constant variable, + @NotNull MyXYSeries realSeries, + @Nullable MyXYSeries imagSeries, + boolean addExtra, + int numberOfSteps) throws ArithmeticException { + + boolean imagExists = false; + + double min = Math.min(minValue, maxValue); + double max = Math.max(minValue, maxValue); + double dist = max - min; + if (addExtra) { + min = min - dist; + max = max + dist; + } + + final double eps = 0.000000001; + + final double defaultStep = Math.max(dist / numberOfSteps, eps); + double step = defaultStep; + + final Point real = new Point(); + final Point imag = new Point(); + + double x = min; + + while (x <= max) { + + boolean needToCalculateRealY = realSeries.needToAdd(step, x); + + if (needToCalculateRealY) { + final Complex c = calculatorExpression(expression, variable, x); + Double y = prepareY(c.realPart()); + + if (y != null) { + real.moveToNextPoint(x, y); + addSingularityPoint(realSeries, real); + realSeries.add(x, y); + } + + boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(step, x); + if (needToCalculateImagY) { + y = prepareY(c.imaginaryPart()); + if (y != null) { + imag.moveToNextPoint(x, y); + addSingularityPoint(imagSeries, imag); + imagSeries.add(x, y); + } + if (c.imaginaryPart() != 0d) { + imagExists = true; + } + } + } else { + boolean needToCalculateImagY = imagSeries != null && imagSeries.needToAdd(step, x); + if (needToCalculateImagY) { + final Complex c = calculatorExpression(expression, variable, x); + Double y = prepareY(c.imaginaryPart()); + if (y != null) { + imag.moveToNextPoint(x, y); + addSingularityPoint(imagSeries, imag); + imagSeries.add(x, y); + } + if (c.imaginaryPart() != 0d) { + imagExists = true; + } + } + } + + step = updateStep(real, step, defaultStep / 2); + + x += step; + } + + return imagExists; + } + + @NotNull + static String getImagFunctionName(@NotNull Constant variable) { + return "g(" + variable.getName() + ")" + " = " + "Im(ƒ(" + variable.getName() + "))"; + } + + @NotNull + private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) { + return "ƒ(" + variable.getName() + ")" + " = " + expression.toString(); + } + + @NotNull + static XYChart prepareChart(final double minValue, + final double maxValue, + @NotNull final Generic expression, + @NotNull final Constant variable, + int bgColor, + boolean interpolate, + int realLineColor, + int imagLineColor) { + final MyXYSeries realSeries = new MyXYSeries(getRealFunctionName(expression, variable), DEFAULT_NUMBER_OF_STEPS * 2); + final MyXYSeries imagSeries = new MyXYSeries(getImagFunctionName(variable), DEFAULT_NUMBER_OF_STEPS * 2); + + boolean imagExists = addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false, DEFAULT_NUMBER_OF_STEPS); + + final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset(); + data.addSeries(realSeries); + if (imagExists) { + data.addSeries(imagSeries); + } + + final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); + renderer.setShowGrid(true); + renderer.setXTitle(variable.getName()); + renderer.setYTitle("f(" + variable.getName() + ")"); + renderer.setChartTitleTextSize(25); + renderer.setAxisTitleTextSize(25); + renderer.setLabelsTextSize(25); + renderer.setLegendTextSize(25); + renderer.setMargins(new int[]{25, 25, 25, 25}); + renderer.setApplyBackgroundColor(true); + renderer.setBackgroundColor(bgColor); + renderer.setMarginsColor(bgColor); + + renderer.setZoomEnabled(true); + renderer.setZoomButtonsVisible(true); + + renderer.addSeriesRenderer(createCommonRenderer(realLineColor)); + if (imagExists) { + renderer.addSeriesRenderer(createImagRenderer(imagLineColor)); + } + + if (interpolate) { + return new CubicLineChart(data, renderer, 0.1f); + } else { + return new ScatterChart(data, renderer); + } + } + + static XYSeriesRenderer createImagRenderer(int color) { + final XYSeriesRenderer imagRenderer = createCommonRenderer(color); + imagRenderer.setStroke(BasicStroke.DASHED); + return imagRenderer; + } + + @NotNull + private static XYSeriesRenderer createCommonRenderer(int color) { + final XYSeriesRenderer renderer = new XYSeriesRenderer(); + renderer.setFillPoints(true); + renderer.setPointStyle(PointStyle.CIRCLE); + renderer.setLineWidth(3); + renderer.setColor(color); + renderer.setStroke(BasicStroke.SOLID); + return renderer; + } + + private static class Point { + private static final double DEFAULT = Double.MIN_VALUE; + + private double x0 = DEFAULT; + private double x1 = DEFAULT; + private double x2 = DEFAULT; + + private double y0 = DEFAULT; + private double y1 = DEFAULT; + private double y2 = DEFAULT; + + private Point() { + } + + public void moveToNextPoint(double x, double y) { + if ( this.x2 == x ) { + return; + } + + this.x0 = this.x1; + this.x1 = this.x2; + this.x2 = x; + + this.y0 = this.y1; + this.y1 = this.y2; + this.y2 = y; + } + + public boolean isFullyDefined() { + return x0 != DEFAULT && x1 != DEFAULT && x2 != DEFAULT && y0 != DEFAULT && y1 != DEFAULT && y2 != DEFAULT; + } + + public double getDx2() { + return x2 - x1; + } + + public double getAbsDx2() { + if ( x2 > x1 ) { + return Math.abs(x2 - x1); + } else { + return Math.abs(x1 - x2); + } + } + + public double getAbsDx1() { + if ( x1 > x0 ) { + return Math.abs(x1 - x0); + } else { + return Math.abs(x0 - x1); + } + } + + public double getAbsDy1() { + if ( y1 > y0 ) { + return Math.abs(y1 - y0); + } else { + return Math.abs(y0 - y1); + } + } + + public double getAbsDy2() { + if ( y2 > y1 ) { + return Math.abs(y2 - y1); + } else { + return Math.abs(y1 - y2); + } + } + + public double getX0() { + return x0; + } + + public double getX1() { + return x1; + } + + public double getX2() { + return x2; + } + + public boolean isX2Defined() { + return x2 != DEFAULT; + } + + public double getY0() { + return y0; + } + + public double getY1() { + return y1; + } + + public double getY2() { + return y2; + } + + public void clearHistory () { + this.x0 = DEFAULT; + this.x1 = DEFAULT; + this.y0 = DEFAULT; + this.y1 = DEFAULT; + } + + public double getAbsDyDx2() { + double dx2 = this.getAbsDx2(); + double dy2 = this.getAbsDy2(); + return dy2 / dx2; + } + + public double getAbsDyDx1() { + double dx1 = this.getAbsDx1(); + double dy1 = this.getAbsDy1(); + return dy1 / dx1; + } + + public double getDyDx1() { + double result = getAbsDyDx1(); + return y1 > y0 ? result : -result; + } + + public double getDyDx2() { + double result = getAbsDyDx2(); + return y2 > y1 ? result : -result; + } + + @Override + public String toString() { + return "Point{" + + "x0=" + x0 + + ", x1=" + x1 + + ", x2=" + x2 + + ", y0=" + y0 + + ", y1=" + y1 + + ", y2=" + y2 + + '}'; + } + } + + private static double updateStep(@NotNull Point real, + double step, + double eps) { + if ( !real.isFullyDefined() ) { + return step; + } else { + double dydx2 = real.getAbsDyDx2(); + double dydx1 = real.getAbsDyDx1(); + + double k = dydx2 / dydx1; + + if ( k > 1 ) { + step = step / k; + } else if ( k > 0 ) { + step = step * k; + } + + return Math.max(step, eps); + } + } + + @NotNull + public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant variable, double x) { + return unwrap(expression.substitute(variable, Expression.valueOf(x)).numeric()); + } + + public static void addSingularityPoint(@NotNull MyXYSeries series, + @NotNull Point point) { + if (point.isFullyDefined()) { + // y or prevY should be more than 1d because if they are too small false singularity may occur (e.g., 1/0.000000000000000001) + // double dy0 = y1 - y0; + // double dx0 = x1 - x0; + // double dydx0 = dy0 / dx0; + + double dy2 = point.getAbsDy2(); + double dx2 = point.getAbsDx2(); + //double dx1 = x2 - x1; + // double dydx1 = dy2 / dx1; + + if ( dy2 > MAX_Y_DIFF && dx2 < MAX_X_DIFF && isDifferentSign(point.getY2(), point.getY1()) && isDifferentSign(point.getDyDx1(), point.getDyDx2())) { + Log.d(CalculatorPlotActivity.class.getName(), "Singularity: " + point); + //Log.d(CalculatorPlotActivity.class.getName(), String.valueOf(prevX + Math.abs(x - prevX) / 2) + ", null"); + series.add(point.getX1() + point.getAbsDx2() / 2, MathHelper.NULL_VALUE); + point.clearHistory(); + } + } + } + + private static boolean isDifferentSign(@NotNull Double y0, @NotNull Double y1) { + return (y0 >= 0 && y1 < 0) || (y1 >= 0 && y0 < 0); + } + + @Nullable + public static Double prepareY(double y) { + if (Double.isNaN(y)) { + return null; + } else { + return y; + } + } + + @NotNull + public static Complex unwrap(@Nullable Generic numeric) { + if (numeric instanceof JsclInteger) { + return Complex.valueOf(((JsclInteger) numeric).intValue(), 0d); + } else if (numeric instanceof NumericWrapper) { + return unwrap(((NumericWrapper) numeric).content()); + } else { + throw new ArithmeticException(); + } + } + + @NotNull + public static Complex unwrap(@Nullable Numeric content) { + if (content instanceof Real) { + return Complex.valueOf(((Real) content).doubleValue(), 0d); + } else if (content instanceof Complex) { + return ((Complex) content); + } else { + throw new ArithmeticException(); + } + } +}