tags:

views:

10535

answers:

9

With android removing the Swing and AWT libraries from Java, I was wondering what solutions have been developed to display simple bar histograms, line graphs and other simple data visualizations in Android?

There is a Google specific 2D library, was just wondering if there were any packages built atop it that allows for the easy creation of graphical data.

Some solutions bandied about on the web have been "just pull down a google chart with a HTTP get" which seems like a fine workaround. However, our eventual expected application usage is in a poor connectivity situation where network usage is expensive (unlocked phones in resource poor settings).

A: 

I don't know a library, but downloading images from the 2D library is really a waste. Maybe that's a nice project to start at code.google.com.

Harald Schilly
+3  A: 

There seems to be a Canvas available, so I'd suggest widening your search to graph drawing packages that extend Canvas, rather than looking for Android-specific ones.

Phil H
A: 

You could use html to build your charts locally and then display the charts in a webview

l_39217_l
+11  A: 

It looks like the creator of this question solved this problem and posted some interesting stuff about it to the internet. I've linked to his solution along with several others that exist now.

  1. rapidandroid.org/wiki/Graphing (question creator solution)
  2. GraphView
  3. Java Charts for Android

Just to summarize: his solution involved using a javascript library (flot) built on top of jQuery. This library was then included in an html page which was loaded into a custom WebView. The custom WebView then read data from a custom Java class made accessible to javascript via the WebView.addJavascriptInterface method. Details can be found at the above link.

The other solutions may present a more straightforward approach.

Waylon Flinn
Heh, thanks for linking our solution back into this thread. Given our initial tight time constraint, the flot solution made the quickest and best looking impact. But the javascript to java cross talk was not an easy thing to debug and step through. I'm sure There will be more direct, native java solutions in the future...but the power of the webkit browser was too tempting to pass up.
dmyung
I tried this solution and it worked perfectly, except for one thing: it's a little bit slow when loading the webview with the graph in it. Anyone had the same problem? I'm only using the emulator with android 1.5 on it, haven't tried other versions either.
Bilthon
I've tried jqPlot with WebView and that was also slow (emulator and HTC Magic - Android 1.6). I haven't yet done any specific timings, but the inital page load seems slow, so it might be a better approach to load the WebView and the JS libraries (jQuery and jqPlot) eagerly in the background, and dynamically modify the page when you want to plot.
Paul Grime
@Waylon Finn Does Flot seem like a good choice if I'm receiving a constant stream of data and want to graph it every 5 secs?
rohanbk
+5  A: 

AChartEngine looks like another good Android chart library. It can be found here: http://www.achartengine.org/index.html

It's the only one I've found that is all Java (no webview stuff), is open source, and the author isn't asking for money even if you end up using it in a pay app.

To use Graphview in your Android app you are required to build an about page that references your usage of Graphview, and if you charge for your app you have to give a donation to the Graphview author.

Java Charts for Android costs over $140 USD for a redistribution license so that you can use it on an app that you place on the Android Market.

mbaird
+1  A: 

I have used the shape library in android to draw a pie chart. But there is just one issue i.e. i am unable to write the percentage (eg. 90%, 40%, etc.). Wonder if someone can improve it further.

package com.myapps.piechart;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;

class GraphicsActivity extends Activity {



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);



    }

    public void setContentView(View view) {
        if (false) { // set to true to test Picture
            ViewGroup vg = new PictureLayout(this);
            vg.addView(view);
            view = vg;


        }

        super.setContentView(view);
    }





}




package com.myapps.piechart;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;


public class PictureLayout extends ViewGroup {
    private final Picture mPicture = new Picture();

    public PictureLayout(Context context) {
        super(context);
    }

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

