65.9K
CodeProject 正在变化。 阅读更多。
Home

在 BlackBerry 自定义字段中创建 XY 坐标图/绘图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (2投票s)

2009年6月17日

GPL3

5分钟阅读

viewsIcon

43165

扩展字段以创建图表/绘图字段

 

介绍 

  绘制 XY 数据集是一项非常常见的任务,本文概述了为 BlackBerry 平台实现此类模块的主要步骤。

当数据以图表形式显示时,应用程序收集或处理的数据将更有意义。例如,在开发一个从外部设备采样数据的蓝牙应用程序时,需要实时记录和绘制数据,并从先前的日志中绘制数据。将这些数据显示给最终用户以图表形式比显示不断闪烁的数字(最大值/最小值不断更新)更具吸引力,并且用户可以轻松记住数据的趋势。

在 BlackBerry 世界中,应用程序可以是 Midlet 应用程序,它在一定程度上可以独立于设备,也可以是设备应用程序(CLDC),它利用所有可用 API 来构建仅限 BlackBerry 的应用程序,类似于日历、浏览器等核心应用程序。

对于本文中概述的图表元素,选择了 CLDC 应用程序。

背景   

  在 BlackBerry 阵营中,与用户交互的基本实体是 Field,例如按钮、标签、下拉列表等都定义为 Fields(对于我们这些从 VisualStudio 转过来的用户来说,有点像 VisualStudio 的控件)。由于 BlackBerry 开发平台是 Java(实际上是 J2ME),它结构非常良好,并且可以广泛利用 Java 的所有特性,如继承、子类化等。自定义 Field 满足了我们图表元素的需求。

在可用的 BlackBerry UI 类中,Mainscreen 类是最常用的与用户交互的类,在这个 Mainscreen 中,Fields 被放置以构成 GUI 界面,并提供了一组具有标准属性和方法的预制 Fields。

这些 Fields 具有一些基本的行为,如 API 文档中所述,但当然它们需要进行扩展。默认情况下,通用 Fields 不能并排显示,因为它们会占据所有水平空间。PDA 屏幕的实际状态需要仔细分配,优化它很重要。为了克服这种默认行为,Manager 类允许自定义 Fields 的定位和大小调整。


1. Chart Custom Field 的实现  

   要实现我们的 Chart CustomField,我们需要一个 **Chart 类**和一个 Chart **Manager 类**。

   Chart 类负责缩放、着色和数据处理。此图表元素由一个用于绘制数据的网格组成,一个用于将数据点有条理地映射到可用屏幕空间的比例/转换方法,以及一个用于遍历数据点的消化数据例程。

1.1 定义 Chart Custom Field  

   Chart Custom Field 将在 MainScreen 上实例化和定位。代码显示了对基本 Field 类的扩展。

import net.rim.device.api.ui.*;
.
.  more classes as needed
.
import java.util.*;


public class drawCharty extends Field implements DrawStyle
{
    
  private int fieldWidth, fieldHeight, xCoord, yCoord;
  private int[] drawSizes;
  

  public drawCharty(int xVal, int yVal, int wVal, int hVal)
  {
    //Create a non focusable field.
    super(Field.NON_FOCUSABLE);

    //Set the x and y coordinates.
    xCoord = xVal;
    yCoord = yVal;

    //width and height.
    fieldWidth = wVal; 
    fieldHeight = hVal; 
    
  } // --- end of constructor ---

  //Get/Set the  coordinates and dimensions of the field.
  public void setXCoord(int xVal)
  {
    if (xVal != xCoord)
    {
      xCoord = xVal;
      invalidate();
    }
  }
  //Get the x coordinate of the field.
  public int getXCoord()
  {
    return xCoord;
  }

  .
  . Similar for the Ycoord, width and height
  .

1.2 绘制绘图网格

   定义了基本字段后,我们需要定义绘制数据点的空间。图表通常会有一个标签和一些数据点范围。在代码中,Vector 参数包含 [maxValue, Label] 形式的对象。完成后,draw_sizes 数组将包含绘图区域的位置和尺寸。

