How to draw a function curve in android? - android

I have a function, y = 50sin(x/50) + 100.
Now what i want to do is to draw a curve for this function, but without any additional info from the graph plotters, I just need a wave on the screen. The X should correspond to my X parameter of the screen and Y to the Y parameter of the screen.
Here is the code of my current view.
public class Sinusoid extends View {
Paint paint = new Paint();
public Sinusoid(Context context) {
super(context);
paint.setColor(Color.RED);
}
#Override
public void onDraw(Canvas canvas) {
//todo draw the line using myFunction
}
private double myFunction(double x){
return 50 * Math.sin(x / 50) + 100;
}
}
Now the question is, what should i fill in my TODO?
Please help, any documentation or example will be very useful.
Thanks in advance.

The idea is to have something like this:
#Override
public void onDraw(Canvas canvas) {
for (int x=0; x<canvas.getWidth();x++) {
canvas.drawPoint(x,(float) myFunction(x),mPaint);
}
}
But you'll eventually need to adjust the value of your function given the canvas height.

Try something like this:
float xv=x0,yv= yo,x,y;
for (int i = 0; i < maxPoints; i++) {
x=i * scalex;
y=function(i)*scaley;
canvas.drawLine(xv, yv, x, y, paint);
xv=x;
yv=y;
}
The graphic will appears continuous.

Related

Drawing background color between limit lines

Im trying to draw colors between limit lines in a LineChart, by extending the LineChart class and overriding the onDraw method. Im doing it as seen in https://github.com/PhilJay/MPAndroidChart/issues/485 where colored rectangles are simply drawn on the canvas:
public class CustomLineChart extends LineChart {
protected Paint mYAxisZonePaint;
protected String[] arrayColors = {"#FFFFFF","#F4D03F","#F5B041","#EB984E","#DC7633"};
#Override
protected void init() {
super.init();
mYAxisZonePaint = new Paint();
mYAxisZonePaint.setStyle(Paint.Style.FILL);
}
#Override
protected void onDraw(Canvas canvas) {
List<LimitLine> limitLines = mAxisLeft.getLimitLines();
if (limitLines == null || limitLines.size() < 4){
super.onDraw(canvas);
return;
}
float[] pts = new float[2];
float startPts = 0;
for (int i = 0; i < limitLines.size(); i++) {
pts[0] = limitLines.get(i).getLimit();
pts[1] = startPts;
mLeftAxisTransformer.pointValuesToPixel(pts);
mYAxisZonePaint.setColor(Color.parseColor(arrayColors[i]));
canvas.drawRect(mViewPortHandler.contentLeft(), pts[1], mViewPortHandler.contentRight(), pts[0], mYAxisZonePaint);
startPts = limitLines.get(i).getLimit();
}
super.onDraw(canvas);
}
}
With the code above im able to draw the colored rectangles as a background behind the data on the line chart as seen here
However when I scroll on the Y-axis, the rectangles will "overflow" into the area where X-axis labels are drawn as demonstrated in this image
How should i draw the colored background so it doesn't color the area where X-axis labels are drawn, similar to how the line data and limit lines are cut off just above the X-axis?
It seems like i have found a solution to my issue. Inside the onDraw method i should restrict the area that the following draw operations can write to. I added the following to my onDraw method:
#Override
protected void onDraw(Canvas canvas) {
List<LimitLine> limitLines = mAxisLeft.getLimitLines();
// Saves current canvas
int clipRestoreCount = canvas.save();
// Makes sure that the colored background cannot be drawn outside the content-rect. Otherwise the colored background would be drawn above and beneath the graph "borders".
canvas.clipRect(mViewPortHandler.getContentRect());
if (limitLines == null || limitLines.size() < 4){
super.onDraw(canvas);
return;
}
float[] pts = new float[2];
float startPts = 0;
for (int i = 0; i < limitLines.size(); i++) {
pts[0] = limitLines.get(i).getLimit();
pts[1] = startPts;
mLeftAxisTransformer.pointValuesToPixel(pts);
mYAxisZonePaint.setColor(Color.parseColor(arrayColors[i]));
canvas.drawRect(mViewPortHandler.contentLeft(), pts[1], mViewPortHandler.contentRight(), pts[0], mYAxisZonePaint);
startPts = limitLines.get(i).getLimit();
}
// Removes the clipping rectangles so rest of graph elements can be drawn (like x-axis/y-axis labels)
canvas.restoreToCount(clipRestoreCount);
super.onDraw(canvas);
}
Result:

