new plotter

This commit is contained in:
Sergey Solovyev 2013-01-18 23:04:45 +04:00
parent 4f2c2701c0
commit bbdec030fe
17 changed files with 664 additions and 744 deletions

View File

@ -11,7 +11,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import org.achartengine.ChartFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.AndroidUtils2;
@ -98,7 +97,6 @@ public final class CalculatorActivityLauncher implements CalculatorEventListener
public static void plotGraph(@NotNull final Context context){
final Intent intent = new Intent();
intent.putExtra(ChartFactory.TITLE, context.getString(R.string.c_graph));
intent.setClass(context, CalculatorPlotActivity.class);
AndroidUtils2.addFlags(intent, false, context);
context.startActivity(intent);

View File

@ -0,0 +1,101 @@
package org.solovyev.android.calculator.plot;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 1/18/13
* Time: 9:03 PM
*/
public abstract class AbstractGraphCalculator implements GraphCalculator {
@NotNull
protected final GraphData next = GraphData.newEmptyInstance();
@NotNull
private final GraphData endGraph = GraphData.newEmptyInstance();
@NotNull
private final GraphData startGraph = GraphData.newEmptyInstance();
@Override
public final void computeGraph(@NotNull XyFunction f, float xMin, float xMax, @NotNull GraphData graph, @NotNull GraphsData graphsData, @NotNull Graph2dDimensions dimensions) {
if (f.getArity() == 0) {
final float v = (float) f.eval();
graph.clear();
graph.push(xMin, v);
graph.push(xMax, v);
return;
}
float yMin = graphsData.getLastYMin();
float yMax = graphsData.getLastYMin();
// prepare graph
if (!graph.empty()) {
if (xMin >= graphsData.getLastXMin()) {
// |------[---erased---|------data----|---erased--]------ old data
// |-------------------[------data----]------------------ new data
// xMin xMax
//
// OR
//
// |------[---erased---|------data----]----------- old data
// |-------------------[------data----<---->]----- new data
// xMin xMax
graph.eraseBefore(xMin);
if ( xMax <= graphsData.getLastXMax() ) {
graph.eraseAfter(xMax);
// nothing to compute
} else {
xMin = graph.getLastX();
compute(f, xMin, xMax, yMin, yMax, endGraph, dimensions);
}
} else {
// |--------------------[-----data----|---erased----]-- old data
// |------[<------------>-----data----]---------------- new data
// xMin xMax
//
// OR
//
// |--------------------[------data--]----|----------- old data
// |-------[<----------->------data--<--->]-----------new data
// xMin xMax
if ( xMax <= graphsData.getLastXMax() ) {
graph.eraseAfter(xMax);
xMax = graph.getFirstX();
compute(f, xMin, xMax, yMin, yMax, startGraph, dimensions);
} else {
compute(f, xMin, graph.getFirstX(), yMin, yMax, startGraph, dimensions);
compute(f, graph.getLastX(), xMax, yMin, yMax, endGraph, dimensions);
}
}
} else {
compute(f, xMin, xMax, yMin, yMax, graph, dimensions);
}
if (!endGraph.empty()) {
// first add ending because it's fast
graph.append(endGraph);
}
if (!startGraph.empty()) {
startGraph.append(graph);
graph.swap(startGraph);
}
next.clear();
endGraph.clear();
startGraph.clear();
}
protected abstract void compute(@NotNull XyFunction f,
float xMin,
float xMax,
float yMin,
float yMax,
@NotNull GraphData graph,
@NotNull Graph2dDimensions dimensions);
}

View File

@ -1,46 +0,0 @@
package org.solovyev.android.calculator.plot;
import org.javia.arity.Function;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 1/5/13
* Time: 7:35 PM
*/
public class ArityPlotFunction {
@NotNull
private Function function;
@NotNull
private PlotLineDef lineDef;
private ArityPlotFunction() {
}
@NotNull
public static ArityPlotFunction newInstance(@NotNull Function function) {
return newInstance(function, PlotLineDef.newDefaultInstance());
}
@NotNull
public static ArityPlotFunction newInstance(@NotNull Function function, @NotNull PlotLineDef lineDef) {
final ArityPlotFunction result = new ArityPlotFunction();
result.function = function;
result.lineDef = lineDef;
return result;
}
@NotNull
public Function getFunction() {
return function;
}
@NotNull
public PlotLineDef getLineDef() {
return lineDef;
}
}

View File

@ -12,7 +12,6 @@ import org.jetbrains.annotations.NotNull;
import org.solovyev.common.math.Point2d;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -46,10 +45,6 @@ public class CalculatorGraph2dView extends View implements GraphView {
**********************************************************************
*/
// view width and height in pixels
private int widthPxs;
private int heightPxs;
@NotNull
private final Matrix matrix = new Matrix();
@ -66,25 +61,8 @@ public class CalculatorGraph2dView extends View implements GraphView {
@NotNull
private GraphViewHelper graphViewHelper = GraphViewHelper.newDefaultInstance();
private final GraphData next = GraphData.newEmptyInstance();
private final GraphData endGraph = GraphData.newEmptyInstance();
@NotNull
private List<GraphData> graphs = new ArrayList<GraphData>(graphViewHelper.getFunctionPlotDefs().size());
// current position of camera in graph coordinates
private float x0;
private float y0;
// graph width in function units (NOT screen pixels)
private float gWidth = 20;
private float lastXMin;
private float lastYMin;
private float lastYMax;
private final GraphsData graphsData = new GraphsData(this);
private float lastTouchXPxs = NO_TOUCH;
private float lastTouchYPxs = NO_TOUCH;
@ -101,6 +79,21 @@ public class CalculatorGraph2dView extends View implements GraphView {
@NotNull
private Scroller scroller;
@NotNull
private final Graph2dDimensions dimensions = new Graph2dDimensions(this);
private final GraphCalculator graphCalculator = new GraphCalculatorImpl();
private boolean mDrawn = false;
/*
**********************************************************************
*
* CONSTRUCTORS
*
**********************************************************************
*/
public CalculatorGraph2dView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
@ -119,39 +112,21 @@ public class CalculatorGraph2dView extends View implements GraphView {
paint.setAntiAlias(false);
textPaint.setAntiAlias(true);
widthPxs = this.getWidth();
heightPxs = this.getHeight();
}
@NotNull
public Bitmap captureScreenshot() {
final Bitmap result = Bitmap.createBitmap(widthPxs, heightPxs, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(result);
onDraw(canvas);
return result;
dimensions.setViewDimensions(this);
}
@Override
public void setXRange(float xMin, float xMax) {
this.gWidth = xMax - xMin;
this.x0 = xMin + gWidth / 2;
this.y0 = 0;
public void init(@NotNull PlotViewDef plotViewDef) {
this.graphViewHelper = GraphViewHelper.newInstance(plotViewDef, Collections.<PlotFunction>emptyList());
}
private void clearAllGraphs() {
for (GraphData graph : graphs) {
graph.clear();
}
while (graphViewHelper.getFunctionPlotDefs().size() > graphs.size()) {
graphs.add(GraphData.newEmptyInstance());
}
}
@Override
public void init(@NotNull FunctionViewDef functionViewDef) {
this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.<PlotFunction>emptyList());
}
/*
**********************************************************************
*
* METHODS
*
**********************************************************************
*/
public void setPlotFunctions(@NotNull List<PlotFunction> plotFunctions) {
@ -163,8 +138,31 @@ public class CalculatorGraph2dView extends View implements GraphView {
}
this.graphViewHelper = this.graphViewHelper.copy(plotFunctions);
clearAllGraphs();
invalidate();
invalidateGraphs();
}
@NotNull
@Override
public List<PlotFunction> getPlotFunctions() {
return this.graphViewHelper.getPlotFunctions();
}
@NotNull
public Bitmap captureScreenshot() {
final Bitmap result = Bitmap.createBitmap(dimensions.getVWidthPxs(), dimensions.getVHeightPxs(), Bitmap.Config.RGB_565);
onDraw(new Canvas(result));
return result;
}
@Override
public void invalidateGraphs() {
if (mDrawn) {
mDrawn = false;
graphsData.clear();
invalidate();
}
}
public void onResume() {
@ -179,149 +177,22 @@ public class CalculatorGraph2dView extends View implements GraphView {
}
protected void onSizeChanged(int w, int h, int ow, int oh) {
widthPxs = w;
heightPxs = h;
clearAllGraphs();
dimensions.setViewDimensions(w, h);
}
protected void onDraw(Canvas canvas) {
if (graphViewHelper.getFunctionPlotDefs().size() == 0) {
return;
}
if (scroller.computeScrollOffset()) {
final float ratio = getRatio();
x0 = scroller.getCurrX() * ratio;
y0 = scroller.getCurrY() * ratio;
if (!scroller.isFinished()) {
invalidate();
}
}
drawGraph(canvas);
}
protected void onDraw(@NotNull Canvas canvas) {
if (!graphViewHelper.getPlotFunctions().isEmpty()) {
// distance from (x,y) to the line (x1,y1) to (x2,y2), squared, multiplied by 4
/*
private float distance(float x1, float y1, float x2, float y2, float x, float y) {
float dx = x2 - x1;
float dy = y2 - y1;
float mx = x - x1;
float my = y - y1;
float up = dx*my - dy*mx;
return up*up*4/(dx*dx + dy*dy);
}
*/
// distance as above when x==(x1+x2)/2.
private float distance2(float x1, float y1, float x2, float y2, float y) {
final float dx = x2 - x1;
final float dy = y2 - y1;
final float up = dx * (y1 + y2 - y - y);
return up * up / (dx * dx + dy * dy);
}
private void computeGraph(@NotNull XyFunction f,
float xMin,
float xMax,
float yMin,
float yMax,
@NotNull GraphData graph) {
if (f.getArity() == 0) {
final float v = (float) f.eval();
graph.clear();
graph.push(xMin, v);
graph.push(xMax, v);
return;
}
// prepare graph
if (!graph.empty()) {
if (xMin >= lastXMin) {
graph.eraseBefore(xMin);
} else {
graph.eraseAfter(xMax);
xMax = Math.min(xMax, graph.firstX());
graph.swap(endGraph);
}
}
if (graph.empty()) {
graph.push(xMin, (float)f.eval(xMin));
}
final float ratio = getRatio();
final float maxStep = 15.8976f * ratio;
final float minStep = .05f * ratio;
float ythresh = ratio;
ythresh = ythresh * ythresh;
float leftX, leftY;
float rightX = graph.topX(), rightY = graph.topY();
int nEval = 1;
while (true) {
leftX = rightX;
leftY = rightY;
if (leftX > xMax) {
break;
}
if (next.empty()) {
float x = leftX + maxStep;
next.push(x, (float) f.eval(x));
++nEval;
}
rightX = next.topX();
rightY = next.topY();
next.pop();
if (leftY != leftY && rightY != rightY) { // NaN
continue;
}
float dx = rightX - leftX;
float middleX = (leftX + rightX) / 2;
float middleY = (float) f.eval(middleX);
++nEval;
boolean middleIsOutside = (middleY < leftY && middleY < rightY) || (leftY < middleY && rightY < middleY);
if (dx < minStep) {
// Calculator.log("minStep");
if (middleIsOutside) {
graph.push(rightX, Float.NaN);
}
graph.push(rightX, rightY);
continue;
}
if (middleIsOutside && ((leftY < yMin && rightY > yMax) || (leftY > yMax && rightY < yMin))) {
graph.push(rightX, Float.NaN);
graph.push(rightX, rightY);
// Calculator.log("+-inf");
continue;
}
if (!middleIsOutside) {
/*
float diff = leftY + rightY - middleY - middleY;
float dy = rightY - leftY;
float dx2 = dx*dx;
float distance = dx2*diff*diff/(dx2+dy*dy);
*/
// Calculator.log("" + dx + ' ' + leftY + ' ' + middleY + ' ' + rightY + ' ' + distance + ' ' + ythresh);
if (distance2(leftX, leftY, rightX, rightY, middleY) < ythresh) {
graph.push(rightX, rightY);
continue;
if (scroller.computeScrollOffset()) {
final float ratio = dimensions.getGraphToViewRatio();
dimensions.setXY(scroller.getCurrX() * ratio, scroller.getCurrY() * ratio);
if (!scroller.isFinished()) {
invalidate();
}
}
next.push(rightX, rightY);
next.push(middleX, middleY);
rightX = leftX;
rightY = leftY;
}
if (!endGraph.empty()) {
graph.append(endGraph);
}
long t2 = System.currentTimeMillis();
// Calculator.log("graph points " + graph.size + " evals " + nEval + " time " + (t2-t1));
next.clear();
endGraph.clear();
drawGraph(canvas);
}
}
private static void graphToPath(@NotNull GraphData graph, @NotNull Path path) {
@ -352,51 +223,30 @@ public class CalculatorGraph2dView extends View implements GraphView {
}
}
private static float getStep(float width) {
float f = 1;
while (width / f > TICKS_COUNT) {
f *= 10;
}
while (width / f < TICKS_COUNT / 10) {
f /= 10;
}
final float r = width / f;
if (r < TICKS_COUNT / 5) {
return f / 5;
} else if (r < TICKS_COUNT / 2) {
return f / 2;
} else {
return f;
}
}
private void drawGraph(@NotNull Canvas canvas) {
final float graphHeight = getGraphHeight();
mDrawn = true;
final float xMin = getXMin();
final float xMax = getXMax(xMin);
final float graphHeight = dimensions.getGraphHeight();
final float yMin = getYMin(graphHeight);
final float yMax = getYMax(graphHeight, yMin);
final float xMin = dimensions.getXMin();
final float xMax = dimensions.getXMax(xMin);
if (yMin < lastYMin || yMax > lastYMax) {
float halfGraphHeight = graphHeight / 2;
lastYMin = yMin - halfGraphHeight;
lastYMax = yMax + halfGraphHeight;
clearAllGraphs();
}
final float yMin = dimensions.getYMin(graphHeight);
final float yMax = dimensions.getYMax(graphHeight, yMin);
final float widthPxs = dimensions.getVWidthPxs();
final float heightPxs = dimensions.getVHeightPxs();
graphsData.checkBoundaries(graphHeight, yMin, yMax);
// set background
canvas.drawColor(graphViewHelper.getFunctionViewDef().getBackgroundColor());
canvas.drawColor(graphViewHelper.getPlotViewDef().getBackgroundColor());
// prepare paint
paint.setStrokeWidth(0);
paint.setAntiAlias(false);
paint.setStyle(Paint.Style.STROKE);
final float ratio = getRatio();
final float ratio = dimensions.getGraphToViewRatio();
float x0px = -xMin / ratio;
if (x0px < 25) {
@ -413,16 +263,16 @@ public class CalculatorGraph2dView extends View implements GraphView {
}
final float tickStep = getStep(gWidth);
final float tickStep = getTickStep(dimensions.getGWidth());
final int tickDigits = countTickDigits(tickStep);
{
// GRID
paint.setPathEffect(new DashPathEffect(new float[]{5, 10}, 0));
paint.setColor(graphViewHelper.getFunctionViewDef().getGridColor());
paint.setColor(graphViewHelper.getPlotViewDef().getGridColor());
textPaint.setColor(graphViewHelper.getFunctionViewDef().getAxisLabelsColor());
textPaint.setColor(graphViewHelper.getPlotViewDef().getAxisLabelsColor());
textPaint.setTextSize(12);
textPaint.setTextAlign(Paint.Align.CENTER);
@ -461,7 +311,7 @@ public class CalculatorGraph2dView extends View implements GraphView {
{
// AXIS
paint.setColor(graphViewHelper.getFunctionViewDef().getAxisColor());
paint.setColor(graphViewHelper.getPlotViewDef().getAxisColor());
canvas.drawLine(x0px, 0, x0px, heightPxs, paint);
canvas.drawLine(0, y0px, widthPxs, y0px, paint);
}
@ -471,13 +321,13 @@ public class CalculatorGraph2dView extends View implements GraphView {
if (lastTouchXPxs != NO_TOUCH && lastTouchYPxs != NO_TOUCH) {
paint.setColor(graphViewHelper.getFunctionViewDef().getGridColor());
paint.setColor(graphViewHelper.getPlotViewDef().getGridColor());
paint.setAlpha(100);
canvas.drawLine(lastTouchXPxs, 0, lastTouchXPxs, heightPxs, paint);
canvas.drawLine(0, lastTouchYPxs, widthPxs, lastTouchYPxs, paint);
final Point2d lastTouch = toGraphCoordinates(lastTouchXPxs, lastTouchYPxs);
final Point2d lastTouch = dimensions.toGraphCoordinates(lastTouchXPxs, lastTouchYPxs);
final String touchLabel = "[" + formatTick(lastTouch.getX(), tickDigits + 1) + ", " + formatTick(lastTouch.getY(), tickDigits + 1) + "]";
canvas.drawText(touchLabel, 0, touchLabel.length(), lastTouchXPxs - 40, lastTouchYPxs - 40, textPaint);
@ -488,7 +338,7 @@ public class CalculatorGraph2dView extends View implements GraphView {
matrix.reset();
matrix.preTranslate(-this.x0, -this.y0);
matrix.preTranslate(-dimensions.getX0(), -dimensions.getY0());
matrix.postScale(1/ratio, -1/ratio);
matrix.postTranslate(widthPxs / 2, heightPxs / 2);
@ -497,16 +347,17 @@ public class CalculatorGraph2dView extends View implements GraphView {
{
//GRAPH
final List<PlotFunction> functionPlotDefs = graphViewHelper.getFunctionPlotDefs();
final List<PlotFunction> functionPlotDefs = graphViewHelper.getPlotFunctions();
// create path once
final Path path = new Path();
for (int i = 0; i < functionPlotDefs.size(); i++) {
final PlotFunction fpd = functionPlotDefs.get(i);
computeGraph(fpd.getXyFunction(), xMin, xMax, lastYMin, lastYMax, graphs.get(i));
graphToPath(graphs.get(i), path);
graphCalculator.computeGraph(fpd.getXyFunction(), xMin, xMax, graphsData.get(i), graphsData, dimensions);
graphToPath(graphsData.get(i), path);
path.transform(matrix);
@ -517,13 +368,14 @@ public class CalculatorGraph2dView extends View implements GraphView {
}
lastXMin = xMin;
graphsData.setLastXMin(xMin);
graphsData.setLastXMax(xMax);
}
/*
**********************************************************************
*
* TICK FORMAT
* TICKS
*
**********************************************************************
*/
@ -557,6 +409,27 @@ public class CalculatorGraph2dView extends View implements GraphView {
}
}
private static float getTickStep(float width) {
float f = 1;
while (width / f > TICKS_COUNT) {
f *= 10;
}
while (width / f < TICKS_COUNT / 10) {
f /= 10;
}
final float r = width / f;
if (r < TICKS_COUNT / 5) {
return f / 5;
} else if (r < TICKS_COUNT / 2) {
return f / 2;
} else {
return f;
}
}
/*
**********************************************************************
*
@ -565,64 +438,32 @@ public class CalculatorGraph2dView extends View implements GraphView {
**********************************************************************
*/
private Point2d toGraphCoordinates(float xPxs, float yPxs) {
return new Point2d(xPxs * getRatio() + getXMin(), - (yPxs * getRatio() + getYMin()));
}
// X
public float getXMin() {
return x0 - gWidth / 2;
}
private float getXMax(float minX) {
return minX + gWidth;
return dimensions.getXMin();
}
public float getXMax() {
return getXMax(getXMin());
return dimensions.getXMax();
}
// Y
@Override
public float getYMin() {
return getYMin(getGraphHeight());
return dimensions.getYMin();
}
public float getYMin(float graphHeight) {
return y0 - graphHeight / 2;
}
@Override
public float getYMax() {
final float graphHeight = getGraphHeight();
return getYMax(graphHeight, getYMin(graphHeight));
return dimensions.getYMax();
}
public float getYMax(float graphHeight, float yMin) {
return yMin + graphHeight;
}
private float getGraphHeight() {
return gWidth * getAspectRatio();
}
private float getRatio() {
if (widthPxs != 0) {
return gWidth / widthPxs;
} else {
return 0;
}
}
private float getAspectRatio() {
if (widthPxs != 0) {
return ((float)heightPxs) / widthPxs;
} else {
return 0;
}
@Override
public void setXRange(float xMin, float xMax) {
this.dimensions.setXRange(xMin, xMax);
}
/*
@ -647,13 +488,11 @@ public class CalculatorGraph2dView extends View implements GraphView {
public void onZoom(boolean zoomIn) {
if (zoomIn) {
if (canZoomIn()) {
gWidth /= 2;
invalidateGraphs();
dimensions.setGWidth(dimensions.getGWidth() / 2);
}
} else {
if (canZoomOut()) {
gWidth *= 2;
invalidateGraphs();
dimensions.setGWidth(dimensions.getGWidth() * 2);
}
}
zoomController.setZoomInEnabled(canZoomIn());
@ -700,7 +539,7 @@ public class CalculatorGraph2dView extends View implements GraphView {
}
public void onTouchUp(float x, float y) {
final float ratio = getRatio();
final float ratio = dimensions.getGraphToViewRatio();
lastTouchXPxs = NO_TOUCH;
lastTouchYPxs = NO_TOUCH;
@ -715,35 +554,26 @@ public class CalculatorGraph2dView extends View implements GraphView {
} else if (asy < asx / 3) {
sy = 0;
}
scroller.fling(Math.round(x0 / ratio), Math.round(y0 / ratio), Math.round(sx), Math.round(sy), Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
scroller.fling(Math.round(dimensions.getX0() / ratio), Math.round(dimensions.getY0() / ratio), Math.round(sx), Math.round(sy), Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
invalidate();
}
public void onTouchZoomDown(float x1, float y1, float x2, float y2) {
zoomTracker.start(gWidth, x1, y1, x2, y2);
zoomTracker.start(dimensions.getGWidth(), x1, y1, x2, y2);
}
public void onTouchZoomMove(float x1, float y1, float x2, float y2) {
if (!zoomTracker.update(x1, y1, x2, y2)) {
return;
}
float targetGwidth = zoomTracker.value;
if (targetGwidth > .25f && targetGwidth < 200) {
gWidth = targetGwidth;
float targetGWidth = zoomTracker.value;
if (targetGWidth > .25f && targetGWidth < 200) {
dimensions.setGWidth(targetGWidth);
}
// scroll(-zoomTracker.moveX, zoomTracker.moveY);
invalidateGraphs();
// Calculator.log("zoom redraw");
}
private void invalidateGraphs() {
clearAllGraphs();
lastYMin = lastYMax = 0;
invalidate();
}
private void scroll(float deltaX, float deltaY) {
final float scale = gWidth / widthPxs;
final float scale = dimensions.getGWidth() / dimensions.getVWidthPxs();
float dx = deltaX * scale;
float dy = deltaY * scale;
final float adx = Math.abs(dx);
@ -753,7 +583,7 @@ public class CalculatorGraph2dView extends View implements GraphView {
} else if (ady < adx / 3) {
dy = 0;
}
x0 += dx;
y0 += dy;
dimensions.increaseXY(dx, dy);
}
}

View File

@ -44,7 +44,7 @@ public class CalculatorPlotFragment extends AbstractCalculatorPlotFragment {
graphView = new CalculatorGraph2dView(getActivity());
}
graphView.init(FunctionViewDef.newInstance(Color.WHITE, Color.WHITE, Color.DKGRAY, getBgColor()));
graphView.init(PlotViewDef.newInstance(Color.WHITE, Color.WHITE, Color.DKGRAY, getBgColor()));
//graphView.setXRange((float)plotBoundaries.getXMin(), (float)plotBoundaries.getXMax());
graphView.setPlotFunctions(plotData.getFunctions());
@ -99,12 +99,4 @@ public class CalculatorPlotFragment extends AbstractCalculatorPlotFragment {
}
}
/*
**********************************************************************
*
* STATIC
*
**********************************************************************
*/
}

View File

@ -0,0 +1,165 @@
package org.solovyev.android.calculator.plot;
import android.view.View;
import org.jetbrains.annotations.NotNull;
import org.solovyev.common.math.Point2d;
/**
* User: serso
* Date: 1/18/13
* Time: 7:59 PM
*/
public class Graph2dDimensions {
@NotNull
private GraphView graphView;
// view width and height in pixels
private int vWidthPxs;
private int vHeightPxs;
// current position of camera in graph coordinates
private float x0;
private float y0;
// graph width in function units (NOT screen pixels)
private float gWidth = 20;
public Graph2dDimensions(@NotNull GraphView graphView) {
this.graphView = graphView;
}
/*
**********************************************************************
*
* METHODS
*
**********************************************************************
*/
@NotNull
Point2d toGraphCoordinates(float xPxs, float yPxs) {
return new Point2d(xPxs * getGraphToViewRatio() + getXMin(), - (yPxs * getGraphToViewRatio() + getYMin()));
}
// X
public float getXMin() {
return x0 - gWidth / 2;
}
float getXMax(float minX) {
return minX + gWidth;
}
public float getXMax() {
return getXMax(getXMin());
}
// Y
public float getYMin() {
return getYMin(getGraphHeight());
}
public float getYMin(float graphHeight) {
return y0 - graphHeight / 2;
}
public float getYMax() {
final float graphHeight = getGraphHeight();
return getYMax(graphHeight, getYMin(graphHeight));
}
public float getYMax(float graphHeight, float yMin) {
return yMin + graphHeight;
}
float getGraphHeight() {
return gWidth * getAspectRatio();
}
float getGraphToViewRatio() {
if (vWidthPxs != 0) {
return gWidth / vWidthPxs;
} else {
return 0;
}
}
private float getAspectRatio() {
if (vWidthPxs != 0) {
return ((float) vHeightPxs) / vWidthPxs;
} else {
return 0;
}
}
public int getVWidthPxs() {
return vWidthPxs;
}
public int getVHeightPxs() {
return vHeightPxs;
}
public float getX0() {
return x0;
}
public float getY0() {
return y0;
}
public float getGWidth() {
return gWidth;
}
/*
**********************************************************************
*
* SETTERS
*
**********************************************************************
*/
public void setXRange(float xMin, float xMax) {
this.gWidth = xMax - xMin;
this.x0 = xMin + gWidth / 2;
this.y0 = 0;
this.graphView.invalidateGraphs();
}
public void setViewDimensions(@NotNull View view) {
this.vWidthPxs = view.getWidth();
this.vHeightPxs = view.getHeight();
this.graphView.invalidateGraphs();
}
public void setGWidth(float gWidth) {
this.gWidth = gWidth;
this.graphView.invalidateGraphs();
}
public void setViewDimensions(int vWidthPxs, int vHeightPxs) {
this.vWidthPxs = vWidthPxs;
this.vHeightPxs = vHeightPxs;
this.graphView.invalidateGraphs();
}
void setXY(float x0, float y0) {
this.x0 = x0;
this.y0 = y0;
}
public void increaseXY(float dx, float dy) {
this.x0 += dx;
this.y0 += dy;
}
}

View File

@ -173,8 +173,8 @@ public class Graph3dView extends GLView implements GraphView {
}
@Override
public void init(@NotNull FunctionViewDef functionViewDef) {
this.graphViewHelper = GraphViewHelper.newInstance(functionViewDef, Collections.<PlotFunction>emptyList());
public void init(@NotNull PlotViewDef plotViewDef) {
this.graphViewHelper = GraphViewHelper.newInstance(plotViewDef, Collections.<PlotFunction>emptyList());
}
@Override
@ -191,7 +191,13 @@ public class Graph3dView extends GLView implements GraphView {
isDirty = true;
}
@Override
@NotNull
@Override
public List<PlotFunction> getPlotFunctions() {
return this.graphViewHelper.getPlotFunctions();
}
@Override
public void setXRange(float xMin, float xMax) {
//To change body of implemented methods use File | Settings | File Templates.
}
@ -216,12 +222,17 @@ public class Graph3dView extends GLView implements GraphView {
return 0; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void invalidateGraphs() {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void onSurfaceCreated(GL10 gl, int width, int height) {
gl.glDisable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
final int backgroundColor = graphViewHelper.getFunctionViewDef().getBackgroundColor();
final int backgroundColor = graphViewHelper.getPlotViewDef().getBackgroundColor();
gl.glClearColor(Color.red(backgroundColor) / 255f, Color.green(backgroundColor) / 255f, Color.blue(backgroundColor) / 255f, Color.alpha(backgroundColor) / 255f);
gl.glShadeModel(useHighQuality3d ? GL10.GL_SMOOTH : GL10.GL_FLAT);
@ -248,8 +259,8 @@ public class Graph3dView extends GLView implements GraphView {
}
if (isDirty) {
ensureGraphsSize(gl);
for (int i = 0; i < graphViewHelper.getFunctionPlotDefs().size(); i++) {
graphs.get(i).update(gl, graphViewHelper.getFunctionPlotDefs().get(i), zoomLevel);
for (int i = 0; i < graphViewHelper.getPlotFunctions().size(); i++) {
graphs.get(i).update(gl, graphViewHelper.getPlotFunctions().get(i), zoomLevel);
}
isDirty = false;
@ -308,7 +319,7 @@ public class Graph3dView extends GLView implements GraphView {
}
private void ensureGraphsSize(@NotNull GL11 gl) {
while (graphViewHelper.getFunctionPlotDefs().size() > graphs.size()) {
while (graphViewHelper.getPlotFunctions().size() > graphs.size()) {
graphs.add(new Graph3d(gl, useHighQuality3d));
}
}

View File

@ -0,0 +1,18 @@
package org.solovyev.android.calculator.plot;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 1/18/13
* Time: 8:58 PM
*/
public interface GraphCalculator {
void computeGraph(@NotNull XyFunction f,
float xMin,
float xMax,
@NotNull GraphData graph,
@NotNull GraphsData graphsData,
@NotNull Graph2dDimensions dimensions);
}

View File

@ -0,0 +1,100 @@
package org.solovyev.android.calculator.plot;
import org.jetbrains.annotations.NotNull;
/**
* User: serso
* Date: 1/18/13
* Time: 8:58 PM
*/
public class GraphCalculatorImpl extends AbstractGraphCalculator {
@Override
protected void compute(@NotNull XyFunction f,
float xMin,
float xMax,
float yMin,
float yMax,
@NotNull GraphData graph,
@NotNull Graph2dDimensions dimensions) {
graph.push(xMin, (float)f.eval(xMin));
final float ratio = dimensions.getGraphToViewRatio();
final float maxStep = 15.8976f * ratio;
final float minStep = .05f * ratio;
float yTheta = ratio;
yTheta = yTheta * yTheta;
float leftX;
float leftY;
float rightX = graph.getLastX();
float rightY = graph.getLastY();
while (true) {
leftX = rightX;
leftY = rightY;
if (leftX > xMax) {
break;
}
if (next.empty()) {
float x = leftX + maxStep;
next.push(x, (float) f.eval(x));
}
rightX = next.getLastX();
rightY = next.getLastY();
next.pop();
if (Float.isNaN(leftY) || Float.isNaN(rightY)) {
continue;
}
float dx = rightX - leftX;
float middleX = (leftX + rightX) / 2;
float middleY = (float) f.eval(middleX);
boolean middleIsOutside = (middleY < leftY && middleY < rightY) || (leftY < middleY && rightY < middleY);
if (dx < minStep) {
// Calculator.log("minStep");
if (middleIsOutside) {
graph.push(rightX, Float.NaN);
}
graph.push(rightX, rightY);
continue;
}
if (middleIsOutside && ((leftY < yMin && rightY > yMax) || (leftY > yMax && rightY < yMin))) {
graph.push(rightX, Float.NaN);
graph.push(rightX, rightY);
// Calculator.log("+-inf");
continue;
}
if (!middleIsOutside) {
if (distance2(leftX, leftY, rightX, rightY, middleY) < yTheta) {
graph.push(rightX, rightY);
continue;
}
}
next.push(rightX, rightY);
next.push(middleX, middleY);
rightX = leftX;
rightY = leftY;
}
}
// distance as above when x==(x1+x2)/2.
private float distance2(float x1, float y1, float x2, float y2, float y) {
final float dx = x2 - x1;
final float dy = y2 - y1;
final float up = dx * (y1 + y2 - y - y);
return up * up / (dx * dx + dy * dy);
}
}

View File

@ -1,5 +1,3 @@
// Copyright (C) 2009 Mihai Preda
package org.solovyev.android.calculator.plot;
import org.jetbrains.annotations.NotNull;
@ -39,7 +37,7 @@ class GraphData {
void push(float x, float y) {
if (size >= allocatedSize) {
makeSpace(size + 1);
makeSpaceAtTheEnd(size + 1);
}
xs[size] = x;
@ -47,9 +45,9 @@ class GraphData {
++size;
}
private void makeSpace(int spaceSize) {
private void makeSpaceAtTheEnd(int newSize) {
int oldAllocatedSize = allocatedSize;
while (spaceSize > allocatedSize) {
while (newSize > allocatedSize) {
allocatedSize += allocatedSize;
}
@ -63,19 +61,20 @@ class GraphData {
}
}
float topX() {
float getLastX() {
return xs[size - 1];
}
float topY() {
float getLastY() {
return ys[size - 1];
}
float firstX() {
float getFirstX() {
return xs[0];
}
float firstY() {
float getFirstY() {
return ys[0];
}
@ -115,36 +114,27 @@ class GraphData {
}
}
int findPosAfter(float x, float y) {
int pos = 0;
while (pos < size && xs[pos] <= x) {
++pos;
int findPositionAfter(float x, float y) {
int position = 0;
while (position < size && xs[position] <= x) {
++position;
}
if (Float.isNaN(y)) {
while (pos < size && ys[pos] != ys[pos]) {
++pos;
while (position < size && Float.isNaN(ys[position])) {
++position;
}
}
// Calculator.log("pos " + pos);
return pos;
return position;
}
void append(GraphData d) {
makeSpace(size + d.size);
int pos = d.findPosAfter(xs[size - 1], ys[size - 1]);
/*
while (pos < d.size && d.xs[pos] <= last) {
++pos;
}
if (last != last) {
while (pos < d.size && d.ys[pos] != d.ys[pos]) {
++pos;
}
}
*/
System.arraycopy(d.xs, pos, xs, size, d.size - pos);
System.arraycopy(d.ys, pos, ys, size, d.size - pos);
size += d.size - pos;
void append(GraphData that) {
makeSpaceAtTheEnd(size + that.size);
int position = that.findPositionAfter(xs[size - 1], ys[size - 1]);
System.arraycopy(that.xs, position, xs, size, that.size - position);
System.arraycopy(that.ys, position, ys, size, that.size - position);
size += that.size - position;
}
public String toString() {

View File

@ -1,5 +1,3 @@
// Copyright (C) 2009-2010 Mihai Preda
package org.solovyev.android.calculator.plot;
import android.graphics.Bitmap;
@ -10,10 +8,13 @@ import java.util.List;
public interface GraphView extends ZoomButtonsController.OnZoomListener, TouchHandler.TouchHandlerListener {
public void init(@NotNull FunctionViewDef functionViewDef);
public void init(@NotNull PlotViewDef plotViewDef);
public void setPlotFunctions(@NotNull List<PlotFunction> plotFunctions);
@NotNull
public List<PlotFunction> getPlotFunctions();
public void onPause();
public void onResume();
@ -30,6 +31,8 @@ public interface GraphView extends ZoomButtonsController.OnZoomListener, TouchHa
float getYMax();
void invalidateGraphs();
/* void increaseDensity();
void decreaseDensity();*/

View File

@ -13,10 +13,10 @@ import java.util.List;
public class GraphViewHelper {
@NotNull
private FunctionViewDef functionViewDef = FunctionViewDef.newDefaultInstance();
private PlotViewDef plotViewDef = PlotViewDef.newDefaultInstance();
@NotNull
private List<PlotFunction> functionPlotDefs = Collections.emptyList();
private List<PlotFunction> plotFunctions = Collections.emptyList();
private GraphViewHelper() {
}
@ -27,12 +27,12 @@ public class GraphViewHelper {
}
@NotNull
public static GraphViewHelper newInstance(@NotNull FunctionViewDef functionViewDef,
public static GraphViewHelper newInstance(@NotNull PlotViewDef plotViewDef,
@NotNull List<PlotFunction> plotFunctions) {
final GraphViewHelper result = new GraphViewHelper();
result.functionViewDef = functionViewDef;
result.functionPlotDefs = Collections.unmodifiableList(plotFunctions);
result.plotViewDef = plotViewDef;
result.plotFunctions = Collections.unmodifiableList(plotFunctions);
return result;
}
@ -41,19 +41,19 @@ public class GraphViewHelper {
public GraphViewHelper copy(@NotNull List<PlotFunction> plotFunctions) {
final GraphViewHelper result = new GraphViewHelper();
result.functionViewDef = functionViewDef;
result.functionPlotDefs = Collections.unmodifiableList(plotFunctions);
result.plotViewDef = plotViewDef;
result.plotFunctions = Collections.unmodifiableList(plotFunctions);
return result;
}
@NotNull
public List<PlotFunction> getFunctionPlotDefs() {
return functionPlotDefs;
public List<PlotFunction> getPlotFunctions() {
return plotFunctions;
}
@NotNull
public FunctionViewDef getFunctionViewDef() {
return functionViewDef;
public PlotViewDef getPlotViewDef() {
return plotViewDef;
}
}

View File

@ -0,0 +1,88 @@
package org.solovyev.android.calculator.plot;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* User: serso
* Date: 1/18/13
* Time: 8:32 PM
*/
public class GraphsData {
@NotNull
private final GraphView graphView;
@NotNull
private List<GraphData> graphs;
private float lastXMin;
private float lastXMax;
private float lastYMin;
private float lastYMax;
public GraphsData(@NotNull GraphView graphView) {
this.graphView = graphView;
graphs = new ArrayList<GraphData>(graphView.getPlotFunctions().size());
}
public void clear() {
for (GraphData graph : graphs) {
graph.clear();
}
while (graphView.getPlotFunctions().size() > graphs.size()) {
graphs.add(GraphData.newEmptyInstance());
}
lastYMin = 0;
lastYMax = 0;
}
@NotNull
public List<GraphData> getGraphs() {
return graphs;
}
public float getLastXMin() {
return lastXMin;
}
public float getLastXMax() {
return lastXMax;
}
public float getLastYMin() {
return lastYMin;
}
public float getLastYMax() {
return lastYMax;
}
void checkBoundaries(float graphHeight, float yMin, float yMax) {
if (yMin < lastYMin || yMax > lastYMax) {
float halfGraphHeight = graphHeight / 2;
clear();
lastYMin = yMin - halfGraphHeight;
lastYMax = yMax + halfGraphHeight;
}
}
public void setLastXMin(float lastXMin) {
this.lastXMin = lastXMin;
}
public void setLastXMax(float lastXMax) {
this.lastXMax = lastXMax;
}
@NotNull
public GraphData get(int i) {
return this.graphs.get(i);
}
}

View File

@ -1,25 +0,0 @@
package org.solovyev.android.calculator.plot;
import android.content.Context;
import android.graphics.Canvas;
import org.achartengine.GraphicalView;
import org.achartengine.chart.AbstractChart;
import org.solovyev.android.calculator.Locator;
public class MyGraphicalView extends GraphicalView {
private static final String TAG = MyGraphicalView.class.getSimpleName();
public MyGraphicalView(Context context, AbstractChart chart) {
super(context, chart);
}
@Override
protected void onDraw(Canvas canvas) {
try {
super.onDraw(canvas);
} catch (RuntimeException e) {
Locator.getInstance().getLogger().error(TAG, e.getMessage(), e);
}
}
}

View File

@ -1,258 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator.plot;
import org.achartengine.model.XYSeries;
import org.achartengine.util.MathHelper;
import java.util.*;
/**
* User: serso
* Date: 12/5/11
* Time: 8:43 PM
*/
/**
* BEST SOLUTION IS TO MODIFY LIBRARY CLASS
* NOTE: this class is a copy of XYSeries with some modifications:
* 1. Possibility to insert point in th emiddle og the range
*/
public class MyXYSeries extends XYSeries {
/**
* The series title.
*/
private String mTitle;
/**
* A list to contain the values for the X axis.
*/
private List<Double> mX = new ArrayList<Double>();
/**
* A list to contain the values for the Y axis.
*/
private List<Double> mY = new ArrayList<Double>();
/**
* The minimum value for the X axis.
*/
private double mMinX = MathHelper.NULL_VALUE;
/**
* The maximum value for the X axis.
*/
private double mMaxX = -MathHelper.NULL_VALUE;
/**
* The minimum value for the Y axis.
*/
private double mMinY = MathHelper.NULL_VALUE;
/**
* The maximum value for the Y axis.
*/
private double mMaxY = -MathHelper.NULL_VALUE;
/**
* The scale number for this series.
*/
private int mScaleNumber;
/**
* Builds a new XY series.
*
* @param title the series title.
*/
public MyXYSeries(String title) {
this(title, 10);
}
public MyXYSeries(String title, int initialCapacity) {
super(title, 0);
this.mX = new ArrayList<Double>(initialCapacity);
this.mY = new ArrayList<Double>(initialCapacity);
mTitle = title;
mScaleNumber = 0;
initRange();
}
public int getScaleNumber() {
return mScaleNumber;
}
/**
* Initializes the range for both axes.
*/
private void initRange() {
mMinX = MathHelper.NULL_VALUE;
mMaxX = -MathHelper.NULL_VALUE;
mMinY = MathHelper.NULL_VALUE;
mMaxY = -MathHelper.NULL_VALUE;
int length = getItemCount();
for (int k = 0; k < length; k++) {
double x = getX(k);
double y = getY(k);
updateRange(x, y);
}
}
/**
* Updates the range on both axes.
*
* @param x the new x value
* @param y the new y value
*/
private void updateRange(double x, double y) {
mMinX = Math.min(mMinX, x);
mMaxX = Math.max(mMaxX, x);
mMinY = Math.min(mMinY, y);
mMaxY = Math.max(mMaxY, y);
}
/**
* Returns the series title.
*
* @return the series title
*/
public String getTitle() {
return mTitle;
}
/**
* Sets the series title.
*
* @param title the series title
*/
public void setTitle(String title) {
mTitle = title;
}
/**
* Adds a new value to the series.
*
* @param x the value for the X axis
* @param y the value for the Y axis
*/
public void add(double x, double y) {
boolean added = false;
for (int i = 0; i < mX.size(); i++ ) {
if ( mX.get(i) > x ) {
mX.add(i, x);
mY.add(i, y);
added = true;
break;
}
}
if ( !added ) {
mX.add(x);
mY.add(y);
}
updateRange(x, y);
}
public boolean needToAdd(double density, double x) {
boolean result = true;
for (Double x1 : mX) {
if (Math.abs(x - x1) < density) {
result = false;
break;
}
}
return result;
}
/**
* Removes an existing value from the series.
*
* @param index the index in the series of the value to remove
*/
public void remove(int index) {
double removedX = mX.remove(index);
double removedY = mY.remove(index);
if (removedX == mMinX || removedX == mMaxX || removedY == mMinY || removedY == mMaxY) {
initRange();
}
}
/**
* Removes all the existing values from the series.
*/
public void clear() {
mX.clear();
mY.clear();
initRange();
}
/**
* Returns the X axis value at the specified index.
*
* @param index the index
* @return the X value
*/
public double getX(int index) {
return mX.get(index);
}
/**
* Returns the Y axis value at the specified index.
*
* @param index the index
* @return the Y value
*/
public double getY(int index) {
return mY.get(index);
}
/**
* Returns the series item count.
*
* @return the series item count
*/
public int getItemCount() {
return mX == null ? 0 : mX.size();
}
/**
* Returns the minimum value on the X axis.
*
* @return the X axis minimum value
*/
public double getMinX() {
return mMinX;
}
/**
* Returns the minimum value on the Y axis.
*
* @return the Y axis minimum value
*/
public double getMinY() {
return mMinY;
}
/**
* Returns the maximum value on the X axis.
*
* @return the X axis maximum value
*/
public double getMaxX() {
return mMaxX;
}
/**
* Returns the maximum value on the Y axis.
*
* @return the Y axis maximum value
*/
public double getMaxY() {
return mMaxY;
}
}

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
* Date: 1/5/13
* Time: 9:11 PM
*/
public class FunctionViewDef {
public class PlotViewDef {
/*
**********************************************************************
@ -38,10 +38,10 @@ public class FunctionViewDef {
private int backgroundColor = DEFAULT_BACKGROUND_COLOR;
private FunctionViewDef() {
private PlotViewDef() {
}
private FunctionViewDef(int axisColor, int axisLabelColor, int gridColor, int backgroundColor) {
private PlotViewDef(int axisColor, int axisLabelColor, int gridColor, int backgroundColor) {
this.axisColor = axisColor;
this.axisLabelsColor = axisLabelColor;
this.gridColor = gridColor;
@ -49,13 +49,13 @@ public class FunctionViewDef {
}
@NotNull
public static FunctionViewDef newDefaultInstance() {
return new FunctionViewDef();
public static PlotViewDef newDefaultInstance() {
return new PlotViewDef();
}
@NotNull
public static FunctionViewDef newInstance(int axisColor, int axisLabelColor, int gridColor, int backgroundColor) {
return new FunctionViewDef(axisColor, axisLabelColor, gridColor, backgroundColor);
public static PlotViewDef newInstance(int axisColor, int axisLabelColor, int gridColor, int backgroundColor) {
return new PlotViewDef(axisColor, axisLabelColor, gridColor, backgroundColor);
}
public int getAxisColor() {

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.calculator.plot;
import jscl.math.Expression;
import jscl.math.function.Constant;
import junit.framework.Assert;
import org.achartengine.model.XYSeries;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* User: serso
* Date: 12/5/11
* Time: 9:07 PM
*/
public class PlotUtilsTest {
@Test
public void testAddXY() throws Exception {
MyXYSeries series = new MyXYSeries("test_01", 100);
PlotUtils.addXY(-10, 10, Expression.valueOf("asin(t)"), new Constant("t"), series, new MyXYSeries("test_01_imag"), false, 200);
testAscSeries(series);
PlotUtils.addXY(-1, 1, Expression.valueOf("asin(t)"), new Constant("t"), series, new MyXYSeries("test_01_imag"), true, 200);
testAscSeries(series);
series = new MyXYSeries("test_02", 1000);
PlotUtils.addXY(-10, 10, Expression.valueOf("1/t"), new Constant("t"), series, new MyXYSeries("test_01_imag"), false, 1000);
testAscSeries(series);
PlotUtils.addXY(-1, 1, Expression.valueOf("1/t"), new Constant("t"), series, new MyXYSeries("test_01_imag"), true, 1000);
testAscSeries(series);
}
public void testAscSeries(@NotNull XYSeries series) {
for ( int i = 0; i < series.getItemCount(); i++ ) {
if (i > 1) {
Assert.assertTrue(series.getX(i - 1) + " > " +series.getX(i) + " at " + i + " of " + series.getItemCount(), series.getX(i - 1) <= series.getX(i));
}
}
}
}