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: https://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:
- Data can come from SQLite database
- XML-based raw file ( refer to my page: https://w2davids.wordpress.com/android-listview-with-iconsimages-and-sharks-with-lasers/)
- Web-Service etc….
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; }
TODO: Upload JAR file and other libs
DroidCharts JAR library:
http://www.filefactory.com/file/b3e83db/n/DroidCharts.jar
Note to self: Update to include Android ActiveRecord-ORM
Update Example including Screen Orientation changes. Show how to pass parameters data values between Activities, amongst other stuff.
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 ).
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 ).
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?
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
I want to add this chart in my xml layout file . How can I do that ?
Also can you make it dynamic …
Hi..great tutorials, can you please upload DroidCharts JAR library again, i have a problem downloading it from filefactory..
thanks
It is ok now..i have found it..thanks
How 2 add the jar file to be used in android project?
Plz guide me
i need 2 draw a bar chart from xml file
plz help
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.
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
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
How to take the values from a database to draw the bar chart??
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?
Hi Jonathan,
You need to change the “range” of the x-axis or do a scrolling window.
mmh I have the code somewhere…
-wagied
0 Pingbacks
GOOGLE
Recent Posts
Archives
Categories
Meta