AlertDialog with TextView - can't use textView.setText method

I'm trying to draw a parabola with delay, using custom view. So far I've learned that I need to use #Override onDraw method, but 1. I can't make my parabola discrete and 2. I don't know how to program it so the shape is created step-by-step (with delay).
I also need to draw it after click of a button, so that is another complication for me. Right now I'm trying to draw a simple line step-by-step but this snippet don't work:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, paint);
int x1 = 10;
int x2 = 100;
int y1 = 10;
int y2 = 100;
int diff = x2-x1;
for (int i = 0; i<diff; i++){
canvas.drawLine(x1, y1, x1+1, y1+1, paint);
x1++;
y1++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
I can give you some tricks but please finish it your self.
You have to use a timer to refresh your component, if use this, it will refresh "onDraw" each 100ms.
private Handler handler = new Handler();
private Runnable AlarmRunnable = new Runnable() {
#Override
public void run() {
handler.postDelayed(this, 100);
invalidate();
}
};
define global variables instead of local.
int cc = 0;
int x1 = 10;
int x2 = 100;
int y1 = 10;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (cc++ > 100) // STOP HANDLER AFTER COUNTER GETS DONE
handler.removeCallbacks(AlarmRunnable);
System.out.println("CC:" + cc);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(lineWidth);
paint.setColor(Color.WHITE);
for (int i = 0; i<cc; i++){
canvas.drawLine(x1, y1, x1 + 1, y1 + 1, paint);
x1++;
y1++;
}
}
start handeler in Constructor
public YOURCOMPONENT(Context context, AttributeSet attrs) {
super(context, attrs);
handler.post(AlarmRunnable);
....
}
Rendering a Discrete Parabola
As for drawing a discrete parabola, you should draw points (or circles with a radius size of your choice, but centered at the points) along the different x and y coordinates with a larger step size.
For example, you can draw a parabola from x=-1 to x=1 with a step size of 1 by drawing at the following (x, y) points: (-1, 0), (0, 4), (1, 0).
You should make sure that the way you scale your x-axis on your graph, is in a way that there is greater than 1 pixel distance between the points to make it look discrete.
Animated onDraw
Regardless of whether your drawing logic within onDraw is correct or not, you are running a long operation with Thread.sleep() on a UI callback, which is bad practice.
Since you are drawing the whole parabola within one call of onDraw, I would assume the whole image is rendered at once rather than animated.
Looking at a similar question, you should create another thread that is in charge of a rendering loop for your custom view, to create an animation where you draw each frame.

How to add a .png inside a canvas?

I have a game where a ball is bounced.. I would like to replace the drawen ball with a soccer-ball in a png format.
I've tried lot of things but nothing worked.
Here is my code for drawing the ball :
public ball() {
position = new Vector(0.0f, 0.0f);
paused_velocity = velocity = new Vector(0.0f, 0.0f);
paused_acceleration = acceleration = new Vector(0.0f, gravity);
radius = 0.0f;
delta = 0;
ball_paint = PaintList.getRandPaint();
start_time = SystemClock.uptimeMillis();
}
public void draw(Canvas canvas) {
canvas.drawCircle(position.X(), position.Y(), radius, ball_paint);
}
thanks a lot
If you png is present a ball.png in your drawable directory..
public Bitmap getBallBitmap()
{
return BitmapFactory.decodeResource(getResources(),
R.drawable.ball);
}
#Override
protected void draw(Canvas canvas)
{
//ideally you should cache the value returned by getBallBitmap()
canvas.drawBitmap(getBallBitmap(),position.X(), position.Y(), null);
}
Note that you should use onDraw() method instead of draw() method as much as possible.

How to make line with rounded (smooth) corners with AndroidPlot

