Click here to Skip to main content
Click here to Skip to main content

Creating a XY Chart/Plot as a Bitmap for Android

By , 26 Jul 2010
Rate this:
Please Sign up or sign in to vote.

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 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.

 @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:

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.

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.

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)

About the Author

becker666
Software Developer (Senior) BSC Inc
United States United States

Becker Cuéllar is an independent developer(C#/C++/J2ME/VB/perl) on the Washington DC corridor and focuses on developing/integrating web interfaces with Databases(MSSQL, MySQL, Oracle, Sybase), with mobile devices, scripting(perl/php/javascript) and with MS and Linux(RHE) based backends. Aditionally a full Mobile architect/developer for custom applications for Windows Mobile NETCF , Blackberry J2ME, Android, PalmOS and iPhone.
 
You'll find him traveling and hiking somewhere on this planet when not working on a project or on a road course race track tweaking his engine and attempting to improve his lap times.

Comments and Discussions

 
Generalscale pb Pinmemberkingpio7-Jun-11 0:20 
Questionquicky_XY? PinmemberAndroidAnnie23-Mar-11 12:27 
AnswerRe: quicky_XY? Pinmemberbecker66623-Mar-11 12:48 
GeneralRe: quicky_XY? PinmemberAndroidAnnie23-Mar-11 14:40 
GeneralNice introduction PinmemberAlex Kucherenko6-Mar-11 21:49 
GeneralMy vote of 5 PinmemberBosah Chude4-Mar-11 2:06 
GeneralMy vote of 5 Pinmembergourav vaidya28-Feb-11 22:37 
GeneralMy vote of 5 PinmemberMember 438714325-Aug-10 2:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140421.2 | Last Updated 27 Jul 2010
Article Copyright 2010 by becker666
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid