2011-10-09 7 views
6

[UPDATE] この質問を終えるには、次の2つの方法でグラフを実装しました(下記参照)。 drawCurve()Canvasfloatの配列を受け取ります。配列は適切に埋められます(タイムスタンプは配列の値インデックスによって想定されます)、0.0から1.0まで変化します。アレイはwindowStartwindowSize値の配列のチャンクを循環的に取りますprepareWindowArray()に送信されます。Androidのカスタム動的グラフ

GraphViewとデータプロバイダ(Bluetoothデバイス)が使用する配列は同じです。中央のクラスは、GraphViewがBluetoothデバイスによって書き込まれているデータを読み取っていないことを保証します。 GraphViewは常に配列をループし、繰り返しごとにそれを再描画するので、Bluetoothデバイスによって書き込まれたデータに従って更新され、Bluetoothデバイスの書き込み頻度をグラフのリフレッシュ頻度に強制することによって、私の信号のアニメーション。

GraphViewinvalidate()方法は、すべてのxミリ秒でグラフを更新するにはTimerを実行Activity、によって呼び出されます。グラフがリフレッシュされる頻度は動的に設定され、Bluetoothデバイス(パケットのヘッダーにある信号の周波数を指定する)からのデータの流れに適応します。

私のGraphViewの完全なコードは、私が以下に書いた答え(答えのセクションにあります)にあります。あなたがエラーを見つけたり、それを最適化する方法を見つけたら、教えてください。大いに感謝しています!

/** 
* Read a buffer array of size greater than "windowSize" and create a window array out of it. 
* A curve is then drawn from this array using "windowSize" points, from left 
* to right. 
* @param canvas is a Canvas object on which the curve will be drawn. Ensure the canvas is the 
* later drawn object at its position or you will not see your curve. 
* @param data is a float array of length > windowSize. The floats must range between 0.0 and 1.0. 
* A value of 0.0 will be drawn at the bottom of the graph, while a value of 1.0 will be drawn at 
* the top of the graph. The range is not tested, so you must ensure to pass proper values, or your 
* graph will look terrible. 
*  0.0 : draw at the bottom of the graph 
*  0.5 : draw in the middle of the graph 
*  1.0 : draw at the top of the graph 
*/ 
private void drawCurve(Canvas canvas, float[] data){ 

    // Create a reference value to determine the stepping between each points to be drawn 
    float incrementX = (mRightSide-mLeftSide)/(float) windowSize; 

    float incrementY = (mBottomSide - mTopSide); 

    // Prepare the array for the graph 
    float[] source = prepareWindowArray(data); 

    // Prepare the curve Path 
    curve = new Path(); 
    // Move at the first point. 
    curve.moveTo(mLeftSide, source[0]*incrementY); 
    // Draw the remaining points of the curve 
    for(int i = 1; i < windowSize; i++){ 
     curve.lineTo(mLeftSide + (i*incrementX), source[i] * incrementY); 
    } 

    canvas.drawPath(curve, curvePaint); 

} 

アレイの円形の動作を実装prepareWindowArray()方法

/** 
* Extract a window array from the data array, and reposition the windowStart 
* index for next iteration 
* @param data the array of data from which we get the window 
* @return an array of float that represent the window 
*/ 
private float[] prepareWindowArray(float[] data){ 
    // Prepare the source array for the graph. 
    float[] source = new float[windowSize]; 

    // Copy the window from the data array into the source array 
    for(int i = 0; i < windowSize; i++){ 
     if(windowStart+i < data.length)       // If the windows holds within the data array 
      source[i] = data[windowStart + i];     // Simply copy the value in the source array 
     else{             // If the window goes beyond the data array 
      source[i] = data[(windowStart + 1)%data.length]; // Loop at the beginning of the data array and copy from there 
     } 
    } 
    // Reposition the buffer index 
    windowStart = windowStart + windowSize; 
    // If the index is beyond the end of the array 
    if(windowStart >= data.length){ 
     windowStart = windowStart % data.length; 
    } 

    return source; 
} 

[/ UPDATE]

私がBluetoothデバイスからデータを読み取るアプリケーションを作っています固定金利。新しいデータがあるたびに、グラフを右側のグラフにプロットし、グラフの残りの部分をリアルタイムで左側に変換します。基本的には、オシロスコープのようになります。

私はカスタムのビューを作成しました。xy軸、タイトルと単位を使用しました。これを行うために、私は単にViewキャンバスにそれらのものを描画します。今私は曲線を描きたい。私は、このメソッドを使用して、既に塗りつぶされた配列から静的なカーブを描くことができます:

public void drawCurve(Canvas canvas){ 

    int left = getPaddingLeft(); 
    int bottom = getHeight()-getPaddingTop(); 
    int middle = (bottom-10)/2 - 10; 

    curvePaint = new Paint(); 
    curvePaint.setColor(Color.GREEN); 
    curvePaint.setStrokeWidth(1f); 
    curvePaint.setDither(true); 
    curvePaint.setStyle(Paint.Style.STROKE); 
    curvePaint.setStrokeJoin(Paint.Join.ROUND); 
    curvePaint.setStrokeCap(Paint.Cap.ROUND); 
    curvePaint.setPathEffect(new CornerPathEffect(10)); 
    curvePaint.setAntiAlias(true); 

    mCurve = new Path(); 
    mCurve.moveTo(left, middle); 
    for(int i = 0; i < mData[0].length; i++) 
     mCurve.lineTo(left + ((float)mData[0][i] * 5), middle-((float)mData[1][i] * 20)); 


    canvas.drawPath(mCurve, curvePaint); 
} 

これは私のようなものです。

My custom GraphView

はまだ(サブ軸が適切にスケーリングされていない)私のグラフ上に定着させるものがありますが、これらは、私は後で修正することができます詳細です。

ここでは、40msごとに曲線を再描画し、古いデータを左側にプッシュし、新しいデータを右側にプロットする動的なもので、この静的グラフ(値の非動的な値を受け取る)を変更し、私は、Bluetoothデバイスによって提供される情報をリアルタイムで視覚化することができました。

既に存在するグラフパッケージがあることは知っていますが、私はこれらのことにちょっと驚いています。私はこのグラフを自分で実装したいと思っています。また、カーブ部分を除いて、私のGraphViewクラスのほとんどが実行されます。

2番目の質問では、グラフに新しい値をどのように送るべきかと思います。私はFIFOスタックのようなものを使うべきですか、単純な倍数の倍数で私が望むものを達成することはできますか?

サイドノートでは、下部の4つのフィールドが既に動的に更新されています。まあ、彼らは "ダイナミック"を偽っているのですが、同じダブル・マトリスを何度も繰り返すので、実際には新鮮な価値を取りません。

ありがとうございました!質問に不明な点がある場合はお知らせください。詳細を更新します。

+0

すべてのプロジェクトにリンクを付けることはできますか?この見て非常に興味深い! –

答えて

7

私の質問に記載されているように、ここで私の問題を解決するために設計したクラスです。

/** 
* A View implementation that displays a scatter graph with 
* automatic unit scaling. 
* 
* Call the <i>setupGraph()</i> method to modify the graph's 
* properties. 
* @author Antoine Grondin 
* 
*/ 

public class GraphView extends View { 

    ////////////////////////////////////////////////////////////////// 
    // Configuration 
    ////////////////////////////////////////////////////////////////// 

    // Set to true to impose the graph properties 
    private static final boolean TEST = false; 

    // Scale configuration 
    private float minX = 0;   // When TEST is true, these values are used to 
    private float maxX = 50;  // Draw the graph 
    private float minY = 0; 
    private float maxY = 100; 

    private String titleText = "A Graph..."; 
    private String xUnitText = "s"; 
    private String yUnitText = "Volts"; 

    // Debugging variables 
    private boolean D = true; 
    private String TAG = "GraphView"; 

    ////////////////////////////////////////////////////////////////// 
    // Member fields 
    ////////////////////////////////////////////////////////////////// 

    // Represent the borders of the View 
    private int mTopSide = 0; 
    private int mLeftSide = 0; 
    private int mRightSide = 0; 
    private int mBottomSide = 0; 
    private int mMiddleX = 0; 
    // Size of a DensityIndependentPixel 
    private float mDips = 0; 

    // Hold the position of the axis in regard to the range of values 
    private int positionOfX = 0; 
    private int positionOfY = 0; 

