Creating Android Charts with DroidCharts


Ever wanted to create sexy charts/graphs for Android ?

Well, here is a nice find called DroidCharts available at: http://code.google.com/p/droidcharts/

Compare with the Javascript/HTML approach in my other page: http://w2davids.wordpress.com/android-charts-the-html5-and-javascript-way/

Chart Types include:

  • Line
  • Pie
  • XY
  • Bar
  • Category Bar

I have made available a JAR library for download: http://www.filefactory.com/file/b3e83db/n/DroidCharts.jar

Hope you guys find it useful!

Some notes:

Note: As you guys/gals may have noticed, the data values are hard-coded..(naughty naughty naughty). This was done to keep things simple as possible. Ideally the data would come from a data source/content provider such as SQLite or Web service. I like the Active Record approach and will provide some details in my next article on doing Active Record for Android android-active-record

Screen Orientation/Rotation with DroidCharts.
Bumping my own Page/Thread – you cant get any more ridiculous than me:
Wanna do cheap tricks with your Android: Say you have a list of data values in a ListView
and upon device rotation change the view automatically changes to Landscape view with a graphical chart view plot. Passing parameters data values between Activity Views using Intent “extras” Bundles.

The Application:
XY Line chart display axis titles and chart legend.

The Code:
Main.java

import android.app.Activity;
import android.os.Bundle;

public class Main extends Activity
{
  /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState)
 {
     super.onCreate(savedInstanceState);
    setContentView(new XYLineChart(getApplicationContext()));
  }
}

XYLineChart.java

import net.droidsolutions.droidcharts.awt.Rectangle2D;
import net.droidsolutions.droidcharts.core.ChartFactory;
import net.droidsolutions.droidcharts.core.JFreeChart;
import net.droidsolutions.droidcharts.core.axis.NumberAxis;
import net.droidsolutions.droidcharts.core.data.XYDataset;
import net.droidsolutions.droidcharts.core.data.xy.XYSeries;
import net.droidsolutions.droidcharts.core.data.xy.XYSeriesCollection;
import net.droidsolutions.droidcharts.core.plot.PlotOrientation;
import net.droidsolutions.droidcharts.core.plot.XYPlot;
import net.droidsolutions.droidcharts.core.renderer.xy.XYLineAndShapeRenderer;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.view.View;

public class XYLineChart extends View
{
/** The view bounds. */
private final Rect mRect = new Rect();
/** The user interface thread handler. */
private final Handler mHandler;

/**
* Creates a new graphical view.
*
* @param context
*          the context
* @param chart
*          the chart to be drawn
*/
public XYLineChart(Context context)
{
super(context);
mHandler = new Handler();
}

@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.getClipBounds(mRect);

final XYDataset dataset = createDataset();
final JFreeChart chart = createChart(dataset);
chart.draw(canvas, new Rectangle2D.Double(0, 0, mRect.width(), mRect.height()));
Paint p = new Paint();
p.setColor(Color.RED);
}

/**
* Schedule a user interface repaint.
*/
public void repaint()
{
mHandler.post(new Runnable()
{
public void run()
{
invalidate();
}
});
}

private XYDataset createDataset()
{
final XYSeries series1 = new XYSeries("Rain");
series1.add(1.0, 1.0);
series1.add(2.0, 4.0);
series1.add(3.0, 3.0);
series1.add(4.0, 5.0);
series1.add(5.0, 5.0);
series1.add(6.0, 7.0);
series1.add(7.0, 7.0);
series1.add(8.0, 8.0);

final XYSeries series2 = new XYSeries("Sunshine");
series2.add(1.0, 5.0);
series2.add(2.0, 7.0);
series2.add(3.0, 6.0);
series2.add(4.0, 8.0);
series2.add(5.0, 4.0);
series2.add(6.0, 4.0);
series2.add(7.0, 2.0);
series2.add(8.0, 1.0);

final XYSeries series3 = new XYSeries("Apples");
series3.add(3.0, 4.0);
series3.add(4.0, 3.0);
series3.add(5.0, 2.0);
series3.add(6.0, 3.0);
series3.add(7.0, 6.0);
series3.add(8.0, 3.0);
series3.add(9.0, 4.0);
series3.add(10.0, 3.0);

final XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series1);
dataset.addSeries(series2);
dataset.addSeries(series3);

return dataset;
}

/**
* Creates a chart.
*
* @param dataset
*          the data for the chart.
*
* @return a chart.
*/
private JFreeChart createChart(final XYDataset dataset)
{

// create the chart...
final JFreeChart chart = ChartFactory.createXYLineChart("Apple Yield", // chart
// title
"Days", // x axis label
"Growth", // y axis label
dataset, // data
PlotOrientation.VERTICAL, true, // include legend
true, // tooltips
false // urls
);

Paint white = new Paint(Paint.ANTI_ALIAS_FLAG);
white.setColor(Color.WHITE);

Paint dkGray = new Paint(Paint.ANTI_ALIAS_FLAG);
dkGray.setColor(Color.DKGRAY);

Paint lightGray = new Paint(Paint.ANTI_ALIAS_FLAG);
lightGray.setColor(Color.LTGRAY);
lightGray.setStrokeWidth(10);

chart.setBackgroundPaint(white);

final XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(dkGray);
plot.setDomainGridlinePaint(lightGray);
plot.setRangeGridlinePaint(lightGray);

final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
plot.setRenderer(renderer);

// change the auto tick unit selection to integer units only...
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());

