new plotter

This commit is contained in:
Sergey Solovyev
2013-01-13 00:26:07 +04:00
parent cbfdf237f0
commit 3f09528f45
42 changed files with 1769 additions and 1046 deletions

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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() {

View File

@@ -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<PlotFunction> getFunctions();
@NotNull
List<PlotFunction> 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);
}

View File

@@ -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<PlotFunction> functions = new ArrayList<PlotFunction>();
@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<Constant> variables = new ArrayList<Constant>(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<PlotFunction>() {
@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<PlotFunction> getFunctions() {
synchronized (functions) {
return new ArrayList<PlotFunction>(functions);
}
}
@NotNull
@Override
public List<PlotFunction> getVisibleFunctions() {
synchronized (functions) {
return Lists.newArrayList(Iterables.filter(functions, new Predicate<PlotFunction>() {
@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;
}
}

View File

@@ -0,0 +1,34 @@
package org.solovyev.android.calculator.plot;
/**
* User: serso
* Date: 10/4/12
* Time: 10:08 PM
*/
public enum GraphLineColor {
// Color.WHITE
white(0xFFFFFFFF),
// Color.GRAY
grey(0xFF888888),
// Color.RED
red(0xFFFF0000),
blue(0xFF10648C),
// Color.GREEN
green(0xFF00FF00);
private final int color;
private GraphLineColor(int color) {
this.color = color;
}
public int getColor() {
return this.color;
}
}

View File

@@ -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<PlotFunction> functions;
private boolean plot3d;
public PlotData(List<PlotFunction> functions, boolean plot3d) {
this.functions = functions;
this.plot3d = plot3d;
}
public List<PlotFunction> getFunctions() {
return functions;
}
public boolean isPlot3d() {
return plot3d;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,12 @@
package org.solovyev.android.calculator.plot;
/**
* User: serso
* Date: 1/5/13
* Time: 10:45 PM
*/
public enum PlotFunctionLineColorType {
color_map,
solid;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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();
}