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:
="1.0"="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);
else
curElt = these_labels.elementAt(draw_only_this_idx);
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);
final Paint paint = new Paint();
paint.setTextSize(15);
left_margin_d = getCurTextLengthInPixels(paint, Double.toString(rounded_max));
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;
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE );
this_g.drawRect(drawSizes[0], drawSizes[1],drawSizes[0]+
drawSizes[2], drawSizes[1]+ drawSizes[3] , paint);
paint.setColor(Color.GRAY );
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);
}
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 );
}
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)
return null;
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;
}
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 ;
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;
String curElt;
String[] cur_elt_array;
Object curElt2;
String[] cur_elt_array2;
final Paint paint = new Paint();
try
{
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;
}
curElt = (String)this_array_list.elementAt(0);
paint.setStyle(Paint.Style.STROKE);
nParms = only_this_idx;
{
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);
cur_x = cur_point.x;
cur_y = cur_point.y;
paint.setColor(Color.GREEN);
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;
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);
if( cur_OBD_val == Double.NaN) continue;
{
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;
}
}
}
return( true);
}
catch (Exception e)
{
return( false);
}
}
With this, the plotting is carried out and the output now looks like the figure below:
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