Friday, January 04, 2013

Android PieChart Implementation using Graphics API


This demo demonstrates the usage of Android Graphics API to create simple 2D Pie chart and handling of touch event on the Pie-chart. This demo draws the 2D Pie chart based on the given data inputs, colors. Data legends are shown at the bottom of the chart as shown in the below image. Also, it handles the touch events and identifies the particular pie slice whether touched or not to support drilled down features.

Pie Chart using Android Graphics API

Let me explain the application in more details, the steps to draw the Pie-Chart.

1. Create a simple view layout containing one Image View. (activity_main.xml)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

<ImageView
    android:id="@+id/image_placeholder"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:adjustViewBounds="false"
    android:background="#000000"
    android:scaleType="center" />

</RelativeLayout>



2. Create a main activity which contains the above layout (MainActivity.java)



    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        //main layout
        setContentView(R.layout.activity_main);
        
        //pie chart parameters
        int data_values[] = { 20,10,25,5,15,25};
        int color_values[] = {Color.MAGENTA, Color.RED, Color.GREEN,Color.BLUE,Color.YELLOW,Color.CYAN};
        String itemnames[] = { "item 1", "item 2", "item 3", "item 4", "item 5","item 6"};
        
        //get the imageview
        ImageView imgView = (ImageView ) findViewById(R.id.image_placeholder);
         
        //create pie chart Drawable and set it to ImageView
        PieChart pieChart = new PieChart(this, imgView, itemnames, data_values, color_values);
        imgView.setImageDrawable(pieChart);

    }


Create  the required pie-chart parameters like data values, names, colors and create PieChart drawable Shape instance and assign to Image View.

3. PieChart Drawable Shape



Create a PieChart Class by extending Drawable Class and  implementation for OnTouchLister.

public class PieChart extends Drawable implements OnTouchListener {....}

Add the constructor with the required parameters for to draw the Pie Chart.


     public PieChart(Context c, View v, String[] data_names, int[] data_values, int[] color_values) {
         context = c;
         view = v;
         this.data_values = data_values;
         this.color_values = color_values;
         this.data_names = data_names;
         
         paint = new Paint();
         view.setOnTouchListener(this);
     }


And Now, Overwrite the draw() method with the below steps

  • Get the screen width & height
  • Calculate the chart area in Rectangle area
  • Calculate the start angle and Sweep angle for each data item
  •  And finally Draw the Arc, legend box, legend text with corresponding paint.




public void draw(Canvas canvas) {
// TODO Auto-generated method stub

//screen width & height
int view_w = view.getWidth();
        int view_h = view.getHeight();
        
    //chart area rectangle
    arc_bounds = new RectF(
                 left_edge,
                 top_edge,
                 right_edge,
                 bottom_edge
         );

        //sum of data values
        for (int datum : data_values)
                value_sum += datum;
        
        float startAngle = 0;
        int i = 0;
  
        for (int datum : data_values) {
                if (datum == 0) continue;
                
                //calculate start & end angle for each data value
                float endAngle = value_sum == 0 ? 0 : 360 * datum / (float) value_sum;
                float newStartAngle = startAngle + endAngle;
                
             
                int flickr_pink = color_values[i % color_values.length];
                paint.setColor(flickr_pink);
                paint.setAntiAlias(true);
                paint.setStyle(Paint.Style.FILL);
                paint.setStrokeWidth(0.5f);
                
                //gradient fill color
                LinearGradient linearGradient = new LinearGradient(arc_bounds.left, arc_bounds.top, arc_bounds.right,arc_bounds.bottom, flickr_pink, Color.WHITE, Shader.TileMode.CLAMP);
                paint.setShader(linearGradient);
                
                //draw fill arc
                canvas.drawArc(arc_bounds, startAngle, endAngle, true, paint);
            
                Paint linePaint = new Paint();
                linePaint.setAntiAlias(true);
                linePaint.setStyle(Paint.Style.STROKE);
                linePaint.setStrokeJoin(Join.ROUND);
                linePaint.setStrokeCap(Cap.ROUND);
                linePaint.setStrokeWidth(0.5f);
                linePaint.setColor(Color.BLACK);

                //draw border arc
                canvas.drawArc(arc_bounds, startAngle, endAngle, true, linePaint);
                
                int barStartX = 50;
                int barWidth = 20;
                int barStartY = view_h-padding_bottom+(i-1)*2*barWidth;
               
                Rect barRect = new Rect(barStartX,barStartY,barStartX+barWidth,barStartY+barWidth);
                
                //draw legend box
                canvas.drawRect(barRect, paint);
                canvas.drawRect(barRect,linePaint);
                
                
                Paint textPaint = new Paint();
                textPaint.setAntiAlias(true);
                textPaint.setColor(Color.WHITE);
                textPaint.setTextSize(30);
                
                //draw legend text
                canvas.drawText(data_names[i], barStartX+2*barWidth, barStartY+barWidth, textPaint);
                
                startAngle = newStartAngle;
                i++;
        }
}


4.. Adding Touch Listener

Now, Implement the onTouchListener with below steps,
  • Get mouse down event
  • Calculate the click angle
  • Check the condition with the start angle and Sweep angle for each data item