    // Index for the graph array window, and size of the window 
    private int windowStart = 0; 
    private int windowSize = 128; 
    private float[] dataSource; 

    // Painting tools 
    private Paint xAxisPaint; 
    private Paint yAxisPaint; 
    private Paint tickPaint; 
    private Paint curvePaint; 
    private Paint backgroundPaint; 

    private TextPaint unitTextPaint; 
    private TextPaint titleTextPaint; 

    // Object to be drawn 

    private Path curve; 
    private Bitmap background; 

    /////////////////////////////////////////////////////////////////////////////// 
    // Constructors 
    /////////////////////////////////////////////////////////////////////////////// 

    public GraphView(Context context) { 
     super(context); 
     init(); 
    } 

    public GraphView(Context context, AttributeSet attrs){ 
     super(context, attrs); 
     init(); 
    } 

    public GraphView(Context context, AttributeSet attrs, int defStyle){ 
     super(context, attrs, defStyle); 
     init(); 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Configuration methods 
    /////////////////////////////////////////////////////////////////////////////// 

    public void setupGraph(String title, String nameOfX, float min_X, float max_X, String nameOfY, float min_Y, float max_Y){ 
     if(!TEST){ 
      titleText = title; 
      xUnitText = nameOfX; 
      yUnitText = nameOfY; 
      minX = min_X; 
      maxX = max_X; 
      minY = min_Y; 
      maxY = max_Y; 
     } 
    } 

    /** 
    * Set the array this GraphView is to work with. 
    * @param data is a float array of length > windowSize. The floats must range between 0.0 and 1.0. 
    * A value of 0.0 will be drawn at the bottom of the graph, while a value of 1.0 will be drawn at 
    * the top of the graph. The range is not tested, so you must ensure to pass proper values, or your 
    * graph will look terrible. 
    *  0.0 : draw at the bottom of the graph 
    *  0.5 : draw in the middle of the graph 
    *  1.0 : draw at the top of the graph 
    */ 
    public void setDataSource(float[] data){ 
     this.dataSource = data; 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Initialization methods 
    /////////////////////////////////////////////////////////////////////////////// 

    private void init(){ 
     initDrawingTools(); 
    } 

    private void initConstants(){ 
     mDips = getResources().getDisplayMetrics().density; 
     mTopSide = (int) (getTop() + 10*mDips); 
     mLeftSide = (int) (getLeft() + 10*mDips); 
     mRightSide = (int) (getMeasuredWidth() - 10*mDips); 
     mBottomSide = (int) (getMeasuredHeight() - 10*mDips); 
     mMiddleX = (mRightSide - mLeftSide)/2 + mLeftSide; 
    } 

    private void initWindowSetting() throws IllegalArgumentException { 

     // Don't do anything if the given values make no sense 
     if(maxX < minX || maxY < minY || 
       maxX == minX || maxY == minY){ 
      throw new IllegalArgumentException("Max and min values make no sense"); 
     } 
     // Transform the values in scanable items 
     float[][] maxAndMin = new float[][]{ 
       {minX, maxX}, 
       {minY, maxY}}; 
     int[] positions = new int[]{positionOfY, positionOfX}; 

     // Place the X and Y axis in regard to the given max and min 
     for(int i = 0; i<2; i++){ 
      if(maxAndMin[i][0] < 0f){ 
       if(maxAndMin[i][1] < 0f){ 
        positions[i] = (int) maxAndMin[i][0]; 
       } else{ 
        positions[i] = 0; 
       } 
      } else if (maxAndMin[i][0] > 0f){ 
       positions[i] = (int) maxAndMin[i][0]; 
      } else { 
       positions[i] = 0; 
      } 
     } 

     // Put the values back in their right place 
     minX = maxAndMin[0][0]; 
     maxX = maxAndMin[0][1]; 
     minY = maxAndMin[1][0]; 
     maxY = maxAndMin[1][1]; 

     positionOfY = mLeftSide + (int) (((positions[0] - minX)/(maxX-minX))*(mRightSide - mLeftSide));  
     positionOfX = mBottomSide - (int) (((positions[1] - minY)/(maxY-minY))*(mBottomSide - mTopSide)); 
    } 

    private void initDrawingTools(){ 

     xAxisPaint = new Paint(); 
     xAxisPaint.setColor(0xff888888); 
     xAxisPaint.setStrokeWidth(1f*mDips); 
     xAxisPaint.setAlpha(0xff); 
     xAxisPaint.setAntiAlias(true); 

     yAxisPaint = xAxisPaint; 

     tickPaint = xAxisPaint; 
     tickPaint.setColor(0xffaaaaaa); 

     curvePaint = new Paint(); 
     curvePaint.setColor(0xff00ff00); 
     curvePaint.setStrokeWidth(1f*mDips); 
     curvePaint.setDither(true); 
     curvePaint.setStyle(Paint.Style.STROKE); 
     curvePaint.setStrokeJoin(Paint.Join.ROUND); 
     curvePaint.setStrokeCap(Paint.Cap.ROUND); 
     curvePaint.setPathEffect(new CornerPathEffect(10)); 
     curvePaint.setAntiAlias(true); 

     backgroundPaint = new Paint(); 
     backgroundPaint.setFilterBitmap(true); 

     titleTextPaint = new TextPaint(); 
     titleTextPaint.setAntiAlias(true); 
     titleTextPaint.setColor(0xffffffff); 
     titleTextPaint.setTextAlign(Align.CENTER); 
     titleTextPaint.setTextSize(20f*mDips); 
     titleTextPaint.setTypeface(Typeface.MONOSPACE); 

     unitTextPaint = new TextPaint(); 
     unitTextPaint.setAntiAlias(true); 
     unitTextPaint.setColor(0xff888888); 
     unitTextPaint.setTextAlign(Align.CENTER); 
     unitTextPaint.setTextSize(20f*mDips); 
     unitTextPaint.setTypeface(Typeface.MONOSPACE); 

    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Overridden methods 
    /////////////////////////////////////////////////////////////////////////////// 

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ 
     super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
    } 

    protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
     regenerateBackground(); 
    } 

    public void onDraw(Canvas canvas){ 
     drawBackground(canvas); 
     if(dataSource != null) 
      drawCurve(canvas, dataSource); 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Drawing methods 
    /////////////////////////////////////////////////////////////////////////////// 

    private void drawX(Canvas canvas){ 
     canvas.drawLine(mLeftSide, positionOfX, mRightSide, positionOfX, xAxisPaint); 
     canvas.drawText(xUnitText, mRightSide - unitTextPaint.measureText(xUnitText)/2, positionOfX - unitTextPaint.getTextSize()/2, unitTextPaint); 
    } 

    private void drawY(Canvas canvas){ 
     canvas.drawLine(positionOfY, mTopSide, positionOfY, mBottomSide, yAxisPaint); 
     canvas.drawText(yUnitText, positionOfY + unitTextPaint.measureText(yUnitText)/2 + 4*mDips, mTopSide + (int) (unitTextPaint.getTextSize()/2), unitTextPaint); 
    } 

    private void drawTick(Canvas canvas){ 
     // No tick at this time 
     // TODO decide how I want to put those ticks, if I want them 
    } 

    private void drawTitle(Canvas canvas){ 
     canvas.drawText(titleText, mMiddleX, mTopSide + (int) (titleTextPaint.getTextSize()/2), titleTextPaint); 
    } 

    /** 
    * Read a buffer array of size greater than "windowSize" and create a window array out of it. 
    * A curve is then drawn from this array using "windowSize" points, from left 
    * to right. 
    * @param canvas is a Canvas object on which the curve will be drawn. Ensure the canvas is the 
    * later drawn object at its position or you will not see your curve. 
    * @param data is a float array of length > windowSize. The floats must range between 0.0 and 1.0. 
    * A value of 0.0 will be drawn at the bottom of the graph, while a value of 1.0 will be drawn at 
    * the top of the graph. The range is not tested, so you must ensure to pass proper values, or your 
    * graph will look terrible. 
    *  0.0 : draw at the bottom of the graph 
    *  0.5 : draw in the middle of the graph 
    *  1.0 : draw at the top of the graph 
    */ 
    private void drawCurve(Canvas canvas, float[] data){ 

     // Create a reference value to determine the stepping between each points to be drawn 
     float incrementX = (mRightSide-mLeftSide)/(float) windowSize; 

     float incrementY = mBottomSide - mTopSide; 

     // Prepare the array for the graph 
     float[] source = prepareWindowArray(data); 

     // Prepare the curve Path 
     curve = new Path(); 
     // Move at the first point. 
     curve.moveTo(mLeftSide, source[0]*incrementY); 
     // Draw the remaining points of the curve 
     for(int i = 1; i < windowSize; i++){ 
      curve.lineTo(mLeftSide + (i*incrementX), source[i] * incrementY); 
     } 

     canvas.drawPath(curve, curvePaint); 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Intimate methods 
    /////////////////////////////////////////////////////////////////////////////// 

    /** 
    * When asked to draw the background, this method will verify if a bitmap of the 
    * background is available. If not, it will regenerate one. Then, it will draw 
    * the background using this bitmap. The use of a bitmap to draw the background 
    * is to avoid unnecessary processing for static parts of the view. 
    */ 
    private void drawBackground(Canvas canvas){ 
     if(background == null){ 
      regenerateBackground(); 
     } 
     canvas.drawBitmap(background, 0, 0, backgroundPaint); 
    } 

    /** 
    * Call this method to force the <i>GraphView</i> to redraw the cache of it's background, 
    * using new properties if you changed them with <i>setupGraph()</i>. 
    */ 
    public void regenerateBackground(){ 
     initConstants(); 
     try{ 
      initWindowSetting(); 
     } catch (IllegalArgumentException e){ 
      Log.e(TAG, "Could not initalize windows.", e); 
      return; 
     } 
     if(background != null){ 
      background.recycle(); 
     } 
     background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); 
     Canvas backgroundCanvas = new Canvas(background); 

     drawX(backgroundCanvas); 
     drawY(backgroundCanvas); 
     drawTick(backgroundCanvas); 
     drawTitle(backgroundCanvas); 

    } 

    /** 
    * Extract a window array from the data array, and reposition the windowStart 
    * index for next iteration 
    * @param data the array of data from which we get the window 
    * @return an array of float that represent the window 
    */ 
    private float[] prepareWindowArray(float[] data){ 
     // Prepare the source array for the graph. 
     float[] source = new float[windowSize]; 

     // Copy the window from the data array into the source array 
     for(int i = 0; i < windowSize; i++){ 
      if(windowStart+i < data.length)       // If the windows holds within the data array 
       source[i] = data[windowStart + i];     // Simply copy the value in the source array 
      else{             // If the window goes beyond the data array 
       source[i] = data[(windowStart + 1)%data.length]; // Loop at the beginning of the data array and copy from there 
      } 
     } 
     // Reposition the buffer index 
     windowStart = windowStart + windowSize; 
     // If the index is beyond the end of the array 
     if(windowStart >= data.length){ 
      windowStart = windowStart % data.length; 
     } 

     return source; 
    } 
} 
4

さて、私はあなたが持っているコードと実際のダイナミックデータですべてを再描画しようとすることから始めます。それが十分に速くない場合のみ、スクロールのような何かを試してみる必要があります...

私はこのような何かを試してみるとよいでしょう。

私はグラフの動的部分を、缶に直接ではなくフレーム間で保持するセカンダリビットマップに描画します。私は、バックグラウンドではなく、グラフのダイナミックな部分を別のビットマップで再スケーリングなどで描画するようにします。

この2次動的ビットマップでは、新しいデータをプロットするときに、先に古いデータをクリアする必要があります。古くなったデータの先頭に静的背景ビットマップの適切なスライスを描画し、それをクリアして背景を素早く元に戻します。新しいデータを描画するだけです。トリックは、この2番目のビットマップを左から右に描画し、最後に左に折り返してやり直すことです。

ソンコードビットマップからキャンバスに移動するには、ビットマップをキャンバスに2つの部分で描画します。ちょうど追加したものの右側の古いデータは、最終的なキャンバスの左側に描画する必要があり、新しいデータをその右側に即座に描画する必要があります。

データを送信するには、循環バッファーがこの種のデータの通常のものになります。グラフの一番外にいれば、気にしません。

+0

あなたのアイデアをお寄せいただき、ありがとうございました。 – AntoineG