Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Creating a XY Chart/Plot as a Bitmap for Android

4.65/5 (23 votes)
26 Jul 2010GPL34 min read 129.2K   110.8K  
Using the Canvas/Bitmap to get a XY plot

Introduction

Plotting an XY set is a very common task, a description of the main steps for implementing such a module for the Android platform is outlined in this article. This goes along with the similar BlackBerry article posted earlier.

Developing on the Android platform is very straightforward and its model of allowing all of the GUI to be defined in XML (although it can also be created by code) is quite good. Most of the graphics samples deal with a View, but in most cases the chart is supposed to be part of a screen defined as a layout XML so here we show the implementation onto an ImageView layout object.

Background

In the Android environment, there is whole set of graphics routines usually a Bitmap holds the pixels, a Canvas is where we can apply the draw calls (writing into the bitmap) and with this we can draw the primitives (text, lines,etc) with a paint which describes the colors, styles and so on. Since the Android developing platform is all Java, it is very well structured and all of the features from Java such as inheritance, subclassing, etc. can be used extensively. For our sample here, it is all kept simple.

The GUI is defined as an XML layout file, the attached sample code shows the basic implementation of an XY chart for the sake of simplicity the X-points are evenly spaced sequential numbers.

1. The XML Layout File Defining the GUI

The ImageView object with the id of testy_img is the one that we will be using to draw the chart on:

XML
<?xml version="1.0" encoding="utf-8"?>

<TableLayout 

    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="fill_parent" 
    android:layout_width="fill_parent"
    android:background="#4B088A">
  
    <TableRow android:layout_width="fill_parent" 
    android:layout_height="wrap_content"
     android:padding="20px"> 
   
     <TextView  
 	android:id="@+id/some" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Some layout  items here"
    android:layout_marginLeft="10px"
    android:layout_marginRight="10px"
    android:textColor="#ff8000"
    android:textStyle="bold"
    />

    </TableRow>

 <View     
  android:layout_width="fill_parent"
	android:layout_height="1dip"
	android:background="#FFE6E6E6"
	 />
	    
    <TableRow>

    <ImageView  
 	android:id="@+id/testy_img" 
 	android:layout_marginLeft="20px"	
 	 android:padding="20px"
    />

   </TableRow>

 <View     
  android:layout_width="fill_parent"
	android:layout_height="1dip"
	android:background="#FFE6E6E6" />

    <TableRow android:layout_width="fill_parent" 

    android:layout_height="wrap_content"
     android:padding="20px"> 

     <TextView  
 	android:id="@+id/more" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="More layout items here"
    android:layout_marginLeft="10px"
    android:layout_marginRight="10px"
   	android:textColor="#ff8000"
    android:textStyle="bold"
    />

    </TableRow>

</TableLayout>

2. The Chart Implementation

To implement our chart, first we create a bitmap where we will be writing and then we associate it with the layout XML object. Once we have the bitmap, we just carry out the chart implementation where we do the scaling, coloring and data digesting. This charting element is comprised of a grid where the data will be plotted, a scale/transpose method so that the data points can be coherently mapped onto the available screen space and a data digesting routine to loop through the data points.

2.1 Defining the Bitmap to Draw

As shown below, first we make the connection to the object from the XML layout, then we create the Bitmap where we will be doing all the drawing which is carried out by the quicky_XY and finally we print this bitmap to the image and hence to the screen.

Java
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.testy); 
        setTitle("Quick XY Plot");
        
        ImageView image = (ImageView) findViewById(R.id.testy_img);
     
       Bitmap emptyBmap = Bitmap.createBitmap(250,
                200, Config.ARGB_8888); 
        
        int width =  emptyBmap.getWidth();
        int height = emptyBmap.getHeight();
        Bitmap charty = Bitmap.createBitmap(width , height , Bitmap.Config.ARGB_8888);
        
        charty = quicky_XY(emptyBmap);
       
         image.setImageBitmap(charty);     
  }

2.2 Drawing the Grid for Plotting

Once the basic Bitmap is defined, we need the associated canvas, this is done with:

Java
Canvas canvas = new Canvas(bitmap) 

and from there on, all of the drawing can be done onto the canvas. We need the grid to define the space where the data points will be drawn, a chart will usually have a label and some range for its data points. In the code shown, the Vector argument contains objects of the form [Label, units, maxValue, minValue] upon completion the drawSizes array will have the location and dimension of the plotting area.