    @Override
    public void addView(View child) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("PictureLayout can host only one direct child");
        }

        super.addView(child);
    }

    @Override
    public void addView(View child, int index) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("PictureLayout can host only one direct child");
        }

        super.addView(child, index);
    }

    @Override
    public void addView(View child, LayoutParams params) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("PictureLayout can host only one direct child");
        }

        super.addView(child, params);
    }

    @Override
    public void addView(View child, int index, LayoutParams params) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("PictureLayout can host only one direct child");
        }

        super.addView(child, index, params);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int count = getChildCount();

        int maxHeight = 0;
        int maxWidth = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }

        maxWidth += getPaddingLeft() + getPaddingRight();
        maxHeight += getPaddingTop() + getPaddingBottom();

        Drawable drawable = getBackground();
        if (drawable != null) {
            //maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            //maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
            maxHeight=200;
            maxWidth=100;
        }

        setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
                resolveSize(maxHeight, heightMeasureSpec));
    }

    private void drawPict(Canvas canvas, int x, int y, int w, int h,
                          float sx, float sy) {
        canvas.save();
        canvas.translate(x, y);
        canvas.clipRect(0, 0, w, h);
        canvas.scale(0.5f, 0.5f);
        canvas.scale(sx, sy, w, h);
        canvas.drawPicture(mPicture);
        canvas.restore();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
        mPicture.endRecording();

        int x = getWidth()/2;
        int y = getHeight()/2;

        if (false) {
            canvas.drawPicture(mPicture);
        } else {
            drawPict(canvas, 0, 0, x, y,  1,  1);
            drawPict(canvas, x, 0, x, y, -1,  1);
            drawPict(canvas, 0, y, x, y,  1, -1);
            drawPict(canvas, x, y, x, y, -1, -1);
        }
    }

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        location[0] = getLeft();
        location[1] = getTop();
        dirty.set(0, 0, getWidth(), getHeight());
        return getParent();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = super.getChildCount();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final int childLeft = getPaddingLeft();
                final int childTop = getPaddingTop();
                child.layout(childLeft, childTop,
                        childLeft + child.getMeasuredWidth(),
                        childTop + child.getMeasuredHeight());

            }
        }
    }
}




package com.myapps.piechart;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ComposePathEffect;
import android.graphics.CornerPathEffect;
import android.graphics.DiscretePathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.ArcShape;
import android.graphics.drawable.shapes.OvalShape;
import android.graphics.drawable.shapes.PathShape;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class ShapeDrawable1 extends GraphicsActivity {



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new SampleView(this));


    }



    private static class SampleView extends View {
        private ShapeDrawable[] mDrawables;

        private static Shader makeSweep() {
            return new SweepGradient(150, 50,
                new int[] { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFF0000 },
                null);
        }

        private static Shader makeLinear() {
            return new LinearGradient(0, 0, 50, 50,
                              new int[] { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF },
                              null, Shader.TileMode.MIRROR);
        }

        private static Shader makeTiling() {
            int[] pixels = new int[] { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0};
            Bitmap bm = Bitmap.createBitmap(pixels, 2, 2,
                                            Bitmap.Config.ARGB_8888);

            return new BitmapShader(bm, Shader.TileMode.REPEAT,
                                        Shader.TileMode.REPEAT);
        }

        private static class MyShapeDrawable extends ShapeDrawable {
            private Paint mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

            public MyShapeDrawable(Shape s) {
                super(s);
                mStrokePaint.setStyle(Paint.Style.STROKE);
            }

            public Paint getStrokePaint() {
                return mStrokePaint;
            }

            @Override protected void onDraw(Shape s, Canvas c, Paint p) {
                s.draw(c, p);
                s.draw(c, mStrokePaint);
            }

          private void setText(String s)
          {
             this.setText(s);  
          }

          private String getText()
          {  
              return this.getText(); 
          }

        }

        public SampleView(Context context) {
            super(context);
            setFocusable(true);

            float[] outerR = new float[] { 12, 12, 12, 12, 0, 0, 0, 0 };
            RectF   inset = new RectF(6, 6, 6, 6);
            float[] innerR = new float[] { 12, 12, 0, 0, 12, 12, 0, 0 };

            Path path = new Path();
            path.moveTo(50, 0);
            path.lineTo(0, 50);
            path.lineTo(50, 100);
            path.lineTo(100, 50);
            path.close();

            mDrawables = new ShapeDrawable[8];
            mDrawables[0] = new ShapeDrawable(new RectShape());
            mDrawables[1] = new ShapeDrawable(new OvalShape());
            mDrawables[2] = new ShapeDrawable(new RoundRectShape(outerR, null,
                                                                 null));
            mDrawables[3] = new ShapeDrawable(new RoundRectShape(outerR, inset,
                                                                 null));
            mDrawables[4] = new ShapeDrawable(new RoundRectShape(outerR, inset,
                                                                 innerR));
            mDrawables[5] = new ShapeDrawable(new PathShape(path, 100, 100));

            //Complete circle
            mDrawables[6] = new MyShapeDrawable(new ArcShape(0, -365));

            //Setting percentage
            mDrawables[7] = new MyShapeDrawable(new ArcShape(0, -90));


            mDrawables[0].getPaint().setColor(0xFFFF0000);
            mDrawables[1].getPaint().setColor(0xFF00FF00);
            mDrawables[2].getPaint().setColor(0xFF0000FF);
            mDrawables[3].getPaint().setShader(makeSweep());
            mDrawables[4].getPaint().setShader(makeLinear());
            mDrawables[5].getPaint().setShader(makeTiling());
            mDrawables[6].getPaint().setColor(0xFF0000FF);
            mDrawables[7].getPaint().setColor(0xFF00FF00);

            PathEffect pe = new DiscretePathEffect(10, 4);
            PathEffect pe2 = new CornerPathEffect(4);
            mDrawables[3].getPaint().setPathEffect(
                                                new ComposePathEffect(pe2, pe));

            MyShapeDrawable msd = (MyShapeDrawable)mDrawables[6];
            msd.getStrokePaint().setStrokeWidth(4);


        }

        @Override protected void onDraw(Canvas canvas) {

            int x = 10;
            int y = 10;
            int width = 300;
            int height = 300;

           /* for (Drawable dr : mDrawables) {
                dr.setBounds(x, y, x + width, y + height);
                dr.draw(canvas);                
                y += height + 5;
            }*/


            /*
            mDrawables[7] = new MyShapeDrawable(new ArcShape(0, -300));
            mDrawables[7].getPaint().setColor(0xFF00FF00);
            mDrawables[7].setBounds(x, y, 150, 150);
            mDrawables[7].draw(canvas);
            y += height + 5;
            */

            //setting width and height and location of Pie Chart

            mDrawables[6].setBounds(x, 100, 200, 270);
            mDrawables[6].draw(canvas);
            y += height + 5;


            mDrawables[7].setBounds(x, 100, 200, 270);
            mDrawables[7].draw(canvas);
            y += height + 5;




        }
    }
}
Maxood
What i did is that i have drawn a circle over a circle, set the percentage of the circle on the top one and rotated it accordingly.
Maxood
+1  A: 

