在Android的自定义动态图 [英] Custom dynamic graph in Android

查看:189
本文介绍了在Android的自定义动态图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

[更新] 结束对这个问题,我使用以下两种方法(见下文)来实现我的曲线图。 drawCurve()收到画布浮动的数组 。阵列被正确填写(时间戳假定由阵列中的值的索引)和变化从0.0到1.0。该阵列发送到 prepareWindowArray()这需要从位置阵列的一大块 windowStart 的<$ C C> windowSize - 值$,以循环的方式。

[UPDATE] To conclude this question, I implemented my graph using the following two methods (see below). drawCurve() receives a Canvas and an array of float. The array is properly filled (timestamps are assumed by the value index in the array) and varies from 0.0 to 1.0. The array is sent to prepareWindowArray() that takes a chunk of the array from position windowStart for windowSize-values, in a circular manner.

使用的GraphView和由数据提供者(蓝牙设备)的阵列是相同的。 A类中间确保GraphView不读书,正在写的蓝牙设备的数据。由于GraphView总是环路直通的阵列和在每次迭代重绘,它将根据所写的蓝牙设备中的数据进行更新,并通过迫使蓝牙设备的写频率的图形的刷新频率,我获得光滑动漫我的信号了。

The array used by the GraphView and by the data provider (a Bluetooth device) is the same. A Class in the middle ensures that GraphView is not reading data that are being written by the Bluetooth device. Since the GraphView always loop thru the array and redraw it at every iteration, it will update according to the data written by the Bluetooth device, and by forcing the write frequency of the Bluetooth device to the refresh frequency of the Graph, I obtain a smooth animation of my signal.

GraphView 无效()方法被调用的活动,其运行定时器刷新图形上每 X 毫秒。时的频率刷新图是动态设置的,以便使其适应于数据从蓝牙设备的流动(其指定其信号在其分组的报头的频率)。

The GraphView's invalidate() method is called by the Activity, which run a Timer to refresh the graph at every x milliseconds. The frequency at which the graph is refreshed is dynamically set, so that it adapt to the flow of data from the Bluetooth device (which specify the frequency of its signal in the header of its packet).

查找我的 GraphView 在我写了下面的答案完全code(在答题部分)。如果你们发现错误或方式来优化它,请让我知道;这将是极大的AP preciated!

Find the complete code of my GraphView in the answer I wrote below (in the answer section). If you guys find errors or way to optimize it, please let me know; it would be greatly appreciated!

/**
 * 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()方法实现数组的圆形行为:

The prepareWindowArray() method that implement the circular behavior of the array:

/**
 * 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;
}

[/更新]

我在做一个应用程序,从蓝牙设备中读取的数据以固定的速率。每次,我有新的数据,我希望他们能够被绘制在图上的权利,并以图表的剩余部分转换为左实时。基本上,就像一个示波器会做。

I'm making an app that read data from a Bluetooth device at a fixed rate. Everytime that I have new data, I want them to be plotted on the graph to the right, and to translate the remainder of the graph to the left in realtime. Basically, like an oscilloscope would do.

所以我做了一个自定义的视图,与XY轴,标题和单位。要做到这一点,我简单地画上查看画布上的那些东西。现在我想画出曲线。我管理使用此方法来绘制从一个已经装满阵列静态曲线:

So I made a custom View, with xy axis, a title and units. To do this, I simply draw those things on the View canvas. Now I want to draw the curve. I manage to draw a static curve from an already filled array using this method:

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);
}

这给了我这样的事情。

It gives me something like this.

还有事情要对我的图(副轴不能正确缩放)修复,但这些细节我可以在以后解决。

There are still things to fix on my graph (the sub-axis are not properly scaling), but these are details I can fix later.

现在我想改变这个静态图(接收值的非动态矩阵的计算)的东西的动态,将重新绘制曲线每40毫秒,推旧数据的左侧并绘制出新数据的权利,所以我可以实时直观的蓝牙设备提供的信息。

Now I want to change this static graph (that receives a non-dynamic matrice of values) with something dynamic that would redraw the curve every 40ms, pushing the old data to the left and plotting the new data to the right, so I could visualise in real time the information provided by the Bluetooth device.

我知道有一些已经存在一些图形软件包,但我有点小白这些东西,我想通过实施这个图自己实践。此外,大多数我GraphView类是除了曲线部分完成。

I know there are some graphing package that exists already, but I'm kinda noob with these things and I'd like to pratice by implementing this graph myself. Also, most of my GraphView class is done, except for the curve part.

第二个问题,我不知道我应该怎么发新值曲线图。我应该使用类似一个FIFO堆栈,或者我可以达到我想要与双打的简单矩阵的计算方法?

Second question, I'm wondering how I should send the new values to the graph. Should I use something like a FIFO stack, or can I achieve what I want with a simple matrice of doubles?

在一个侧面说明,在底部的4个领域已经动态更新。那么,他们是那种又伪造的动态,它们环通相同的双矩阵的计算方法,并再次,他们实际上并不需要新的价值观。

On a side note, the 4 fields at the bottom are already dynamically updated. Well, they are kind of faking the "dynamic", they loop thru the same double matrice again and again, they don't actually take fresh values.

感谢您的时间!如果有什么地方不清楚我的问题,让我知道,我会更多的细节更新。

Thanks for your time! If something's unclear about my question, let me know and I'll update it with more details.

推荐答案

正如我的问题,下面是我专为解决我的问题的类。

As mentioned in my question, here's the class that I designed to solve my problems.

/**
 * 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;
    }
}

这篇关于在Android的自定义动态图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