return chart;
}
}

Some extra code:
For passing in the graph parameters, I use a ContentValues object.

/** Called when the activity is first created. */
		@Override
		public void onCreate(Bundle savedInstanceState)
			{
				Log.d(tag, "Creating View");
				super.onCreate(savedInstanceState);

				// DataSet Description
				ContentValues datasetDescription = new ContentValues();
				datasetDescription.put("chart_title", "Journal Overview");
				datasetDescription.put("x_axis", "Day");
				datasetDescription.put("y_axis", "Severity");
				datasetDescription.put("include_legend", true);
				datasetDescription.put("include_tooltips", true);
				datasetDescription.put("include_urls", false);

                                XYLineChartView mView = new XYLineChartView(this, 
                                                               datasetDescription, createDataset(allJournalEntries));
			       setContentView(mView);
                       }

private XYDataset createDataset(List<JournalEntry> data)
			{
				final XYSeriesCollection dataset = new XYSeriesCollection();
				final XYSeries series1 = new XYSeries("Symptoms");
				final XYSeries series2 = new XYSeries("Reactions");

				int counter = 0;
			        
                                // POPULATE YOUR OWN DATA SERIES WITH ACTUAL DATA
                                  
                                // ADD DATA SERIES TO DATA SET
				dataset.addSeries(series1);
				dataset.addSeries(series2);
				return dataset;
			}

For modifying the axis orientation, label colors etc. here is some code:

	/**
		 * Creates a chart.
		 * 
		 * @param dataset
		 *          the data for the chart.
		 * 
		 * @return a chart.
		 */
		private JFreeChart createChart(final XYDataset dataset)
			{
				String chartTitle = datasetDescription.getAsString("chart_title");
				String XAxisLabel = datasetDescription.getAsString("x_axis");
				String YAxisLabel = datasetDescription.getAsString("y_axis");
				boolean includeLegend = datasetDescription.getAsBoolean("include_legend");
				boolean includeToolTips = datasetDescription.getAsBoolean("include_tooltips");
				boolean includeURLs = datasetDescription.getAsBoolean("include_urls");

				// Create Empty Chart
				chart = ChartFactory.createXYLineChart(chartTitle, XAxisLabel, YAxisLabel, dataset, PlotOrientation.VERTICAL, includeLegend, includeToolTips, includeURLs);

				Paint black = new Paint(Paint.ANTI_ALIAS_FLAG);
				black.setColor(Color.BLACK);

				Paint dkGray = new Paint(Paint.ANTI_ALIAS_FLAG);
				dkGray.setColor(Color.DKGRAY);

				Paint lightGray = new Paint(Paint.ANTI_ALIAS_FLAG);
				lightGray.setColor(Color.LTGRAY);
				lightGray.setStrokeWidth(10);

				Paint white = new Paint(Paint.ANTI_ALIAS_FLAG);
				white.setColor(Color.WHITE);

				// Set chart background
				setChartBackground(black);

				final XYPlot plot = chart.getXYPlot();
				plot.setBackgroundPaint(dkGray);
				plot.setDomainGridlinePaint(lightGray);
				plot.setRangeGridlinePaint(lightGray);
				plot.setDomainGridlinePaint(white);
				plot.setRangeGridlinePaint(white);

				final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
				renderer.setSeriesLinesVisible(0, true);
				renderer.setItemLabelPaint(white);
				plot.setRenderer(renderer);

				//Range axis: White labels, white ticks, label at 270 deg orientation (upright)
				final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
				rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
				rangeAxis.setLabelPaint(white);
				rangeAxis.setTickLabelPaint(white);
				rangeAxis.setLabelAngle(270.0);
				
				// Domain axis: White labels and white ticks
				final NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
				domainAxis.setLabelPaint(white);
				domainAxis.setTickLabelPaint(white);
				
				return chart;
			}