public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
//mouse down event
if( event.getAction() == MotionEvent.ACTION_DOWN)
{

double clickAngle;
//relative x & y position
float xPos = event.getX() - arc_bounds.centerX();
float yPos = event.getY() - arc_bounds.centerY();
//calcuate the click angle
clickAngle = Math.atan2(yPos,xPos) * 180 / Math.PI;
if(clickAngle < 0)
clickAngle = 360 + clickAngle;
float startAngle = 0;
int itemIndex = 0;
for (int datum : data_values) {
if (datum == 0) continue;

float endAngle = value_sum == 0 ? 0 : 360 * datum / (float) value_sum;
float newStartAngle = startAngle + endAngle;
 
//check the condition of start angle & end angle of data item.
            if(arc_bounds.contains(event.getX(),event.getY())  && clickAngle > startAngle && clickAngle < newStartAngle)
            {
            
            Toast.makeText(context, data_names[itemIndex] + "  is clicked!!", Toast.LENGTH_LONG).show();
            Log.d(TAG,"pie item is clicked-->" + data_names[itemIndex]);
            break;
            }
           
            itemIndex++;
startAngle = newStartAngle;

}
}

return false;
}


the below image shows the Toast message when the particular pie slice is touched. Based on this event, drilled down feature will be supported like starting activity or reloading the view with different data values.




PieChart with OnTouchLister Implementation


Conclusion:
The above sample demo application uses the Android Graphics API to draw simple 2D PieChart and supports the touch events. calculation used for detecting the Pie Slice is shown in the above sample. This sample will help to create interactive charts, decision like starting new activity, different view based on the pie slice selection.


Saturday, November 19, 2011

Andorid - Graphics & Animation Tutorial 1 -Circular Gauge

This sample demonstrates usage of Android Graphics & Animation APIs to create animated Graphic Controls like Gauge, Thermometer. In this series, Part 1 discusses about to create animated circular Gauge, which can be used to show Speed, Pressure in Graphics gauge format
instead of showing in simple text format.

Figure 1: shows the screen shot of circular gauge application, it can show value from the range 0-100, currently the pointer is pointing to set value of 45. Range can be customized and Any unit
can be associated with the Gauge.





















1.CricularGauge Screen

This is the background screen consists of Circular Gauge Image, It draws the Image bitmap during OnDraw() method call.

public class CircularGaugeScreen extends View
{
private final int RADIUS_O = 100;
private final int RADIUS_I = 95;
private final int POINTER_BASE = 8;
private int endAngle = 0;
private Bitmap gaugeBitmap = null;
private Bitmap gaugeBitmap2 = null;
 
 
public CircularGaugeScreen(Context context) {
super(context);
// TODO Auto-generated constructor stub
// decode an image with transparency
InputStream is = context.getResources().openRawResource(R.drawable.circulargauge);
gaugeBitmap = BitmapFactory.decodeStream(is);
// create a deep copy of it using getPixels() into different configs
int w = gaugeBitmap.getWidth();
int h = gaugeBitmap.getHeight();
int[] pixels = new int[w*h];
gaugeBitmap.getPixels(pixels, 0, w, 0, 0, w, h);
gaugeBitmap2 = Bitmap.createBitmap(pixels, 0, w, w, h,
Bitmap.Config.ARGB_8888);
}
 
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(gaugeBitmap2, 100, 100, null);
}
}

2. Circular Gauge Pointer.

This is the front screen which draws the pointer and uses Animation for the automatic rotation of the pointer. If user sets any set value then Animation stops and shows set value.
RotateAnimation class is used for the Animation and rotation angle changes from 0 to 360 degree.

private void CreateAnimation(int startAngle, int endAngle,Canvas canvas)
{
rotateAnimation = new RotateAnimation(startAngle, endAngle,centerX, centerY);
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);
rotateAnimation.setDuration(50000);
rotateAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
rotateAnimation.setAnimationListener(this);
startAnimation(rotateAnimation);
}


getArrowPath() is called during OnDraw() method call, which draws the pointer based on the current angle.


private Path getArrowPath(int centerX, int centerY,int width, int angle, int radius)
{

Path path = new Path();
path.setFillType(FillType.EVEN_ODD);
int x, y, sx,sy;
double rdeg1 = (Math.PI * (90 - angle)) / 180;
//fill the base line first
int iradius = width/2;
sx = (int) (centerX + (iradius * Math.cos(rdeg1)));
sy = (int) (centerY + (iradius * Math.sin(rdeg1)));
path.moveTo(sx, sy);
double rdeg2 = (Math.PI * (270-angle)) / 180;
x = (int) (centerX + (iradius * Math.cos(rdeg2)));
y = (int) (centerY + (iradius * Math.sin(rdeg2)));
path.lineTo(x, y);
double rdeg = (Math.PI * angle) / 180;
//fill the arrow line 1
x = (int) (centerX + (radius * Math.cos(rdeg)));
y = (int) (centerY + (radius * Math.sin(rdeg)));
path.lineTo(x, y);
//fill the arrow line 2
path.moveTo(sx, sy);
path.close();
return path;
}

3.CircularGuageLayout

Main screen which combines both the background screen (CircularGaugeScreen) and foreground screen ( CircularGaugePointer) and adds UI controls for setting the value.

public class CircularGaugeLayout extends RelativeLayout {
private final CircularGaugeScreen speedoMeterScreen;
private final CircularGaugePointer speedoMeterPointer;
 
public CircularGaugeLayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
speedoMeterScreen = new CircularGaugeScreen(context);
speedoMeterPointer = new CircularGaugePointer(context);
addView(speedoMeterScreen);
addView(speedoMeterPointer);
TableLayout layout = new TableLayout(context);
addView(layout);
}
}

Summary:
In this article, you have learnt to use Android Graphics & Animation APIs. Post here if you have any queries.Have Learning!!!