Interesting why everyone forget about aiCharts for Android. Try google a little by keywords: "Android Charts". and aiCharts will be first in the list. It is the only one solution on market that was specially written for Android and is not a java code re-compilation of old library... Great set of functionality, a lot of samples, documentation, great support.

More about aiCharts library - http://www.artfulbits.com/android/aicharts.aspx

AlexK
Thanks for advertising your library.
Janusz
Hey if it works, more power to ya. But the demo screens make it look a little inflexible -- seems it renders a single image?
Brian Lacy
please tell what you expect... description "a little inflexible" does not describe anything to me. YouTube movie about aiCharts shows how flexible they can be. All other depends on developer skills only.
AlexK
You forgot to add "expensive" to the list of features.
fiXedd
expensive?! Ha! U simply do not count what kind of functionality you have in hands.1) Try to write own library and then calculate efforts spent on it. Library price is in 100 hundred times less in compare to efforts you have to spent to do the same job by own hands.2) Look on other products on market. Any professional Java chart library cost greater 400 USD. For example: https://www.dundas.com/Order/Step1.aspx or http://www.componentsource.com/index.htmlOur library cost every cent you spent on it... All I have to say that price is very reasonable in compare to other solutions on market.
AlexK
@AlexK - Don't let the haters get you riled up. There are always people that say something is too expensive or think they can do a better job themselves. I think a big part of this is the inability of most developers to give accurate estimates of the time required to produce a deliverable. Sure, writing a charting library sounds quick and easy--until you actually start doing it.
Dana Holt
A: 

Bumping this with ChartDroid (http://code.google.com/p/chartdroid/wiki/Screenshots). I have used it yet, but it looks like it should be good.

A: 

Android charts using Javascript/HTML http://w2davids.wordpress.com/android-charts-the-html5-and-javascript-way/