22 thoughts on “Creating Android Charts with DroidCharts

  1. I’m having an issue with getting this to work. I’ve imported the jar file and added it to my build path and I get the following error. Would love some help on that.

    UNEXPECTED TOP-LEVEL EXCEPTION:
    java.lang.IllegalArgumentException: already added: Lnet/droidsolutions/droidcharts/awt/FlatteningPathIterator;
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.dex.file.ClassDefsSection.add(ClassDefsSection.java:123)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.dex.file.DexFile.add(DexFile.java:143)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.command.dexer.Main.processClass(Main.java:301)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.command.dexer.Main.processFileBytes(Main.java:278)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.command.dexer.Main.access$100(Main.java:56)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:229)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:244)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:130)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:108)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.command.dexer.Main.processOne(Main.java:247)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.command.dexer.Main.processAllFiles(Main.java:183)
    [2010-11-04 15:46:39 - Chart Test] at com.android.dx.command.dexer.Main.run(Main.java:139)
    [2010-11-04 15:46:39 - Chart Test] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    [2010-11-04 15:46:39 - Chart Test] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    [2010-11-04 15:46:39 - Chart Test] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    [2010-11-04 15:46:39 - Chart Test] at java.lang.reflect.Method.invoke(Method.java:597)
    [2010-11-04 15:46:39 - Chart Test] at com.android.ide.eclipse.adt.internal.sdk.DexWrapper.run(Unknown Source)
    [2010-11-04 15:46:39 - Chart Test] at com.android.ide.eclipse.adt.internal.build.ApkBuilder.executeDx(Unknown Source)
    [2010-11-04 15:46:39 - Chart Test] at com.android.ide.eclipse.adt.internal.build.ApkBuilder.build(Unknown Source)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:629)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:172)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:203)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:255)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:258)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:311)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:343)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.AutoBuildJob.doBuild(AutoBuildJob.java:144)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.events.AutoBuildJob.run(AutoBuildJob.java:242)
    [2010-11-04 15:46:39 - Chart Test] at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54)
    [2010-11-04 15:46:39 - Chart Test] 1 error; aborting
    [2010-11-04 15:46:39 - Chart Test] Conversion to Dalvik format failed with error 1

    • Hi Brandon,

      I have encountered the Dex file format converson issue before.
      It usually results, when you have a duplicate class or the Android
      version differs.
      I have tested it on Android 1.5 and 2.1.

      What I usually do just to make sure ..is before I build , I go Project-> Clean.

      How have you added this JAR file to your classpath if I may ask?
      What I normallly do, is:
      1. Create a lib/ folder at the top level
      2. Copy my JAR file into it
      3. Click on Configure Build Path
      4. Add External JAR ..(adding the JAR )
      5. Click on Order and Export (same view )..and export it!

      Hope that helps…

      From your error:
      > java.lang.IllegalArgumentException: already added: Lnet/droidsolutions/droidcharts/awt/FlatteningPathIterator;

      it seems it’s already added?

      • I know it looks like it’s already added but I don’t know why. This is a freshly created Android 2.1 app, with this jar as the only additional external library.

        I think part of the problem might be I had the jar in a source folder instead of just a folder. I’ve gotten that error to go away but I still can’t build. For some reason it thinks my main.xml layout file is in there twice.

        Error generating final archive: duplicate entry: res/layout/main.xml

        It’s not.

      • Yep…hahha..now I remember it was the icon (Android) which was a duplicated.

        Simple solution: 1. Rename your main.xml to mymain.xml. 2. Then change your Main.java ..and reference setContentView( R.id.mymain );

        I good test – see that you can autocomplete..(since Eclipse will build…then a new Dex file will be created ).

  2. Yep…hahha..now I remember it was the icon (Android) which was a duplicated.

    Simple solution:
    1. Rename your main.xml to mymain.xml.
    2. Then change your Main.java ..and reference setContentView( R.id.mymain );

    I good test – see that you can autocomplete on the R.id. * to get mymain poppin up
    (since Eclipse will automatically build/compile …then a new Dex file will be created ).

  3. You my friend, are a beautiful human being. I’m good to go here. But why did I have to change that? What was being considered the original?

  4. I think it’s just my bad…when I did the build on the DroidCharts.jar..I did’nt want to disturb the original version ..so I left things as is.

    So, I ended up with the same probs you did, and figured Android’s dex converter must be seeing double literally…hahah.

    Remember that your most important file in Android ..is R.java…it get’s rebuilt each time you add anything to your Eclipse project – ie. unique object reference does matter!

    Keep well,

    -W

  5. Hi..great tutorials, can you please upload DroidCharts JAR library again, i have a problem downloading it from filefactory..

    thanks

  6. Hi Davids,

    Nice tutorial and appreciate that you share your work and help others learn a lot of stuffs. Could you please share a zip or jar of your codes. It would really help everyone who reports of errors. I personally didn’t find the codes to work, a simple working version of your project would do. For example i didnt find a way to figure out where i can include this class (XYLineChartView ) and the last two bunch of methods that you have shared in this page, bit confusing for me. Please help out.

  7. Hi!
    Good Job.
    But how can I put a button or other UI element in the chart (e.g. for selecting a chart or or back button)?

    Jens

  8. Your jar and example work perfectly. I was wondering however how you took the droidcharts source (from svn) and compiled the jar file to be used in the project. I can’t seem to figure out that part.
    -D

  9. Hello! I am Jonathan Sansalone and I am using DroidChart to create some xyline charts, but i have a problem with the series: The grid is filled by where the lines pass. How can i solve this?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s