Drawing background color between limit lines - android

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:

Related

Making bars with GraphView

I have been using Graphview for some time and mostly LineGraph and PointGraph, link to GraphView can be found here: Link to GraphView.
But now I need a LineGraph that would fill the whole 0-100 and 100-200 when needed. For example when the point is 70, it would fill the whole 0-100 space, which would look something like this.
Another requirement is that is still needs to be like a LineGraph since it needs to be able to move to the right.
Does anyone have an idea how this could be done using GraphView or if it can be done at all with GraphView.
Or maybe if I set the point to be 50 and line thickness so it would cover exactly +/- 50 then it would also be the same but the problem here is that the line thickness is different on every screen.
You can use a custom shape for the PointGraphSeries to get the effect that you like. In the following code I am creating a custom rectangle. This can give you some ideas on what to do:
int mX = 0;
private void addPoints(double point, PointsGraphSeries<DataPoint> series) {
point = Math.floor(point / 100) * 100;
DataPoint dataPoint = new DataPoint(mX++, point);
series.appendData(dataPoint, false, 100);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GraphView graph = findViewById(R.id.graph);
// Set manual X bounds
graph.getViewport().setXAxisBoundsManual(true);
graph.getViewport().setMinX(0);
graph.getViewport().setMaxX(10);
// Set manual Y bounds
graph.getViewport().setYAxisBoundsManual(true);
graph.getViewport().setMinY(0);
graph.getViewport().setMaxY(1000);
// Set up the number of division for horizontal and vertical units
graph.getGridLabelRenderer().setNumHorizontalLabels(11);
graph.getGridLabelRenderer().setNumVerticalLabels(11);
PointsGraphSeries<DataPoint> series = new PointsGraphSeries<>();
series.setCustomShape(new PointsGraphSeries.CustomShape() {
#Override
public void draw(Canvas canvas,
Paint paint,
float x,
float y,
DataPointInterface dataPoint) {
canvas.drawRect(x, y - 100, x + 175, y, paint);
}
});
int[] points = {450, 512, 323, 240, 70, 790};
for (int i = 0; i < points.length; i++) {
addPoints(points[i], series);
}
graph.addSeries(series);
}
This will give you the following picture based on the provided points:

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.

Android TransitionDrawable not fading

I have an ImageView and I am trying to fade from one image to the next using this code:
Drawable bgs[] = new Drawable[2];
public void redraw(int[][] grid) {
bgs[0] = bgs[1];
bgs[1] = new GameDrawable(grid, prefs.colors);
if (bgs[0] == null) {
gameField.setImageDrawable(bgs[1]);
} else {
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
}
gameField is correctly referenced as an ImageView.
gameDrawable simply extends Drawable and draws the grid.
On each move and action the new GameDrawable is being rendered correctly but there is no fading whatsoever. The new image is simply displayed instantaneously. I have tried lengthening the transition time and swapping the order of the drawables with no effect.
Any help on is appreciated.
Update: I have now set my transition to something ridiculously long like 500000. The first drawable shows for a few seconds and then suddenly the second drawable appears. So still no transition.
Update 2:
I think my Drawable might be implemented incorrectly, so I have attached the code.
public class GameDrawable extends Drawable {
private Paint paint = new Paint();
private float blockWidth = 1;
private int[][] myGrid;
private int myColor;
private List<Point> myPoints;
public GameDrawable(int[][] grid) {
super();
this.myGrid = grid;
this.myColor = colors[yourColor];
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(0);
this.myPoints = yourPoints;
}
#Override
public void draw(Canvas canvas) {
float height = getBounds().height();
float width = getBounds().width();
blockWidth = width / myGrid.length;
if (height / myGrid.length < blockWidth) {
blockWidth = height / myGrid.length;
}
for (int x = 0; x < myGrid.length; x++) {
for (int y = 0; y < myGrid[x].length; y++) {
paint.setColor(colors[myGrid[x][y]]);
canvas.drawRect(x * blockWidth, y * blockWidth, (x+1)*blockWidth, (y+1)*blockWidth, paint);
}
}
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Looking at your code, I see a problem at the line
bgs[0] = bgs[1];
bgs[1] has not yet been defined before this line and so bgs[0] is null for the first method call. Because of this, (bgs[0] == null) is true, and so the later defined bgs[1] is directly set to the gameField ImageView.
Use corrected code below.
Drawable bgs[] = new Drawable[2];
Drawable firstDrawable = getResources().getDrawable(R.drawable.transparent);
public void redraw(int[][] grid) {
bgs[0] = firstDrawable;
bgs[1] = new GameDrawable(grid, prefs.colors);
firstDrawable = bgs[1];
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
Note that TransitionDrawable does not work properly when the Drawable sizes are different. So you may need to resize firstDrawable beforehand.
EXTRA: I would avoid setCrossFadeEnabled(true) since the whole TransitionDrawable becomes translucent during the transition, revealing the background. Sometimes, this creates a "blinking" effect and destroys the smoothness of the transition.
EDIT: Looking at your custom Drawable implementation, I think the problem lies in the line
canvas.drawColor(Color.WHITE);
in the draw() method.
I looked at TransitionDrawable.java source and found that setAlpha is called on the drawables to get the cross fade effect. However, your canvas has a solid white color and setAlpha() only affects the paint. Hope this is your answer.
EDIT 2: The actual problem, as pointed out by Michael, was that TransitionDrawable's setAlpha() calls on the Drawables were rendered ineffective due to paint.setColor() in the GameDrawable's draw() method overriding the paint's alpha value set by the TransitionDrawable.

How to draw a function curve in 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.

Drawing to the canvas on Android - screen flickering/tearing

I have an isometric map which I draw to the canvas. When I try and move the map around, these black lines flicker in between some of the tiles making the whole thing look rather shoddy.
Here is the relevant code from my updating thread
public void run() {
Canvas c;
while (isRunning){
c = null;
try {
c = cellMap.getHolder().lockCanvas(null);
synchronized (cellMap.getHolder()) {
cellMap.onDraw(c);
}
} finally {
if( c != null){
cellMap.getHolder().unlockCanvasAndPost(c);
}
}
}
}
and from my CellMap class (which extends SurfaceView):
public void onDraw(Canvas canvas){
canvas.drawColor(Color.BLACK);
int x = 0;
int y = 0;
for(int i = 0; i < mapSize; i++){
for(int j = 0; j < mapSize; j++){
x = (i-j) * 30 + xOffset;
y = (i+j) * 15 + yOffset;
mapCells[i][j].draw(canvas, paint, x, y);
}
}
mapCells[][] is an array of objects which contain the Bitmap image that needs to be drawn. The draw() function is only 1 line canvas.drawBitmap(bitmapImage, x, y, null)
I have discovered that removing the line Canvas.draw(Color.black) gets rid of the flicker. However, when I move the map the canvas is not cleared so I still see the image from the previous frames. I'd imagine it is because it is taking too long to draw the bitmaps? but the map is only very small at the moment.
I found the problem. When moving the map, the offset values were being changed. This lead to sections of the map being drawn temporarily with different offset values - creating the tearing. duh!
I solved this by copying the xOffset and yOffset values at the start of the onDraw() method, and using those values for the update.

Categories

Resources