new plotter
This commit is contained in:
parent
3a55ff6fb7
commit
9ad431ffd3
@ -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,38 +437,36 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.setFunctionPlotDefs(arityFunctions);
|
|
||||||
graphView.setXRange((float)plotBoundaries.getXMin(), (float)plotBoundaries.getXMax());
|
graphView.setXRange((float)plotBoundaries.getXMin(), (float)plotBoundaries.getXMax());
|
||||||
|
graphView.setFunctionPlotDefs(arityFunctions);
|
||||||
|
|
||||||
graphContainer.addView((View) graphView);
|
graphContainer.addView((View) graphView);
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user