  // these_labels has [maxValue, text]  entries
  public void draw_the_grid(Graphics this_g, Vector these_labels) 
  {
      
     double rounded_max;
     double rounded_min;
     Object curElt;  
     String[] cur_elt_array;
     int left_margin_d, right_margin_d;
   
     // work out the margins -- only 2 items to avoid confusion 
     switch(these_labels.size() )
     {
         //set it up for 1-elt chart
         case 1:  //work out margin space according to max value to be plotteed
                curElt = these_labels.elementAt(0);
                cur_elt_array  = (String[]) curElt;  
                rounded_max  =       Math.ceil(Double.valueOf(cur_elt_array[0]).doubleValue() );
                 left_margin_d = getFont().getAdvance(Double.toString(rounded_max));

                //keep the position for later drawing
                int[] tmp_draw_sizes = {2 + left_margin_d, 25,fieldWidth - 2 + left_margin_d ,fieldHeight - 25 -5};
                drawSizes = tmp_draw_sizes; //keep it for later processing

                break;

         case 2:  //same as above but now left and right margins
         
                curElt = these_labels.elementAt(0);
                cur_elt_array  = (String[]) curElt;  
                rounded_max  = Math.ceil(Double.valueOf(cur_elt_array[0]).doubleValue() );
                 left_margin_d = getFont().getAdvance(Double.toString(rounded_max));

                curElt = these_labels.elementAt(1);
                cur_elt_array  = (String[]) curElt;  
                rounded_max  = Math.ceil(Double.valueOf(cur_elt_array[0]).doubleValue() );
                 right_margin_d = getFont().getAdvance(Double.toString(rounded_max));
                
                //keep the position for later drawing
                int[] tmp_draw_sizes1 = {2 + left_margin_d, 25,fieldWidth -  left_margin_d -right_margin_d -4 , fieldHeight - 25 -5};
                drawSizes = tmp_draw_sizes1; //keep it for later processing
                
                break;
         default:
                   //error on allowed dimensions
     } 
     
     //with the margins worked out draw the plotting grid
     this_g.fillRoundRect(drawSizes[0], drawSizes[1], drawSizes[2], drawSizes[3],1,1); 
  
     this_g.setColor( Color.LIGHTGREY );

     this_g.drawRect(drawSizes[0],drawSizes[1],drawSizes[2],drawSizes[3]);

     // draw the mesh 
     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));
            this_g.drawLine(drawSizes[0]+ (i * drawSizes[2] / 5), drawSizes[1], drawSizes[0] + (i * drawSizes[2] / 5), drawSizes[1] + drawSizes[3]);
     }


    //print the labels and we are good 
     switch(these_labels.size() )
     {
         case 1:
                this_g.setColor( Color.RED );
                curElt = these_labels.elementAt(0);
                cur_elt_array  = (String[]) curElt; 
                 rounded_max  = Math.ceil(Double.valueOf(cur_elt_array[0]).doubleValue() );
                 print_axis_values_4_grid(this_g, "mph" , Double.toString(rounded_max) , "0", cur_elt_array[1] , 2 ,0 );
  
                break;
         case 2:
                this_g.setColor( Color.RED );
                curElt = these_labels.elementAt(0);
                cur_elt_array  = (String[]) curElt; 
                 rounded_max  = Math.ceil(Double.valueOf(cur_elt_array[0]).doubleValue() );
                 print_axis_values_4_grid(this_g, "mph" , Double.toString(rounded_max) , "0", cur_elt_array[1] , 2 ,0  );

                this_g.setColor( Color.BLUE );
                curElt = these_labels.elementAt(1);
                cur_elt_array  = (String[]) curElt; 
                 rounded_max  = Math.ceil(Double.valueOf(cur_elt_array[0]).doubleValue() );
                 print_axis_values_4_grid(this_g, "sec" , Double.toString(rounded_max) , "0", cur_elt_array[1] , drawSizes[0] + drawSizes[2] +3 ,1  );
                break;
                
    }  // --- ed of draw_the_grid ---