Java
public static void  draw_the_grid(Canvas this_g,  Vector these_labels)
     {         
        double rounded_max = 0.0;
        double rounded_min = 0.0;
        double rounded_max_temp;
        Object curElt;  
        String[] cur_elt_array;
        int left_margin_d, right_margin_d;      

        if( draw_only_this_idx == -1)      
           curElt = these_labels.elementAt(0);  // default  it to 1st one if non set 
        else
           curElt = these_labels.elementAt(draw_only_this_idx);  // now just the 1st elt
           
        cur_elt_array = (String[])curElt;

        rounded_max = get_ceiling_or_floor (Double.parseDouble(cur_elt_array[2]) , true);
        rounded_min = get_ceiling_or_floor (Double.parseDouble(cur_elt_array[3]) , false);

       // ok so now we have the max value of the set just get a cool ceiling and we go on
        final Paint paint = new Paint();  
        paint.setTextSize(15);
        
       left_margin_d =  getCurTextLengthInPixels(paint, Double.toString(rounded_max));
       //keep the position for later drawing -- leave space for the legend
       int p_height = 170;
       int p_width = 220;
       int[] tmp_draw_sizes = {2 + left_margin_d, 25,p_width - 2 - 
		left_margin_d ,p_height - 25 -5};
       drawSizes = tmp_draw_sizes; //keep it for later processing
        
        //with the mzrgins worked out draw the plotting grid
       paint.setStyle(Paint.Style.FILL); 
       paint.setColor(Color.WHITE );  
       
       // Android does by coords
       this_g.drawRect(drawSizes[0], drawSizes[1],drawSizes[0]+ 
		drawSizes[2], drawSizes[1]+ drawSizes[3] , paint);
       
       paint.setColor(Color.GRAY );       
       
        // finally draw the grid      
       
       paint.setStyle(Paint.Style.STROKE); 
       this_g.drawRect(drawSizes[0], drawSizes[1],drawSizes[0]+ 
		drawSizes[2], drawSizes[1]+ drawSizes[3] , paint);

           for(int i=1; i < 5 ; i++)
           {
               this_g.drawLine(drawSizes[0], drawSizes[1] + 
		(i * drawSizes[3] / 5), drawSizes[0] + drawSizes[2], 
		drawSizes[1] + (i * drawSizes[3] / 5), paint);
               this_g.drawLine(drawSizes[0]+ (i * drawSizes[2] / 5), 
		drawSizes[1], drawSizes[0] + (i * drawSizes[2] / 5), 
		drawSizes[1] + drawSizes[3], paint);
           }

          // good for one value
           print_axis_values_4_grid(this_g, cur_elt_array[1] , 
		Double.toString(rounded_max) , Double.toString(rounded_min), 
		cur_elt_array[0] , 2 ,0 );
         
     }  // --- end of draw_grid --- 

2.3. Plotting and Scaling

The data points need a proper mapping from the data ranges to the screen coordinates, this is accomplished with the scale method shown below. Looping through the data points and calling a drawLine between two successive points will wrap up our Chart. The data points are passed as a Vector containing the data and will now call the plot_array_list.