I have a small problem with ploting my graph. On a picture below is what I have already done.
The graph should represent the actual signal strength of available Wi-Fi network(s). It's a simple XYPlot here data are represented with SimpleXYSeries (values are dynamically created).
Here is a little snippet of code (only for example):
plot = (XYPlot) findViewById(R.id.simplexyPlot);
series1 = new SimpleXYSeries(Arrays.asList(series1Numbers),
SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Link 1");
f1 = new LineAndPointFormatter(color.getColor(), null,
Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);
The example in the picture is a dynamic simulation of dB changes. Everything works, I guess, correctly, but what I want to achieve is to have line with "rounded" corners (see the picture to see what I mean).
I already tried to customize LineFormatter:
f1.getFillPaint().setStrokeJoin(Join.ROUND);
f1.getFillPaint().setStrokeWidth(8);
But this didn't work as expected.
Note: The Wifi Analyzer application has a similar graph and its graph has the rounded corners I want. It looks like this:
You can use Path.cubicTo() method. It draws a line using cubic spline algorithm which results in the smoothing effect you want.
Checkout the answer to a similar question here, where a guy is talking about cubic splines. There is a short algorithm showing how to calculate input parameters for Path.cubicTo() method. You can play with divider values to achieve required smoothness. For example, in the picture below I divided by 5 instead of 3. Hope this helps.
I have spent some time and implemented a SplineLineAndPointFormatter class, which does the stuff you need in androidplot library. It uses same technics. Here is how androidplot example applications looks like. You just need to use it instead of LineAndPointFormatter.
Here is code example and the class I wrote.
f1 = new SplineLineAndPointFormatter(color.getColor(), null,
Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);
Here is the class doing the magic. It is based on version 0.6.1 of androidplot library.
package com.androidplot.xy;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import com.androidplot.ui.SeriesRenderer;
import com.androidplot.util.ValPixConverter;
public class SplineLineAndPointFormatter extends LineAndPointFormatter {
public SplineLineAndPointFormatter() { }
public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor) {
super(lineColor, vertexColor, fillColor, null);
}
public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor, FillDirection fillDir) {
super(lineColor, vertexColor, fillColor, null, fillDir);
}
#Override
public Class<? extends SeriesRenderer> getRendererClass() {
return SplineLineAndPointRenderer.class;
}
#Override
public SeriesRenderer getRendererInstance(XYPlot plot) {
return new SplineLineAndPointRenderer(plot);
}
public static class SplineLineAndPointRenderer extends LineAndPointRenderer<BezierLineAndPointFormatter> {
static class Point {
public float x, y, dx, dy;
public Point(PointF pf) { x = pf.x; y = pf.y; }
}
private Point prev, point, next;
private int pointsCounter;
public SplineLineAndPointRenderer(XYPlot plot) {
super(plot);
}
#Override
protected void appendToPath(Path path, final PointF thisPoint, PointF lastPoint) {
pointsCounter--;
if (point == null) {
point = new Point(thisPoint);
point.dx = ((point.x - prev.x) / 5);
point.dy = ((point.y - prev.y) / 5);
return;
} else if (next == null) {
next = new Point(thisPoint);
} else {
prev = point;
point = next;
next = new Point(thisPoint);
}
point.dx = ((next.x - prev.x) / 5);
point.dy = ((next.y - prev.y) / 5);
path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y);
if (pointsCounter == 1) { // last point
next.dx = ((next.x - point.x) / 5);
next.dy = ((next.y - point.y) / 5);
path.cubicTo(point.x + point.dx, point.y + point.dy, next.x - next.dx, next.y - next.dy, next.x, next.y);
}
}
#Override
protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAndPointFormatter formatter) {
Number y = series.getY(0);
Number x = series.getX(0);
if (x == null || y == null) throw new IllegalArgumentException("no null values in xyseries permitted");
XYPlot p = getPlot();
PointF thisPoint = ValPixConverter.valToPix(x, y, plotArea,
p.getCalculatedMinX(), p.getCalculatedMaxX(), p.getCalculatedMinY(), p.getCalculatedMaxY());
prev = new Point(thisPoint);
point = next = null;
pointsCounter = series.size();
super.drawSeries(canvas, plotArea, series, formatter);
}
}
}
1- I guess that you only use a few points to draw graphs of signals. All graph/chart applications try to connect points with direct lines and then your chart will be shown. So if you only use three points, your graph will looks like a triangle! If you want your graph to be curved, you have to add more points. Then it comes out like a curve.
2- Or you can find any library that can draw sin graph, for example GraphView Library. Then try to draw this function:
So it looks like to this:
Then translate it to (a,0), so result seems like what you want.
3- And another way, you can use built in Math.sin in Java:
Chose for example 1000 point in range a to b and compute value of above function for each point and finally create a path and show them in a canvas.
You can use quadTo (float x1, float y1, float x2, float y2) that simplify drawing quad curves for you. The documentation says:
Add a quadratic bezier from the last point, approaching control point
(x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
this contour, the first point is automatically set to (0,0).
Parameters
x1 The x-coordinate of the control point on a quadratic curve
y1 The y-coordinate of the control point on a quadratic curve
x2 The x-coordinate of the end point on a quadratic curve
y2 The y-coordinate of the end point on a quadratic curve
Finally, I add a simple class that extends View and can draw a curve that looks like what you want:
public class SinWave extends View {
private float first_X = 50;
private float first_Y = 230;
private float end_X = 100;
private float end_Y = 230;
private float Max = 50;
public SinWave(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint() {
{
setStyle(Paint.Style.STROKE);
setStrokeCap(Paint.Cap.ROUND);
setStrokeWidth(0.7f);
setAntiAlias(true);
setColor(0xFFFF00FF);
}
};
final Path path = new Path();
path.moveTo(first_X, first_Y);
path.quadTo((first_X + end_X)/2, Max, end_X, end_Y);
canvas.drawPath(path, paint);
}
}
The result must look like this:
You can add more methods to the class and change it to increase performance!
There's always been a smooth line renderer in Androidplot: BezierLineAndPointRenderer, which like the implementations above uses Android's built in Bezier drawing routines cubicTo(...) & quadTo(...). The problem is that using Beziers to draw smooth lines in this way creates a false line that overshoots the actual control points by varying amounts, which you can see happening if you look closely at the image above.
The solution is to use the Catmull-Rom spline interpolation, which is now finally supported by Androidplot. Details here: http://androidplot.com/smooth-curves-and-androidplot/
Just use ChartFactory.getCubeLineChartView instead of ChartFactory.getLineChartView using achart engine
In some simple cases, this could help:
mPaint.pathEffect = CornerPathEffect(radius)
even in combination with
path.lineTo(x,y)
try this:
symbol = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
paint.setColor(-7829368);
paint.setStrokeJoin(Paint.Join.ROUND); // set the join to round you want
paint.setStrokeCap(Paint.Cap.ROUND); // set the paint cap to round too
paint.setPathEffect(new CornerPathEffect(10) );
paint.setStyle(Paint.Style.STROKE);
symbol.moveTo(50.0F, 230.0F);
symbol.lineTo(75.0F, 100.0F);
symbol.lineTo(100.0F, 230.0F);
most of the info found here

How to make an Android app that prints touched points and draw them?

I want to make a small app. You will touch the screen and draw something and it will list points you pass and draw small green 3x3 rectangles for each fifth point. I use onTouchEvent for listing points using TextView and send it to setContentView. However, I have problem in drawing. I checked examples for drawing (onDraw) but I am not able to get it working for both printing point plus drawing green dots. Any help would be great, thanks.
Here you are, a quick sample of drawing on SurfaceView.
public class FunPanel extends SurfaceView {
class Point {
int X;
int Y;
public Point() {
X = Y = -1;
}
}
private ArrayList<Point> mPoints = new ArrayList<Point>();
private Point mCurPoint = new Point();
private Bitmap mBitmap = ....// your desired image
#Override
public void doDraw(Canvas canvas) {
if( !(mPoints.size() % 5) ) {
canvas.drawBitmap(mBitmap, mCurPoint.X, mCurPoint.Y, null);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mCurPoint.X = (int) event.getX() - mBitmap.getWidth() / 2;
mCurPoint.Y = (int) event.getY() - mBitmap.getHeight() / 2;
mPoints.add(mCurPoint);
return super.onTouchEvent(event);
}
}
It's not entirely clear what you're trying to do, but have a look at this It should get you started in the right direction. Basically extend a View and override the onDraw(Canvas) to draw the Rectangles and override the onTouchEvent(MotionEvent) to grab the touch points from the screen.

Categories

Resources