new plotter

This commit is contained in:
Sergey Solovyev 2013-01-16 00:02:22 +04:00
parent 3a55ff6fb7
commit 9ad431ffd3
3 changed files with 107 additions and 83 deletions

View File

@ -4,12 +4,7 @@ package org.solovyev.android.calculator.plot;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.*;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@ -18,6 +13,7 @@ import android.widget.ZoomButtonsController;
import org.javia.arity.Function; import org.javia.arity.Function;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -35,6 +31,14 @@ public class CalculatorGraph2dView extends View implements GraphView {
private static final float TICKS_COUNT = 15; private static final float TICKS_COUNT = 15;
public static final int TICK_SIZE_PXS = 3; public static final int TICK_SIZE_PXS = 3;
private static final DecimalFormat tickFormat = new DecimalFormat("##0.#####E0");
private static final int MAX_TICK_DIGITS = 4;
private static final String[] TICK_FORMATS = new String[MAX_TICK_DIGITS];
static {
for(int i = 0; i < MAX_TICK_DIGITS; i++) {
TICK_FORMATS[i] = "%." + i + "f";
}
}
/* /*
********************************************************************** **********************************************************************
* *
@ -130,8 +134,9 @@ public class CalculatorGraph2dView extends View implements GraphView {
@Override @Override
public void setXRange(float xMin, float xMax) { public void setXRange(float xMin, float xMax) {
this.x0 = xMin + graphWidth / 2;
this.graphWidth = xMax - xMin; this.graphWidth = xMax - xMin;
this.x0 = xMin + graphWidth / 2;
this.y0 = 0;
} }
private void clearAllGraphs() { private void clearAllGraphs() {
@ -178,7 +183,6 @@ public class CalculatorGraph2dView extends View implements GraphView {
widthPxs = w; widthPxs = w;
heightPxs = h; heightPxs = h;
clearAllGraphs(); clearAllGraphs();
// points = new float[w+w];
} }
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
@ -186,9 +190,9 @@ public class CalculatorGraph2dView extends View implements GraphView {
return; return;
} }
if (scroller.computeScrollOffset()) { if (scroller.computeScrollOffset()) {
final float scale = graphWidth / widthPxs; final float ration = getRatio();
x0 = scroller.getCurrX() * scale; x0 = scroller.getCurrX() * ration;
y0 = scroller.getCurrY() * scale; y0 = scroller.getCurrY() * ration;
if (!scroller.isFinished()) { if (!scroller.isFinished()) {
invalidate(); invalidate();
} }
@ -235,13 +239,7 @@ public class CalculatorGraph2dView extends View implements GraphView {
float yMax, float yMax,
@NotNull GraphData graph) { @NotNull GraphData graph) {
if (function.arity() == 0) { if (function.arity() == 0) {
float v = (float) function.eval(); final float v = (float) function.eval();
if (v < -10000f) {
v = -10000f;
}
if (v > 10000f) {
v = 10000f;
}
graph.clear(); graph.clear();
graph.push(xMin, v); graph.push(xMin, v);
graph.push(xMax, v); graph.push(xMax, v);
@ -262,10 +260,10 @@ public class CalculatorGraph2dView extends View implements GraphView {
graph.push(xMin, eval(function, xMin)); graph.push(xMin, eval(function, xMin));
} }
final float scale = getRatio(); final float ratio = getRatio();
final float maxStep = 15.8976f / scale; final float maxStep = 15.8976f * ratio;
final float minStep = .05f / scale; final float minStep = .05f * ratio;
float ythresh = 1 / scale; float ythresh = ratio;
ythresh = ythresh * ythresh; ythresh = ythresh * ythresh;
@ -354,9 +352,9 @@ public class CalculatorGraph2dView extends View implements GraphView {
final float y = ys[i]; final float y = ys[i];
final float x = xs[i]; final float x = xs[i];
if (y != y) { if (Float.isNaN(y)) {
newCurve = true; newCurve = true;
} else { // !NaN } else {
if (newCurve) { if (newCurve) {
path.moveTo(x, y); path.moveTo(x, y);
newCurve = false; newCurve = false;
@ -387,45 +385,6 @@ public class CalculatorGraph2dView extends View implements GraphView {
} }
} }
private static StringBuilder b = new StringBuilder();
private static char[] buf = new char[20];
@NotNull
private static CharSequence formatTick(final float tickValue) {
/*int pos = 0;
boolean addDot = false;
final boolean negative = tickValue < 0;
int absValue = Math.round(Math.abs(tickValue) * 100);
for (int i = 0; i < 2; ++i) {
int digit = absValue % 10;
absValue /= 10;
if (digit != 0 || addDot) {
buf[pos++] = (char) ('0' + digit);
addDot = true;
}
}
if (addDot) {
buf[pos++] = '.';
}
if (absValue == 0) {
buf[pos++] = '0';
}
while (absValue != 0) {
buf[pos++] = (char) ('0' + (absValue % 10));
absValue /= 10;
}
if (negative) {
buf[pos++] = '-';
}
b.setLength(0);
b.append(buf, 0, pos);
b.reverse();
return b;*/
return Float.toString(tickValue);
}
private void drawGraph(@NotNull Canvas canvas) { private void drawGraph(@NotNull Canvas canvas) {
final float graphHeight = getGraphHeight(); final float graphHeight = getGraphHeight();
@ -452,14 +411,14 @@ public class CalculatorGraph2dView extends View implements GraphView {
final float ratio = getRatio(); final float ratio = getRatio();
float x0px = -xMin * ratio; float x0px = -xMin / ratio;
if (x0px < 25) { if (x0px < 25) {
x0px = 25; x0px = 25;
} else if (x0px > widthPxs - 3) { } else if (x0px > widthPxs - 3) {
x0px = widthPxs - 3; x0px = widthPxs - 3;
} }
float y0px = yMax * ratio; float y0px = yMax / ratio;
if (y0px < 3) { if (y0px < 3) {
y0px = 3; y0px = 3;
} else if (y0px > heightPxs - 15) { } else if (y0px > heightPxs - 15) {
@ -478,37 +437,35 @@ public class CalculatorGraph2dView extends View implements GraphView {
textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setTextAlign(Paint.Align.CENTER);
final float step = getStep(graphWidth); final float step = getStep(graphWidth);
final int tickDigits = countTickDigits(step);
// round xMin and init first tick // round xMin and init first tick
float tick = ((int) (xMin / step)) * step; float tick = ((int) (xMin / step)) * step;
final float y2 = y0px + TICK_SIZE_PXS; final float y2 = y0px + TICK_SIZE_PXS;
float stepPxs = step * ratio; final float stepPxs = step / ratio;
for (float xPxs = (tick - xMin) * ratio; xPxs <= widthPxs; xPxs += stepPxs, tick += step) {
for (float xPxs = (tick - xMin) / ratio; xPxs <= widthPxs; xPxs += stepPxs, tick += step) {
// draw grid line // draw grid line
canvas.drawLine(xPxs, 0, xPxs, heightPxs, paint); canvas.drawLine(xPxs, 0, xPxs, heightPxs, paint);
if (Math.abs(tick) >= .001f) { final CharSequence tickLabel = formatTick(tick, tickDigits);
final CharSequence tickLabel = formatTick(tick);
// draw tick label // draw tick label
canvas.drawText(tickLabel, 0, tickLabel.length(), xPxs, y2 + 10, textPaint); canvas.drawText(tickLabel, 0, tickLabel.length(), xPxs, y2 + 10, textPaint);
}
} }
final float x1 = x0px - TICK_SIZE_PXS; final float x1 = x0px - TICK_SIZE_PXS;
tick = ((int) (yMin / step)) * step; tick = ((int) (yMin / step)) * step;
textPaint.setTextAlign(Paint.Align.RIGHT); textPaint.setTextAlign(Paint.Align.RIGHT);
for (float y = heightPxs - (tick - yMin) * ratio; y >= 0; y -= stepPxs, tick += step) { for (float y = heightPxs - (tick - yMin) / ratio; y >= 0; y -= stepPxs, tick += step) {
canvas.drawLine(0, y, widthPxs, y, paint); canvas.drawLine(0, y, widthPxs, y, paint);
if (Math.abs(tick) >= .001f) { final CharSequence tickLabel = formatTick(tick, tickDigits);
final CharSequence tickLabel = formatTick(tick);
// draw tick label // draw tick label
canvas.drawText(tickLabel, 0, tickLabel.length(), x1, y + 4, textPaint); canvas.drawText(tickLabel, 0, tickLabel.length(), x1, y + 4, textPaint);
}
} }
paint.setPathEffect(null); paint.setPathEffect(null);
@ -525,7 +482,7 @@ public class CalculatorGraph2dView extends View implements GraphView {
matrix.reset(); matrix.reset();
matrix.preTranslate(-this.x0, -this.y0); matrix.preTranslate(-this.x0, -this.y0);
matrix.postScale(ratio, -ratio); matrix.postScale(1/ratio, -1/ratio);
matrix.postTranslate(widthPxs / 2, heightPxs / 2); matrix.postTranslate(widthPxs / 2, heightPxs / 2);
paint.setAntiAlias(false); paint.setAntiAlias(false);
@ -556,6 +513,43 @@ public class CalculatorGraph2dView extends View implements GraphView {
lastXMin = xMin; lastXMin = xMin;
} }
/*
**********************************************************************
*
* TICK FORMAT
*
**********************************************************************
*/
@NotNull
public static CharSequence formatTick(final float tickValue, final int tickDigits) {
String result = "0";
if (tickValue != 0f) {
if (tickDigits < MAX_TICK_DIGITS) {
result = String.format(TICK_FORMATS[tickDigits], tickValue);
} else {
// x.xxE-10 notation
result = tickFormat.format(tickValue);
}
}
return result;
}
public static int countTickDigits(float step) {
if ( step >= 1 ) {
return 0;
} else {
int tickDigits = 0;
while ( step < 1 ) {
step *= 10;
tickDigits++;
}
return tickDigits;
}
}
/* /*
********************************************************************** **********************************************************************
* *
@ -605,8 +599,8 @@ public class CalculatorGraph2dView extends View implements GraphView {
} }
private float getRatio() { private float getRatio() {
if (graphWidth != 0) { if (widthPxs != 0) {
return widthPxs / graphWidth; return graphWidth / widthPxs;
} else { } else {
return 0; return 0;
} }
@ -705,7 +699,7 @@ public class CalculatorGraph2dView extends View implements GraphView {
} else if (asy < asx / 3) { } else if (asy < asx / 3) {
sy = 0; 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(x0 / ratio), Math.round(y0 / ratio), Math.round(sx), Math.round(sy), Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
invalidate(); invalidate();
} }

View File

@ -73,8 +73,8 @@ public class CalculatorPlotFragment extends AbstractCalculatorPlotFragment {
} }
graphView.init(FunctionViewDef.newInstance(Color.WHITE, Color.WHITE, Color.DKGRAY, getBgColor())); graphView.init(FunctionViewDef.newInstance(Color.WHITE, Color.WHITE, Color.DKGRAY, getBgColor()));
graphView.setXRange((float)plotBoundaries.getXMin(), (float)plotBoundaries.getXMax());
graphView.setFunctionPlotDefs(arityFunctions); graphView.setFunctionPlotDefs(arityFunctions);
graphView.setXRange((float)plotBoundaries.getXMin(), (float)plotBoundaries.getXMax());
graphContainer.addView((View) graphView); graphContainer.addView((View) graphView);
} }

View File

@ -0,0 +1,30 @@
package org.solovyev.android.calculator.plot;
import junit.framework.Assert;
import org.junit.Test;
/**
* User: serso
* Date: 1/15/13
* Time: 9:58 PM
*/
public class CalculatorGraph2dViewTest {
@Test
public void testFormatTick() throws Exception {
Assert.assertEquals("23324", CalculatorGraph2dView.formatTick(23324.0f, 0));
Assert.assertEquals("23324.1", CalculatorGraph2dView.formatTick(23324.1f, 1));
}
@Test
public void testCountTickDigits() throws Exception {
Assert.assertEquals(0, CalculatorGraph2dView.countTickDigits(1));
Assert.assertEquals(0, CalculatorGraph2dView.countTickDigits(10));
Assert.assertEquals(0, CalculatorGraph2dView.countTickDigits(100));
Assert.assertEquals(1, CalculatorGraph2dView.countTickDigits(0.9f));
Assert.assertEquals(1, CalculatorGraph2dView.countTickDigits(0.2f));
Assert.assertEquals(1, CalculatorGraph2dView.countTickDigits(0.1f));
Assert.assertEquals(2, CalculatorGraph2dView.countTickDigits(0.099f));
Assert.assertEquals(3, CalculatorGraph2dView.countTickDigits(0.009f));
}
}