1.3. 覆盖 Paint 事件

   Paint 事件是我们自定义 Field 的地方。

  public void paint(Graphics graphics)
  {
      //set background of our field
      graphics.setColor( Color.AQUA ); 
      graphics.fillRoundRect(0, 0, getWidth(), getHeight(), 15, 15);  
      graphics.setColor( Color.LIGHTBLUE );
      graphics.drawRoundRect(0, 0, getWidth(), getHeight(), 15, 15);  

      graphics.setColor( Color.BEIGE );
   
      // labels_vector arg has [maxVal, label] objects
      draw_the_grid(graphics , labels_vector); 

  } // --- end of paint---
      

1.4. 实例化类和当前输出

   下面的代码显示了实例化过程。如果我们在这里停止,输出将如上图所示。

  
  .
  .  
  .
  LabelField title = new LabelField("Charty CustomField V 0.6", LabelField.ELLIPSIS
  setTitle(title);

  add(new LabelField("Label before charty",0, -1, Field.FIELD_HCENTER));

  //instantiate the class
  drawCharty  charty = new drawCharty(35,10, 200,120);
  add(charty);

  add(new LabelField("Label after charty",0, -1, Field.FIELD_HCENTER));
    
 

BB_custfield_3.jpg 

在这里我们可以看到,尽管我们传递了我们类的坐标和尺寸,但它并没有被正确放置。Manager 类将负责这一点。 

 

1.5. 绘图和缩放  

数据点需要从数据范围正确映射到屏幕坐标,这通过下面所示的 scale 方法来实现。遍历数据点并在两个连续点之间调用 drawLine 将完成我们的 Chart CustomField。数据点作为包含数据的 Vector 传递,我们的 **Paint** 事件现在将调用 **plot_array_list**。

    private XYPoint  scale_point(int this_x , double this_y  , XYPoint 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;
        XYPoint temp = new XYPoint();   
        
        if (maxY == minY)  //skip bad data
            return null;

        //don't touch it if bad data
        try
        {
        //if (! ( this_x == null) )
                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 boolean plot_array_list(Graphics this_g, Vector this_array_list , Vector these_labels , String this_title  )  
        {
             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
             XYPoint cur_point = new XYPoint();
     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; 
   
             Object curElt;  
             String[] cur_elt_array;
     
             draw_the_grid(this_g, these_labels);  

             try 
             {
                   points_2_plot = this_array_list.size();
    
                   //'Work it out so it prints the scrolled section if required
                        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 = this_array_list.elementAt(0);
                   cur_elt_array  = (String[]) curElt;
                   
                   //the lines plotted have to look good
                    this_g.setDrawingStyle(Graphics.DRAWSTYLE_AALINES, true);
                   
                      
                   //skip 1st elt of array as it is the x-axis value 
                   for(  nParms = 1 ; nParms < cur_elt_array.length ; nParms++ )
                   {
                       curElt = these_labels.elementAt(nParms -1);
                       cur_elt_array  = (String[]) curElt;
                       cur_maxY= Integer.parseInt(cur_elt_array[0]);
  
                       curElt = this_array_list.elementAt(cur_start_x);
                       cur_elt_array  = (String[]) curElt;
    
                       cur_point = scale_point(Integer.parseInt(cur_elt_array[0]), Double.parseDouble(cur_elt_array[nParms ] ), 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;
                       
                       prev_x = cur_x;
                       prev_y = cur_y;

                       switch (nParms)
                       {
                          case 1:
                            this_g.setColor( Color.RED );
                            break;
                          case 2:
                            this_g.setColor( Color.BLUE );
                            break;
                       }

                       //go and plot this parm 
                       for (lRow = cur_start_x +1 ; lRow< cur_start_x + cur_points_2_plot ; lRow++)
                       {
                            curElt = this_array_list.elementAt(lRow);
                            cur_elt_array  = (String[]) curElt;
        
                            if (cur_elt_array[0] == null) 
                                 cur_elt_array[0] = "0";
 
                            if (! (cur_elt_array[nParms ] == null ) )   //skip bad one
                            {                  
  
                               cur_point = scale_point(Integer.parseInt(cur_elt_array[0]), Double.parseDouble( cur_elt_array[nParms ]), 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;
 
                               this_g.drawLine( prev_x, prev_y, cur_x, cur_y); 
                               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


            invalidate();
            return( true);
        }
        catch (Exception e)
        {
            return( false);
            //Console.out some message for debugging
        }

    } // --- end of plot_array_list ---

至此,绘图完成,输出现在如下图所示。

BB_custfield_3.jpg 

 

2. 定义 Chart Custom Field 的 Manager 类  

我们的输出几乎完成了,唯一的问题是定位 Field,伴随的 Manager 类完成了实现。

import net.rim.device.api.ui.*;
.
.  more classes as needed
.
import java.util.*;


// add a custom manager to handle this 
public class chartyManager extends Manager 
{
    
    int myHeight;
    int myWidth;
    
  public chartyManager()
  {
    //Disable scrolling in this manager.
    super(Manager.NO_HORIZONTAL_SCROLL |
      Manager.NO_VERTICAL_SCROLL);
  }

  //Override sublayout.
  protected void sublayout(int width, int height) {
    drawCharty field;
    int x_pos=0, y_pos=0;

    //Loop thru total number of fields within
    //this manager. In our case only our chart Field will be here
    int numberOfFields = getFieldCount();

    for (int i = 0; i < numberOfFields; i++)
    {
      //Get the field. 
      field = (drawCharty)getField(i); 

      //Obtain the custom x and y coordinates for
      setPositionChild(field, field.getXCoord(), field.getYCoord());

      x_pos=field.getXCoord();
      y_pos=field.getYCoord();


      //Layout the field.
      layoutChild(field, field.getPreferredWidth(), field.getPreferredHeight());
      width = field.getPreferredWidth();
      height = field.getPreferredHeight();
      myHeight=field.getPreferredHeight();
      myWidth=field.getPreferredWidth();

    }
    //Set the manager's extent
    
       setExtent(width + x_pos, height + y_pos );
  }


  public int getPreferredWidth()
  {
    return myWidth;  
  }

  public int getPreferredHeight()
  {
    return myHeight ; 
  }
}

3. 实例化 Manager 类和最终输出  

现在我们可以根据坐标任意定位图表。最后的语法如下。
  
  .
  .  
  .
  LabelField title = new LabelField("Charty CustomField V 0.6", LabelField.ELLIPSIS
  setTitle(title);

  add(new LabelField("Label before charty",0, -1, Field.FIELD_HCENTER));

  //instantiate the class
  drawCharty  charty = new drawCharty(35,10, 200,120);
  // and its manager class 
  chartyManager myManager = new chartyManager();
  myManager.add(charty);
  add (myManager);


  add(new LabelField("Label after charty",0, -1, Field.FIELD_HCENTER));
    
输出

    

最终想法

所示的类工作得相当好。当我们只需要实时绘图时,对例程进行了一些修改,将 Vector 改为 Queue 数据结构,因为我们需要能够随着数据的到来进行绘制,并具有滚动功能,但基本组件是相同的。根据需求,可以应用更多修改。

如果需要的是图形密集型应用程序而不是绘图,例如 RPG(角色扮演游戏),基本概念仍然相同:背景图像相当于网格,绘图就像玩家在背景图像中导航(例如,穿过迷宫)。在这种情况下,应非常仔细地进行优化,因为当叠加位图并进行此类计算时,它会非常消耗 CPU,并且计算应尽可能保持为整数,因为浮点转换会消耗 CPU,当所有像素都必须经过一些数学计算时,情况会更糟。对于这种图形密集型应用程序,绝对应该考虑双缓冲的基本技术。  
© . All rights reserved.