Java
private static Point  scale_point(int this_x , double this_y  , Point drawPoint , 
            int scr_x  , int scr_y  , int scr_width  , int src_height  , 
            double maxX  , double minX  , double  maxY  , double minY  )
       {
           int temp_x, temp_y;
           Point temp = new Point();   
           
           if (maxY == minY)  //skip bad data
               return null;

           //don't touch it if is nothing
           try
           {
                   temp_x = scr_x + (int)( ((double)this_x - minX) * 
			((double)scr_width / (maxX - minX)) );
                   temp_y = scr_y + (int)( (maxY - this_y) * 
			((double)src_height / (maxY - minY)) );
                
                   temp.x = temp_x;
                   temp.y= temp_y;
                   drawPoint = temp;
           } 
           catch  (Exception e)
           {
        
              return (null);
           }
           
           return temp;
           
       } // --- end of scale_point --


    public static boolean plot_array_list(Canvas this_g, Vector this_array_list , 
	Vector these_labels , String this_title , int only_this_idx ) 
    {
             int idx;
             int lRow ;
             int nParms;
             int  i, points_2_plot, shifted_idx ; 
             int prev_x, prev_y ;
             int cur_x=0, cur_y=0 ; 
             //Dim ShowMarker As Object
             Point cur_point = new Point();
            cur_point.set(0,0);
     
             double cur_maxX, cur_minX, cur_maxY=20, cur_minY=0, cur_rangeY;
             int cur_start_x, cur_points_2_plot; 
   
            int POINTS_TO_CHANGE = 30;
            double cur_OBD_val;
  
             //Object curElt;  
             String curElt; 
             String[] cur_elt_array;
             Object curElt2;  
             String[] cur_elt_array2;
     
             final Paint paint = new Paint();

             try // catch in this block for some thing
             {       
                   points_2_plot = this_array_list.size();
                   {
                        cur_start_x = 0;
                        cur_points_2_plot = points_2_plot;
                        cur_maxX = cur_points_2_plot;
                        cur_minX = 0;
                   }
  
                   //'Create the plot points for this series from the ChartPoints array:
   
                   curElt = (String)this_array_list.elementAt(0);
                   
                   //the lines have to come out good
                    paint.setStyle(Paint.Style.STROKE);
//                  
                   //for(  nParms = 0 ; nParms < cur_elt_array.length ; nParms++ )
                   nParms = only_this_idx;
                   {
    
                       //get cur item labels
                        curElt2 = these_labels.elementAt(nParms);
                        cur_elt_array2  = (String[]) curElt2;
                        
                        cur_maxY = get_ceiling_or_floor 
			(Double.parseDouble(cur_elt_array2[2]) , true);
                        cur_minY = get_ceiling_or_floor 
			(Double.parseDouble(cur_elt_array2[3]) , false);
                        
                        cur_points_2_plot = this_array_list.size();
                        cur_maxX = cur_points_2_plot;
    
                      curElt = (String)this_array_list.elementAt(0);
                      cur_OBD_val = Double.parseDouble( curElt);
                      
                      cur_point = scale_point(0, cur_OBD_val, cur_point, 
                              drawSizes[0], drawSizes[1], drawSizes[2], drawSizes[3], 
                              cur_maxX, cur_minX, cur_maxY, 
		      cur_minY); //'(CInt(curAxisValues.Mins(nParms - 2) / 5) + 1) * 5)
                       
                       cur_x = cur_point.x;
                       cur_y = cur_point.y;

                       paint.setColor(Color.GREEN);
                       
                      // the point is only cool when samples are low
                       if ( cur_points_2_plot < POINTS_TO_CHANGE)
                         this_g.drawRect(cur_x-2, cur_y-2, cur_x-2 + 4,
						cur_y-2+ 4 , paint); 
                       
                       prev_x = cur_x;
                       prev_y = cur_y;

                       //'go and plot point for this parm -- pont after the 1st one 
                       for (lRow = cur_start_x +1 ; lRow< cur_start_x + 
					cur_points_2_plot -1 ; lRow++)
                       {        
                           curElt = (String)this_array_list.elementAt(lRow);
                           
                           cur_OBD_val = Double.parseDouble( curElt); 
                                 
                            //'work out an approx if cur Y values not avail(e.g. nothing)
                           // if (! (cur_elt_array[nParms ] == null ) )   //skip bad one
                             if( cur_OBD_val == Double.NaN) continue;  //skip bad one
                            {                  
       
                               
                                cur_point=scale_point(lRow, cur_OBD_val, cur_point,  
                                    drawSizes[0], drawSizes[1], 
				drawSizes[2], drawSizes[3], 
                                    cur_maxX, cur_minX, cur_maxY, cur_minY);
    
                                cur_x = cur_point.x;
                                cur_y = cur_point.y;
                                
                                if ( cur_points_2_plot < POINTS_TO_CHANGE)
                                   this_g.drawRect(cur_x-2, cur_y-2, cur_x-2 +4, 
				cur_y-2 + 4, paint ); 
 
                               this_g.drawLine( prev_x, prev_y, cur_x, cur_y, paint);
                               prev_x = cur_x;
                               prev_y = cur_y;
                          
                            } // ' if end of this_array(lRow, nParms - 1)<> nothing
                                 
                   } // end of for lrow
                               
               } // end of for nParmns

            //this_g.invalidate();
            return( true);
        }
        catch (Exception e)
        {
            return( false);
        }

    } // --- end of plot_array_list  --

With this, the plotting is carried out and the output now looks like the figure below:

android_xy.gif

Final Thoughts

The methods shown work rather well, when we needed to do real-time plotting a slight modification of the routine was used where instead of a Vector, a Queue data structure was used since we needed to be able to plot the data as it came in as well as having a scrolling feature but the basic components were the same. Many more modifications can be applied depending on the requirements. One of these mods could be using the drawLines call, but here we will need to setup the lines array argument which may not differ much from our plot_array_list method.

History

  • 27th July, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)