summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEino-Ville Talvala <etalvala@google.com>2014-02-21 17:45:53 -0800
committerEino-Ville Talvala <etalvala@google.com>2014-02-25 23:39:32 +0000
commit6224eda509d436a575f801942337da92a6c18767 (patch)
tree87997a1d54999fc369a96caa7404767857310335
parent71b639c59e0bf8800ecb655629e28bdf2d2bf373 (diff)
downloadandroidplot-6224eda509d436a575f801942337da92a6c18767.tar.gz
Initial checkin of androidplot v0.6.0
This commit is a snapshot of androidplot at tag 0.6.0: commit 24dfe5d708dd409611fff1aa803ce3324a00db85 Author: bamboo <bamboo@androidplot.com> Date: Sat Jul 20 20:06:07 2013 +0000 [maven-release-plugin] prepare release 0.6.0 Change-Id: I84fb6af3e0c96b1d3d14e21f518a9b49e1126cef
-rw-r--r--.gitignore7
-rw-r--r--.idea/ant.xml3
-rw-r--r--.idea/codeStyleSettings.xml13
-rw-r--r--.idea/compiler.xml57
-rw-r--r--.idea/copyright/AndroidPlot_Apache_2_0.xml9
-rw-r--r--.idea/copyright/profiles_settings.xml7
-rw-r--r--.idea/encodings.xml9
-rw-r--r--.idea/inspectionProfiles/Project_Default.xml153
-rw-r--r--.idea/inspectionProfiles/profiles_settings.xml7
-rw-r--r--.idea/libraries/Maven__com_google_android_android_4_1_1_4.xml13
-rw-r--r--.idea/libraries/Maven__commons_codec_commons_codec_1_3.xml13
-rw-r--r--.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml13
-rw-r--r--.idea/libraries/Maven__junit_junit_4_8_1.xml13
-rw-r--r--.idea/libraries/Maven__mockit_jmockit_0_999_3.xml13
-rw-r--r--.idea/libraries/Maven__org_apache_httpcomponents_httpclient_4_0_1.xml13
-rw-r--r--.idea/libraries/Maven__org_apache_httpcomponents_httpcore_4_0_1.xml13
-rw-r--r--.idea/libraries/Maven__org_json_json_20080701.xml13
-rw-r--r--.idea/libraries/Maven__org_khronos_opengl_api_gl1_1_android_2_1_r1.xml13
-rw-r--r--.idea/libraries/Maven__xerces_xmlParserAPIs_2_6_2.xml13
-rw-r--r--.idea/libraries/Maven__xpp3_xpp3_1_1_4c.xml13
-rw-r--r--.idea/misc.xml100
-rw-r--r--.idea/modules.xml11
-rw-r--r--.idea/runConfigurations/DemoApp.xml22
-rw-r--r--.idea/uiDesigner.xml125
-rw-r--r--.idea/vcs.xml7
-rw-r--r--AndroidPlot-Core/.classpath9
-rw-r--r--AndroidPlot-Core/.project29
-rw-r--r--AndroidPlot-Core/androidplot-core.iml27
-rw-r--r--AndroidPlot-Core/pom.xml182
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/LineRegion.java101
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/Plot.java868
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/PlotListener.java45
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/Series.java44
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/exception/PlotRenderException.java23
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/pie/PieChart.java93
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/pie/PieRenderer.java234
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/pie/PieWidget.java47
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/pie/Segment.java48
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/pie/SegmentFormatter.java179
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/AnchorPosition.java33
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModel.java161
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModelable.java75
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/DynamicTableModel.java286
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/FixedTableModel.java148
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/Formatter.java61
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutManager.java268
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutMetric.java69
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetric.java87
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetrics.java67
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/RenderBundle.java47
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/Resizable.java40
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesAndFormatterList.java91
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesRenderer.java73
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeLayoutType.java34
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetric.java59
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetrics.java90
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/TableModel.java51
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/TableOrder.java22
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/TableSizingMethod.java28
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/TextOrientationType.java23
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/XLayoutStyle.java27
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/XPositionMetric.java70
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/YLayoutStyle.java26
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/YPositionMetric.java100
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java34
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java189
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/Widget.java403
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/Configurator.java346
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/DisplayDimensions.java45
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/FontUtils.java65
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/ListOrganizer.java120
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/MultiSynch.java86
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/PaintUtils.java65
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/PixelUtils.java217
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/PlotStatistics.java118
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/Redrawer.java112
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/ValPixConverter.java96
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/ZHash.java163
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/ZIndexable.java93
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/util/ZLinkedList.java78
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/AxisValueLabelFormatter.java34
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/BarFormatter.java72
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/BarRenderer.java369
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointFormatter.java50
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointRenderer.java43
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/BoundaryMode.java26
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/FillDirection.java36
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointFormatter.java182
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java224
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabelFormatter.java75
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabeler.java22
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/RectRegion.java181
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/SimpleXYSeries.java283
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/StepFormatter.java45
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/StepRenderer.java164
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/ValueMarker.java156
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XValueMarker.java58
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYAxisType.java22
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYFramingModel.java22
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphBounds.java27
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphWidget.java1252
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYLegendWidget.java248
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlot.java1344
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlotZoomPan.java384
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYRegionFormatter.java73
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeries.java51
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java65
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java52
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStep.java60
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepCalculator.java75
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepMode.java28
-rw-r--r--AndroidPlot-Core/src/main/java/com/androidplot/xy/YValueMarker.java58
-rw-r--r--AndroidPlot-Core/src/main/javadoc/com/androidplot/series/package.html26
-rw-r--r--AndroidPlot-Core/src/main/javadoc/com/androidplot/ui/package.html26
-rw-r--r--AndroidPlot-Core/src/main/javadoc/com/androidplot/util/package.html26
-rw-r--r--AndroidPlot-Core/src/main/javadoc/com/androidplot/xy/package.html26
-rw-r--r--AndroidPlot-Core/src/main/javadoc/doc-files/barplot1.jpgbin0 -> 23053 bytes
-rw-r--r--AndroidPlot-Core/src/main/javadoc/doc-files/lineplot1.jpgbin0 -> 60544 bytes
-rw-r--r--AndroidPlot-Core/src/main/javadoc/overview.html20
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/LineRegionTest.java84
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/PlotTest.java483
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/RegionTest.java59
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockCanvas.java34
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockContext.java428
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockLooper.java25
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPaint.java38
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPixelUtils.java22
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPointF.java25
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockRectF.java94
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockSizeMetrics.java21
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/mock/MockWidget.java20
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/ui/DynamicTableModelTest.java245
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/ui/FixedTableModelTest.java254
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/util/ConfiguratorTest.java99
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/util/ListOrganizerTest.java95
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/util/ValPixConverterTest.java149
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/xy/RectRegionTest.java82
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java134
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java101
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/xy/XYPlotTest.java478
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/xy/XYSeriesRendererTest.java99
-rw-r--r--AndroidPlot-Core/src/test/java/com/androidplot/xy/XYStepCalculatorTest.java69
-rw-r--r--DynamicXYPlotExample.iml37
-rw-r--r--Examples/AndroidPlot-Examples.iml20
-rw-r--r--Examples/DemoApp/.classpath10
-rw-r--r--Examples/DemoApp/.project39
-rw-r--r--Examples/DemoApp/AndroidManifest.xml85
-rw-r--r--Examples/DemoApp/DemoApp.iml39
-rw-r--r--Examples/DemoApp/ant.properties46
-rw-r--r--Examples/DemoApp/build.xml92
-rw-r--r--Examples/DemoApp/pom.xml61
-rw-r--r--Examples/DemoApp/proguard-project.txt26
-rw-r--r--Examples/DemoApp/project.properties43
-rwxr-xr-xExamples/DemoApp/res/drawable/graph_background.pngbin0 -> 133 bytes
-rw-r--r--Examples/DemoApp/res/drawable/icon.pngbin0 -> 8764 bytes
-rw-r--r--Examples/DemoApp/res/layout/bar_plot_example.xml114
-rw-r--r--Examples/DemoApp/res/layout/demo_app_widget.xml31
-rw-r--r--Examples/DemoApp/res/layout/dual_scale_xy_plot_example.xml52
-rw-r--r--Examples/DemoApp/res/layout/dynamicxyplot_example.xml46
-rw-r--r--Examples/DemoApp/res/layout/listview_example.xml9
-rw-r--r--Examples/DemoApp/res/layout/listview_example_item.xml28
-rw-r--r--Examples/DemoApp/res/layout/main.xml73
-rw-r--r--Examples/DemoApp/res/layout/orientation_sensor_example.xml98
-rw-r--r--Examples/DemoApp/res/layout/pie_chart.xml56
-rw-r--r--Examples/DemoApp/res/layout/simple_xy_plot_example.xml45
-rw-r--r--Examples/DemoApp/res/layout/step_chart_example.xml19
-rw-r--r--Examples/DemoApp/res/layout/time_series_example.xml29
-rw-r--r--Examples/DemoApp/res/layout/touch_zoom_example.xml51
-rw-r--r--Examples/DemoApp/res/layout/xy_plot_with_bq_img_example.xml48
-rw-r--r--Examples/DemoApp/res/layout/xyregion_example.xml94
-rw-r--r--Examples/DemoApp/res/values-hdpi/dimens.xml6
-rw-r--r--Examples/DemoApp/res/values-ldpi/dimens.xml4
-rw-r--r--Examples/DemoApp/res/values/attrs.xml6
-rw-r--r--Examples/DemoApp/res/values/dimens.xml15
-rw-r--r--Examples/DemoApp/res/values/strings.xml9
-rw-r--r--Examples/DemoApp/res/values/style.xml34
-rw-r--r--Examples/DemoApp/res/xml/demo_app_widget_provider_info.xml26
-rw-r--r--Examples/DemoApp/res/xml/line_point_formatter_with_plf1.xml7
-rw-r--r--Examples/DemoApp/res/xml/line_point_formatter_with_plf2.xml7
-rw-r--r--Examples/DemoApp/res/xml/pie_segment_formatter1.xml4
-rw-r--r--Examples/DemoApp/res/xml/pie_segment_formatter2.xml4
-rw-r--r--Examples/DemoApp/res/xml/pie_segment_formatter3.xml4
-rw-r--r--Examples/DemoApp/res/xml/pie_segment_formatter4.xml4
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/BarPlotExampleActivity.java425
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/DualScaleXYPlotExampleActivity.java200
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/DynamicXYPlotActivity.java252
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/GlobalDefs.java24
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/ListViewActivity.java96
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/MainActivity.java132
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/OrientationSensorExampleActivity.java249
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/SimplePieChartActivity.java117
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/SimpleXYPlotActivity.java83
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/StepChartExampleActivity.java140
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/TimeSeriesActivity.java128
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/TouchZoomExampleActivity.java208
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/XYPlotWithBgImgActivity.java131
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/XYRegionExampleActivity.java433
-rw-r--r--Examples/DemoApp/src/com/androidplot/demos/widget/DemoAppWidgetProvider.java87
-rw-r--r--Examples/pom.xml150
-rw-r--r--androidplot.iml14
-rw-r--r--pom.xml131
201 files changed, 20332 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..01ce2d5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/.metadata/
+/AndroidPlot-Core/target/
+/AndroidPlot-Core/.settings/
+/Examples/DemoApp/.settings/
+/Examples/DemoApp/bin/
+/Examples/DemoApp/gen/
+/Examples/DemoApp/target/ \ No newline at end of file
diff --git a/.idea/ant.xml b/.idea/ant.xml
new file mode 100644
index 0000000..8dcb7fc
--- /dev/null
+++ b/.idea/ant.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4" />
+
diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml
new file mode 100644
index 0000000..d63a15e
--- /dev/null
+++ b/.idea/codeStyleSettings.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectCodeStyleSettingsManager">
+ <option name="PER_PROJECT_SETTINGS">
+ <value>
+ <XML>
+ <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
+ </XML>
+ </value>
+ </option>
+ </component>
+</project>
+
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..4802278
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <option name="DEFAULT_COMPILER" value="Javac" />
+ <excludeFromCompile>
+ <directory url="file://$PROJECT_DIR$/Examples/StepChartExample/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/SimpleXYPlotExample/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/TimeSeriesExample/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/OrientationSensorExample/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/DynamicXYPlotExample/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/HelperExample/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/DemoApp/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/TimedXYPlotExample/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/Quickstart/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/DemoApp/target/generated-sources/annotations" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/Examples/DemoApp/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/AndroidPlot-Core/target/generated-sources/annotations" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/AndroidPlot-Core/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/AndroidPlot-Core/gen" includeSubdirectories="true" />
+ <directory url="file://$PROJECT_DIR$/gen" includeSubdirectories="true" />
+ </excludeFromCompile>
+ <resourceExtensions>
+ <entry name=".+\.(properties|xml|html|dtd|tld)" />
+ <entry name=".+\.(gif|png|jpeg|jpg)" />
+ </resourceExtensions>
+ <wildcardResourcePatterns>
+ <entry name="?*.properties" />
+ <entry name="?*.xml" />
+ <entry name="?*.gif" />
+ <entry name="?*.png" />
+ <entry name="?*.jpeg" />
+ <entry name="?*.jpg" />
+ <entry name="?*.html" />
+ <entry name="?*.dtd" />
+ <entry name="?*.tld" />
+ <entry name="?*.ftl" />
+ </wildcardResourcePatterns>
+ <annotationProcessing>
+ <profile default="true" name="Default" enabled="false">
+ <processorPath useClasspath="true" />
+ </profile>
+ <profile default="false" name="Maven default annotation processors profile" enabled="true">
+ <sourceOutputDir name="target/generated-sources/annotations" />
+ <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+ <outputRelativeToContentRoot value="true" />
+ <processorPath useClasspath="true" />
+ <module name="androidplot-core" />
+ <module name="DemoApp" />
+ </profile>
+ </annotationProcessing>
+ <bytecodeTargetLevel>
+ <module name="androidplot-core" target="1.6" />
+ </bytecodeTargetLevel>
+ </component>
+</project>
+
diff --git a/.idea/copyright/AndroidPlot_Apache_2_0.xml b/.idea/copyright/AndroidPlot_Apache_2_0.xml
new file mode 100644
index 0000000..af0b58d
--- /dev/null
+++ b/.idea/copyright/AndroidPlot_Apache_2_0.xml
@@ -0,0 +1,9 @@
+<component name="CopyrightManager">
+ <copyright>
+ <option name="notice" value="Copyright &amp;#36;today.year AndroidPlot.com&#10;&#10; Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10; you may not use this file except in compliance with the License.&#10; You may obtain a copy of the License at&#10;&#10; http://www.apache.org/licenses/LICENSE-2.0&#10;&#10; Unless required by applicable law or agreed to in writing, software&#10; distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10; See the License for the specific language governing permissions and&#10; limitations under the License." />
+ <option name="keyword" value="Copyright" />
+ <option name="allowReplaceKeyword" value="" />
+ <option name="myName" value="AndroidPlot Apache 2.0" />
+ <option name="myLocal" value="true" />
+ </copyright>
+</component> \ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..a8fb46f
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,7 @@
+<component name="CopyrightManager">
+ <settings default="AndroidPlot Apache 2.0">
+ <module2copyright>
+ <element module="All" copyright="AndroidPlot Apache 2.0" />
+ </module2copyright>
+ </settings>
+</component> \ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..b7e4cd4
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+ <file url="file://$PROJECT_DIR$" charset="UTF-8" />
+ <file url="file://$PROJECT_DIR$/AndroidPlot-Core" charset="UTF-8" />
+ <file url="file://$PROJECT_DIR$/Examples/DemoApp" charset="UTF-8" />
+ </component>
+</project>
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..bfdc003
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,153 @@
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0" is_locked="false">
+ <option name="myName" value="Project Default" />
+ <option name="myLocal" value="false" />
+ <inspection_tool class="AbstractBeanReferencesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="AssertEqualsBetweenInconvertibleTypesTestNG" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="AutowiredDependenciesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="ComponentNotRegistered" enabled="false" level="WARNING" enabled_by_default="false">
+ <option name="CHECK_ACTIONS" value="true" />
+ <option name="IGNORE_NON_PUBLIC" value="true" />
+ </inspection_tool>
+ <inspection_tool class="ComponentRegistrationProblems" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="ContextComponentScanInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="ContextJavaBeanUnresolvedMethodsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="DialogTitleCapitalization" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="DuplicatedBeanNamesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="ELDeferredExpressionsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="ELMethodSignatureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="ELSpecValidationInJSP" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="ELValidationInJSP" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="EjbClassBasicInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbClassWarningsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="EjbDomInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbEntityClassInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbEntityHomeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbEntityInterfaceInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbEnvironmentInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbInterceptorInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbInterceptorWarningsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="EjbInterfaceMethodInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbInterfaceSignatureInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbProhibitedPackageUsageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="EjbQlInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbRemoteRequirementsInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbSessionHomeInterfaceInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="EjbStaticAccessInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="EjbThisExpressionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="ExtensionPointBeanClass" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="FlowRequiredBeanTypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="HardcodedActionUrl" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="InjectionValueTypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="InspectionDescriptionNotFoundInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="InspectionMappingConsistency" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="InspectionUsingGrayColors" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="IntentionDescriptionNotFoundInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="JavaeeApplicationDomInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="JdkProxiedBeanTypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="JsfJamExtendsClassInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="JspAbsolutePathInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="JspDirectiveInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="JspPropertiesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="MVCPathVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="ManagedBeanClassInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="MimeType" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="MissedExecutable" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="PlayCustomTagNameInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="PlayPropertyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="PluginXmlValidity" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="ReferencesToClassesFromDefaultPackagesInJSPFile" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="RequiredBeanTypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SassScssResolvedByNameOnly" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
+ <inspection_tool class="SassUnresolvedFunction" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SassUnresolvedImport" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SassUnresolvedMixin" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SassUnresolvedPlaceholderSelector" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SassUnresolvedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SelfIncludingJspFiles" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="ServletWithoutMappingInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringBatchModel" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringBeanAutowiringInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringBeanConstructorArgInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringBeanDepedencyCheckInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringBeanInstantiationInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringBeanLookupMethodInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringBeanNameConventionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringContextConfigurationInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringDataJpaMethodInconsistencyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringElInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringFacetInspection" enabled="false" level="WARNING" enabled_by_default="false">
+ <option name="checkTestFiles" value="false" />
+ </inspection_tool>
+ <inspection_tool class="SpringFactoryMethodInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringHandlersSchemasHighlighting" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringIncorrectResourceTypeInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringInjectionValueConsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringInjectionValueStyleInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringIntegrationDeprecations21" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringIntegrationModel" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringJavaAutowiringInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringJavaConfigExternalBeansErrorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringJavaConfigInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringMVCInitBinder" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringMVCViewInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringMessageDispatcherWebXmlInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringOsgiElementsInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringOsgiListenerInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringOsgiServiceCommonInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringPlaceholdersInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringPublicFactoryMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringRequiredAnnotationInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringRequiredPropertyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringScopesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringSecurityDebugActivatedInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringSecurityFiltersConfiguredInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringSecurityModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringStaticMembersAutowiringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringTransactionalComponentInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SpringWebServiceAnnotationsInconsistencyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SpringWebServicesConfigurationsInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlConstantConditionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlDeliverTableNameInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlDeprecateTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlDropIndexedColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlIdentifierInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlInsertValuesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlPostgresqlSelectFromProcedureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlResolveInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlShouldBeInGroupByInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="SqlTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="Struts2ModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="StrutsInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="StrutsTilesInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="StrutsValidatorFormInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="StrutsValidatorInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="TaglibDomModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="TelReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="UnparsedCustomBeanInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="UnresolvedMessageChannel" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="UseJBColor" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="UtilSchemaInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="ValidatorConfigModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="ValidatorModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="WebProperties" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="WebWarnings" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="WebflowConfigModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="WebflowModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="WebflowSetupInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+ <inspection_tool class="dependsOnMethodTestNG" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="groupsTestNG" enabled="false" level="WARNING" enabled_by_default="false">
+ <option name="groups">
+ <value>
+ <list size="0" />
+ </value>
+ </option>
+ </inspection_tool>
+ </profile>
+</component> \ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..3b31283
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+ <settings>
+ <option name="PROJECT_PROFILE" value="Project Default" />
+ <option name="USE_PROJECT_PROFILE" value="true" />
+ <version value="1.0" />
+ </settings>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__com_google_android_android_4_1_1_4.xml b/.idea/libraries/Maven__com_google_android_android_4_1_1_4.xml
new file mode 100644
index 0000000..fca0234
--- /dev/null
+++ b/.idea/libraries/Maven__com_google_android_android_4_1_1_4.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: com.google.android:android:4.1.1.4">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/com/google/android/android/4.1.1.4/android-4.1.1.4.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/com/google/android/android/4.1.1.4/android-4.1.1.4-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/com/google/android/android/4.1.1.4/android-4.1.1.4-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__commons_codec_commons_codec_1_3.xml b/.idea/libraries/Maven__commons_codec_commons_codec_1_3.xml
new file mode 100644
index 0000000..3688001
--- /dev/null
+++ b/.idea/libraries/Maven__commons_codec_commons_codec_1_3.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: commons-codec:commons-codec:1.3">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/commons-codec/commons-codec/1.3/commons-codec-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/commons-codec/commons-codec/1.3/commons-codec-1.3-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/commons-codec/commons-codec/1.3/commons-codec-1.3-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml b/.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml
new file mode 100644
index 0000000..b770f56
--- /dev/null
+++ b/.idea/libraries/Maven__commons_logging_commons_logging_1_1_1.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: commons-logging:commons-logging:1.1.1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__junit_junit_4_8_1.xml b/.idea/libraries/Maven__junit_junit_4_8_1.xml
new file mode 100644
index 0000000..bb76199
--- /dev/null
+++ b/.idea/libraries/Maven__junit_junit_4_8_1.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: junit:junit:4.8.1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.8.1/junit-4.8.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.8.1/junit-4.8.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.8.1/junit-4.8.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__mockit_jmockit_0_999_3.xml b/.idea/libraries/Maven__mockit_jmockit_0_999_3.xml
new file mode 100644
index 0000000..99058c9
--- /dev/null
+++ b/.idea/libraries/Maven__mockit_jmockit_0_999_3.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: mockit:jmockit:0.999.3">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/mockit/jmockit/0.999.3/jmockit-0.999.3.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/mockit/jmockit/0.999.3/jmockit-0.999.3-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/mockit/jmockit/0.999.3/jmockit-0.999.3-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__org_apache_httpcomponents_httpclient_4_0_1.xml b/.idea/libraries/Maven__org_apache_httpcomponents_httpclient_4_0_1.xml
new file mode 100644
index 0000000..a9ca4c6
--- /dev/null
+++ b/.idea/libraries/Maven__org_apache_httpcomponents_httpclient_4_0_1.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.apache.httpcomponents:httpclient:4.0.1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__org_apache_httpcomponents_httpcore_4_0_1.xml b/.idea/libraries/Maven__org_apache_httpcomponents_httpcore_4_0_1.xml
new file mode 100644
index 0000000..eee5c06
--- /dev/null
+++ b/.idea/libraries/Maven__org_apache_httpcomponents_httpcore_4_0_1.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.apache.httpcomponents:httpcore:4.0.1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__org_json_json_20080701.xml b/.idea/libraries/Maven__org_json_json_20080701.xml
new file mode 100644
index 0000000..b86a8bf
--- /dev/null
+++ b/.idea/libraries/Maven__org_json_json_20080701.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.json:json:20080701">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/json/json/20080701/json-20080701.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/json/json/20080701/json-20080701-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/json/json/20080701/json-20080701-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__org_khronos_opengl_api_gl1_1_android_2_1_r1.xml b/.idea/libraries/Maven__org_khronos_opengl_api_gl1_1_android_2_1_r1.xml
new file mode 100644
index 0000000..2fe6214
--- /dev/null
+++ b/.idea/libraries/Maven__org_khronos_opengl_api_gl1_1_android_2_1_r1.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.khronos:opengl-api:gl1.1-android-2.1_r1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/khronos/opengl-api/gl1.1-android-2.1_r1/opengl-api-gl1.1-android-2.1_r1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/khronos/opengl-api/gl1.1-android-2.1_r1/opengl-api-gl1.1-android-2.1_r1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/khronos/opengl-api/gl1.1-android-2.1_r1/opengl-api-gl1.1-android-2.1_r1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__xerces_xmlParserAPIs_2_6_2.xml b/.idea/libraries/Maven__xerces_xmlParserAPIs_2_6_2.xml
new file mode 100644
index 0000000..96cae4c
--- /dev/null
+++ b/.idea/libraries/Maven__xerces_xmlParserAPIs_2_6_2.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: xerces:xmlParserAPIs:2.6.2">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/xerces/xmlParserAPIs/2.6.2/xmlParserAPIs-2.6.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/xerces/xmlParserAPIs/2.6.2/xmlParserAPIs-2.6.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/xerces/xmlParserAPIs/2.6.2/xmlParserAPIs-2.6.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/Maven__xpp3_xpp3_1_1_4c.xml b/.idea/libraries/Maven__xpp3_xpp3_1_1_4c.xml
new file mode 100644
index 0000000..26694b3
--- /dev/null
+++ b/.idea/libraries/Maven__xpp3_xpp3_1_1_4c.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: xpp3:xpp3:1.1.4c">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/xpp3/xpp3/1.1.4c/xpp3-1.1.4c.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/xpp3/xpp3/1.1.4c/xpp3-1.1.4c-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/xpp3/xpp3/1.1.4c/xpp3-1.1.4c-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..689ddbb
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
+ <component name="FrameworkDetectionExcludesConfiguration">
+ <file type="android" url="file://$PROJECT_DIR$/AndroidPlot-Core/src/AndroidManifest.xml" />
+ <file type="android" url="file://$PROJECT_DIR$/Examples/DemoApp" />
+ <file type="android" url="file://$PROJECT_DIR$/Tests/AndroidManifest.xml" />
+ </component>
+ <component name="IdProvider" IDEtalkID="96A70927FCB4C9DA89E754C51C667B37" />
+ <component name="JavadocGenerationManager">
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="OPTION_SCOPE" value="protected" />
+ <option name="OPTION_HIERARCHY" value="true" />
+ <option name="OPTION_NAVIGATOR" value="true" />
+ <option name="OPTION_INDEX" value="true" />
+ <option name="OPTION_SEPARATE_INDEX" value="true" />
+ <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+ <option name="OPTION_DEPRECATED_LIST" value="true" />
+ <option name="OTHER_OPTIONS" value="" />
+ <option name="HEAP_SIZE" />
+ <option name="LOCALE" />
+ <option name="OPEN_IN_BROWSER" value="true" />
+ </component>
+ <component name="MavenProjectsManager">
+ <option name="originalFiles">
+ <list>
+ <option value="$PROJECT_DIR$/AndroidPlot-Lib/pom.xml" />
+ <option value="$PROJECT_DIR$/pom.xml" />
+ <option value="$PROJECT_DIR$/Examples/DemoApp/pom.xml" />
+ </list>
+ </option>
+ </component>
+ <component name="ProjectResources">
+ <default-html-doctype>http://www.w3.org/1999/xhtml</default-html-doctype>
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="Maven Android 4.1 Platform" project-jdk-type="Android SDK">
+ <output url="file://$PROJECT_DIR$/out" />
+ </component>
+ <component name="RegexUtilComponent" text="1900-01-01 2007/08/13 1900.01.01 1900 01 01 1900-01.01 1900 13 01 1900 02 31" flags="0" regex="(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])" mode="0" />
+ <component name="SvnBranchConfigurationManager">
+ <option name="myConfigurationMap">
+ <map>
+ <entry key="$PROJECT_DIR$">
+ <value>
+ <SvnBranchConfiguration>
+ <option name="branchUrls">
+ <list>
+ <option value="https://bridalveil.jira.com/svn/ANDROIDPLOT/branches" />
+ <option value="https://bridalveil.jira.com/svn/ANDROIDPLOT/tags" />
+ </list>
+ </option>
+ <option name="trunkUrl" value="https://bridalveil.jira.com/svn/ANDROIDPLOT/trunk" />
+ </SvnBranchConfiguration>
+ </value>
+ </entry>
+ <entry key="$PROJECT_DIR$/AndroidPlot-Core">
+ <value>
+ <SvnBranchConfiguration>
+ <option name="branchUrls">
+ <list>
+ <option value="https://bridalveil.jira.com/svn/ANDROIDPLOT/branches" />
+ <option value="https://bridalveil.jira.com/svn/ANDROIDPLOT/tags" />
+ </list>
+ </option>
+ <option name="trunkUrl" value="https://bridalveil.jira.com/svn/ANDROIDPLOT/trunk" />
+ </SvnBranchConfiguration>
+ </value>
+ </entry>
+ <entry key="$PROJECT_DIR$/Examples/DemoApp">
+ <value>
+ <SvnBranchConfiguration>
+ <option name="trunkUrl" value="https://androidplot.jira.com/svn/ANDROIDPLOT/trunk/Examples/DemoApp" />
+ </SvnBranchConfiguration>
+ </value>
+ </entry>
+ <entry key="$PROJECT_DIR$/Examples/OrientationSensorExample">
+ <value>
+ <SvnBranchConfiguration>
+ <option name="branchUrls">
+ <list>
+ <option value="https://bridalveil.jira.com/svn/ANDROIDPLOT/branches" />
+ <option value="https://bridalveil.jira.com/svn/ANDROIDPLOT/tags" />
+ </list>
+ </option>
+ <option name="trunkUrl" value="https://bridalveil.jira.com/svn/ANDROIDPLOT/trunk" />
+ </SvnBranchConfiguration>
+ </value>
+ </entry>
+ </map>
+ </option>
+ <option name="mySupportsUserInfoFilter" value="true" />
+ </component>
+ <component name="WebServicesPlugin" addRequiredLibraries="true" />
+</project>
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..a79b83b
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/Examples/DemoApp/DemoApp.iml" filepath="$PROJECT_DIR$/Examples/DemoApp/DemoApp.iml" />
+ <module fileurl="file://$PROJECT_DIR$/androidplot.iml" filepath="$PROJECT_DIR$/androidplot.iml" />
+ <module fileurl="file://$PROJECT_DIR$/AndroidPlot-Core/androidplot-core.iml" filepath="$PROJECT_DIR$/AndroidPlot-Core/androidplot-core.iml" />
+ </modules>
+ </component>
+</project>
+
diff --git a/.idea/runConfigurations/DemoApp.xml b/.idea/runConfigurations/DemoApp.xml
new file mode 100644
index 0000000..733ea80
--- /dev/null
+++ b/.idea/runConfigurations/DemoApp.xml
@@ -0,0 +1,22 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration default="false" name="DemoApp" type="AndroidRunConfigurationType" factoryName="Android Application">
+ <module name="DemoApp" />
+ <option name="ACTIVITY_CLASS" value="" />
+ <option name="MODE" value="default_activity" />
+ <option name="DEPLOY" value="true" />
+ <option name="TARGET_SELECTION_MODE" value="USB_DEVICE" />
+ <option name="PREFERRED_AVD" value="" />
+ <option name="USE_COMMAND_LINE" value="true" />
+ <option name="COMMAND_LINE" value="" />
+ <option name="WIPE_USER_DATA" value="false" />
+ <option name="DISABLE_BOOT_ANIMATION" value="false" />
+ <option name="NETWORK_SPEED" value="full" />
+ <option name="NETWORK_LATENCY" value="none" />
+ <option name="CLEAR_LOGCAT" value="true" />
+ <RunnerSettings RunnerId="AndroidDebugRunner" />
+ <RunnerSettings RunnerId="Run" />
+ <ConfigurationWrapper RunnerId="AndroidDebugRunner" />
+ <ConfigurationWrapper RunnerId="Run" />
+ <method />
+ </configuration>
+</component> \ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..1e7cce4
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Palette2">
+ <group name="Swing">
+ <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+ </item>
+ <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+ <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+ <initial-values>
+ <property name="text" value="Button" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="RadioButton" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="CheckBox" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="Label" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+ <preferred-size width="-1" height="20" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+ </item>
+ </group>
+ </component>
+</project>
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..ab55cf1
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project>
+
diff --git a/AndroidPlot-Core/.classpath b/AndroidPlot-Core/.classpath
new file mode 100644
index 0000000..dcd1f46
--- /dev/null
+++ b/AndroidPlot-Core/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" output="target/classes" path="src/main/java"/>
+ <classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/AndroidPlot-Core/.project b/AndroidPlot-Core/.project
new file mode 100644
index 0000000..1cb89ea
--- /dev/null
+++ b/AndroidPlot-Core/.project
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>AndroidPlot-Core</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.maven.ide.eclipse.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ <nature>org.maven.ide.eclipse.maven2Nature</nature>
+ </natures>
+</projectDescription>
diff --git a/AndroidPlot-Core/androidplot-core.iml b/AndroidPlot-Core/androidplot-core.iml
new file mode 100644
index 0000000..53f8e0a
--- /dev/null
+++ b/AndroidPlot-Core/androidplot-core.iml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" scope="TEST" name="Maven: mockit:jmockit:0.999.3" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.8.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.android:android:4.1.1.4" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.0.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.0.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.3" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.khronos:opengl-api:gl1.1-android-2.1_r1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: xerces:xmlParserAPIs:2.6.2" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: xpp3:xpp3:1.1.4c" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.json:json:20080701" level="project" />
+ </component>
+</module>
+
diff --git a/AndroidPlot-Core/pom.xml b/AndroidPlot-Core/pom.xml
new file mode 100644
index 0000000..ffdb40b
--- /dev/null
+++ b/AndroidPlot-Core/pom.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.androidplot</groupId>
+ <artifactId>androidplot</artifactId>
+ <version>0.6.0</version>
+ </parent>
+ <artifactId>androidplot-core</artifactId>
+ <!--<version>${applicationVersion}</version>-->
+ <name>AndroidPlot-Core</name>
+ <description>AndroidPlot core library</description>
+
+ <profiles>
+ <profile>
+ <id>default</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <properties>
+ <path.to.rt.jar>${java.home}/lib/rt.jar</path.to.rt.jar>
+ </properties>
+ </profile>
+ <profile>
+ <id>osx</id>
+ <activation>
+ <os>
+ <family>mac</family>
+ </os>
+ </activation>
+ <properties>
+ <path.to.rt.jar>${java.home}/../Classes/classes.jar</path.to.rt.jar>
+ </properties>
+ </profile>
+ </profiles>
+
+ <!--<repositories>
+ <repository>
+ <id>central</id>
+ <name>Maven Central</name>
+ <url>http://repo1.maven.org/maven2/</url>
+ </repository></repositories>
+-->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.4</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <show>public</show>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.2</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+
+
+
+ <plugin>
+ <groupId>com.pyx4me</groupId>
+ <artifactId>proguard-maven-plugin</artifactId>
+ <version>2.0.4</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>proguard</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <obfuscate>true</obfuscate>
+ <options>
+ <option>-allowaccessmodification</option>
+ <option>-keep public class * {public *;}</option>
+ <option>-keepclassmembers enum * {public static **[] values();public static ** valueOf(java.lang.String);}</option>
+ <option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod</option>
+ </options>
+ <!--<injar>${project.build.finalName}.jar</injar>
+ <outjar>obfuscated/${project.build.finalName}-release.jar</outjar>-->
+ <outputDirectory>${project.build.directory}</outputDirectory>
+ <libs>
+ <!--<lib>${java.home}/lib/rt.jar</lib>-->
+ <lib>${path.to.rt.jar}</lib>
+ </libs>
+ <addMavenDescriptor>false</addMavenDescriptor>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.7</version>
+ <inherited>false</inherited>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>mockit</groupId>
+ <artifactId>jmockit</artifactId>
+ <version>0.999.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ </dependency>
+ <!--<dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <version>4.1.1.4</version>
+ <scope>provided</scope>
+ </dependency>-->
+ </dependencies>
+</project> \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/LineRegion.java b/AndroidPlot-Core/src/main/java/com/androidplot/LineRegion.java
new file mode 100644
index 0000000..0f53711
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/LineRegion.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.androidplot;
+
+/**
+ * A one dimensional region represented by a starting and ending value.
+ */
+public class LineRegion {
+ private Number minVal;
+ private Number maxVal;
+
+ public LineRegion(Number val1, Number v2) {
+ if (val1.doubleValue() < v2.doubleValue()) {
+ this.setMinVal(val1);
+ this.setMaxVal(v2);
+ } else {
+ this.setMinVal(v2);
+ this.setMaxVal(val1);
+ }
+ }
+
+ public static Number measure(Number val1, Number val2) {
+ return new LineRegion(val1, val2).length();
+ }
+
+ public Number length() {
+ return maxVal.doubleValue() - minVal.doubleValue();
+ }
+
+ /**
+ * Tests whether a value is within the given range
+ * @param value
+ * @return
+ */
+ public boolean contains(Number value) {
+ return value.doubleValue() >= minVal.doubleValue() && value.doubleValue() <= maxVal.doubleValue();
+ }
+
+ public boolean intersects(LineRegion lineRegion) {
+ return intersects(lineRegion.getMinVal(), lineRegion.getMaxVal());
+ }
+
+ /**
+ * Tests whether this segment intersects another
+ * @param line2Min
+ * @param line2Max
+ * @return
+ */
+ public boolean intersects(Number line2Min, Number line2Max) {
+
+ //double l1min = getMinVal() == null ? Double.NEGATIVE_INFINITY : getMinVal().doubleValue();
+ //double l1max = getMaxVal() == null ? Double.POSITIVE_INFINITY : getMaxVal().doubleValue();
+
+ //double l2min = line2Min == null ? Double.NEGATIVE_INFINITY : line2Min.doubleValue();
+ //double l2max = line2Max == null ? Double.POSITIVE_INFINITY : line2Max.doubleValue();
+
+
+ // is this line completely within line2?
+ if(line2Min.doubleValue() <= this.minVal.doubleValue() && line2Max.doubleValue() >= this.maxVal.doubleValue()) {
+ return true;
+ // is line1 partially within line2
+ } else return contains(line2Min) || contains(line2Max);
+ }
+
+ public Number getMinVal() {
+ return minVal;
+ }
+
+ public void setMinVal(Number minVal) {
+ if(minVal == null) {
+ throw new NullPointerException("Region values can never be null.");
+ }
+ this.minVal = minVal;
+ }
+
+ public Number getMaxVal() {
+ return maxVal;
+ }
+
+ public void setMaxVal(Number maxVal) {
+ if(maxVal == null) {
+ throw new NullPointerException("Region values can never be null.");
+ }
+ this.maxVal = maxVal;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/Plot.java b/AndroidPlot-Core/src/main/java/com/androidplot/Plot.java
new file mode 100644
index 0000000..e47e568
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/Plot.java
@@ -0,0 +1,868 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot;
+
+import android.content.Context;
+import android.graphics.*;
+import android.os.Build;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.*;
+import com.androidplot.ui.Formatter;
+import com.androidplot.ui.TextOrientationType;
+import com.androidplot.ui.widget.TextLabelWidget;
+import com.androidplot.ui.widget.Widget;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.util.Configurator;
+import com.androidplot.util.DisplayDimensions;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.ui.XLayoutStyle;
+import com.androidplot.ui.YLayoutStyle;
+
+import java.util.*;
+
+/**
+ * Base class for all other Plot implementations..
+ */
+public abstract class Plot<SeriesType extends Series, FormatterType extends Formatter, RendererType extends SeriesRenderer>
+ extends View implements Resizable{
+ private static final String TAG = Plot.class.getName();
+ private static final String XML_ATTR_PREFIX = "androidplot";
+
+ private static final String ATTR_TITLE = "title";
+ private static final String ATTR_RENDER_MODE = "renderMode";
+
+ public DisplayDimensions getDisplayDimensions() {
+ return displayDims;
+ }
+
+ public enum BorderStyle {
+ ROUNDED,
+ SQUARE,
+ NONE
+ }
+
+ /**
+ * The RenderMode used by a Plot to draw it's self onto the screen. The RenderMode can be set
+ * in two ways.
+ *
+ * In an xml layout:
+ *
+ * <code>
+ * <com.androidplot.xy.XYPlot
+ * android:id="@+id/mySimpleXYPlot"
+ * android:layout_width="fill_parent"
+ * android:layout_height="fill_parent"
+ * title="@string/sxy_title"
+ * renderMode="useBackgroundThread"/>
+ * </code>
+ *
+ * Programatically:
+ *
+ * <code>
+ * XYPlot myPlot = new XYPlot(context "MyPlot", Plot.RenderMode.USE_MAIN_THREAD);
+ * </code>
+ *
+ * A Plot's RenderMode cannot be changed after the plot has been initialized.
+ * @since 0.5.1
+ */
+ public enum RenderMode {
+ /**
+ * Use a second thread and an off-screen buffer to do drawing. This is the preferred method
+ * of drawing dynamic data and static data that consists of a large number of points. This mode
+ * provides more efficient CPU utilization at the cost of increased memory usage. As of
+ * version 0.5.1 this is the default RenderMode.
+ *
+ * XML value: use_background_thread
+ * @since 0.5.1
+ */
+ USE_BACKGROUND_THREAD,
+
+ /**
+ * Do everything in the primary thread. This is the preferred method of drawing static charts
+ * and dynamic data that consists of a small number of points. This mode uses less memory at
+ * the cost of poor CPU utilization.
+ *
+ * XML value: use_main_thread
+ * @since 0.5.1
+ */
+ USE_MAIN_THREAD
+ }
+ private BoxModel boxModel = new BoxModel(3, 3, 3, 3, 3, 3, 3, 3);
+ private BorderStyle borderStyle = Plot.BorderStyle.SQUARE;
+ private float borderRadiusX = 15;
+ private float borderRadiusY = 15;
+ private boolean drawBorderEnabled = true;
+ private Paint borderPaint;
+ private Paint backgroundPaint;
+ private LayoutManager layoutManager;
+ private TextLabelWidget titleWidget;
+ private DisplayDimensions displayDims = new DisplayDimensions();
+ private RenderMode renderMode = RenderMode.USE_MAIN_THREAD;
+ private final BufferedCanvas pingPong = new BufferedCanvas();
+
+ // used to get rid of flickering when drawing offScreenBitmap to the visible Canvas.
+ private final Object renderSynch = new Object();
+
+ /**
+ * Used for caching renderer instances. Note that once a renderer is initialized it remains initialized
+ * for the life of the application; does not and should not be destroyed until the application exits.
+ */
+ private LinkedList<RendererType> renderers;
+
+ /**
+ * Associates lists series and formatter pairs with the class of the Renderer used to render them.
+ */
+ private LinkedHashMap<Class, SeriesAndFormatterList<SeriesType,FormatterType>> seriesRegistry;
+
+ private final ArrayList<PlotListener> listeners;
+
+ private Thread renderThread;
+ private boolean keepRunning = false;
+ private boolean isIdle = true;
+
+ {
+ listeners = new ArrayList<PlotListener>();
+ seriesRegistry = new LinkedHashMap<Class, SeriesAndFormatterList<SeriesType,FormatterType>>();
+ renderers = new LinkedList<RendererType>();
+ borderPaint = new Paint();
+ borderPaint.setColor(Color.rgb(150, 150, 150));
+ borderPaint.setStyle(Paint.Style.STROKE);
+ borderPaint.setStrokeWidth(1.0f);
+ borderPaint.setAntiAlias(true);
+ backgroundPaint = new Paint();
+ backgroundPaint.setColor(Color.DKGRAY);
+ backgroundPaint.setStyle(Paint.Style.FILL);
+ }
+
+
+ /**
+ * Any rendering that utilizes a buffer from this class should synchronize rendering on the instance of this class
+ * that is being used.
+ */
+ private class BufferedCanvas {
+ private volatile Bitmap bgBuffer; // all drawing is done on this buffer.
+ private volatile Bitmap fgBuffer;
+ private Canvas canvas = new Canvas();
+
+ /**
+ * Call this method once drawing on a Canvas retrieved by {@link #getCanvas()} to mark
+ * the buffer as fully rendered. Failure to call this method will result in nothing being drawn.
+ */
+ public synchronized void swap() {
+ Bitmap tmp = bgBuffer;
+ bgBuffer = fgBuffer;
+ fgBuffer = tmp;
+ }
+
+ public synchronized void resize(int h, int w) {
+ if (w <= 0 || h <= 0) {
+ bgBuffer = null;
+ fgBuffer = null;
+ } else {
+ bgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
+ fgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
+ }
+ }
+
+
+ /**
+ * Get a Canvas for drawing. Actual drawing should be synchronized on the instance
+ * of BufferedCanvas being used.
+ * @return The Canvas instance to draw onto. Returns null if drawing buffers have not
+ * been initialized a la {@link #resize(int, int)}.
+ */
+ public synchronized Canvas getCanvas() {
+ if(bgBuffer != null) {
+ canvas.setBitmap(bgBuffer);
+ return canvas;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return The most recent fully rendered Bitmsp
+ */
+ public Bitmap getBitmap() {
+ return fgBuffer;
+ }
+ }
+
+ /**
+ * Convenience constructor - wraps {@link #Plot(android.content.Context, String, com.androidplot.Plot.RenderMode)}.
+ * RenderMode is set to {@link RenderMode#USE_BACKGROUND_THREAD}.
+ * @param context
+ * @param title The display title of this Plot.
+ */
+ public Plot(Context context, String title) {
+ this(context, title, RenderMode.USE_MAIN_THREAD);
+ }
+
+ /**
+ * Used for programmatic instantiation.
+ * @param context
+ * @param title The display title of this Plot.
+ */
+ public Plot(Context context, String title, RenderMode mode) {
+ super(context);
+ this.renderMode = mode;
+ init(null, null);
+ setTitle(title);
+ }
+
+
+ /**
+ * Required by super-class. Extending class' implementations should add
+ * the following code immediately before exiting to ensure that loadAttrs
+ * is called only once by the derived class:
+ * <code>
+ * if(getClass().equals(DerivedPlot.class) {
+ * loadAttrs(context, attrs);
+ * }
+ * </code>
+ *
+ * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet)}
+ * for an example.
+ * @param context
+ * @param attrs
+ */
+ public Plot(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ /**
+ * Required by super-class. Extending class' implementations should add
+ * the following code immediately before exiting to ensure that loadAttrs
+ * is called only once by the derived class:
+ * <code>
+ * if(getClass().equals(DerivedPlot.class) {
+ * loadAttrs(context, attrs);
+ * }
+ * </code>
+ *
+ * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet, int)}
+ * for an example.
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public Plot(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context, attrs);
+ }
+
+ /**
+ * Can be overridden by derived classes to control hardware acceleration state.
+ * Note that this setting is only used on Honeycomb and later environments.
+ * @return True if hardware acceleration is allowed, false otherwise.
+ * @since 0.5.1
+ */
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ protected boolean isHwAccelerationSupported() {
+ return false;
+ }
+
+ /**
+ * Sets the render mode used by the Plot.
+ * WARNING: This method is not currently designed for general use outside of Configurator.
+ * Attempting to reassign the render mode at runtime will result in unexpected behavior.
+ * @param mode
+ */
+ public void setRenderMode(RenderMode mode) {
+ this.renderMode = mode;
+ }
+
+ /**
+ * Concrete implementations should do any final setup / initialization
+ * here. Immediately following this method's invocation, AndroidPlot assumes
+ * that the Plot instance is ready for final configuration via the Configurator.
+ */
+ protected abstract void onPreInit();
+
+
+ private void init(Context context, AttributeSet attrs) {
+ PixelUtils.init(getContext());
+ layoutManager = new LayoutManager();
+ titleWidget = new TextLabelWidget(layoutManager, new SizeMetrics(25,
+ SizeLayoutType.ABSOLUTE, 100,
+ SizeLayoutType.ABSOLUTE),
+ TextOrientationType.HORIZONTAL);
+ titleWidget.position(0, XLayoutStyle.RELATIVE_TO_CENTER, 0,
+ YLayoutStyle.ABSOLUTE_FROM_TOP, AnchorPosition.TOP_MIDDLE);
+
+ onPreInit();
+ // make sure the title widget is always the topmost widget:
+ layoutManager.moveToTop(titleWidget);
+ if(context != null && attrs != null) {
+ loadAttrs(attrs);
+ }
+
+ layoutManager.onPostInit();
+ Log.d(TAG, "AndroidPlot RenderMode: " + renderMode);
+ if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
+ renderThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ keepRunning = true;
+ while (keepRunning) {
+ isIdle = false;
+ synchronized (pingPong) {
+ Canvas c = pingPong.getCanvas();
+ renderOnCanvas(c);
+ pingPong.swap();
+ }
+ synchronized (renderSynch) {
+ postInvalidate();
+ // prevent this thread from becoming an orphan
+ // after the view is destroyed
+ if (keepRunning) {
+ try {
+ renderSynch.wait();
+ } catch (InterruptedException e) {
+ keepRunning = false;
+ }
+ }
+ }
+ }
+ System.out.println("AndroidPlot render thread finished.");
+ }
+ });
+ }
+ }
+
+ /**
+ * Parse XML Attributes. Should only be called once and at the end of the base class constructor.
+ *
+ * @param attrs
+ */
+ private void loadAttrs(AttributeSet attrs) {
+
+ if (attrs != null) {
+ // filter out androidplot prefixed attrs:
+ HashMap<String, String> attrHash = new HashMap<String, String>();
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ String attrName = attrs.getAttributeName(i);
+
+ // case insensitive check to see if this attr begins with our prefix:
+ if (attrName.toUpperCase().startsWith(XML_ATTR_PREFIX.toUpperCase())) {
+ attrHash.put(attrName.substring(XML_ATTR_PREFIX.length() + 1), attrs.getAttributeValue(i));
+ }
+ }
+ Configurator.configure(getContext(), this, attrHash);
+ }
+ }
+
+ public RenderMode getRenderMode() {
+ return renderMode;
+ }
+
+ public synchronized boolean addListener(PlotListener listener) {
+ return !listeners.contains(listener) && listeners.add(listener);
+ }
+
+ public synchronized boolean removeListener(PlotListener listener) {
+ return listeners.remove(listener);
+ }
+
+ protected void notifyListenersBeforeDraw(Canvas canvas) {
+ for (PlotListener listener : listeners) {
+ listener.onBeforeDraw(this, canvas);
+ }
+ }
+
+ protected void notifyListenersAfterDraw(Canvas canvas) {
+ for (PlotListener listener : listeners) {
+ listener.onAfterDraw(this, canvas);
+ }
+ }
+
+ /**
+ * @param series
+ */
+ public synchronized boolean addSeries(SeriesType series, FormatterType formatter) {
+ Class rendererClass = formatter.getRendererClass();
+ SeriesAndFormatterList<SeriesType, FormatterType> sfList = seriesRegistry.get(rendererClass);
+
+ // if there is no list for this renderer type, we need to getInstance one:
+ if(sfList == null) {
+ // make sure there is not already an instance of this renderer available:
+ if(getRenderer(rendererClass) == null) {
+ renderers.add((RendererType) formatter.getRendererInstance(this));
+ }
+ sfList = new SeriesAndFormatterList<SeriesType,FormatterType>();
+ seriesRegistry.put(rendererClass, sfList);
+ }
+
+ // if this series implements PlotListener, add it as a listener:
+ if(series instanceof PlotListener) {
+ addListener((PlotListener)series);
+ }
+
+ // do nothing if this series already associated with the renderer:
+ if(sfList.contains(series)) {
+ return false;
+ } else {
+ sfList.add(series, formatter);
+ return true;
+ }
+ }
+
+ public synchronized boolean removeSeries(SeriesType series, Class rendererClass) {
+ boolean result = seriesRegistry.get(rendererClass).remove(series);
+ if(seriesRegistry.get(rendererClass).size() <= 0) {
+ seriesRegistry.remove(rendererClass);
+ }
+
+ // if series implements PlotListener, remove it from listeners:
+ if(series instanceof PlotListener) {
+ removeListener((PlotListener) series);
+ }
+ return result;
+ }
+
+ /**
+ * Remove all occorrences of series from all renderers
+ * @param series
+ */
+ public synchronized void removeSeries(SeriesType series) {
+
+ // remove all occurrences of series from all renderers:
+ for(Class rendererClass : seriesRegistry.keySet()) {
+ seriesRegistry.get(rendererClass).remove(series);
+ }
+
+ // remove empty SeriesAndFormatterList instances from the registry:
+ for(Iterator<SeriesAndFormatterList<SeriesType,FormatterType>> it = seriesRegistry.values().iterator(); it.hasNext();) {
+ if(it.next().size() <= 0) {
+ it.remove();
+ }
+ }
+
+ // if series implements PlotListener, remove it from listeners:
+ if (series instanceof PlotListener) {
+ removeListener((PlotListener) series);
+ }
+ }
+
+ /**
+ * Remove all series from all renderers
+ */
+ public void clear() {
+ for(Iterator<SeriesAndFormatterList<SeriesType,FormatterType>> it = seriesRegistry.values().iterator(); it.hasNext();) {
+ it.next();
+ it.remove();
+ }
+ }
+
+ public boolean isEmpty() {
+ return seriesRegistry.isEmpty();
+ }
+
+ public FormatterType getFormatter(SeriesType series, Class rendererClass) {
+ return seriesRegistry.get(rendererClass).getFormatter(series);
+ }
+
+ public SeriesAndFormatterList<SeriesType,FormatterType> getSeriesAndFormatterListForRenderer(Class rendererClass) {
+ return seriesRegistry.get(rendererClass);
+ }
+
+ /**
+ * Returns a list of all series assigned to the various renderers within the Plot.
+ * The returned List will contain no duplicates.
+ * @return
+ */
+ public Set<SeriesType> getSeriesSet() {
+ Set<SeriesType> seriesSet = new LinkedHashSet<SeriesType>();
+ for (SeriesRenderer renderer : getRendererList()) {
+ List<SeriesType> seriesList = getSeriesListForRenderer(renderer.getClass());
+ if (seriesList != null) {
+ for (SeriesType series : seriesList) {
+ seriesSet.add(series);
+ }
+ }
+ }
+ return seriesSet;
+ }
+
+ public List<SeriesType> getSeriesListForRenderer(Class rendererClass) {
+ SeriesAndFormatterList<SeriesType,FormatterType> lst = seriesRegistry.get(rendererClass);
+ if(lst == null) {
+ return null;
+ } else {
+ return lst.getSeriesList();
+ }
+ }
+
+ public RendererType getRenderer(Class rendererClass) {
+ for(RendererType renderer : renderers) {
+ if(renderer.getClass() == rendererClass) {
+ return renderer;
+ }
+ }
+ return null;
+ }
+
+ public List<RendererType> getRendererList() {
+ return renderers;
+ }
+
+ public void setMarkupEnabled(boolean enabled) {
+ this.layoutManager.setMarkupEnabled(enabled);
+ }
+
+ /**
+ * Causes the plot to be redrawn.
+ * @since 0.5.1
+ */
+ public void redraw() {
+
+ if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
+
+ // only enter synchronized block if the call is expected to block OR
+ // if the render thread is idle, so we know that we won't have to wait to
+ // obtain a lock.
+ if (isIdle) {
+ synchronized (renderSynch) {
+ renderSynch.notify();
+ }
+ }
+ } else if(renderMode == RenderMode.USE_MAIN_THREAD) {
+
+ // are we on the UI thread?
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ invalidate();
+ } else {
+ postInvalidate();
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode);
+ }
+ }
+
+ @Override
+ public synchronized void layout(final DisplayDimensions dims) {
+ displayDims = dims;
+ layoutManager.layout(displayDims);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ synchronized(renderSynch) {
+ keepRunning = false;
+ renderSynch.notify();
+ }
+ }
+
+
+ @Override
+ protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) {
+
+ // update pixel conversion values
+ PixelUtils.init(getContext());
+
+ // disable hardware acceleration if it's not explicitly supported
+ // by the current Plot implementation. this check only applies to
+ // honeycomb and later environments.
+ if (Build.VERSION.SDK_INT >= 11) {
+ if (!isHwAccelerationSupported() && isHardwareAccelerated()) {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+ }
+
+ // pingPong is only used in background rendering mode.
+ if(renderMode == RenderMode.USE_BACKGROUND_THREAD) {
+ pingPong.resize(h, w);
+ }
+
+ RectF cRect = new RectF(0, 0, w, h);
+ RectF mRect = boxModel.getMarginatedRect(cRect);
+ RectF pRect = boxModel.getPaddedRect(mRect);
+
+ layout(new DisplayDimensions(cRect, mRect, pRect));
+ super.onSizeChanged(w, h, oldw, oldh);
+ if(renderThread != null && !renderThread.isAlive()) {
+ renderThread.start();
+ }
+ }
+
+ /**
+ * Called whenever the plot needs to be drawn via the Handler, which invokes invalidate().
+ * Should never be called directly; use {@link #redraw()} instead.
+ * @param canvas
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
+ synchronized(pingPong) {
+ Bitmap bmp = pingPong.getBitmap();
+ if(bmp != null) {
+ canvas.drawBitmap(bmp, 0, 0, null);
+ }
+ }
+ } else if (renderMode == RenderMode.USE_MAIN_THREAD) {
+ renderOnCanvas(canvas);
+ } else {
+ throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode);
+ }
+ }
+
+ /**
+ * Renders the plot onto a canvas. Used by both main thread to draw directly
+ * onto the View's canvas as well as by background draw to render onto a
+ * Bitmap buffer. At the end of the day this is the main entry for a plot's
+ * "heavy lifting".
+ * @param canvas
+ */
+ protected synchronized void renderOnCanvas(Canvas canvas) {
+ try {
+ // any series interested in synchronizing with plot should
+ // implement PlotListener.onBeforeDraw(...) and do a read lock from within its
+ // invocation. This is the entry point into that call:
+ notifyListenersBeforeDraw(canvas);
+ try {
+ // need to completely erase what was on the canvas before redrawing, otherwise
+ // some odd aliasing artifacts begin to build up around the edges of aa'd entities
+ // over time.
+ canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
+ if (backgroundPaint != null) {
+ drawBackground(canvas, displayDims.marginatedRect);
+ }
+
+ layoutManager.draw(canvas);
+
+ if (getBorderPaint() != null) {
+ drawBorder(canvas, displayDims.marginatedRect);
+ }
+ } catch (PlotRenderException e) {
+ Log.e(TAG, "Exception while rendering Plot.", e);
+ e.printStackTrace();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while rendering Plot.", e);
+ }
+ } finally {
+ isIdle = true;
+ // any series interested in synchronizing with plot should
+ // implement PlotListener.onAfterDraw(...) and do a read unlock from within that
+ // invocation. This is the entry point for that invocation.
+ notifyListenersAfterDraw(canvas);
+ }
+ }
+
+
+ /**
+ * Sets the visual style of the plot's border.
+ * @param style
+ * @param radiusX Sets the X radius for BorderStyle.ROUNDED. Use null for all other styles.
+ * @param radiusY Sets the Y radius for BorderStyle.ROUNDED. Use null for all other styles.
+ */
+ public void setBorderStyle(BorderStyle style, Float radiusX, Float radiusY) {
+ if (style == Plot.BorderStyle.ROUNDED) {
+ if (radiusX == null || radiusY == null){
+ throw new IllegalArgumentException("radiusX and radiusY cannot be null when using BorderStyle.ROUNDED");
+ }
+ this.borderRadiusX = radiusX;
+ this.borderRadiusY = radiusY;
+ }
+ this.borderStyle = style;
+ }
+
+ /**
+ * Draws the plot's outer border.
+ * @param canvas
+ * @throws PlotRenderException
+ */
+ protected void drawBorder(Canvas canvas, RectF dims) {
+ switch (borderStyle) {
+ case ROUNDED:
+ canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, borderPaint);
+ break;
+ case SQUARE:
+ canvas.drawRect(dims, borderPaint);
+ break;
+ default:
+ }
+ }
+
+ protected void drawBackground(Canvas canvas, RectF dims) {
+ switch (borderStyle) {
+ case ROUNDED:
+ canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, backgroundPaint);
+ break;
+ case SQUARE:
+ canvas.drawRect(dims, backgroundPaint);
+ break;
+ default:
+ }
+ }
+
+ /**
+ *
+ * @return The displayed title of this Plot.
+ */
+ public String getTitle() {
+ return getTitleWidget().getText();
+ }
+
+ /**
+ *
+ * @param title The title to display on this Plot.
+ */
+ public void setTitle(String title) {
+ titleWidget.setText(title);
+ }
+
+ public LayoutManager getLayoutManager() {
+ return layoutManager;
+ }
+
+ public void setLayoutManager(LayoutManager layoutManager) {
+ this.layoutManager = layoutManager;
+ }
+
+ public TextLabelWidget getTitleWidget() {
+ return titleWidget;
+ }
+
+ public void setTitleWidget(TextLabelWidget titleWidget) {
+ this.titleWidget = titleWidget;
+ }
+
+ public Paint getBackgroundPaint() {
+ return backgroundPaint;
+ }
+
+ public void setBackgroundPaint(Paint backgroundPaint) {
+ this.backgroundPaint = backgroundPaint;
+ }
+
+ /**
+ * Convenience method - wraps the individual setMarginXXX methods into a single method.
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ */
+ public void setPlotMargins(float left, float top, float right, float bottom) {
+ setPlotMarginLeft(left);
+ setPlotMarginTop(top);
+ setPlotMarginRight(right);
+ setPlotMarginBottom(bottom);
+ }
+
+ /**
+ * Convenience method - wraps the individual setPaddingXXX methods into a single method.
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ */
+ public void setPlotPadding(float left, float top, float right, float bottom) {
+ setPlotPaddingLeft(left);
+ setPlotPaddingTop(top);
+ setPlotPaddingRight(right);
+ setPlotPaddingBottom(bottom);
+ }
+
+ public float getPlotMarginTop() {
+ return boxModel.getMarginTop();
+ }
+
+ public void setPlotMarginTop(float plotMarginTop) {
+ boxModel.setMarginTop(plotMarginTop);
+ }
+
+ public float getPlotMarginBottom() {
+ return boxModel.getMarginBottom();
+ }
+
+ public void setPlotMarginBottom(float plotMarginBottom) {
+ boxModel.setMarginBottom(plotMarginBottom);
+ }
+
+ public float getPlotMarginLeft() {
+ return boxModel.getMarginLeft();
+ }
+
+ public void setPlotMarginLeft(float plotMarginLeft) {
+ boxModel.setMarginLeft(plotMarginLeft);
+ }
+
+ public float getPlotMarginRight() {
+ return boxModel.getMarginRight();
+ }
+
+ public void setPlotMarginRight(float plotMarginRight) {
+ boxModel.setMarginRight(plotMarginRight);
+ }
+
+ public float getPlotPaddingTop() {
+ return boxModel.getPaddingTop();
+ }
+
+ public void setPlotPaddingTop(float plotPaddingTop) {
+ boxModel.setPaddingTop(plotPaddingTop);
+ }
+
+ public float getPlotPaddingBottom() {
+ return boxModel.getPaddingBottom();
+ }
+
+ public void setPlotPaddingBottom(float plotPaddingBottom) {
+ boxModel.setPaddingBottom(plotPaddingBottom);
+ }
+
+ public float getPlotPaddingLeft() {
+ return boxModel.getPaddingLeft();
+ }
+
+ public void setPlotPaddingLeft(float plotPaddingLeft) {
+ boxModel.setPaddingLeft(plotPaddingLeft);
+ }
+
+ public float getPlotPaddingRight() {
+ return boxModel.getPaddingRight();
+ }
+
+ public void setPlotPaddingRight(float plotPaddingRight) {
+ boxModel.setPaddingRight(plotPaddingRight);
+ }
+
+ public Paint getBorderPaint() {
+ return borderPaint;
+ }
+
+ /**
+ * Set's the paint used to draw the border. Note that this method
+ * copies borderPaint and set's the copy's Paint.Style attribute to
+ * Paint.Style.STROKE.
+ * @param borderPaint
+ */
+ public void setBorderPaint(Paint borderPaint) {
+ if(borderPaint == null) {
+ this.borderPaint = null;
+ } else {
+ this.borderPaint = new Paint(borderPaint);
+ this.borderPaint.setStyle(Paint.Style.STROKE);
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/PlotListener.java b/AndroidPlot-Core/src/main/java/com/androidplot/PlotListener.java
new file mode 100644
index 0000000..e622215
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/PlotListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot;
+
+import android.graphics.Canvas;
+
+/**
+ * Defines methods used for monitoring events generated by a Plot.
+ */
+public interface PlotListener {
+
+ /**
+ * Fired immediately before the Plot "source" is drawn onto canvas.
+ * Commonly used by implementing Series instances to activate a read
+ * lock on it's self in preparation for the Plot's imminent reading
+ * of that series.
+ * @param source
+ * @param canvas
+ */
+ public void onBeforeDraw(Plot source, Canvas canvas);
+
+ /**
+ * Fired immediately after the Plot "source" is drawn onto canvas.
+ * Just as onBeforeDraw(...) is commonly used by Series implementations
+ * to activate a read lock, this method is commonly used to release that
+ * same lock.
+ * @param source
+ * @param canvas
+ */
+ public void onAfterDraw(Plot source, Canvas canvas);
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/Series.java b/AndroidPlot-Core/src/main/java/com/androidplot/Series.java
new file mode 100644
index 0000000..7fc90f5
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/Series.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot;
+
+/**
+ * Base interface for all Series implementations
+ */
+public interface Series<T> {
+
+ /**
+ *
+ * @return The title of this Series.
+ */
+ public String getTitle();
+
+
+
+ // used primarily for synchronization. can also be used to hang a condition on updates.
+
+ /**
+ * Called whenever the plot initiates a read of a Series. In most cases this means that
+ * a complete read of the Series contents will proceed.
+ *//*
+ public void onReadBegin();
+
+ *//**
+ * Called when a Plot concludes reading of a Series.
+ *//*
+ public void onReadEnd();*/
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/exception/PlotRenderException.java b/AndroidPlot-Core/src/main/java/com/androidplot/exception/PlotRenderException.java
new file mode 100644
index 0000000..0515e2a
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/exception/PlotRenderException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.exception;
+
+public class PlotRenderException extends Exception {
+ public PlotRenderException(String message) {
+ super(message);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieChart.java b/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieChart.java
new file mode 100644
index 0000000..56f609d
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieChart.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.pie;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import com.androidplot.Plot;
+import com.androidplot.ui.AnchorPosition;
+import com.androidplot.ui.SizeLayoutType;
+import com.androidplot.ui.SizeMetrics;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.ui.XLayoutStyle;
+import com.androidplot.ui.YLayoutStyle;
+
+public class PieChart extends Plot<Segment, SegmentFormatter, PieRenderer> {
+
+ private static final int DEFAULT_PIE_WIDGET_H_DP = 18;
+ private static final int DEFAULT_PIE_WIDGET_W_DP = 10;
+
+ private static final int DEFAULT_PIE_WIDGET_Y_OFFSET_DP = 0;
+ private static final int DEFAULT_PIE_WIDGET_X_OFFSET_DP = 0;
+
+ public void setPieWidget(PieWidget pieWidget) {
+ this.pieWidget = pieWidget;
+ }
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private PieWidget pieWidget;
+
+ public PieChart(Context context, String title) {
+ super(context, title);
+ }
+
+ public PieChart(Context context, String title, RenderMode mode) {
+ super(context, title, mode);
+ }
+
+ public PieChart(Context context, AttributeSet attributes) {
+ super(context, attributes);
+ }
+
+ @Override
+ protected void onPreInit() {
+ pieWidget = new PieWidget(
+ getLayoutManager(),
+ this,
+ new SizeMetrics(
+ PixelUtils.dpToPix(DEFAULT_PIE_WIDGET_H_DP),
+ SizeLayoutType.FILL,
+ PixelUtils.dpToPix(DEFAULT_PIE_WIDGET_W_DP),
+ SizeLayoutType.FILL));
+
+ pieWidget.position(
+ PixelUtils.dpToPix(DEFAULT_PIE_WIDGET_X_OFFSET_DP),
+ XLayoutStyle.ABSOLUTE_FROM_CENTER,
+ PixelUtils.dpToPix(DEFAULT_PIE_WIDGET_Y_OFFSET_DP),
+ YLayoutStyle.ABSOLUTE_FROM_CENTER,
+ AnchorPosition.CENTER);
+
+ pieWidget.setPadding(10, 10, 10, 10);
+
+ // TODO: can't remember why this getClass() check is neccessary. test if it actually is...
+ /*if (getClass().equals(PieChart.class) && attrs != null) {
+ loadAttrs(context, attrs);
+ }*/
+ }
+
+ public PieWidget getPieWidget() {
+ return pieWidget;
+ }
+
+ public void addSegment(Segment segment, SegmentFormatter formatter) {
+ addSeries(segment, formatter);
+ }
+
+ public void removeSegment(Segment segment) {
+ removeSeries(segment);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieRenderer.java b/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieRenderer.java
new file mode 100644
index 0000000..86364aa
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieRenderer.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2013 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.pie;
+
+import android.graphics.*;
+
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.SeriesRenderer;
+
+import java.util.Set;
+
+public class PieRenderer extends SeriesRenderer<PieChart, Segment, SegmentFormatter> {
+
+ // starting angle to use when drawing the first radial line of the first segment.
+ @SuppressWarnings("FieldCanBeLocal")
+ private float startDeg = 0;
+ private float endDeg = 360;
+
+ // TODO: express donut in units other than px.
+ private float donutSize = 0.5f;
+ private DonutMode donutMode = DonutMode.PERCENT;
+
+ public enum DonutMode {
+ PERCENT,
+ DP,
+ PIXELS
+ }
+
+ public PieRenderer(PieChart plot) {
+ super(plot);
+ }
+
+ public float getRadius(RectF rect) {
+ return rect.width() < rect.height() ? rect.width() / 2 : rect.height() / 2;
+ }
+
+ @Override
+ public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
+
+ float radius = getRadius(plotArea);
+ PointF origin = new PointF(plotArea.centerX(), plotArea.centerY());
+
+ double[] values = getValues();
+ double scale = calculateScale(values);
+ float offset = startDeg;
+ Set<Segment> segments = getPlot().getSeriesSet();
+
+ //PointF lastRadial = calculateLineEnd(origin, radius, offset);
+
+ RectF rec = new RectF(origin.x - radius, origin.y - radius, origin.x + radius, origin.y + radius);
+
+ int i = 0;
+ for (Segment segment : segments) {
+ float lastOffset = offset;
+ float sweep = (float) (scale * (values[i]) * 360);
+ offset += sweep;
+ //PointF radial = calculateLineEnd(origin, radius, offset);
+ drawSegment(canvas, rec, segment, getPlot().getFormatter(segment, PieRenderer.class),
+ radius, lastOffset, sweep);
+ //lastRadial = radial;
+ i++;
+ }
+ }
+
+ protected void drawSegment(Canvas canvas, RectF bounds, Segment seg, SegmentFormatter f,
+ float rad, float startAngle, float sweep) {
+ canvas.save();
+
+ float cx = bounds.centerX();
+ float cy = bounds.centerY();
+
+ float donutSizePx;
+ switch(donutMode) {
+ case PERCENT:
+ donutSizePx = donutSize * rad;
+ break;
+ case PIXELS:
+ donutSizePx = (donutSize > 0)?donutSize:(rad + donutSize);
+ break;
+ default:
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ // vertices of the first radial:
+ PointF r1Outer = calculateLineEnd(cx, cy, rad, startAngle);
+ PointF r1Inner = calculateLineEnd(cx, cy, donutSizePx, startAngle);
+
+ // vertices of the second radial:
+ PointF r2Outer = calculateLineEnd(cx, cy, rad, startAngle + sweep);
+ PointF r2Inner = calculateLineEnd(cx, cy, donutSizePx, startAngle + sweep);
+
+ Path clip = new Path();
+
+ //float outerStroke = f.getOuterEdgePaint().getStrokeWidth();
+ //float halfOuterStroke = outerStroke / 2;
+
+ // leave plenty of room on the outside for stroked borders;
+ // necessary because the clipping border is ugly
+ // and cannot be easily anti aliased. Really we only care about masking off the
+ // radial edges.
+ clip.arcTo(new RectF(bounds.left - rad,
+ bounds.top - rad,
+ bounds.right + rad,
+ bounds.bottom + rad),
+ startAngle, sweep);
+ clip.lineTo(cx, cy);
+ clip.close();
+ canvas.clipPath(clip);
+
+ Path p = new Path();
+ p.arcTo(bounds, startAngle, sweep);
+ p.lineTo(r2Inner.x, r2Inner.y);
+
+ // sweep back to original angle:
+ p.arcTo(new RectF(
+ cx - donutSizePx,
+ cy - donutSizePx,
+ cx + donutSizePx,
+ cy + donutSizePx),
+ startAngle + sweep, -sweep);
+
+ p.close();
+
+ // fill segment:
+ canvas.drawPath(p, f.getFillPaint());
+
+ // draw radial lines
+ canvas.drawLine(r1Inner.x, r1Inner.y, r1Outer.x, r1Outer.y, f.getRadialEdgePaint());
+ canvas.drawLine(r2Inner.x, r2Inner.y, r2Outer.x, r2Outer.y, f.getRadialEdgePaint());
+
+ // draw inner line:
+ canvas.drawCircle(cx, cy, donutSizePx, f.getInnerEdgePaint());
+
+ // draw outer line:
+ canvas.drawCircle(cx, cy, rad, f.getOuterEdgePaint());
+ canvas.restore();
+
+ PointF labelOrigin = calculateLineEnd(cx, cy,
+ (rad-((rad- donutSizePx)/2)), startAngle + (sweep/2));
+
+ // TODO: move segment labelling outside the segment drawing loop
+ // TODO: so that the labels will not be clipped by the edge of the next
+ // TODO: segment being drawn.
+ drawSegmentLabel(canvas, labelOrigin, seg, f);
+ }
+
+ protected void drawSegmentLabel(Canvas canvas, PointF origin,
+ Segment seg, SegmentFormatter f) {
+ canvas.drawText(seg.getTitle(), origin.x, origin.y, f.getLabelPaint());
+
+ }
+
+ @Override
+ protected void doDrawLegendIcon(Canvas canvas, RectF rect, SegmentFormatter formatter) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ /**
+ * Determines how many counts there are per cent of whatever the
+ * pie chart is displaying as a fraction, 1 being 100%.
+ */
+ private double calculateScale(double[] values) {
+ double total = 0;
+ for (int i = 0; i < values.length; i++) {
+ total += values[i];
+ }
+
+ return (1d / total);
+ }
+
+ private double[] getValues() {
+ Set<Segment> segments = getPlot().getSeriesSet();
+ double[] result = new double[segments.size()];
+ int i = 0;
+ for (Segment seg : getPlot().getSeriesSet()) {
+ result[i] = seg.getValue().doubleValue();
+ i++;
+ }
+ return result;
+ }
+
+ private PointF calculateLineEnd(float x, float y, float rad, float deg) {
+ return calculateLineEnd(new PointF(x, y), rad, deg);
+ }
+
+ private PointF calculateLineEnd(PointF origin, float rad, float deg) {
+
+ double radians = deg * Math.PI / 180F;
+ double x = rad * Math.cos(radians);
+ double y = rad * Math.sin(radians);
+
+ // convert to screen space:
+ return new PointF(origin.x + (float) x, origin.y + (float) y);
+ }
+
+ public void setDonutSize(float size, DonutMode mode) {
+ switch(mode) {
+ case PERCENT:
+ if(size < 0 || size > 1) {
+ throw new IllegalArgumentException(
+ "Size parameter must be between 0 and 1 when operating in PERCENT mode.");
+ }
+ break;
+ case PIXELS:
+ break;
+ default:
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+ donutMode = mode;
+ donutSize = size;
+ }
+
+ public void setStartDeg(float deg) {
+ startDeg = deg;
+ }
+
+ public void setEndDeg(float deg) {
+ endDeg = deg;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieWidget.java b/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieWidget.java
new file mode 100644
index 0000000..ebfa8f8
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/pie/PieWidget.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.pie;
+
+import android.graphics.*;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.LayoutManager;
+import com.androidplot.ui.SizeMetrics;
+import com.androidplot.ui.widget.Widget;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Visualizes data as a pie chart.
+ */
+public class PieWidget extends Widget {
+
+ private PieChart pieChart;
+
+ public PieWidget(LayoutManager layoutManager, PieChart pieChart, SizeMetrics metrics) {
+ super(layoutManager, metrics);
+ this.pieChart = pieChart;
+ }
+
+ @Override
+ protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException {
+
+ for(PieRenderer renderer : pieChart.getRendererList()) {
+ renderer.render(canvas, widgetRect);
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/pie/Segment.java b/AndroidPlot-Core/src/main/java/com/androidplot/pie/Segment.java
new file mode 100644
index 0000000..1b18f15
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/pie/Segment.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.pie;
+
+import com.androidplot.Series;
+
+public class Segment implements Series<Number> {
+
+ private String title;
+
+ private Number value;
+
+ public Segment(String title, Number value) {
+ this.title = title;
+ this.setValue(value);
+ }
+
+ @Override
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public Number getValue() {
+ return value;
+ }
+
+ public void setValue(Number value) {
+ this.value = value;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/pie/SegmentFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/pie/SegmentFormatter.java
new file mode 100644
index 0000000..5c75950
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/pie/SegmentFormatter.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2013 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.pie;
+
+import android.graphics.Color;
+import android.graphics.Paint;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.ui.Formatter;
+
+public class SegmentFormatter extends Formatter<PieChart> {
+
+ private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT;
+ private static final int DEFAULT_EDGE_COLOR = Color.BLACK;
+ private static final int DEFAULT_LABEL_COLOR = Color.WHITE;
+ private static final float DEFAULT_EDGE_THICKNESS = 3;
+ private static final float DEFAULT_LABEL_MARKER_THICKNESS = 3;
+ private static final float DEFAULT_LABEL_FONT_SIZE = 18;
+
+ private Paint innerEdgePaint;
+ private Paint outerEdgePaint;
+ private Paint radialEdgePaint;
+ private Paint fillPaint;
+
+ private Paint labelPaint;
+ private Paint labelMarkerPaint;
+
+ {
+ setFillPaint(new Paint());
+ // outer edge:
+ setOuterEdgePaint(new Paint());
+ getOuterEdgePaint().setStyle(Paint.Style.STROKE);
+ getOuterEdgePaint().setStrokeWidth(DEFAULT_EDGE_THICKNESS);
+ getOuterEdgePaint().setAntiAlias(true);
+
+ // inner edge:
+ setInnerEdgePaint(new Paint());
+ getInnerEdgePaint().setStyle(Paint.Style.STROKE);
+ getInnerEdgePaint().setStrokeWidth(DEFAULT_EDGE_THICKNESS);
+ getInnerEdgePaint().setAntiAlias(true);
+
+ // radial edge:
+ setRadialEdgePaint(new Paint());
+ getRadialEdgePaint().setStyle(Paint.Style.STROKE);
+ getRadialEdgePaint().setStrokeWidth(DEFAULT_EDGE_THICKNESS);
+ getRadialEdgePaint().setAntiAlias(true);
+
+ // label paint:
+ setLabelPaint(new Paint());
+ getLabelPaint().setColor(DEFAULT_LABEL_COLOR);
+ getLabelPaint().setTextSize(DEFAULT_LABEL_FONT_SIZE);
+ getLabelPaint().setAntiAlias(true);
+ getLabelPaint().setTextAlign(Paint.Align.CENTER);
+ //getLabelPaint().setShadowLayer(5, 4, 4, Color.BLACK);
+
+ // label marker paint:
+ setLabelMarkerPaint(new Paint());
+ getLabelMarkerPaint().setColor(DEFAULT_LABEL_COLOR);
+ getLabelMarkerPaint().setStrokeWidth(DEFAULT_LABEL_MARKER_THICKNESS);
+ }
+
+ /**
+ * Should only be used in conjunction with calls to configure()...
+ */
+ public SegmentFormatter() {}
+
+ public SegmentFormatter(Integer fillColor) {
+ if(fillColor != null) {
+ getFillPaint().setColor(fillColor);
+ } else {
+ getFillPaint().setColor(DEFAULT_FILL_COLOR);
+ }
+ }
+
+ public SegmentFormatter(Integer fillColor, Integer borderColor) {
+ this(fillColor);
+ getInnerEdgePaint().setColor(borderColor);
+ getOuterEdgePaint().setColor(borderColor);
+ getRadialEdgePaint().setColor(borderColor);
+ }
+
+ public SegmentFormatter(Integer fillColor, Integer outerEdgeColor,
+ Integer innerEdgeColor, Integer radialEdgeColor) {
+ this(fillColor);
+
+
+ if(getOuterEdgePaint() != null) {
+ getOuterEdgePaint().setColor(outerEdgeColor);
+ } else {
+ outerEdgePaint = new Paint();
+ getOuterEdgePaint().setColor(DEFAULT_EDGE_COLOR);
+ }
+
+ if (getInnerEdgePaint() != null) {
+ getInnerEdgePaint().setColor(innerEdgeColor);
+ } else {
+ outerEdgePaint = new Paint();
+ getInnerEdgePaint().setColor(DEFAULT_EDGE_COLOR);
+ }
+
+ if (getRadialEdgePaint() != null) {
+ getRadialEdgePaint().setColor(radialEdgeColor);
+ } else {
+ radialEdgePaint = new Paint();
+ getRadialEdgePaint().setColor(DEFAULT_EDGE_COLOR);
+ }
+ }
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return PieRenderer.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(PieChart plot) {
+ return new PieRenderer(plot);
+ }
+
+ public Paint getInnerEdgePaint() {
+ return innerEdgePaint;
+ }
+
+ public void setInnerEdgePaint(Paint innerEdgePaint) {
+ this.innerEdgePaint = innerEdgePaint;
+ }
+
+ public Paint getOuterEdgePaint() {
+ return outerEdgePaint;
+ }
+
+ public void setOuterEdgePaint(Paint outerEdgePaint) {
+ this.outerEdgePaint = outerEdgePaint;
+ }
+
+ public Paint getRadialEdgePaint() {
+ return radialEdgePaint;
+ }
+
+ public void setRadialEdgePaint(Paint radialEdgePaint) {
+ this.radialEdgePaint = radialEdgePaint;
+ }
+
+ public Paint getFillPaint() {
+ return fillPaint;
+ }
+
+ public void setFillPaint(Paint fillPaint) {
+ this.fillPaint = fillPaint;
+ }
+
+ public Paint getLabelPaint() {
+ return labelPaint;
+ }
+
+ public void setLabelPaint(Paint labelPaint) {
+ this.labelPaint = labelPaint;
+ }
+
+ public Paint getLabelMarkerPaint() {
+ return labelMarkerPaint;
+ }
+
+ public void setLabelMarkerPaint(Paint labelMarkerPaint) {
+ this.labelMarkerPaint = labelMarkerPaint;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/AnchorPosition.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/AnchorPosition.java
new file mode 100644
index 0000000..e1f0b2e
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/AnchorPosition.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+/**
+ * Enumeration of possible anchor positions that a {@link com.androidplot.ui.widget.Widget} can use. There are a total
+ * 8 possible anchor positions representing each corner of the Widget and the point exactly between each corner.
+ */
+public enum AnchorPosition {
+ TOP_MIDDLE,
+ LEFT_TOP, // default
+ LEFT_MIDDLE,
+ LEFT_BOTTOM,
+ RIGHT_TOP,
+ RIGHT_MIDDLE,
+ RIGHT_BOTTOM,
+ BOTTOM_MIDDLE,
+ CENTER
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModel.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModel.java
new file mode 100644
index 0000000..e80728c
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModel.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+
+/**
+ * Convenience implementation of {@link BoxModelable}.
+ */
+public class BoxModel implements BoxModelable{
+
+ private float marginLeft;
+ private float marginTop;
+ private float marginRight;
+ private float marginBottom;
+
+
+ private float paddingLeft;
+ private float paddingTop;
+ private float paddingRight;
+ private float paddingBottom;
+ //private RectF marginRect;
+ //private RectF paddingRect;
+
+ public BoxModel() {
+
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ public BoxModel(float marginLeft, float marginTop, float marginRight, float marginBottom,
+ float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) {
+ this.marginLeft = marginLeft;
+ this.marginTop = marginTop;
+ this.marginRight = marginRight;
+ this.marginBottom = marginBottom;
+
+ this.paddingLeft = paddingLeft;
+ this.paddingTop = paddingTop;
+ this.paddingRight = paddingRight;
+ this.paddingBottom = paddingBottom;
+ }
+
+ /**
+ * Returns a RectF instance describing the inner edge of the margin layer.
+ * @param boundsRect
+ * @return
+ */
+ public RectF getMarginatedRect(RectF boundsRect) {
+ return new RectF( boundsRect.left + getMarginLeft(),
+ boundsRect.top + getMarginTop(),
+ boundsRect.right - getMarginRight(),
+ boundsRect.bottom - getMarginBottom());
+ }
+
+ /**
+ * Returns a RectF instance describing the inner edge of the padding layer.
+ * @param marginRect
+ * @return
+ */
+ public RectF getPaddedRect(RectF marginRect) {
+ return new RectF(marginRect.left + getPaddingLeft(),
+ marginRect.top+getPaddingTop(),
+ marginRect.right - getPaddingRight(),
+ marginRect.bottom - getPaddingBottom());
+ }
+
+ @Override
+ public void setMargins(float left, float top, float right, float bottom) {
+ setMarginLeft(left);
+ setMarginTop(top);
+ setMarginRight(right);
+ setMarginBottom(bottom);
+ }
+
+ @Override
+ public void setPadding(float left, float top, float right, float bottom) {
+ setPaddingLeft(left);
+ setPaddingTop(top);
+ setPaddingRight(right);
+ setPaddingBottom(bottom);
+ }
+
+
+ public float getMarginLeft() {
+ return marginLeft;
+ }
+
+ public void setMarginLeft(float marginLeft) {
+ this.marginLeft = marginLeft;
+ }
+
+ public float getMarginTop() {
+ return marginTop;
+ }
+
+ public void setMarginTop(float marginTop) {
+ this.marginTop = marginTop;
+ }
+
+ public float getMarginRight() {
+ return marginRight;
+ }
+
+ public void setMarginRight(float marginRight) {
+ this.marginRight = marginRight;
+ }
+
+ public float getMarginBottom() {
+ return marginBottom;
+ }
+
+ public void setMarginBottom(float marginBottom) {
+ this.marginBottom = marginBottom;
+ }
+
+ public float getPaddingLeft() {
+ return paddingLeft;
+ }
+
+ public void setPaddingLeft(float paddingLeft) {
+ this.paddingLeft = paddingLeft;
+ }
+
+ public float getPaddingTop() {
+ return paddingTop;
+ }
+
+ public void setPaddingTop(float paddingTop) {
+ this.paddingTop = paddingTop;
+ }
+
+ public float getPaddingRight() {
+ return paddingRight;
+ }
+
+ public void setPaddingRight(float paddingRight) {
+ this.paddingRight = paddingRight;
+ }
+
+ public float getPaddingBottom() {
+ return paddingBottom;
+ }
+
+ public void setPaddingBottom(float paddingBottom) {
+ this.paddingBottom = paddingBottom;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModelable.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModelable.java
new file mode 100644
index 0000000..7389e85
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/BoxModelable.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+
+/**
+ * Encapsulates the functionality of a BoxModel.
+ * See http://www.w3.org/TR/CSS21/box.html for a good explanation of how
+ * the box model works.
+ */
+public interface BoxModelable {
+ /**
+ * Returns a RectF instance describing the inner edge of the margin layer.
+ * @param boundsRect
+ * @return
+ */
+ public RectF getMarginatedRect(RectF boundsRect);
+
+ /**
+ * Returns a RectF instance describing the inner edge of the padding layer.
+ * @param marginRect
+ * @return
+ */
+ public RectF getPaddedRect(RectF marginRect);
+
+
+ public void setMargins(float left, float top, float right, float bottom);
+
+ public void setPadding(float left, float top, float right, float bottom);
+
+ public float getMarginLeft();
+
+ public void setMarginLeft(float marginLeft);
+
+ public float getMarginTop();
+
+ public void setMarginTop(float marginTop);
+
+ public float getMarginRight();
+
+ public void setMarginRight(float marginRight);
+
+ public float getMarginBottom();
+
+ public float getPaddingLeft();
+
+ public void setPaddingLeft(float paddingLeft);
+
+ public float getPaddingTop();
+
+ public void setPaddingTop(float paddingTop);
+
+ public float getPaddingRight();
+
+ public void setPaddingRight(float paddingRight);
+
+ public float getPaddingBottom();
+
+ public void setPaddingBottom(float paddingBottom);
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/DynamicTableModel.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/DynamicTableModel.java
new file mode 100644
index 0000000..1654054
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/DynamicTableModel.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+
+import java.util.Iterator;
+
+/**
+ * Encapsulates the visual aspects of a table; number of rows and columns
+ * and the height and width in pixels of each element within the table.
+ * There is no support (yet) for variable size cells within a table; all
+ * cells within a table share the same dimensions.
+ *
+ * The DynamicTableModel provides an Iterator implementation which returns a RectF
+ * of each subsequent cell, based on the order of the plot. Tables with
+ * an order of COLUMN_MAJOR are traversed left to right column by column until
+ * the end of the row is reached, then proceeding to the next row.
+ * Tables with an order of ROW_MAJOR are traversed top to bottom row by row
+ * until the end of the row is reached, then proceeding to the next column.
+ */
+public class DynamicTableModel extends TableModel {
+
+ //private float cellWidth;
+ //private float cellHeight;
+ //private TableSizingMethod rowSizingMethod;
+ //private TableSizingMethod columnSizingMethod;
+
+
+ private int numRows;
+ private int numColumns;
+
+ private Float cellWidth;
+ private Float cellHeight;
+
+ private CellSizingMethod rowSizingMethod;
+ private CellSizingMethod columnSizingMethod;
+
+ /**
+ * Convenience method. Sets order to ROW_MAJOR.
+ * @param numColumns
+ * @param numRows
+ */
+ public DynamicTableModel(int numColumns, int numRows) {
+ this(numColumns, numRows, TableOrder.ROW_MAJOR);
+
+ }
+
+ public DynamicTableModel(int numColumns, int numRows, TableOrder order) {
+ super(order);
+ this.numColumns = numColumns;
+ //this.cellWidth = cellWidth;
+ //this.rowSizingMethod = rowSizingMethod;
+ this.numRows = numRows;
+ //this.cellHeight = cellHeight;
+ //this.columnSizingMethod = columnSizingMethod;
+ //this.order = order;
+ }
+
+ /*public DynamicTableModel(Number colVal, CellSizingMethod colSzMethod, Number rowVal, CellSizingMethod rowSzMethod, TableOrder order) {
+ if(colVal == null || rowVal == null) {
+ throw new NullPointerException();
+ }
+ columnSizingMethod = colSzMethod;
+ switch(columnSizingMethod) {
+ case FILL:
+ numColumns = colVal.intValue();
+ break;
+ case FIXED:
+ cellWidth = colVal.floatValue();
+ break;
+ }
+ rowSzMethod = rowSzMethod;
+ }*/
+
+ @Override
+ public TableModelIterator getIterator(RectF tableRect, int totalElements) {
+ return new TableModelIterator(this, tableRect, totalElements);
+ }
+
+ /**
+ * Calculates the dimensions of a single element of this table with
+ * tableRect representing the overall dimensions of the table.
+ * @param tableRect Dimensions/position of the table
+ * @return a RectF representing the first (top-left) element in
+ * the tableRect passed in.
+ */
+ public RectF getCellRect(RectF tableRect, int numElements) {
+ RectF cellRect = new RectF();
+ cellRect.left = tableRect.left;
+ cellRect.top = tableRect.top;
+ //cellRect.bottom = getElementHeightPix(tableRect);
+ cellRect.bottom = tableRect.top + calculateCellSize(tableRect, TableModel.Axis.ROW, numElements);
+ //cellRect.right = getElementWidthPix(tableRect);
+ cellRect.right = tableRect.left + calculateCellSize(tableRect, TableModel.Axis.COLUMN, numElements);
+ return cellRect;
+ }
+
+ /**
+ * Figure out the size of a single cell across the specified axis.
+ * @param tableRect
+ * @param axis
+ * @param numElementsInTable
+ * @return
+ */
+ private float calculateCellSize(RectF tableRect,
+ Axis axis,
+ int numElementsInTable) {
+ //float elementSizeInPix = 0;
+ int axisElements = 0;
+
+ float axisSizePix = 0;
+ switch (axis) {
+ case ROW:
+ //elementSizeInPix = cellHeight;
+ axisElements = numRows;
+ axisSizePix = tableRect.height();
+ break;
+ case COLUMN:
+ //elementSizeInPix = cellWidth;
+ axisElements = numColumns;
+ axisSizePix = tableRect.width();
+ break;
+ }
+ //if (elementSizeInPix != 0) {
+ // return elementSizeInPix;
+ if(axisElements != 0) {
+ return axisSizePix / axisElements;
+ } else {
+ return axisSizePix / numElementsInTable;
+ }
+ }
+
+
+
+ public int getNumRows() {
+ return numRows;
+ }
+
+ public void setNumRows(int numRows) {
+ this.numRows = numRows;
+ }
+
+ public int getNumColumns() {
+ return numColumns;
+ }
+
+ public void setNumColumns(int numColumns) {
+ this.numColumns = numColumns;
+ }
+
+/* public void setCellWidth(Float cellWidth) {
+ this.cellWidth = cellWidth;
+ }
+
+ public Float getCellWidth() {
+ return cellWidth;
+ }
+
+ public Float getCellHeight() {
+ return cellHeight;
+ }
+
+ public void setCellHeight(Float cellHeight) {
+ this.cellHeight = cellHeight;
+ }*/
+
+ private class TableModelIterator implements Iterator<RectF> {
+ private boolean isOk = true;
+ int lastColumn = 0; // most recent column iterated
+ int lastRow = 0; // most recent row iterated
+ int lastElement = 0; // last element index iterated
+ private DynamicTableModel dynamicTableModel;
+ private RectF tableRect;
+ private RectF lastElementRect;
+ private int totalElements;
+ private TableOrder order;
+
+ private int calculatedNumElements;
+ private int calculatedRows; // number of rows to be iterated
+ private int calculatedColumns; // number of columns to be iterated
+
+ public TableModelIterator(DynamicTableModel dynamicTableModel, RectF tableRect, int totalElements) {
+ this.dynamicTableModel = dynamicTableModel;
+ this.tableRect = tableRect;
+ this.totalElements = totalElements;
+ order = dynamicTableModel.getOrder();
+
+ // unlimited columns:
+ if(dynamicTableModel.getNumColumns() == 0 && dynamicTableModel.getNumRows() >= 1) {
+ calculatedRows = dynamicTableModel.getNumRows();
+
+ // round up:
+ calculatedColumns = new Float((totalElements / (float) calculatedRows) + 0.5).intValue();
+ } else if(dynamicTableModel.getNumRows() == 0 && dynamicTableModel.getNumColumns() >= 1) {
+ //order = TableOrder.ROW_MAJOR;
+ calculatedColumns = dynamicTableModel.getNumColumns();
+ calculatedRows = new Float((totalElements / (float) calculatedColumns) + 0.5).intValue();
+ // unlimited rows and columns (impossible) so default a single row with n columns:
+ }else if(dynamicTableModel.getNumColumns() == 0 && dynamicTableModel.getNumRows() == 0) {
+ calculatedRows = 1;
+ calculatedColumns = totalElements;
+ } else {
+ //order = dynamicTableModel.getOrder();
+ calculatedRows = dynamicTableModel.getNumRows();
+ calculatedColumns = dynamicTableModel.getNumColumns();
+ }
+ calculatedNumElements = calculatedRows * calculatedColumns;
+ lastElementRect = dynamicTableModel.getCellRect(tableRect, totalElements);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return isOk && lastElement < calculatedNumElements;
+ }
+
+ @Override
+ public RectF next() {
+ if(!hasNext()) {
+ isOk = false;
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (lastElement == 0) {
+ lastElement++;
+ return lastElementRect;
+ }
+
+ RectF nextElementRect = new RectF(lastElementRect);
+
+ switch (order) {
+ case ROW_MAJOR:
+ if (dynamicTableModel.getNumColumns() > 0 && lastColumn >= (dynamicTableModel.getNumColumns() - 1)) {
+ // move to the begining of the next row down:// move to the begining of the next row down:
+ nextElementRect.offsetTo(tableRect.left, lastElementRect.bottom);
+ lastColumn = 0;
+ lastRow++;
+ } else {
+ // move to the next column over:
+ nextElementRect.offsetTo(lastElementRect.right, lastElementRect.top);
+ lastColumn++;
+ }
+ break;
+ case COLUMN_MAJOR:
+ if (dynamicTableModel.getNumRows() > 0 && lastRow >= (dynamicTableModel.getNumRows() - 1)) {
+ // move to the top of the next column over:
+ nextElementRect.offsetTo(lastElementRect.right, tableRect.top);
+ lastRow = 0;
+ lastColumn++;
+ } else {
+ // move to the next row down:
+ nextElementRect.offsetTo(lastElementRect.left, lastElementRect.bottom);
+ lastRow++;
+ }
+ break;
+ // unknown/unsupported enum val:
+ default:
+ isOk = false;
+ throw new IllegalArgumentException();
+ }
+ lastElement++;
+ lastElementRect = nextElementRect;
+ return nextElementRect;
+
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/FixedTableModel.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/FixedTableModel.java
new file mode 100644
index 0000000..4c7c265
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/FixedTableModel.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+
+import java.util.Iterator;
+
+public class FixedTableModel extends TableModel {
+ private float cellWidth;
+ private float cellHeight;
+ protected FixedTableModel(float cellWidth, float cellHeight, TableOrder order) {
+ super(order);
+ setCellWidth(cellWidth);
+ setCellHeight(cellHeight);
+ }
+
+ @Override
+ public Iterator<RectF> getIterator(RectF tableRect, int totalElements) {
+ return new FixedTableModelIterator(this, tableRect, totalElements);
+ }
+
+ public float getCellWidth() {
+ return cellWidth;
+ }
+
+ public void setCellWidth(float cellWidth) {
+ this.cellWidth = cellWidth;
+ }
+
+ public float getCellHeight() {
+ return cellHeight;
+ }
+
+ public void setCellHeight(float cellHeight) {
+ this.cellHeight = cellHeight;
+ }
+
+ private class FixedTableModelIterator implements Iterator<RectF> {
+
+ private FixedTableModel model;
+ private RectF tableRect;
+ private RectF lastRect;
+ private int numElements;
+ private int lastElement;
+ protected FixedTableModelIterator(FixedTableModel model, RectF tableRect, int numElements) {
+ this.model = model;
+ this.tableRect = tableRect;
+ this.numElements = numElements;
+ lastRect = new RectF(
+ tableRect.left,
+ tableRect.top,
+ tableRect.left + model.getCellWidth(),
+ tableRect.top + model.getCellHeight());
+ }
+
+ @Override
+ public boolean hasNext() {
+ // was this the last element or is there no room in either axis for another cell?
+ return !(lastElement >= numElements || (isColumnFinished() && isRowFinished()));
+ }
+
+ private boolean isColumnFinished() {
+ return lastRect.bottom + model.getCellHeight() > tableRect.height();
+ }
+
+ private boolean isRowFinished() {
+ return lastRect.right + model.getCellWidth() > tableRect.width();
+ }
+
+ @Override
+ public RectF next() {
+ try {
+ if (lastElement == 0) {
+ return lastRect;
+ }
+
+ if (lastElement >= numElements) {
+ throw new IndexOutOfBoundsException();
+ }
+ switch (model.getOrder()) {
+ case ROW_MAJOR:
+ if (isColumnFinished()) {
+ moveOverAndUp();
+ } else {
+ moveDown();
+ }
+ break;
+ case COLUMN_MAJOR:
+ if (isRowFinished()) {
+ moveDownAndBack();
+ } else {
+ moveOver();
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ return lastRect;
+ } finally {
+ lastElement++;
+ }
+ }
+
+ private void moveDownAndBack() {
+ //RectF rect = new RectF(lastRect);
+ lastRect.offsetTo(tableRect.left, lastRect.bottom);
+ //return rect;
+ }
+
+ private void moveOverAndUp() {
+ //RectF rect = new RectF(lastRect);
+ lastRect.offsetTo(lastRect.right, tableRect.top);
+ //return rect;
+ }
+
+ private void moveOver() {
+ //RectF rect = new RectF(lastRect);
+ lastRect.offsetTo(lastRect.right, lastRect.top);
+ //return rect;
+ }
+
+ private void moveDown() {
+ //RectF rect = new RectF(lastRect);
+ lastRect.offsetTo(lastRect.left, lastRect.bottom);
+ //return rect;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/Formatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/Formatter.java
new file mode 100644
index 0000000..dbebaa2
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/Formatter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.content.Context;
+import com.androidplot.Plot;
+import com.androidplot.util.Configurator;
+
+/**
+ * Base class of all Formatters. Encapsulates visual elements of a series; line style, color etc.
+ * Implementors of this class should include both a default constructor and a one argument
+ * constructor in the following form:
+ *
+ * <pre>
+ * {@code
+ * // provided as a convenience to users; allows instantiation and
+ * // xml configuration in a single line.
+ * public MyFormatter(Context ctx, int xmlCfgId) {
+ * // prevent configuration of classes derived from this one:
+ * if (getClass().equals(MyFormatter.class)) {
+ * Configurator.configure(ctx, this, xmlCfgId);
+ * }
+ * }
+ * </pre>
+ */
+public abstract class Formatter<PlotType extends Plot> {
+
+ public Formatter<PlotType> configure(Context ctx, int xmlCfgId) {
+ Configurator.configure(ctx, this, xmlCfgId);
+ return this;
+ }
+
+
+
+ /**
+ *
+ * @return The Class of SeriesRenderer that should be used.
+ */
+ public abstract Class<? extends SeriesRenderer> getRendererClass();
+
+ /**
+ *
+ * @return An instance of SeriesRenderer that took plot as an argument to its constructor.
+ */
+ public abstract SeriesRenderer getRendererInstance(PlotType plot);
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutManager.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutManager.java
new file mode 100644
index 0000000..1983ca7
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutManager.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.*;
+import android.view.MotionEvent;
+import android.view.View;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.widget.Widget;
+import com.androidplot.util.DisplayDimensions;
+import com.androidplot.util.ZLinkedList;
+
+public class LayoutManager extends ZLinkedList<Widget>
+ implements View.OnTouchListener, Resizable {
+ private boolean drawAnchorsEnabled = false;
+ private Paint anchorPaint;
+ private boolean drawOutlinesEnabled = false;
+ private Paint outlinePaint;
+ private boolean drawOutlineShadowsEnabled = false;
+ private Paint outlineShadowPaint;
+ private boolean drawMarginsEnabled = false;
+ private Paint marginPaint;
+ private boolean drawPaddingEnabled = false;
+ private Paint paddingPaint;
+ private DisplayDimensions displayDims = new DisplayDimensions();
+
+ // cache of widget rects
+ //private HashMap<Widget, DisplayDimensions> widgetRects;
+
+ {
+ //widgetRects = new HashMap<Widget, DisplayDimensions>();
+ anchorPaint = new Paint();
+ anchorPaint.setStyle(Paint.Style.FILL);
+ anchorPaint.setColor(Color.GREEN);
+ outlinePaint = new Paint();
+ outlinePaint.setColor(Color.GREEN);
+ outlinePaint.setStyle(Paint.Style.STROKE);
+ marginPaint = new Paint();
+ marginPaint.setColor(Color.YELLOW);
+ marginPaint.setStyle(Paint.Style.FILL);
+ marginPaint.setAlpha(200);
+ paddingPaint= new Paint();
+ paddingPaint.setColor(Color.BLUE);
+ paddingPaint.setStyle(Paint.Style.FILL);
+ paddingPaint.setAlpha(200);
+ }
+
+ /**
+ * Invoked immediately following XML configuration.
+ */
+ public synchronized void onPostInit() {
+ for(Widget w : elements()) {
+ w.onPostInit();
+ }
+ }
+
+ public LayoutManager() {
+ }
+
+ public void setMarkupEnabled(boolean enabled) {
+ setDrawOutlinesEnabled(enabled);
+ setDrawAnchorsEnabled(enabled);
+ setDrawMarginsEnabled(enabled);
+ setDrawPaddingEnabled(enabled);
+ setDrawOutlineShadowsEnabled(enabled);
+ }
+
+ public void draw(Canvas canvas) throws PlotRenderException {
+ if(isDrawMarginsEnabled()) {
+ drawSpacing(canvas, displayDims.canvasRect, displayDims.marginatedRect, marginPaint);
+ }
+ if (isDrawPaddingEnabled()) {
+ drawSpacing(canvas, displayDims.marginatedRect, displayDims.paddedRect, paddingPaint);
+ }
+ for (Widget widget : elements()) {
+ //int canvasState = canvas.save(Canvas.ALL_SAVE_FLAG); // preserve clipping etc
+ try {
+ canvas.save(Canvas.ALL_SAVE_FLAG);
+ PositionMetrics metrics = widget.getPositionMetrics();
+ float elementWidth = widget.getWidthPix(displayDims.paddedRect.width());
+ float elementHeight = widget.getHeightPix(displayDims.paddedRect.height());
+ PointF coords = widget.getElementCoordinates(elementHeight,
+ elementWidth, displayDims.paddedRect, metrics);
+
+ //RectF widgetRect = new RectF(coords.x, coords.y, coords.x + elementWidth, coords.y + elementHeight);
+ //DisplayDimensions dims = widgetRects.get(widget);
+ DisplayDimensions dims = widget.getWidgetDimensions();
+ //RectF widgetRect = widgetRects.get(widget);
+
+ if (drawOutlineShadowsEnabled) {
+ canvas.drawRect(dims.canvasRect, outlineShadowPaint);
+ }
+
+ // not positive why this is, but the rect clipped by clipRect is 1 less than the one drawn by drawRect.
+ // so this is necessary to avoid clipping borders. I suspect that its a floating point
+ // jitter issue.
+ if (widget.isClippingEnabled()) {
+ //RectF clipRect = new RectF(l-1, t-1, r + 1, b + 1);
+ //canvas.clipRect(clipRect, Region.Op.REPLACE);
+ canvas.clipRect(dims.canvasRect, Region.Op.INTERSECT);
+ }
+ widget.draw(canvas, dims.canvasRect);
+
+ //RectF marginatedWidgetRect = widget.getMarginatedRect(dims.canvasRect);
+ //RectF paddedWidgetRect = widget.getPaddedRect(marginatedWidgetRect);
+
+ if (drawMarginsEnabled) {
+ drawSpacing(canvas, dims.canvasRect, dims.marginatedRect, getMarginPaint());
+ }
+
+ if (drawPaddingEnabled) {
+ drawSpacing(canvas, dims.marginatedRect, dims.paddedRect, getPaddingPaint());
+ }
+
+ if (drawAnchorsEnabled) {
+ PointF anchorCoords =
+ Widget.getAnchorCoordinates(coords.x, coords.y, elementWidth,
+ elementHeight, metrics.getAnchor());
+ drawAnchor(canvas, anchorCoords);
+ }
+
+
+ if (drawOutlinesEnabled) {
+ outlinePaint.setAntiAlias(true);
+ canvas.drawRect(dims.canvasRect, outlinePaint);
+ }
+ } finally {
+ //canvas.restoreToCount(canvasState); // restore clipping etc.
+ canvas.restore();
+ }
+ }
+ }
+
+ private void drawSpacing(Canvas canvas, RectF outer, RectF inner, Paint paint) {
+ //int saved = canvas.save(Canvas.ALL_SAVE_FLAG);
+ try {
+ canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.clipRect(inner, Region.Op.DIFFERENCE);
+ canvas.drawRect(outer, paint);
+ //canvas.restoreToCount(saved);
+ } finally {
+ canvas.restore();
+ }
+ }
+
+ protected void drawAnchor(Canvas canvas, PointF coords) {
+ float anchorSize = 4;
+ canvas.drawRect(coords.x-anchorSize, coords.y-anchorSize, coords.x+anchorSize, coords.y+anchorSize, anchorPaint);
+
+ }
+
+ public boolean isDrawOutlinesEnabled() {
+ return drawOutlinesEnabled;
+ }
+
+ public void setDrawOutlinesEnabled(boolean drawOutlinesEnabled) {
+ this.drawOutlinesEnabled = drawOutlinesEnabled;
+ }
+
+ public Paint getOutlinePaint() {
+ return outlinePaint;
+ }
+
+ public void setOutlinePaint(Paint outlinePaint) {
+ this.outlinePaint = outlinePaint;
+ }
+
+ public boolean isDrawAnchorsEnabled() {
+ return drawAnchorsEnabled;
+ }
+
+ public void setDrawAnchorsEnabled(boolean drawAnchorsEnabled) {
+ this.drawAnchorsEnabled = drawAnchorsEnabled;
+ }
+
+ public boolean isDrawMarginsEnabled() {
+ return drawMarginsEnabled;
+ }
+
+ public void setDrawMarginsEnabled(boolean drawMarginsEnabled) {
+ this.drawMarginsEnabled = drawMarginsEnabled;
+ }
+
+ public Paint getMarginPaint() {
+ return marginPaint;
+ }
+
+ public void setMarginPaint(Paint marginPaint) {
+ this.marginPaint = marginPaint;
+ }
+
+ public boolean isDrawPaddingEnabled() {
+ return drawPaddingEnabled;
+ }
+
+ public void setDrawPaddingEnabled(boolean drawPaddingEnabled) {
+ this.drawPaddingEnabled = drawPaddingEnabled;
+ }
+
+ public Paint getPaddingPaint() {
+ return paddingPaint;
+ }
+
+ public void setPaddingPaint(Paint paddingPaint) {
+ this.paddingPaint = paddingPaint;
+ }
+
+ public boolean isDrawOutlineShadowsEnabled() {
+ return drawOutlineShadowsEnabled;
+ }
+
+ public void setDrawOutlineShadowsEnabled(boolean drawOutlineShadowsEnabled) {
+ this.drawOutlineShadowsEnabled = drawOutlineShadowsEnabled;
+ if(drawOutlineShadowsEnabled && outlineShadowPaint == null) {
+ // use a default shadow effect in the case where none has been set:
+ outlineShadowPaint = new Paint();
+ outlineShadowPaint.setColor(Color.DKGRAY);
+ outlineShadowPaint.setStyle(Paint.Style.FILL);
+ outlineShadowPaint.setShadowLayer(3, 5, 5, Color.BLACK);
+ }
+ }
+
+ public Paint getOutlineShadowPaint() {
+ return outlineShadowPaint;
+ }
+
+ public void setOutlineShadowPaint(Paint outlineShadowPaint) {
+ this.outlineShadowPaint = outlineShadowPaint;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Recalculates layouts for all widgets using last set
+ * DisplayDimensions. Care should be excersized when choosing when
+ * to call this method as it is a relatively slow operation.
+ */
+ public void refreshLayout() {
+ //widgetRects.clear();
+ for (Widget widget : elements()) {
+ widget.layout(displayDims);
+ }
+ }
+
+ @Override
+ public void layout(final DisplayDimensions dims) {
+ this.displayDims = dims;
+
+ refreshLayout();
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutMetric.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutMetric.java
new file mode 100644
index 0000000..9061913
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/LayoutMetric.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+
+
+abstract class LayoutMetric<LayoutType extends Enum> {
+
+ private LayoutType layoutType;
+
+ //private LayoutType layoutType;
+ private float value;
+ //private float lastRow;
+
+ public LayoutMetric(float value, LayoutType layoutType) {
+ validatePair(value, layoutType);
+ set(value, layoutType);
+ //setLayoutType(layoutType);
+ //setValue(value);
+ //setLayoutType(layoutType);
+ }
+
+ /**
+ * Verifies that the values passed in are valid for the layout algorithm being used.
+ * @param value
+ * @param layoutType
+ */
+ protected abstract void validatePair(float value, LayoutType layoutType);
+
+ public void set(float value, LayoutType layoutType) {
+ validatePair(value, layoutType);
+ this.value = value;
+ this.layoutType = layoutType;
+ }
+
+ public float getValue() {
+ return value;
+ }
+
+ public void setValue(float value) {
+ validatePair(value, layoutType);
+ this.value = value;
+ }
+
+ public abstract float getPixelValue(float size);
+
+ public LayoutType getLayoutType() {
+ return layoutType;
+ }
+
+ public void setLayoutType(LayoutType layoutType) {
+ validatePair(value, layoutType);
+ this.layoutType = layoutType;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetric.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetric.java
new file mode 100644
index 0000000..7183760
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetric.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+public abstract class PositionMetric<LayoutType extends Enum> extends LayoutMetric<LayoutType> {
+
+ protected enum Origin {
+ FROM_BEGINING,
+ FROM_CENTER,
+ FROM_END
+ }
+
+ protected enum LayoutMode {
+ ABSOLUTE,
+ RELATIVE
+ }
+
+ public PositionMetric(float value, LayoutType layoutType) {
+ super(value, layoutType);
+ }
+
+ /**
+ * Throws IllegalArgumentException if there is a problem.
+ * @param value
+ * @param layoutMode
+ * @throws IllegalArgumentException
+ */
+ protected static void validateValue(float value, LayoutMode layoutMode) throws IllegalArgumentException {
+ switch(layoutMode) {
+ case ABSOLUTE:
+ break;
+ case RELATIVE:
+ if(value < -1 || value > 1) {
+ throw new IllegalArgumentException("Relative layout values must be within the range of -1 to 1.");
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown LayoutMode: " + layoutMode);
+ }
+
+ }
+
+ protected float getAbsolutePosition(float size, Origin origin) {
+ switch(origin) {
+ case FROM_BEGINING:
+ return getValue();
+ case FROM_CENTER:
+ return (size/2f) + getValue();
+ case FROM_END:
+ return size - getValue();
+ default:
+ throw new IllegalArgumentException("Unsupported Origin: " + origin);
+ }
+ }
+
+ protected float getRelativePosition(float size, Origin origin) {
+ //throw new UnsupportedOperationException("Not yet implemented.");
+
+ switch(origin) {
+ case FROM_BEGINING:
+ return size * getValue();
+ case FROM_CENTER:
+ return (size/2f) + ((size/2f) * getValue());
+ case FROM_END:
+ return size + (size*getValue());
+ default:
+ throw new IllegalArgumentException("Unsupported Origin: " + origin);
+ }
+
+ }
+
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetrics.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetrics.java
new file mode 100644
index 0000000..34d06a3
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/PositionMetrics.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+public class PositionMetrics implements Comparable<PositionMetrics> {
+
+ private XPositionMetric xPositionMetric;
+ private YPositionMetric yPositionMetric;
+ private AnchorPosition anchor;
+ private float layerDepth;
+
+ public PositionMetrics(float x, XLayoutStyle xLayoutStyle, float y, YLayoutStyle yLayoutStyle, AnchorPosition anchor) {
+ setXPositionMetric(new XPositionMetric(x, xLayoutStyle));
+ setYPositionMetric(new YPositionMetric(y, yLayoutStyle));
+ setAnchor(anchor);
+
+ }
+
+ public YPositionMetric getYPositionMetric() {
+ return yPositionMetric;
+ }
+
+ public void setYPositionMetric(YPositionMetric yPositionMetric) {
+ this.yPositionMetric = yPositionMetric;
+ }
+
+ public AnchorPosition getAnchor() {
+ return anchor;
+ }
+
+ public void setAnchor(AnchorPosition anchor) {
+ this.anchor = anchor;
+ }
+
+ @Override
+ public int compareTo(PositionMetrics o) {
+ if(this.layerDepth < o.layerDepth) {
+ return -1;
+ } else if(this.layerDepth == o.layerDepth) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ public XPositionMetric getXPositionMetric() {
+ return xPositionMetric;
+ }
+
+ public void setXPositionMetric(XPositionMetric xPositionMetric) {
+ this.xPositionMetric = xPositionMetric;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/RenderBundle.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/RenderBundle.java
new file mode 100644
index 0000000..75303ac
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/RenderBundle.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import com.androidplot.Series;
+import com.androidplot.xy.XYSeriesFormatter;
+
+public abstract class RenderBundle<RenderBundleType extends RenderBundle, SeriesType extends Series, SeriesFormatterType extends XYSeriesFormatter> {
+ //private XYDataset series;
+ private Series series;
+ private SeriesFormatterType formatter;
+
+ public RenderBundle(SeriesType series, SeriesFormatterType formatter) {
+ this.formatter = formatter;
+ this.series = series;
+ }
+
+ public Series getSeries() {
+ return series;
+ }
+
+ public void setSeries(Series series) {
+ this.series = series;
+ }
+
+ public SeriesFormatterType getFormatter() {
+ return formatter;
+ }
+
+ public void setFormatter(SeriesFormatterType formatter) {
+ this.formatter = formatter;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/Resizable.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/Resizable.java
new file mode 100644
index 0000000..34a1c89
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/Resizable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import com.androidplot.util.DisplayDimensions;
+
+/**
+ * Used by classes that depend on dimensional values to lay themselves out and draw.
+ * Consideration should be given to synchronizing with any draw routines that also
+ * exist within the class.
+ */
+public interface Resizable {
+
+ /**
+ * Called when a change to the class' dimensions is made. This method is responsible
+ * for cascading calls to update for any logical children of this class, for example
+ * the Plot class is responsible for updating the LayoutManager. Note that while dims
+ * is marked final in this interface, the compiler will not enforce it. Implementors of
+ * this method should take care not to make changes to dims as this will affect parent
+ * Resizables in likely undesired ways.
+ * @param dims
+ */
+ public void layout(final DisplayDimensions dims);
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesAndFormatterList.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesAndFormatterList.java
new file mode 100644
index 0000000..7d90e32
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesAndFormatterList.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import com.androidplot.Series;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Associates a Series with a Formatter.
+ * @param <SeriesType>
+ * @param <FormatterType>
+ */
+public class SeriesAndFormatterList<SeriesType extends Series, FormatterType> {
+ private LinkedList<SeriesType> seriesList;
+ private LinkedList<FormatterType> formatterList;
+ {
+ seriesList = new LinkedList<SeriesType>();
+ formatterList = new LinkedList<FormatterType>();
+ }
+
+ public boolean contains(SeriesType series) {
+ return seriesList.contains(series);
+ }
+
+ public int size() {
+ return seriesList.size();
+ }
+
+ public List<SeriesType> getSeriesList() {
+ return seriesList;
+ }
+
+ public List<FormatterType> getFormatterList() {
+ return formatterList;
+ }
+
+ public boolean add(SeriesType series, FormatterType formatter) {
+ if(series == null || formatter == null) {
+ throw new IllegalArgumentException("series and formatter must not be null.");
+ }
+ if(seriesList.contains(series)) {
+ return false;
+ }
+ seriesList.add(series);
+ formatterList.add(formatter);
+ return true;
+ }
+
+ public boolean remove(SeriesType series) {
+ int index = seriesList.indexOf(series);
+ if(index < 0) {
+ return false;
+ }
+ seriesList.remove(index);
+ formatterList.remove(index);
+ return true;
+ }
+
+
+ public FormatterType getFormatter(SeriesType series) {
+ return formatterList.get(seriesList.indexOf(series));
+ }
+
+ public FormatterType getFormatter(int index) {
+ return formatterList.get(index);
+ }
+
+ public SeriesType getSeries(int index) {
+ return seriesList.get(index);
+ }
+
+ public FormatterType setFormatter(SeriesType series, FormatterType formatter) {
+ return formatterList.set(seriesList.indexOf(series), formatter);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesRenderer.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesRenderer.java
new file mode 100644
index 0000000..b32f12d
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SeriesRenderer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.graphics.Region;
+import com.androidplot.Series;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.Plot;
+
+public abstract class SeriesRenderer
+ <PlotType extends Plot, SeriesType extends Series, SeriesFormatterType extends Formatter> {
+ private PlotType plot;
+
+ public SeriesRenderer(PlotType plot) {
+ this.plot = plot;
+ }
+
+ public PlotType getPlot() {
+ return plot;
+ }
+
+ public void setPlot(PlotType plot) {
+ this.plot = plot;
+ }
+
+ public SeriesAndFormatterList<SeriesType,SeriesFormatterType> getSeriesAndFormatterList() {
+ return plot.getSeriesAndFormatterListForRenderer(getClass());
+ }
+
+ public SeriesFormatterType getFormatter(SeriesType series) {
+ return (SeriesFormatterType) plot.getFormatter(series, getClass());
+ }
+
+ public void render(Canvas canvas, RectF plotArea) throws PlotRenderException {
+ onRender(canvas, plotArea);
+ }
+ public abstract void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException;
+
+ /**
+ * Draw the legend icon in the rect passed in.
+ * @param canvas
+ * @param rect
+ */
+ protected abstract void doDrawLegendIcon(Canvas canvas, RectF rect, SeriesFormatterType formatter);
+
+ public void drawSeriesLegendIcon(Canvas canvas, RectF rect, SeriesFormatterType formatter) {
+ //int state = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ try {
+ canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.clipRect(rect, Region.Op.INTERSECT);
+ doDrawLegendIcon(canvas, rect, formatter);
+ //canvas.restoreToCount(state);
+ } finally {
+ canvas.restore();
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeLayoutType.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeLayoutType.java
new file mode 100644
index 0000000..7cb6ff3
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeLayoutType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+/**
+ * SizeLayoutType is an enumeration of algorithms available for calculating an arbitrary dimension of a widget.
+ * Each algorithm also takes a single value called "val" in this doc.
+ * ABSOLUTE - Val is treated as absolute. If val is 5 then the size of the widget along the associated axis is 5 pixels.
+ *
+ * RELATIVE - Val represents the percentage of the display that the widget should fill along the associated axis. For example,
+ * if the total size of the owning plot is 120 pixels and val is set to 50 then the size of the widget along the associated axis
+ * is 60; 50% of 120 = 60.
+ *
+ * FILL - Widget completely fills along the associated axis, minus
+ */
+public enum SizeLayoutType {
+ ABSOLUTE,
+ RELATIVE,
+ FILL
+} \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetric.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetric.java
new file mode 100644
index 0000000..57e57c0
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetric.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+/**
+ * Encapsulates a sizing algorithm and an associated value.
+ *
+ * The available algorithms list are stored in the {@link SizeLayoutType} enumeration.
+ *
+ */
+public class SizeMetric extends LayoutMetric<SizeLayoutType> {
+
+ public SizeMetric(float value, SizeLayoutType layoutType) {
+ super(value, layoutType);
+ }
+
+ protected void validatePair(float value, SizeLayoutType layoutType) {
+ switch(layoutType) {
+ case RELATIVE:
+ if(value < 0 || value > 1) {
+ throw new IllegalArgumentException("SizeMetric Relative and Hybrid layout values must be within the range of 0 to 1.");
+ }
+ case ABSOLUTE:
+ case FILL:
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public float getPixelValue(float size) {
+ //switch(layoutType)
+ switch(getLayoutType()) {
+ case ABSOLUTE:
+ return getValue();
+ case RELATIVE:
+ return getValue() * size;
+ case FILL:
+ return size - getValue();
+ default:
+ throw new IllegalArgumentException("Unsupported LayoutType: " + this.getLayoutType());
+ }
+ }
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetrics.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetrics.java
new file mode 100644
index 0000000..28f0ce5
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/SizeMetrics.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+import com.androidplot.util.PixelUtils;
+
+/**
+ * Encapsulates sizing preferences associated with a Widget; how/if it scales etc.
+ */
+public class SizeMetrics {
+ private SizeMetric heightMetric;
+ private SizeMetric widthMetric;
+
+ /**
+ * Convenience constructor. Wraps {@link #SizeMetrics(SizeMetric, SizeMetric)}.
+ * @param height Height value used algorithm to calculate the height of the associated widget(s).
+ * @param heightLayoutType Algorithm used to calculate the height of the associated widget(s).
+ * @param width Width value used algorithm to calculate the width of the associated widget(s).
+ * @param widthLayoutType Algorithm used to calculate the width of the associated widget(s).
+ */
+ public SizeMetrics(float height, SizeLayoutType heightLayoutType, float width, SizeLayoutType widthLayoutType) {
+ heightMetric = new SizeMetric(height, heightLayoutType);
+ widthMetric = new SizeMetric(width, widthLayoutType);
+ }
+
+ /**
+ * Creates a new SizeMetrics instance using the specified size layout algorithm and value.
+ * See {@link SizeMetric} for details on what can be passed in.
+ * @param heightMetric
+ * @param widthMetric
+ */
+ public SizeMetrics(SizeMetric heightMetric, SizeMetric widthMetric) {
+ this.heightMetric = heightMetric;
+ this.widthMetric = widthMetric;
+ }
+
+ public SizeMetric getHeightMetric() {
+ return heightMetric;
+ }
+
+ public void setHeightMetric(SizeMetric heightMetric) {
+ this.heightMetric = heightMetric;
+ }
+
+ public SizeMetric getWidthMetric() {
+ return widthMetric;
+ }
+
+ /**
+ * Calculates a RectF with calculated width and height. The top-left corner is set to 0,0.
+ * @param canvasRect
+ * @return
+ */
+ public RectF getRectF(RectF canvasRect) {
+ return new RectF(
+ 0,
+ 0,
+ widthMetric.getPixelValue(canvasRect.width()),
+ heightMetric.getPixelValue(canvasRect.height()));
+ }
+
+ /**
+ * Same as getRectF but with edges rounded to the nearest full pixel.
+ * @param canvasRect
+ * @return
+ */
+ public RectF getRoundedRect(RectF canvasRect) {
+ return PixelUtils.nearestPixRect(0, 0, widthMetric.getPixelValue(canvasRect.width()),
+ heightMetric.getPixelValue(canvasRect.height()));
+ }
+
+ public void setWidthMetric(SizeMetric widthMetric) {
+ this.widthMetric = widthMetric;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableModel.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableModel.java
new file mode 100644
index 0000000..96eaa7a
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableModel.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+
+import java.util.Iterator;
+
+public abstract class TableModel {
+ private TableOrder order;
+
+ protected TableModel(TableOrder order) {
+ setOrder(order);
+ }
+
+ public abstract Iterator<RectF> getIterator(RectF tableRect, int totalElements);
+
+ //public abstract RectF getCellRect(RectF tableRect, int numElements);
+
+ public TableOrder getOrder() {
+ return order;
+ }
+
+ public void setOrder(TableOrder order) {
+ this.order = order;
+ }
+
+ public enum Axis {
+ ROW,
+ COLUMN
+ }
+
+ public enum CellSizingMethod {
+ FIXED,
+ FILL
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableOrder.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableOrder.java
new file mode 100644
index 0000000..5af231e
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableOrder.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+public enum TableOrder {
+ ROW_MAJOR, // standard c-style
+ COLUMN_MAJOR
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableSizingMethod.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableSizingMethod.java
new file mode 100644
index 0000000..501fb92
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TableSizingMethod.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+/**
+ * The sizing methods available to a table.
+ * AUTO: The table is divided evenly int tableSize/numElements sections.
+ * FIXED: Each element in the table has a predefined number of pixels
+ * regardless of what the table's dimensions actually are.
+ */
+public enum TableSizingMethod {
+ AUTO,
+ FIXED
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/TextOrientationType.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TextOrientationType.java
new file mode 100644
index 0000000..0b78ddd
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/TextOrientationType.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+public enum TextOrientationType {
+ HORIZONTAL,
+ VERTICAL_ASCENDING,
+ VERTICAL_DESCENDING
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/XLayoutStyle.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/XLayoutStyle.java
new file mode 100644
index 0000000..9264bf0
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/XLayoutStyle.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+public enum XLayoutStyle {
+ ABSOLUTE_FROM_LEFT,
+ ABSOLUTE_FROM_RIGHT,
+ ABSOLUTE_FROM_CENTER,
+ RELATIVE_TO_LEFT,
+ RELATIVE_TO_RIGHT,
+ RELATIVE_TO_CENTER
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/XPositionMetric.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/XPositionMetric.java
new file mode 100644
index 0000000..fac02d2
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/XPositionMetric.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+
+import com.androidplot.ui.PositionMetric;
+import com.androidplot.ui.XLayoutStyle;
+
+public class XPositionMetric extends PositionMetric<XLayoutStyle> {
+
+ //private XLayoutStyle layoutType;
+
+ public XPositionMetric(float value, XLayoutStyle layoutStyle) {
+ super(value, layoutStyle);
+ validatePair(value, layoutStyle);
+ //this.layoutStyle = layoutStyle;
+ }
+
+ /**
+ * Throws IllegalArgumentException if there is a problem.
+ * @param value
+ */
+ protected void validatePair(float value, XLayoutStyle layoutStyle) {
+ switch(layoutStyle) {
+ case ABSOLUTE_FROM_LEFT:
+ case ABSOLUTE_FROM_RIGHT:
+ case ABSOLUTE_FROM_CENTER:
+ validateValue(value, PositionMetric.LayoutMode.ABSOLUTE);
+ break;
+ case RELATIVE_TO_LEFT:
+ case RELATIVE_TO_RIGHT:
+ case RELATIVE_TO_CENTER:
+ validateValue(value, PositionMetric.LayoutMode.RELATIVE);
+ }
+ }
+
+ @Override
+ public float getPixelValue(float size) {
+ switch(getLayoutType()) {
+ case ABSOLUTE_FROM_LEFT:
+ return this.getAbsolutePosition(size, PositionMetric.Origin.FROM_BEGINING);
+ case ABSOLUTE_FROM_RIGHT:
+ return this.getAbsolutePosition(size, PositionMetric.Origin.FROM_END);
+ case ABSOLUTE_FROM_CENTER:
+ return this.getAbsolutePosition(size, PositionMetric.Origin.FROM_CENTER);
+ case RELATIVE_TO_LEFT:
+ return this.getRelativePosition(size, PositionMetric.Origin.FROM_BEGINING);
+ case RELATIVE_TO_RIGHT:
+ return this.getRelativePosition(size, PositionMetric.Origin.FROM_END);
+ case RELATIVE_TO_CENTER:
+ return this.getRelativePosition(size, PositionMetric.Origin.FROM_CENTER);
+ default:
+ throw new IllegalArgumentException("Unsupported LayoutType: " + this.getLayoutType());
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/YLayoutStyle.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/YLayoutStyle.java
new file mode 100644
index 0000000..64fb467
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/YLayoutStyle.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+public enum YLayoutStyle {
+ ABSOLUTE_FROM_TOP,
+ ABSOLUTE_FROM_BOTTOM,
+ ABSOLUTE_FROM_CENTER,
+ RELATIVE_TO_TOP,
+ RELATIVE_TO_BOTTOM,
+ RELATIVE_TO_CENTER
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/YPositionMetric.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/YPositionMetric.java
new file mode 100644
index 0000000..1101c1e
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/YPositionMetric.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import com.androidplot.ui.PositionMetric;
+import com.androidplot.ui.YLayoutStyle;
+
+public class YPositionMetric extends PositionMetric<YLayoutStyle> {
+ /*
+ public enum YLayoutStyle {
+ ABSOLUTE_FROM_TOP,
+ ABSOLUTE_FROM_BOTTOM,
+ ABSOLUTE_FROM_CENTER,
+ RELATIVE_TO_TOP,
+ RELATIVE_TO_BOTTOM,
+ RELATIVE_TO_CENTER
+ }
+ */
+
+ //private YLayoutStyle layoutType;
+
+ public YPositionMetric(float value, YLayoutStyle layoutStyle) {
+ super(value, layoutStyle);
+ //this.layoutStyle = layoutStyle;
+
+
+ }
+
+ /*
+ @Override
+ public void set(float value, YLayoutStyle layoutType) {
+ validatePair(value, layoutType);
+ super.set(value, layoutType);
+ }
+
+ @Override
+ public void setLayoutType(YLayoutStyle layoutType) {
+ validatePair(getValue(), layoutType);
+ super.setLayoutType(layoutType);
+ }
+
+ @Override
+ public void setValue(float value) {
+ validatePair(value, getLayoutType());
+ super.setValue(value);
+ }
+ */
+
+ /**
+ * Throws IllegalArgumentException if there is a problem.
+ * @param value
+ */
+ protected void validatePair(float value, YLayoutStyle layoutStyle) {
+ switch(layoutStyle) {
+ case ABSOLUTE_FROM_TOP:
+ case ABSOLUTE_FROM_BOTTOM:
+ case ABSOLUTE_FROM_CENTER:
+ validateValue(value, PositionMetric.LayoutMode.ABSOLUTE);
+ break;
+ case RELATIVE_TO_TOP:
+ case RELATIVE_TO_BOTTOM:
+ case RELATIVE_TO_CENTER:
+ validateValue(value, PositionMetric.LayoutMode.RELATIVE);
+ }
+ }
+
+ @Override
+ public float getPixelValue(float size) {
+ switch(getLayoutType()) {
+ case ABSOLUTE_FROM_TOP:
+ return this.getAbsolutePosition(size, PositionMetric.Origin.FROM_BEGINING);
+ case ABSOLUTE_FROM_BOTTOM:
+ return this.getAbsolutePosition(size, PositionMetric.Origin.FROM_END);
+ case ABSOLUTE_FROM_CENTER:
+ return this.getAbsolutePosition(size, PositionMetric.Origin.FROM_CENTER);
+ case RELATIVE_TO_TOP:
+ return this.getRelativePosition(size, PositionMetric.Origin.FROM_BEGINING);
+ case RELATIVE_TO_BOTTOM:
+ return this.getRelativePosition(size, PositionMetric.Origin.FROM_END);
+ case RELATIVE_TO_CENTER:
+ return this.getRelativePosition(size, PositionMetric.Origin.FROM_CENTER);
+ default:
+ throw new IllegalArgumentException("Unsupported LayoutType: " + this.getLayoutType());
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java
new file mode 100644
index 0000000..f3da9fc
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui.widget;
+
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.LayoutManager;
+import com.androidplot.ui.SizeMetrics;
+
+public class EmptyWidget extends Widget {
+
+ public EmptyWidget(LayoutManager layoutManager, SizeMetrics sizeMetrics) {
+ super(layoutManager, sizeMetrics);
+ }
+ @Override
+ protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException {
+ // nothing to do
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java
new file mode 100644
index 0000000..96615b8
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui.widget;
+
+import android.graphics.*;
+import android.util.Log;
+import com.androidplot.ui.*;
+import com.androidplot.util.FontUtils;
+
+public class TextLabelWidget extends Widget {
+ private static final String TAG = TextLabelWidget.class.getName();
+
+ private String text;
+ private Paint labelPaint;
+
+ private TextOrientationType orientation;
+
+ private boolean autoPackEnabled = true;
+
+ {
+ labelPaint = new Paint();
+ labelPaint.setColor(Color.WHITE);
+ labelPaint.setAntiAlias(true);
+ labelPaint.setTextAlign(Paint.Align.CENTER);
+ }
+
+ public TextLabelWidget(LayoutManager layoutManager, SizeMetrics sizeMetrics) {
+ this(layoutManager, sizeMetrics, TextOrientationType.HORIZONTAL);
+ }
+
+ public TextLabelWidget(LayoutManager layoutManager, String title, SizeMetrics sizeMetrics, TextOrientationType orientation) {
+ this(layoutManager, sizeMetrics, orientation);
+ setText(title);
+ }
+
+ public TextLabelWidget(LayoutManager layoutManager, SizeMetrics sizeMetrics, TextOrientationType orientation) {
+ super(layoutManager, new SizeMetrics(0, SizeLayoutType.ABSOLUTE, 0, SizeLayoutType.ABSOLUTE));
+ //this.plot = plot;
+ //this.setWidth(labelPaint.measureText(plot.getTitle()));
+ //this.setHeight(labelPaint.getFontMetrics().top);
+ setSize(sizeMetrics);
+ this.orientation = orientation;
+ }
+
+ @Override
+ protected void onMetricsChanged(SizeMetrics olds, SizeMetrics news) {
+ if(autoPackEnabled) {
+ pack();
+ }
+ }
+
+ @Override
+ public void onPostInit() {
+ if(autoPackEnabled) {
+ pack();
+ }
+ }
+
+ //protected abstract String getText();
+
+ /**
+ * Sets the dimensions of the widget to exactly contain the text contents
+ */
+ public void pack() {
+ Log.d(TAG, "Packing...");
+ Rect size = FontUtils.getStringDimensions(text, getLabelPaint());
+ if(size == null) {
+ Log.w(TAG, "Attempt to pack empty text.");
+ return;
+ }
+ switch(orientation) {
+ case HORIZONTAL:
+ setSize(new SizeMetrics(size.height(), SizeLayoutType.ABSOLUTE, size.width()+2, SizeLayoutType.ABSOLUTE));
+ break;
+ case VERTICAL_ASCENDING:
+ case VERTICAL_DESCENDING:
+ setSize(new SizeMetrics(size.width(), SizeLayoutType.ABSOLUTE, size.height()+2, SizeLayoutType.ABSOLUTE));
+ break;
+ }
+ refreshLayout();
+
+ }
+
+ /**
+ * Do not call this method directly. It is indirectly invoked every time a plot is
+ * redrawn.
+ * @param canvas The Canvas to draw onto
+ * @param widgetRect the size and coordinates of this widget
+ */
+ @Override
+ public void doOnDraw(Canvas canvas, RectF widgetRect) {
+ if(text == null || text.length() == 0) {
+ return;
+ }
+ //FontUtils.getStringDimensions(text, labelPaint);
+ float vOffset = labelPaint.getFontMetrics().descent;
+ PointF start = getAnchorCoordinates(widgetRect,
+ AnchorPosition.CENTER);
+
+ // BEGIN ROTATION CALCULATION
+ //int canvasState = canvas.save(Canvas.ALL_SAVE_FLAG);
+
+ try {
+ canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.translate(start.x, start.y);
+ switch (orientation) {
+ case HORIZONTAL:
+ break;
+ case VERTICAL_ASCENDING:
+ canvas.rotate(-90);
+ break;
+ case VERTICAL_DESCENDING:
+ canvas.rotate(90);
+ break;
+ default:
+
+ throw new UnsupportedOperationException("Orientation " + orientation + " not yet implemented for TextLabelWidget.");
+ }
+ canvas.drawText(text, 0, vOffset, labelPaint);
+ } finally {
+ //canvas.restoreToCount(canvasState);
+ canvas.restore();
+ }
+
+ // END ROTATION CALCULATION
+ }
+
+ public Paint getLabelPaint() {
+ return labelPaint;
+ }
+
+ public void setLabelPaint(Paint labelPaint) {
+ this.labelPaint = labelPaint;
+
+ // when paint changes, packing params change too so check
+ // to see if we need to resize:
+ if(autoPackEnabled) {
+ pack();
+ }
+ }
+
+ public TextOrientationType getOrientation() {
+ return orientation;
+ }
+
+ public void setOrientation(TextOrientationType orientation) {
+ this.orientation = orientation;
+ if(autoPackEnabled) {
+ pack();
+ }
+ }
+
+ public boolean isAutoPackEnabled() {
+ return autoPackEnabled;
+ }
+
+ public void setAutoPackEnabled(boolean autoPackEnabled) {
+ this.autoPackEnabled = autoPackEnabled;
+ if(autoPackEnabled) {
+ pack();
+ }
+ }
+
+ public void setText(String text) {
+ Log.d(TAG, "Setting textLabel to: " + text);
+ this.text = text;
+ if(autoPackEnabled) {
+ pack();
+ }
+ }
+
+ public String getText() {
+ return text;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/Widget.java b/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/Widget.java
new file mode 100644
index 0000000..1e6e663
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/ui/widget/Widget.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui.widget;
+
+import android.graphics.*;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.*;
+import com.androidplot.util.DisplayDimensions;
+import com.androidplot.ui.XLayoutStyle;
+import com.androidplot.ui.YLayoutStyle;
+import com.androidplot.util.PixelUtils;
+
+/**
+ * A Widget is a graphical sub-element of a Plot that can be positioned relative
+ * to the bounds of the Plot.
+ */
+public abstract class Widget implements BoxModelable, Resizable {
+
+ private Paint borderPaint;
+ private Paint backgroundPaint;
+ private boolean clippingEnabled = true;
+ private BoxModel boxModel = new BoxModel();
+ private SizeMetrics sizeMetrics;
+ private DisplayDimensions plotDimensions = new DisplayDimensions();
+ private DisplayDimensions widgetDimensions = new DisplayDimensions();
+ private boolean isVisible = true;
+ private PositionMetrics positionMetrics;
+ private LayoutManager layoutManager;
+
+ public Widget(LayoutManager layoutManager, SizeMetric heightMetric, SizeMetric widthMetric) {
+ this(layoutManager, new SizeMetrics(heightMetric, widthMetric));
+ }
+
+ public Widget(LayoutManager layoutManager, SizeMetrics sizeMetrics) {
+ this.layoutManager = layoutManager;
+ SizeMetrics oldSize = this.sizeMetrics;
+ setSize(sizeMetrics);
+ onMetricsChanged(oldSize, sizeMetrics);
+ }
+
+ public DisplayDimensions getWidgetDimensions() {
+ return widgetDimensions;
+ }
+
+ public AnchorPosition getAnchor() {
+ return getPositionMetrics().getAnchor();
+ }
+
+ public void setAnchor(AnchorPosition anchor) {
+ getPositionMetrics().setAnchor(anchor);
+ }
+
+
+ /**
+ * Same as {@link #position(float, com.androidplot.ui.XLayoutStyle, float, com.androidplot.ui.YLayoutStyle, com.androidplot.ui.AnchorPosition)}
+ * but with the anchor parameter defaulted to the upper left corner.
+ * @param x
+ * @param xLayoutStyle
+ * @param y
+ * @param yLayoutStyle
+ */
+ public void position(float x, XLayoutStyle xLayoutStyle, float y, YLayoutStyle yLayoutStyle) {
+ position(x, xLayoutStyle, y, yLayoutStyle, AnchorPosition.LEFT_TOP);
+ }
+
+ /**
+ * @param x X-Coordinate of the top left corner of element. When using RELATIVE, must be a value between 0 and 1.
+ * @param xLayoutStyle LayoutType to use when orienting this element's X-Coordinate.
+ * @param y Y_VALS_ONLY-Coordinate of the top-left corner of element. When using RELATIVE, must be a value between 0 and 1.
+ * @param yLayoutStyle LayoutType to use when orienting this element's Y_VALS_ONLY-Coordinate.
+ * @param anchor The point of reference used by this positioning call.
+ */
+ public void position(float x, XLayoutStyle xLayoutStyle, float y,
+ YLayoutStyle yLayoutStyle, AnchorPosition anchor) {
+ setPositionMetrics(new PositionMetrics(x, xLayoutStyle, y, yLayoutStyle, anchor));
+ layoutManager.addToTop(this);
+ }
+
+ /**
+ * Can be overridden by subclasses to respond to resizing events.
+ *
+ * @param oldSize
+ * @param newSize
+ */
+ protected void onMetricsChanged(SizeMetrics oldSize, SizeMetrics newSize) {
+ }
+
+ /**
+ * Can be overridden by subclasses to handle any final resizing etc. that
+ * can only be done after XML configuration etc. has completed.
+ */
+ public void onPostInit() {
+ }
+
+ /**
+ * Determines whether or not point lies within this Widget.
+ *
+ * @param point
+ * @return
+ */
+ public boolean containsPoint(PointF point) {
+ //return outlineRect != null && outlineRect.contains(point.x, point.y);
+ return widgetDimensions.canvasRect.contains(point.x, point.y);
+ }
+
+ public void setSize(SizeMetrics sizeMetrics) {
+ this.sizeMetrics = sizeMetrics;
+ }
+
+
+ public void setWidth(float width) {
+ sizeMetrics.getWidthMetric().setValue(width);
+ }
+
+ public void setWidth(float width, SizeLayoutType layoutType) {
+ sizeMetrics.getWidthMetric().set(width, layoutType);
+ }
+
+ public void setHeight(float height) {
+ sizeMetrics.getHeightMetric().setValue(height);
+ }
+
+ public void setHeight(float height, SizeLayoutType layoutType) {
+ sizeMetrics.getHeightMetric().set(height, layoutType);
+ }
+
+ public SizeMetric getWidthMetric() {
+ return sizeMetrics.getWidthMetric();
+ }
+
+ public SizeMetric getHeightMetric() {
+ return sizeMetrics.getHeightMetric();
+ }
+
+ public float getWidthPix(float size) {
+ return sizeMetrics.getWidthMetric().getPixelValue(size);
+ }
+
+ public float getHeightPix(float size) {
+ return sizeMetrics.getHeightMetric().getPixelValue(size);
+ }
+
+ public RectF getMarginatedRect(RectF widgetRect) {
+ return boxModel.getMarginatedRect(widgetRect);
+ }
+
+ public RectF getPaddedRect(RectF widgetMarginRect) {
+ return boxModel.getPaddedRect(widgetMarginRect);
+ }
+
+ public void setMarginRight(float marginRight) {
+ boxModel.setMarginRight(marginRight);
+ }
+
+ public void setMargins(float left, float top, float right, float bottom) {
+ boxModel.setMargins(left, top, right, bottom);
+ }
+
+ public void setPadding(float left, float top, float right, float bottom) {
+ boxModel.setPadding(left, top, right, bottom);
+ }
+
+ public float getMarginTop() {
+ return boxModel.getMarginTop();
+ }
+
+ public void setMarginTop(float marginTop) {
+ boxModel.setMarginTop(marginTop);
+ }
+
+ public float getMarginBottom() {
+ return boxModel.getMarginBottom();
+ }
+
+ @Override
+ public float getPaddingLeft() {
+ return boxModel.getPaddingLeft();
+ }
+
+ @Override
+ public void setPaddingLeft(float paddingLeft) {
+ boxModel.setPaddingLeft(paddingLeft);
+ }
+
+ @Override
+ public float getPaddingTop() {
+ return boxModel.getPaddingTop();
+ }
+
+ @Override
+ public void setPaddingTop(float paddingTop) {
+ boxModel.setPaddingTop(paddingTop);
+ }
+
+ @Override
+ public float getPaddingRight() {
+ return boxModel.getPaddingRight();
+ }
+
+ @Override
+ public void setPaddingRight(float paddingRight) {
+ boxModel.setPaddingRight(paddingRight);
+ }
+
+ @Override
+ public float getPaddingBottom() {
+ return boxModel.getPaddingBottom();
+ }
+
+ @Override
+ public void setPaddingBottom(float paddingBottom) {
+ boxModel.setPaddingBottom(paddingBottom);
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ public void setMarginBottom(float marginBottom) {
+ boxModel.setMarginBottom(marginBottom);
+ }
+
+ public float getMarginLeft() {
+ return boxModel.getMarginLeft();
+ }
+
+ public void setMarginLeft(float marginLeft) {
+ boxModel.setMarginLeft(marginLeft);
+ }
+
+ public float getMarginRight() {
+ return boxModel.getMarginRight();
+ }
+
+ /**
+ * Causes the pixel dimensions used for rendering this Widget
+ * to be recalculated. Should be called any time a parameter that factors
+ * into this Widget's size or position is altered.
+ */
+ public synchronized void refreshLayout() {
+ if(positionMetrics == null) {
+ // make sure positionMetrics have been set. this method can be
+ // automatically called during xml configuration of certain params
+ // before the widget is fully configured.
+ return;
+ }
+ float elementWidth = getWidthPix(plotDimensions.paddedRect.width());
+ float elementHeight = getHeightPix(plotDimensions.paddedRect.height());
+ PointF coords = getElementCoordinates(elementHeight,
+ elementWidth, plotDimensions.paddedRect, positionMetrics);
+
+ RectF widgetRect = new RectF(coords.x, coords.y,
+ coords.x + elementWidth, coords.y + elementHeight);
+ RectF marginatedWidgetRect = getMarginatedRect(widgetRect);
+ RectF paddedWidgetRect = getPaddedRect(marginatedWidgetRect);
+ widgetDimensions = new DisplayDimensions(widgetRect,
+ marginatedWidgetRect, paddedWidgetRect);
+ }
+
+ @Override
+ public synchronized void layout(final DisplayDimensions plotDimensions) {
+ this.plotDimensions = plotDimensions;
+ refreshLayout();
+ }
+
+ public PointF getElementCoordinates(float height, float width, RectF viewRect, PositionMetrics metrics) {
+ float x = metrics.getXPositionMetric().getPixelValue(viewRect.width()) + viewRect.left;
+ float y = metrics.getYPositionMetric().getPixelValue(viewRect.height()) + viewRect.top;
+ PointF point = new PointF(x, y);
+ return PixelUtils.sub(point, getAnchorOffset(width, height, metrics.getAnchor()));
+ }
+
+ public static PointF getAnchorOffset(float width, float height, AnchorPosition anchorPosition) {
+ PointF point = new PointF();
+ switch (anchorPosition) {
+ case LEFT_TOP:
+ break;
+ case LEFT_MIDDLE:
+ point.set(0, height / 2);
+ break;
+ case LEFT_BOTTOM:
+ point.set(0, height);
+ break;
+ case RIGHT_TOP:
+ point.set(width, 0);
+ break;
+ case RIGHT_BOTTOM:
+ point.set(width, height);
+ break;
+ case RIGHT_MIDDLE:
+ point.set(width, height / 2);
+ break;
+ case TOP_MIDDLE:
+ point.set(width / 2, 0);
+ break;
+ case BOTTOM_MIDDLE:
+ point.set(width / 2, height);
+ break;
+ case CENTER:
+ point.set(width / 2, height / 2);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported anchor location: " + anchorPosition);
+ }
+ return point;
+ }
+
+ public static PointF getAnchorCoordinates(RectF widgetRect, AnchorPosition anchorPosition) {
+ return PixelUtils.add(new PointF(widgetRect.left, widgetRect.top),
+ getAnchorOffset(widgetRect.width(), widgetRect.height(), anchorPosition));
+ }
+
+ public static PointF getAnchorCoordinates(float x, float y, float width, float height, AnchorPosition anchorPosition) {
+ return getAnchorCoordinates(new RectF(x, y, x+width, y+height), anchorPosition);
+ }
+
+ public void draw(Canvas canvas, RectF widgetRect) throws PlotRenderException {
+ //outlineRect = widgetRect;
+ if (isVisible()) {
+ if (backgroundPaint != null) {
+ drawBackground(canvas, widgetDimensions.canvasRect);
+ }
+
+ /* RectF marginatedRect = new RectF(outlineRect.left + marginLeft,
+ outlineRect.top + marginTop,
+ outlineRect.right - marginRight,
+ outlineRect.bottom - marginBottom);*/
+
+ /*RectF marginatedRect = boxModel.getMarginatedRect(widgetRect);
+ RectF paddedRect = boxModel.getPaddedRect(marginatedRect);*/
+ doOnDraw(canvas, widgetDimensions.paddedRect);
+
+ if (borderPaint != null) {
+ drawBorder(canvas, widgetDimensions.paddedRect);
+ }
+ }
+ }
+
+ protected void drawBorder(Canvas canvas, RectF paddedRect) {
+ canvas.drawRect(paddedRect, borderPaint);
+ }
+
+ protected void drawBackground(Canvas canvas, RectF widgetRect) {
+ canvas.drawRect(widgetRect, backgroundPaint);
+ }
+
+ /**
+ * @param canvas The Canvas to draw onto
+ * @param widgetRect the size and coordinates of this widget
+ */
+ protected abstract void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException;
+
+ public Paint getBorderPaint() {
+ return borderPaint;
+ }
+
+ public void setBorderPaint(Paint borderPaint) {
+ this.borderPaint = borderPaint;
+ }
+
+ public Paint getBackgroundPaint() {
+ return backgroundPaint;
+ }
+
+ public void setBackgroundPaint(Paint backgroundPaint) {
+ this.backgroundPaint = backgroundPaint;
+ }
+
+ public boolean isClippingEnabled() {
+ return clippingEnabled;
+ }
+
+ public void setClippingEnabled(boolean clippingEnabled) {
+ this.clippingEnabled = clippingEnabled;
+ }
+
+ public boolean isVisible() {
+ return isVisible;
+ }
+
+ public void setVisible(boolean visible) {
+ isVisible = visible;
+ }
+
+ public PositionMetrics getPositionMetrics() {
+ return positionMetrics;
+ }
+
+ public void setPositionMetrics(PositionMetrics positionMetrics) {
+ this.positionMetrics = positionMetrics;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/Configurator.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/Configurator.java
new file mode 100644
index 0000000..a4f7ae0
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/Configurator.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.graphics.Color;
+import android.util.Log;
+import android.util.TypedValue;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+
+/**
+ * Utility class for "configuring" objects via XML config files. Supports the following field types:
+ * String
+ * Enum
+ * int
+ * float
+ * boolean
+ * <p/>
+ * Config files should be stored in /res/xml. Given the XML configuration /res/xml/myConfig.xml, one can apply the
+ * configuration to an Object instance as follows:
+ * <p/>
+ * MyObject obj = new MyObject();
+ * Configurator.configure(obj, R.xml.myConfig);
+ * <p/>
+ * WHAT IT DOES:
+ * Given a series of parameters stored in an XML file, Configurator iterates through each parameter, using the name
+ * as a map to the field within a given object. For example:
+ * <p/>
+ * <pre>
+ * {@code
+ * <config car.engine.sparkPlug.condition="poor"/>
+ * }
+ * </pre>
+ * <p/>
+ * Given a Car instance car and assuming the method setCondition(String) exists within the SparkPlug class,
+ * Configurator does the following:
+ * <p/>
+ * <pre>
+ * {@code
+ * car.getEngine().getSparkPlug().setCondition("poor");
+ * }
+ * </pre>
+ * <p/>
+ * Now let's pretend that setCondition takes an instance of the Condition enum as it's argument.
+ * Configurator then does the following:
+ * <p/>
+ * car.getEngine().getSparkPlug().setCondition(Condition.valueOf("poor");
+ * <p/>
+ * Now let's look at how ints are handled. Given the following xml:
+ * <p/>
+ * <config car.engine.miles="100000"/>
+ * <p/>
+ * would result in:
+ * car.getEngine.setMiles(Integer.ParseInt("100000");
+ * <p/>
+ * That's pretty straight forward. But colors are expressed as ints too in Android
+ * but can be defined using hex values or even names of colors. When Configurator
+ * attempts to parse a parameter for a method that it knows takes an int as it's argument,
+ * Configurator will first attempt to parse the parameter as a color. Only after this
+ * attempt fails will Configurator resort to Integer.ParseInt. So:
+ * <p/>
+ * <config car.hood.paint.color="Red"/>
+ * <p/>
+ * would result in:
+ * car.getHood().getPaint().setColor(Color.parseColor("Red");
+ * <p/>
+ * Next lets talk about float. Floats can appear in XML a few different ways in Android,
+ * especially when it comes to defining dimensions:
+ * <p/>
+ * <config height="10dp" depth="2mm" width="5em"/>
+ * <p/>
+ * Configurator will correctly parse each of these into their corresponding real pixel value expressed as a float.
+ * <p/>
+ * One last thing to keep in mind when using Configurator:
+ * Values for Strings and ints can be assigned to localized values, allowing
+ * a cleaner solution for those developing apps to run on multiple form factors
+ * or in multiple languages:
+ * <p/>
+ * <config thingy.description="@string/thingyDescription"
+ * thingy.titlePaint.textSize=""/>
+ */
+@SuppressWarnings("WeakerAccess")
+public abstract class Configurator {
+
+ private static final String TAG = Configurator.class.getName();
+ protected static final String CFG_ELEMENT_NAME = "config";
+
+ protected static int parseResId(Context ctx, String prefix, String value) {
+ String[] split = value.split("/");
+ // is this a localized resource?
+ if (split.length > 1 && split[0].equalsIgnoreCase(prefix)) {
+ String pack = split[0].replace("@", "");
+ String name = split[1];
+ return ctx.getResources().getIdentifier(name, pack, ctx.getPackageName());
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ protected static int parseIntAttr(Context ctx, String value) {
+ try {
+ return ctx.getResources().getColor(parseResId(ctx, "@color", value));
+ } catch (IllegalArgumentException e1) {
+ try {
+ return Color.parseColor(value);
+ } catch (IllegalArgumentException e2) {
+ // wasn't a color so try parsing as a plain old int:
+ return Integer.parseInt(value);
+ }
+ }
+ }
+
+ /**
+ * Treats value as a float parameter. First value is tested to see whether
+ * it contains a resource identifier. Failing that, it is tested to see whether
+ * a dimension suffix (dp, em, mm etc.) exists. Failing that, it is evaluated as
+ * a plain old float.
+ * @param ctx
+ * @param value
+ * @return
+ */
+ protected static float parseFloatAttr(Context ctx, String value) {
+ try {
+ return ctx.getResources().getDimension(parseResId(ctx, "@dimen", value));
+ } catch (IllegalArgumentException e1) {
+ try {
+ return PixelUtils.stringToDimension(value);
+ } catch (Exception e2) {
+ return Float.parseFloat(value);
+ }
+ }
+ }
+
+ protected static String parseStringAttr(Context ctx, String value) {
+ try {
+ return ctx.getResources().getString(parseResId(ctx, "@string", value));
+ } catch (IllegalArgumentException e1) {
+ return value;
+ }
+ }
+
+
+ protected static Method getSetter(Class clazz, final String fieldId) throws NoSuchMethodException {
+ Method[] methods = clazz.getMethods();
+
+ String methodName = "set" + fieldId;
+ for (Method method : methods) {
+ if (method.getName().equalsIgnoreCase(methodName)) {
+ return method;
+ }
+ }
+ throw new NoSuchMethodException("No such public method (case insensitive): " +
+ methodName + " in " + clazz);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static Method getGetter(Class clazz, final String fieldId) throws NoSuchMethodException {
+ Log.d(TAG, "Attempting to find getter for " + fieldId + " in class " + clazz.getName());
+ String firstLetter = fieldId.substring(0, 1);
+ String methodName = "get" + firstLetter.toUpperCase() + fieldId.substring(1, fieldId.length());
+ return clazz.getMethod(methodName);
+ }
+
+ /**
+ * Returns the object containing the field specified by path.
+ * @param obj
+ * @param path Path through member hierarchy to the destination field.
+ * @return null if the object at path cannot be found.
+ * @throws java.lang.reflect.InvocationTargetException
+ *
+ * @throws IllegalAccessException
+ */
+ protected static Object getObjectContaining(Object obj, String path) throws
+ InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+ if(obj == null) {
+ throw new NullPointerException("Attempt to call getObjectContaining(Object obj, String path) " +
+ "on a null Object instance. Path was: " + path);
+ }
+ Log.d(TAG, "Looking up object containing: " + path);
+ int separatorIndex = path.indexOf(".");
+
+ // not there yet, descend deeper:
+ if (separatorIndex > 0) {
+ String lhs = path.substring(0, separatorIndex);
+ String rhs = path.substring(separatorIndex + 1, path.length());
+
+ // use getter to retrieve the instance
+ Method m = getGetter(obj.getClass(), lhs);
+ if(m == null) {
+ throw new NullPointerException("No getter found for field: " + lhs + " within " + obj.getClass());
+ }
+ Log.d(TAG, "Invoking " + m.getName() + " on instance of " + obj.getClass().getName());
+ Object o = m.invoke(obj);
+ // delve into o
+ return getObjectContaining(o, rhs);
+ //} catch (NoSuchMethodException e) {
+ // TODO: log a warning
+ // return null;
+ //}
+ } else {
+ // found it!
+ return obj;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object[] inflateParams(Context ctx, Class[] params, String[] vals) throws NoSuchMethodException,
+ InvocationTargetException, IllegalAccessException {
+ Object[] out = new Object[params.length];
+ int i = 0;
+ for (Class param : params) {
+ if (Enum.class.isAssignableFrom(param)) {
+ out[i] = param.getMethod("valueOf", String.class).invoke(null, vals[i].toUpperCase());
+ } else if (param.equals(Float.TYPE)) {
+ out[i] = parseFloatAttr(ctx, vals[i]);
+ } else if (param.equals(Integer.TYPE)) {
+ out[i] = parseIntAttr(ctx, vals[i]);
+ } else if (param.equals(Boolean.TYPE)) {
+ out[i] = Boolean.valueOf(vals[i]);
+ } else if (param.equals(String.class)) {
+ out[i] = parseStringAttr(ctx, vals[i]);
+ } else {
+ throw new IllegalArgumentException(
+ "Error inflating XML: Setter requires param of unsupported type: " + param);
+ }
+ i++;
+ }
+ return out;
+ }
+
+ /**
+ *
+ * @param ctx
+ * @param obj
+ * @param xmlFileId ID of the XML config file within /res/xml
+ */
+ public static void configure(Context ctx, Object obj, int xmlFileId) {
+ XmlResourceParser xrp = ctx.getResources().getXml(xmlFileId);
+ try {
+ HashMap<String, String> params = new HashMap<String, String>();
+ while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
+ xrp.next();
+ String name = xrp.getName();
+ if (xrp.getEventType() == XmlResourceParser.START_TAG) {
+ if (name.equalsIgnoreCase(CFG_ELEMENT_NAME))
+ for (int i = 0; i < xrp.getAttributeCount(); i++) {
+ params.put(xrp.getAttributeName(i), xrp.getAttributeValue(i));
+ }
+ break;
+ }
+ }
+ configure(ctx, obj, params);
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ xrp.close();
+ }
+ }
+
+ public static void configure(Context ctx, Object obj, HashMap<String, String> params) {
+ for (String key : params.keySet()) {
+ try {
+ configure(ctx, obj, key, params.get(key));
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "Error inflating XML: Setter for field \"" + key + "\" does not exist. ");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Recursively descend into an object using key as the pathway and invoking the corresponding setter
+ * if one exists.
+ *
+ * @param key
+ * @param value
+ */
+ protected static void configure(Context ctx, Object obj, String key, String value)
+ throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+ Object o = getObjectContaining(obj, key);
+ if (o != null) {
+ int idx = key.lastIndexOf(".");
+ String fieldId = idx > 0 ? key.substring(idx + 1, key.length()) : key;
+
+ Method m = getSetter(o.getClass(), fieldId);
+ Class[] paramTypes = m.getParameterTypes();
+ // TODO: add support for generic type params
+ if (paramTypes.length >= 1) {
+
+ // split on "|"
+ // TODO: add support for String args containing a |
+ String[] paramStrs = value.split("\\|");
+ if (paramStrs.length == paramTypes.length) {
+
+ Object[] oa = inflateParams(ctx, paramTypes, paramStrs);
+ Log.d(TAG, "Invoking " + m.getName() + " with arg(s) " + argArrToString(oa));
+ m.invoke(o, oa);
+ } else {
+ throw new IllegalArgumentException("Error inflating XML: Unexpected number of argments passed to \""
+ + m.getName() + "\". Expected: " + paramTypes.length + " Got: " + paramStrs.length);
+ }
+ } else {
+ // Obvious this is not a setter
+ throw new IllegalArgumentException("Error inflating XML: no setter method found for param \"" +
+ fieldId + "\".");
+ }
+ }
+ }
+
+ protected static String argArrToString(Object[] args) {
+ String out = "";
+ for(Object obj : args) {
+ out += (obj == null ? (out += "[null] ") :
+ ("[" + obj.getClass() + ": " + obj + "] "));
+ }
+ return out;
+ }
+}
+
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/DisplayDimensions.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/DisplayDimensions.java
new file mode 100644
index 0000000..0ae5a7a
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/DisplayDimensions.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import android.graphics.RectF;
+
+/**
+ * Convenience class for managing BoxModel data
+ */
+public class DisplayDimensions {
+
+ public final RectF canvasRect;
+ public final RectF marginatedRect;
+ public final RectF paddedRect;
+
+ // init to 1 to avoid potential divide by zero errors (yet to be observed in practice)
+ private static final RectF initRect;
+
+ static {
+ initRect = new RectF(1, 1, 1, 1);
+ }
+
+ public DisplayDimensions() {
+ this(initRect, initRect, initRect);
+ }
+ public DisplayDimensions(RectF canvasRect, RectF marginatedRect, RectF paddedRect) {
+ this.canvasRect = canvasRect;
+ this.marginatedRect = marginatedRect;
+ this.paddedRect = paddedRect;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/FontUtils.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/FontUtils.java
new file mode 100644
index 0000000..d4bbfd2
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/FontUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+public class FontUtils {
+
+ /**
+ * Determines the height of the tallest character that can be drawn by paint.
+ * @param paint
+ * @return
+ */
+ public static float getFontHeight(Paint paint) {
+ Paint.FontMetrics metrics = paint.getFontMetrics();
+ return (-metrics.ascent) + metrics.descent;
+ //return (-metrics.top) + metrics.bottom;
+ }
+
+ /**
+ * Get the smallest rect that ecompasses the text to be drawn using paint.
+ * @param text
+ * @param paint
+ * @return
+ */
+ public static Rect getPackedStringDimensions(String text, Paint paint) {
+ Rect size = new Rect();
+ paint.getTextBounds(text, 0, text.length(), size);
+ return size;
+ }
+
+ /**
+ * Like getPackedStringDimensions except adds extra space to accommodate all
+ * characters that can be drawn regardless of whether or not they exist in text.
+ * This ensures a more uniform appearance for things that have dynamic text.
+ * @param text
+ * @param paint
+ * @return
+ */
+ public static Rect getStringDimensions(String text, Paint paint) {
+ Rect size = new Rect();
+ if(text == null || text.length() == 0) {
+ return null;
+ }
+ paint.getTextBounds(text, 0, text.length(), size);
+ size.bottom = size.top + (int) getFontHeight(paint);
+ return size;
+ }
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/ListOrganizer.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/ListOrganizer.java
new file mode 100644
index 0000000..ea2cea6
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/ListOrganizer.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import java.util.List;
+
+/**
+ * Utility class providing additional element organization operations.
+ * @param <ElementType>
+ */
+public class ListOrganizer<ElementType> implements ZIndexable<ElementType> {
+ private List<ElementType> list;
+
+ public ListOrganizer(List<ElementType> list) {
+ this.list = list;
+ }
+
+
+ public boolean moveToTop(ElementType element) {
+ if(list.remove(element)) {
+ list.add(list.size(), element);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean moveAbove(ElementType objectToMove, ElementType reference) {
+ if(objectToMove == reference) {
+ throw new IllegalArgumentException("Illegal argument to moveAbove(A, B); A cannot be equal to B.");
+ }
+
+
+ list.remove(objectToMove);
+ int refIndex = list.indexOf(reference);
+ list.add(refIndex + 1, objectToMove);
+ return true;
+ //widgetOrder.remove(element);
+
+ }
+
+ public boolean moveBeneath(ElementType objectToMove, ElementType reference) {
+ if (objectToMove == reference) {
+ throw new IllegalArgumentException("Illegal argument to moveBeaneath(A, B); A cannot be equal to B.");
+ }
+
+ list.remove(objectToMove);
+ int refIndex = list.indexOf(reference);
+ list.add(refIndex, objectToMove);
+ return true;
+
+ }
+
+ public boolean moveToBottom(ElementType key) {
+
+ //int widgetIndex = widgetOrder.indexOf(key);
+ list.remove(key);
+ //list.add(list.size(), key);
+ list.add(0, key);
+ return true;
+ //widgetOrder.remove(key);
+ }
+
+ public boolean moveUp(ElementType key) {
+ int widgetIndex = list.indexOf(key);
+ if(widgetIndex == -1) {
+ // key not found:
+ return false;
+ }
+ if(widgetIndex >= list.size()-1) {
+ // already at the top:
+ return true;
+ }
+
+ ElementType widgetAbove = list.get(widgetIndex+1);
+ return moveAbove(key, widgetAbove);
+ }
+
+ public boolean moveDown(ElementType key) {
+ int widgetIndex = list.indexOf(key);
+ if(widgetIndex == -1) {
+ // key not found:
+ return false;
+ }
+ if(widgetIndex <= 0) {
+ // already at the bottom:
+ return true;
+ }
+
+ ElementType widgetBeneath = list.get(widgetIndex-1);
+ return moveBeneath(key, widgetBeneath);
+ }
+
+ @Override
+ public List<ElementType> elements() {
+ return list;
+ }
+
+ public void addToBottom(ElementType element) {
+ list.add(0, element);
+ }
+
+ public void addToTop(ElementType element) {
+ list.add(list.size(), element);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/MultiSynch.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/MultiSynch.java
new file mode 100644
index 0000000..2e2fa88
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/MultiSynch.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility class for obtaining synch lock across multiple objects.
+ */
+public abstract class MultiSynch {
+
+ /**
+ * Callback class for doing work from within a MultiSynch.
+ */
+ public interface Action {
+
+ /**
+ * Invoked by MultiSynch.run(...)
+ * @param params
+ */
+ public void run(Object[] params);
+ }
+
+
+ /**
+ *
+ * @param params
+ * @param synchSet Set of objects to be synchronized upon
+ * @param action Action to be invoked once full synchronization has been obtained.
+ */
+ public static void run(Object[] params, Set synchSet, Action action) {
+ run(params, synchSet.toArray(), action, 0);
+ }
+
+ /**
+ * @param params
+ * @param synchList List of objects to be synchronized upon
+ * @param action Action to be invoked once full synchronization has been obtained.
+ */
+ public static void run(Object[] params, List synchList, Action action) {
+ run(params, synchList.toArray(), action, 0);
+ }
+
+ /**
+ * @param params
+ * @param synchArr Array of objects to be synchronized upon
+ * @param action Action to be invoked once full synchronization has been obtained.
+ */
+ public static void run(Object[] params, Object[] synchArr, Action action) {
+ run(params, synchArr, action, 0);
+ }
+
+ /**
+ * Recursively synchs on each item in SynchList
+ * @param params
+ * @param synchArr
+ * @param action
+ * @param depth
+ */
+ private static void run(Object[] params, Object[] synchArr, Action action, int depth) {
+ if (synchArr != null) {
+ synchronized (synchArr[depth]) {
+ if (depth < synchArr.length - 1) {
+ run(params, synchArr, action, ++depth);
+ } else {
+ action.run(params);
+ }
+ }
+ }
+ action.run(params);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/PaintUtils.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/PaintUtils.java
new file mode 100644
index 0000000..b2cd713
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/PaintUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+import android.graphics.Paint;
+
+/**
+ * Convenience methods that operate on Paint instances. These methods primarily deal with
+ * converting pixel values to/from dp values.
+ */
+public class PaintUtils {
+
+ /*public static Paint getPaint() {
+ Paint p = new Paint();
+ return p;
+ }*/
+
+ /**
+ * Sets a paint instance's line stroke size in dp
+ * @param paint
+ * @param lineSizeDp
+ */
+ public static void setLineSizeDp(Paint paint, float lineSizeDp){
+ paint.setStrokeWidth(PixelUtils.dpToPix(lineSizeDp));
+ }
+
+ /**
+ * Sets a paint instance's font size in dp
+ * @param paint
+ * @param fontSizeDp
+ */
+ public static void setFontSizeDp(Paint paint, float fontSizeDp){
+ paint.setTextSize(PixelUtils.dpToPix(fontSizeDp));
+ }
+
+ /**
+ * Set a paint instance's shadowing using dp values
+ * @param paint
+ * @param radiusDp
+ * @param dxDp
+ * @param dyDp
+ * @param color
+ */
+ public static void setShadowDp(Paint paint, float radiusDp, float dxDp, float dyDp, int color) {
+ float radius = PixelUtils.dpToPix(radiusDp);
+ float dx = PixelUtils.dpToPix(dxDp);
+ float dy = PixelUtils.dpToPix(dyDp);
+ paint.setShadowLayer(radius, dx, dy, color);
+ }
+
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/PixelUtils.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/PixelUtils.java
new file mode 100644
index 0000000..ea6efed
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/PixelUtils.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PixelUtils {
+ private static DisplayMetrics metrics;
+ private static final float FLOAT_INT_AVG_NUDGE = 0.5f;
+ //private static float SCALE = 1; // pix per dp
+ //private static int X_PIX = 1; // total display horizontal pix
+ //private static int Y_PIX = 1; // total display vertical pix
+
+ /**
+ * Recalculates scale value etc. Should be called when an application starts or
+ * whenever the screen is rotated.
+ */
+ public static void init(Context ctx) {
+ //DisplayMetrics dm = ctx.getResources().getDisplayMetrics();
+ //SCALE = dm.density;
+ //X_PIX = dm.widthPixels;
+ //Y_PIX = dm.heightPixels;
+ metrics = ctx.getResources().getDisplayMetrics();
+
+ }
+
+ public static PointF add(PointF lhs, PointF rhs) {
+ return new PointF(lhs.x + rhs.x, lhs.y + rhs.y);
+ }
+
+ public static PointF sub(PointF lhs, PointF rhs) {
+ return new PointF(lhs.x - rhs.x, lhs.y - rhs.y);
+ }
+
+ /**
+ * Converts a sub-pixel accurate RectF to a Rect
+ * using the closest matching full pixel vals. This is
+ * useful for clipping operations etc.
+ * @param rectIn The rect to be converted
+ * @return
+ */
+ /*public static Rect toRect(RectF rectIn) {
+ return new Rect(
+ (int) (rectIn.left + FLOAT_INT_AVG_NUDGE),
+ (int) (rectIn.top + FLOAT_INT_AVG_NUDGE),
+ (int) (rectIn.right + FLOAT_INT_AVG_NUDGE),
+ (int) (rectIn.bottom + FLOAT_INT_AVG_NUDGE));
+ }*/
+
+ /**
+ * Converts a sub-pixel accurate RectF to
+ * a single pixel accurate rect. This is helpful
+ * for clipping operations which dont do a good job with
+ * subpixel vals.
+ * @param in
+ * @return
+ */
+ public static RectF sink(RectF in) {
+ return nearestPixRect(in.left, in.top, in.right, in.bottom);
+ }
+
+ public static RectF nearestPixRect(float left, float top, float right, float bottom) {
+ return new RectF(
+ (int) (left + FLOAT_INT_AVG_NUDGE),
+ (int) (top + FLOAT_INT_AVG_NUDGE),
+ (int) (right + FLOAT_INT_AVG_NUDGE),
+ (int) (bottom + FLOAT_INT_AVG_NUDGE));
+ }
+
+ /**
+ * Converts a dp value to pixels.
+ * @param dp
+ * @return Pixel value of dp.
+ */
+ public static float dpToPix(float dp) {
+ //return SCALE * dp + FLOAT_INT_AVG_NUDGE;
+ //InternalDimension id = new InternalDimension(dp, TypedValue.COMPLEX_UNIT_DIP);
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
+
+ }
+
+ /**
+ * Converts an sp value to pixels.
+ * @param sp
+ * @return Pixel value of sp.
+ */
+ @SuppressWarnings("SameParameterValue")
+ public static float spToPix(float sp) {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics);
+ }
+
+
+ /**
+ *
+ * @param fraction A float value between 0 and 1.
+ * @return Number of pixels fraction represents on the current device's display.
+ */
+ public static float fractionToPixH(float fraction) {
+ return metrics.heightPixels * fraction;
+
+ }
+
+ /**
+ *
+ * @param fraction A float value between 0 and 1.
+ * @return Number of pixels fraction represents on the current device's display.
+ */
+ public static float fractionToPixW(float fraction) {
+ return metrics.widthPixels * fraction;
+ }
+
+
+ /**
+ *
+ * CODE BELOW IS ADAPTED IN PART FROM MINDRIOT'S SAMPLE CODE HERE:
+ * http://stackoverflow.com/questions/8343971/how-to-parse-a-dimension-string-and-convert-it-to-a-dimension-value
+ */
+ // -- Initialize dimension string to constant lookup.
+ public static final Map<String, Integer> dimensionConstantLookup = initDimensionConstantLookup();
+
+ private static Map<String, Integer> initDimensionConstantLookup() {
+ Map<String, Integer> m = new HashMap<String, Integer>();
+ m.put("px", TypedValue.COMPLEX_UNIT_PX);
+ m.put("dip", TypedValue.COMPLEX_UNIT_DIP);
+ m.put("dp", TypedValue.COMPLEX_UNIT_DIP);
+ m.put("sp", TypedValue.COMPLEX_UNIT_SP);
+ m.put("pt", TypedValue.COMPLEX_UNIT_PT);
+ m.put("in", TypedValue.COMPLEX_UNIT_IN);
+ m.put("mm", TypedValue.COMPLEX_UNIT_MM);
+ return Collections.unmodifiableMap(m);
+ }
+
+ // -- Initialize pattern for dimension string.
+ private static final Pattern DIMENSION_PATTERN = Pattern.compile("^\\s*(\\d+(\\.\\d+)*)\\s*([a-zA-Z]+)\\s*$");
+
+ /*public static int stringToDimensionPixelSize(String dimension, DisplayMetrics metrics) {
+ // -- Mimics TypedValue.complexToDimensionPixelSize(int data, DisplayMetrics metrics).
+ InternalDimension internalDimension = stringToInternalDimension(dimension);
+ final float value = internalDimension.value;
+ final float f = TypedValue.applyDimension(internalDimension.unit, value, metrics);
+ final int res = (int) (f + 0.5f);
+ if (res != 0) return res;
+ if (value == 0) return 0;
+ if (value > 0) return 1;
+ return -1;
+ }*/
+
+ public static float stringToDimension(String dimension) {
+ // -- Mimics TypedValue.complexToDimension(int data, DisplayMetrics metrics).
+ InternalDimension internalDimension = stringToInternalDimension(dimension);
+ return TypedValue.applyDimension(internalDimension.unit, internalDimension.value, metrics);
+ }
+
+ private static InternalDimension stringToInternalDimension(String dimension) {
+ // -- Match target against pattern.
+ Matcher matcher = DIMENSION_PATTERN.matcher(dimension);
+ if (matcher.matches()) {
+ // -- Match found.
+ // -- Extract value.
+ float value = Float.valueOf(matcher.group(1));
+ // -- Extract dimension units.
+ String unit = matcher.group(3).toLowerCase();
+ // -- Get Android dimension constant.
+ Integer dimensionUnit = dimensionConstantLookup.get(unit);
+ if (dimensionUnit == null) {
+ // -- Invalid format.
+ throw new NumberFormatException();
+ } else {
+ // -- Return valid dimension.
+ return new InternalDimension(value, dimensionUnit);
+ }
+ } else {
+ // -- Invalid format.
+ throw new NumberFormatException();
+ }
+ }
+
+ private static class InternalDimension {
+ float value;
+ int unit;
+
+ public InternalDimension(float value, int unit) {
+ this.value = value;
+ this.unit = unit;
+ }
+ }
+
+
+}
+
+
+
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/PlotStatistics.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/PlotStatistics.java
new file mode 100644
index 0000000..9703aee
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/PlotStatistics.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import com.androidplot.Plot;
+import com.androidplot.PlotListener;
+
+/**
+ * !!! THIS CLASS IS STILL UNDER DEVELOPMENT AND MAY CONTAIN BUGS !!!
+ * Gathers performance statistics from a Plot. Instances of PlotStatistics
+ * should never be added to more than one Plot, otherwise the statiscs will
+ * be invalid.
+ */
+public class PlotStatistics implements PlotListener {
+ long minRenderTimeMs;
+ long maxRenderTimeMs;
+ long avgRenderTimeMs;
+ long fps;
+ long updateDelayMs;
+
+
+ long longestRenderMs = 0;
+ long shortestRenderMs = 0;
+ long lastStart = 0;
+ long lastLatency = 0;
+ long lastAnnotation;
+ long latencySamples = 0;
+ long latencySum = 0;
+ String annotationString = "";
+
+ private Paint paint;
+ {
+ paint = new Paint();
+ paint.setTextAlign(Paint.Align.CENTER);
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(30);
+ resetCounters();
+ }
+
+
+ private boolean annotatePlotEnabled;
+
+
+
+ public PlotStatistics(long updateDelayMs, boolean annotatePlotEnabled) {
+ this.updateDelayMs = updateDelayMs;
+ this.annotatePlotEnabled = annotatePlotEnabled;
+ }
+
+ public void setAnnotatePlotEnabled(boolean enabled) {
+ this.annotatePlotEnabled = enabled;
+ }
+
+ private void resetCounters() {
+ longestRenderMs = 0;
+ shortestRenderMs = 999999999;
+ latencySamples = 0;
+ latencySum = 0;
+ }
+
+ private void annotatePlot(Plot source, Canvas canvas) {
+ long nowMs = System.currentTimeMillis();
+ // throttle the update frequency:
+ long msSinceUpdate = (nowMs - lastAnnotation);
+ if(msSinceUpdate >= updateDelayMs) {
+
+ float avgLatency = latencySamples > 0 ? latencySum/latencySamples : 0;
+ String overallFPS = String.format("%.2f", latencySamples > 0 ? (1000f/msSinceUpdate) * latencySamples : 0);
+ String potentialFPS = String.format("%.2f", latencySamples > 0 ? 1000f/avgLatency : 0);
+ annotationString = "FPS (potential): " + potentialFPS + " FPS (actual): " + overallFPS + " Latency (ms) Avg: " + lastLatency + " \nMin: " + shortestRenderMs +
+ " Max: " + longestRenderMs;
+ lastAnnotation = nowMs;
+ resetCounters();
+ }
+ RectF r = source.getDisplayDimensions().canvasRect;
+ if(annotatePlotEnabled) {
+ canvas.drawText(annotationString, r.centerX(), r.centerY(), paint);
+ }
+ }
+
+ @Override
+ public void onBeforeDraw(Plot source, Canvas canvas) {
+ lastStart = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onAfterDraw(Plot source, Canvas canvas) {
+ lastLatency = System.currentTimeMillis() - lastStart;
+ if(lastLatency < shortestRenderMs) {
+ shortestRenderMs = lastLatency;
+ }
+
+ if(lastLatency > longestRenderMs) {
+ longestRenderMs = lastLatency;
+ }
+ latencySum += lastLatency;
+ latencySamples++;
+ annotatePlot(source, canvas);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/Redrawer.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/Redrawer.java
new file mode 100644
index 0000000..6602dbb
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/Redrawer.java
@@ -0,0 +1,112 @@
+package com.androidplot.util;
+
+import android.util.Log;
+import com.androidplot.Plot;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility class for invoking Plot.redraw() on a backgorund thread
+ * at a set frequency.
+ */
+public class Redrawer implements Runnable {
+
+ private static final String TAG = Redrawer.class.getName();
+
+ private List<Plot> plots;
+ private long sleepTime;
+ private boolean keepRunning;
+ private boolean keepAlive;
+
+ /**
+ *
+ * @param plots List of Plot instances to be redrawn
+ * @param maxRefreshRate Desired frequency at which to redraw plots.
+ * @param startImmediately If true, invokes run() immediately after construction.
+ */
+ public Redrawer(List<Plot> plots, float maxRefreshRate, boolean startImmediately) {
+ this.plots = plots;
+ setMaxRefreshRate(maxRefreshRate);
+ new Thread(this).start();
+ if(startImmediately) {
+ run();
+ }
+ }
+
+ public Redrawer(Plot plot, float maxRefreshRate, boolean startImmediately) {
+ this(Arrays.asList(new Plot[]{plot}), maxRefreshRate, startImmediately);
+ }
+
+ /**
+ * Temporarily stop redrawing the plot.
+ */
+ public synchronized void pause() {
+ keepRunning = false;
+ notify();
+ Log.d(TAG, "Redrawer paused.");
+ }
+
+ /**
+ * Start/resume redrawing the plot.
+ */
+ public synchronized void start() {
+ keepRunning = true;
+ notify();
+ Log.d(TAG, "Redrawer started.");
+ }
+
+ /**
+ * Internally, this causes
+ * the refresh thread to exit. Should always be called
+ * before exiting the application.
+ */
+ public synchronized void finish() {
+ keepRunning = false;
+ keepAlive = false;
+ notify();
+ }
+
+ @Override
+ public void run() {
+ keepAlive = true;
+ try {
+ while(keepAlive) {
+ if(keepRunning) {
+ // redraw plot(s) and sleep in an interruptible state for a
+ // max of sleepTime ms.
+ // TODO: record start and end timestamps and
+ // TODO: calculate sleepTime from that, in order to more accurately
+ // TODO: meet desired refresh rate.
+ for(Plot plot : plots) {
+ plot.redraw();
+ }
+ synchronized (this) {
+ wait(sleepTime);
+ }
+ } else {
+ // sleep until notified
+ synchronized (this) {
+ wait();
+ }
+ }
+ }
+ } catch(InterruptedException e) {
+
+ } finally {
+ Log.d(TAG, "Redrawer thread exited.");
+ }
+ }
+
+ /**
+ * Set the maximum refresh rate that Redrawer should use. Actual
+ * refresh rate could be slower.
+ * @param refreshRate Refresh rate in Hz.
+ */
+ public void setMaxRefreshRate(float refreshRate) {
+ sleepTime = (long)(1000 / refreshRate);
+ Log.d(TAG, "Set Redrawer refresh rate to " +
+ refreshRate + "( " + sleepTime + " ms)");
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/ValPixConverter.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/ValPixConverter.java
new file mode 100644
index 0000000..d7a0d5a
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/ValPixConverter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * Utility methods for converting pixel coordinates into real values and vice versa.
+ */
+public class ValPixConverter {
+ private static final int ZERO = 0;
+
+
+ public static float valToPix(double val, double min, double max, float lengthPix, boolean flip) {
+ if(lengthPix <= ZERO) {
+ throw new IllegalArgumentException("Length in pixels must be greater than 0.");
+ }
+ double range = range(min, max);
+ double scale = lengthPix / range;
+ double raw = val - min;
+ float pix = (float)(raw * scale);
+
+ if(flip) {
+ pix = (lengthPix - pix);
+ }
+ return pix;
+ }
+
+ public static double range(double min, double max) {
+ return (max-min);
+ }
+
+
+ public static double valPerPix(double min, double max, float lengthPix) {
+ double valRange = range(min, max);
+ return valRange/lengthPix;
+ }
+
+ /**
+ * Convert a value in pixels to the type passed into min/max
+ * @param pix
+ * @param min
+ * @param max
+ * @param lengthPix
+ * @param flip True if the axis should be reversed before calculated. This is the case
+ * with the y axis for screen coords.
+ * @return
+ */
+ public static double pixToVal(float pix, double min, double max, float lengthPix, boolean flip) {
+ if(pix < ZERO) {
+ throw new IllegalArgumentException("pixel values cannot be negative.");
+ }
+
+ if(lengthPix <= ZERO) {
+ throw new IllegalArgumentException("Length in pixels must be greater than 0.");
+ }
+ float pMult = pix;
+ if(flip) {
+ pMult = lengthPix - pix;
+ }
+ double range = range(min, max);
+ return ((range / lengthPix) * pMult) + min;
+ }
+
+ /**
+ * Converts a real value into a pixel value.
+ * @param x Real d (domain) component of the point to convert.
+ * @param y Real y (range) component of the point to convert.
+ * @param plotArea
+ * @param minX Minimum visible real value on the d (domain) axis.
+ * @param maxX Maximum visible real value on the y (domain) axis.
+ * @param minY Minimum visible real value on the y (range) axis.
+ * @param maxY Maximum visible real value on the y (range axis.
+ * @return
+ */
+ public static PointF valToPix(Number x, Number y, RectF plotArea, Number minX, Number maxX, Number minY, Number maxY) {
+ float pixX = ValPixConverter.valToPix(x.doubleValue(), minX.doubleValue(), maxX.doubleValue(), plotArea.width(), false) + (plotArea.left);
+ float pixY = ValPixConverter.valToPix(y.doubleValue(), minY.doubleValue(), maxY.doubleValue(), plotArea.height(), true) + plotArea.top;
+ return new PointF(pixX, pixY);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/ZHash.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/ZHash.java
new file mode 100644
index 0000000..56d4665
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/ZHash.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Concrete implementation of ZIndexable. Provides fast element retrieval via hash key. Also provides
+ * mutable ordering (z indexing) of elements.
+ */
+public class ZHash<KeyType, ValueType> implements ZIndexable<KeyType> {
+
+ private HashMap<KeyType, ValueType> hash;
+ //private LinkedList<KeyType> zlist;
+ //private ListOrganizer<KeyType> listOrganizer;
+ private ZLinkedList<KeyType> zlist;
+
+ {
+ hash = new HashMap<KeyType, ValueType>();
+ zlist = new ZLinkedList<KeyType>();
+ //listOrganizer = new ListOrganizer<KeyType>(zlist);
+ }
+
+ public int size() {
+ return zlist.size();
+ }
+
+
+ public ValueType get(KeyType key) {
+ return hash.get(key);
+ }
+
+ public List<KeyType> getKeysAsList() {
+ return zlist;
+ }
+
+ /**
+ * If key already exists within the structure, it's value is replaced with the new value and
+ * it's existing order is maintained.
+ * @param key
+ * @param value
+ */
+ public synchronized void addToTop(KeyType key, ValueType value) {
+ if(hash.containsKey(key)) {
+ hash.put(key, value);
+ //throw new IllegalArgumentException("Key already exists in series structure...duplicates not permitted.");
+ } else {
+ hash.put(key, value);
+ zlist.addToTop(key);
+ //zlist.addToTop(key);
+ }
+ }
+
+ /**
+ * If key already exists within the structure, it's value is replaced with the new value and
+ * it's existing order is maintained.
+ * @param key
+ * @param value
+ */
+ public synchronized void addToBottom(KeyType key, ValueType value) {
+ if(hash.containsKey(key)) {
+ hash.put(key, value);
+ //throw new IllegalArgumentException("Key already exists in series structure...duplicates not permitted.");
+ } else {
+ hash.put(key, value);
+ zlist.addToBottom(key);
+ //zlist.addToBottom(key);
+ }
+ }
+
+ public synchronized boolean moveToTop(KeyType element) {
+ if(!hash.containsKey(element)) {
+ return false;
+ } else {
+ return zlist.moveToTop(element);
+ }
+ }
+
+ public synchronized boolean moveAbove(KeyType objectToMove, KeyType reference) {
+ if(objectToMove == reference) {
+ throw new IllegalArgumentException("Illegal argument to moveAbove(A, B); A cannot be equal to B.");
+ }
+ if(!hash.containsKey(reference) || !hash.containsKey(objectToMove)) {
+ return false;
+ } else {
+ return zlist.moveAbove(objectToMove, reference);
+ }
+ }
+
+ public synchronized boolean moveBeneath(KeyType objectToMove, KeyType reference) {
+ if(objectToMove == reference) {
+ throw new IllegalArgumentException("Illegal argument to moveBeaneath(A, B); A cannot be equal to B.");
+ }
+ if(!hash.containsKey(reference) || !hash.containsKey(objectToMove)) {
+ return false;
+ } else {
+ return zlist.moveBeneath(objectToMove, reference);
+ }
+ }
+
+ public synchronized boolean moveToBottom(KeyType key) {
+ if(!hash.containsKey(key)) {
+ return false;
+ } else {
+ return zlist.moveToBottom(key);
+ }
+ }
+
+ public synchronized boolean moveUp(KeyType key) {
+ if (!hash.containsKey(key)) {
+ return false;
+ } else {
+ return zlist.moveUp(key);
+ }
+ }
+
+ public synchronized boolean moveDown(KeyType key) {
+ if (!hash.containsKey(key)) {
+ return false;
+ } else {
+ return zlist.moveDown(key);
+ }
+ }
+
+ @Override
+ public List<KeyType> elements() {
+ return zlist;
+ }
+
+ /**
+ *
+ * @return Ordered list of keys.
+ */
+ public List<KeyType> keys() {
+ return elements();
+ }
+
+
+ public synchronized boolean remove(KeyType key) {
+ if(hash.containsKey(key)) {
+ hash.remove(key);
+ zlist.remove(key);
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/ZIndexable.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/ZIndexable.java
new file mode 100644
index 0000000..44e9f88
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/ZIndexable.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import java.util.List;
+
+/**
+ * Encapsulates the concept of z-indexable objects; Each object is stored above or below each other object and may
+ * be moved up and down in the queue relative to other elements in the hash or absolutely to the front or back of the queue.
+ *
+ * Note that the method names correspond to the order of items drawn directly on top of one another using an iterator;
+ * the first element drawn (lowest z-index) is effectively the "bottom" element.
+ * @param <ElementType>
+ */
+public interface ZIndexable<ElementType> {
+
+ /**
+ * Move above all other elements
+ * @param element
+ * @return
+ */
+ public boolean moveToTop(ElementType element);
+
+
+ /**
+ * Move above the specified element
+ * @param objectToMove
+ * @param reference
+ * @return
+ */
+ public boolean moveAbove(ElementType objectToMove, ElementType reference);
+
+
+ /**
+ * Move beneath the specified element
+ *
+ * @param objectToMove
+ * @param reference
+ * @return
+ */
+ public boolean moveBeneath(ElementType objectToMove, ElementType reference);
+
+ /**
+ * Move beneath all other elements
+ * @param key
+ * @return
+ */
+ public boolean moveToBottom(ElementType key);
+
+
+ /**
+ * Move up by one element
+ * @param key
+ * @return
+ */
+ public boolean moveUp(ElementType key);
+
+ /**
+ * Move down by one element
+ * @param key
+ * @return
+ */
+ public boolean moveDown(ElementType key);
+
+ public List<ElementType> elements();
+
+
+ /**
+ * Add beneath all other elements
+ * @param element
+ */
+ //public void addToBottom(ElementType element);
+
+ /**
+ * Add above all other elements
+ * @param element
+ */
+ //public void addToTop(ElementType element);
+} \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/util/ZLinkedList.java b/AndroidPlot-Core/src/main/java/com/androidplot/util/ZLinkedList.java
new file mode 100644
index 0000000..8924a25
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/util/ZLinkedList.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class ZLinkedList<Type> extends LinkedList<Type> implements ZIndexable<Type> {
+
+ //private LinkedList<Type> list;
+ private ListOrganizer<Type> organizer;
+
+ {
+ //list = new LinkedList<Type>();
+ organizer = new ListOrganizer<Type>(this);
+ }
+
+
+ @Override
+ public boolean moveToTop(Type element) {
+ return organizer.moveToTop(element);
+ }
+
+ @Override
+ public boolean moveAbove(Type objectToMove, Type reference) {
+ return organizer.moveAbove(objectToMove, reference);
+ }
+
+ @Override
+ public boolean moveBeneath(Type objectToMove, Type reference) {
+ return organizer.moveBeneath(objectToMove, reference);
+ }
+
+ @Override
+ public boolean moveToBottom(Type key) {
+ return organizer.moveToBottom(key);
+ }
+
+ @Override
+ public boolean moveUp(Type key) {
+ return organizer.moveUp(key);
+ }
+
+ @Override
+ public boolean moveDown(Type key) {
+ return organizer.moveDown(key);
+ }
+
+ @Override
+ public List<Type> elements() {
+ return organizer.elements();
+ }
+
+ public void addToBottom(Type element) {
+ organizer.addToBottom(element);
+ }
+
+ public void addToTop(Type element) {
+ organizer.addToTop(element);
+ }
+
+
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/AxisValueLabelFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/AxisValueLabelFormatter.java
new file mode 100644
index 0000000..d61d74b
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/AxisValueLabelFormatter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+public class AxisValueLabelFormatter {
+ //private Paint textPaint;
+ private int color;
+
+ public AxisValueLabelFormatter(int color) {
+ this.color = color;
+ }
+
+ public int getColor() {
+ return color;
+ }
+
+ public void setColor(int color) {
+ this.color = color;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/BarFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BarFormatter.java
new file mode 100644
index 0000000..fff7720
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BarFormatter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+import android.graphics.Paint;
+import com.androidplot.ui.SeriesRenderer;
+
+public class BarFormatter extends LineAndPointFormatter {
+
+ public Paint getFillPaint() {
+ return fillPaint;
+ }
+
+ public void setFillPaint(Paint fillPaint) {
+ this.fillPaint = fillPaint;
+ }
+
+ public Paint getBorderPaint() {
+ return borderPaint;
+ }
+
+ public void setBorderPaint(Paint borderPaint) {
+ this.borderPaint = borderPaint;
+ }
+
+ private Paint fillPaint;
+ private Paint borderPaint;
+
+ {
+ fillPaint = new Paint();
+ //fillPaint.setColor(Color.RED);
+ fillPaint.setStyle(Paint.Style.FILL);
+ fillPaint.setAlpha(100);
+ borderPaint = new Paint();
+ borderPaint.setStyle(Paint.Style.STROKE);
+ borderPaint.setAlpha(100);
+ }
+
+ /**
+ * Should only be used in conjunction with calls to configure()...
+ */
+ public BarFormatter() {
+ }
+
+ public BarFormatter(int fillColor, int borderColor) {
+ fillPaint.setColor(fillColor);
+ borderPaint.setColor(borderColor);
+ }
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return BarRenderer.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(XYPlot plot) {
+ return new BarRenderer(plot);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/BarRenderer.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BarRenderer.java
new file mode 100644
index 0000000..22fc38e
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BarRenderer.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import android.graphics.Canvas;
+import android.graphics.RectF;
+
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.util.ValPixConverter;
+
+/**
+ * Renders a point as a Bar
+ */
+public class BarRenderer<T extends BarFormatter> extends XYSeriesRenderer<T> {
+
+ private BarRenderStyle renderStyle = BarRenderStyle.OVERLAID; // default Render Style
+ private BarWidthStyle widthStyle = BarWidthStyle.FIXED_WIDTH; // default Width Style
+ private float barWidth = 5;
+ private float barGap = 1;
+
+ public enum BarRenderStyle {
+ OVERLAID, // bars are overlaid in descending y-val order (largest val in back)
+ STACKED, // bars are drawn stacked vertically on top of each other
+ SIDE_BY_SIDE // bars are drawn horizontally next to each-other
+ }
+
+ public enum BarWidthStyle {
+ FIXED_WIDTH, // bar width is always barWidth
+ VARIABLE_WIDTH // bar width is calculated so that there is only barGap between each bar
+ }
+
+ public BarRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ /**
+ * Sets the width of the bars when using the FIXED_WIDTH render style
+ * @param barWidth
+ */
+ public void setBarWidth(float barWidth) {
+ this.barWidth = barWidth;
+ }
+
+ /**
+ * Sets the size of the gap between the bar (or bar groups) when using the VARIABLE_WIDTH render style
+ * @param barGap
+ */
+ public void setBarGap(float barGap) {
+ this.barGap = barGap;
+ }
+
+ public void setBarRenderStyle(BarRenderStyle renderStyle) {
+ this.renderStyle = renderStyle;
+ }
+
+ public void setBarWidthStyle(BarWidthStyle widthStyle) {
+ this.widthStyle = widthStyle;
+ }
+
+ public void setBarWidthStyle(BarWidthStyle style, float value) {
+ setBarWidthStyle(style);
+ switch (style) {
+ case FIXED_WIDTH:
+ setBarWidth(value);
+ break;
+ case VARIABLE_WIDTH:
+ setBarGap(value);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void doDrawLegendIcon(Canvas canvas, RectF rect, BarFormatter formatter) {
+ canvas.drawRect(rect, formatter.getFillPaint());
+ canvas.drawRect(rect, formatter.getBorderPaint());
+ }
+
+ /**
+ * Retrieves the BarFormatter instance that corresponds with the series passed in.
+ * Can be overridden to return other BarFormatters as a result of touch events etc.
+ * @param index index of the point being rendered.
+ * @param series XYSeries to which the point being rendered belongs.
+ * @return
+ */
+ @SuppressWarnings("UnusedParameters")
+ protected T getFormatter(int index, XYSeries series) {
+ return getFormatter(series);
+ }
+
+ public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
+
+ List<XYSeries> sl = getPlot().getSeriesListForRenderer(this.getClass());
+
+ TreeMap<Number, BarGroup> axisMap = new TreeMap<Number, BarGroup>();
+
+ // dont try to render anything if there's nothing to render.
+ if(sl == null) return;
+
+ /*
+ * Build the axisMap (yVal,BarGroup)... a TreeMap of BarGroups
+ * BarGroups represent a point on the X axis where a single or group of bars need to be drawn.
+ */
+
+ // For each Series
+ for(XYSeries series : sl) {
+ BarGroup barGroup;
+
+ // For each value in the series
+ for(int i = 0; i < series.size(); i++) {
+
+ if (series.getX(i) != null) {
+
+ // get a new bar object
+ Bar b = new Bar(series,i,plotArea);
+
+ // Find or create the barGroup
+ if (axisMap.containsKey(b.intX)) {
+ barGroup = axisMap.get(b.intX);
+ } else {
+ barGroup = new BarGroup(b.intX,plotArea);
+ axisMap.put(b.intX, barGroup);
+ }
+ barGroup.addBar(b);
+ }
+
+ }
+ }
+
+ // Loop through the axisMap linking up prev pointers
+ BarGroup prev, current;
+ prev = null;
+ for(Entry<Number, BarGroup> mapEntry : axisMap.entrySet()) {
+ current = mapEntry.getValue();
+ current.prev = prev;
+ prev = current;
+ }
+
+
+ // The default gap between each bar section
+ int gap = (int) barGap;
+
+ // Determine roughly how wide (rough_width) this bar should be. This is then used as a default width
+ // when there are gaps in the data or for the first/last bars.
+ float f_rough_width = ((plotArea.width() - ((axisMap.size() - 1) * gap)) / (axisMap.size() - 1));
+ int rough_width = (int) f_rough_width;
+ if (rough_width < 0) rough_width = 0;
+ if (gap > rough_width) {
+ gap = rough_width / 2;
+ }
+
+ //Log.d("PARAMTER","PLOT_WIDTH=" + plotArea.width());
+ //Log.d("PARAMTER","BAR_GROUPS=" + axisMap.size());
+ //Log.d("PARAMTER","ROUGH_WIDTH=" + rough_width);
+ //Log.d("PARAMTER","GAP=" + gap);
+
+ /*
+ * Calculate the dimensions of each barGroup and then draw each bar within it according to
+ * the Render Style and Width Style.
+ */
+
+ for(Number key : axisMap.keySet()) {
+
+ BarGroup barGroup = axisMap.get(key);
+
+ // Determine the exact left and right X for the Bar Group
+ switch (widthStyle) {
+ case FIXED_WIDTH:
+ // use intX and go halfwidth either side.
+ barGroup.leftX = barGroup.intX - (int) (barWidth / 2);
+ barGroup.width = (int) barWidth;
+ barGroup.rightX = barGroup.leftX + barGroup.width;
+ break;
+ case VARIABLE_WIDTH:
+ if (barGroup.prev != null) {
+ if (barGroup.intX - barGroup.prev.intX - gap - 1 > (int)(rough_width * 1.5)) {
+ // use intX and go halfwidth either side.
+ barGroup.leftX = barGroup.intX - (rough_width / 2);
+ barGroup.width = rough_width;
+ barGroup.rightX = barGroup.leftX + barGroup.width;
+ } else {
+ // base left off prev right to get the gap correct.
+ barGroup.leftX = barGroup.prev.rightX + gap + 1;
+ if (barGroup.leftX > barGroup.intX) barGroup.leftX = barGroup.intX;
+ // base right off intX + halfwidth.
+ barGroup.rightX = barGroup.intX + (rough_width / 2);
+ // calculate the width
+ barGroup.width = barGroup.rightX - barGroup.leftX;
+ }
+ } else {
+ // use intX and go halfwidth either side.
+ barGroup.leftX = barGroup.intX - (rough_width / 2);
+ barGroup.width = rough_width;
+ barGroup.rightX = barGroup.leftX + barGroup.width;
+ }
+ break;
+ default:
+ break;
+ }
+
+ //Log.d("BAR_GROUP", "rough_width=" + rough_width + " width=" + barGroup.width + " <" + barGroup.leftX + "|" + barGroup.intX + "|" + barGroup.rightX + ">");
+
+ /*
+ * Draw the bars within the barGroup area.
+ */
+ switch (renderStyle) {
+ case OVERLAID:
+ Collections.sort(barGroup.bars, new BarComparator());
+ for (Bar b : barGroup.bars) {
+ BarFormatter formatter = b.formatter();
+ PointLabelFormatter plf = formatter.getPointLabelFormatter();
+ PointLabeler pointLabeler = null;
+ if (formatter != null) {
+ pointLabeler = formatter.getPointLabeler();
+ }
+ //Log.d("BAR", b.series.getTitle() + " <" + b.barGroup.leftX + "|" + b.barGroup.intX + "|" + b.barGroup.rightX + "> " + b.intY);
+ if (b.barGroup.width >= 2) {
+ canvas.drawRect(b.barGroup.leftX, b.intY, b.barGroup.rightX, b.barGroup.plotArea.bottom, formatter.getFillPaint());
+ }
+ canvas.drawRect(b.barGroup.leftX, b.intY, b.barGroup.rightX, b.barGroup.plotArea.bottom, formatter.getBorderPaint());
+ if(plf != null && pointLabeler != null) {
+ canvas.drawText(pointLabeler.getLabel(b.series, b.seriesIndex), b.intX + plf.hOffset, b.intY + plf.vOffset, plf.getTextPaint());
+ }
+ }
+ break;
+ case SIDE_BY_SIDE:
+ int width = (int) barGroup.width / barGroup.bars.size();
+ int leftX = barGroup.leftX;
+ Collections.sort(barGroup.bars, new BarComparator());
+ for (Bar b : barGroup.bars) {
+ BarFormatter formatter = b.formatter();
+ PointLabelFormatter plf = formatter.getPointLabelFormatter();
+ PointLabeler pointLabeler = null;
+ if (formatter != null) {
+ pointLabeler = formatter.getPointLabeler();
+ }
+ //Log.d("BAR", "width=" + width + " <" + leftX + "|" + b.intX + "|" + (leftX + width) + "> " + b.intY);
+ if (b.barGroup.width >= 2) {
+ canvas.drawRect(leftX, b.intY, leftX + width, b.barGroup.plotArea.bottom, formatter.getFillPaint());
+ }
+ canvas.drawRect(leftX, b.intY, leftX + width, b.barGroup.plotArea.bottom, formatter.getBorderPaint());
+ if(plf != null && pointLabeler != null) {
+ canvas.drawText(pointLabeler.getLabel(b.series, b.seriesIndex), leftX + width/2 + plf.hOffset, b.intY + plf.vOffset, plf.getTextPaint());
+ }
+ leftX = leftX + width;
+ }
+ break;
+ case STACKED:
+ int bottom = (int) barGroup.plotArea.bottom;
+ Collections.sort(barGroup.bars, new BarComparator());
+ for (Bar b : barGroup.bars) {
+ BarFormatter formatter = b.formatter();
+ PointLabelFormatter plf = formatter.getPointLabelFormatter();
+ PointLabeler pointLabeler = null;
+ if (formatter != null) {
+ pointLabeler = formatter.getPointLabeler();
+ }
+ int height = (int) b.barGroup.plotArea.bottom - b.intY;
+ int top = bottom - height;
+ //Log.d("BAR", "top=" + top + " bottom=" + bottom + " height=" + height);
+ if (b.barGroup.width >= 2) {
+ canvas.drawRect(b.barGroup.leftX, top, b.barGroup.rightX, bottom, formatter.getFillPaint());
+ }
+ canvas.drawRect(b.barGroup.leftX, top, b.barGroup.rightX, bottom, formatter.getBorderPaint());
+ if(plf != null && pointLabeler != null) {
+ canvas.drawText(pointLabeler.getLabel(b.series, b.seriesIndex), b.intX + plf.hOffset, b.intY + plf.vOffset, plf.getTextPaint());
+ }
+ bottom = top;
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ }
+
+ private class Bar {
+ public XYSeries series;
+ public int seriesIndex;
+ public double yVal, xVal;
+ public int intX, intY;
+ public float pixX, pixY;
+ public BarGroup barGroup;
+
+ public Bar(XYSeries series, int seriesIndex, RectF plotArea) {
+ this.series = series;
+ this.seriesIndex = seriesIndex;
+
+ this.xVal = series.getX(seriesIndex).doubleValue();
+ this.pixX = ValPixConverter.valToPix(xVal, getPlot().getCalculatedMinX().doubleValue(), getPlot().getCalculatedMaxX().doubleValue(), plotArea.width(), false) + (plotArea.left);
+ this.intX = (int) pixX;
+
+ if (series.getY(seriesIndex) != null) {
+ this.yVal = series.getY(seriesIndex).doubleValue();
+ this.pixY = ValPixConverter.valToPix(yVal, getPlot().getCalculatedMinY().doubleValue(), getPlot().getCalculatedMaxY().doubleValue(), plotArea.height(), true) + plotArea.top;
+ this.intY = (int) pixY;
+ } else {
+ this.yVal = 0;
+ this.pixY = plotArea.bottom;
+ this.intY = (int) pixY;
+ }
+ }
+ public BarFormatter formatter() {
+ return getFormatter(seriesIndex, series);
+ }
+ }
+
+ private class BarGroup {
+ public ArrayList<Bar> bars;
+ public int intX;
+ public int width, leftX, rightX;
+ public RectF plotArea;
+ public BarGroup prev;
+
+ public BarGroup(int intX, RectF plotArea) {
+ // Setup the TreeMap with the required comparator
+ this.bars = new ArrayList<Bar>(); // create a comparator that compares series title given the index.
+ this.intX = intX;
+ this.plotArea = plotArea;
+ }
+
+ public void addBar(Bar bar) {
+ bar.barGroup = this;
+ this.bars.add(bar);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public class BarComparator implements Comparator<Bar>{
+ @Override
+ public int compare(Bar bar1, Bar bar2) {
+ switch (renderStyle) {
+ case OVERLAID:
+ return Integer.valueOf(bar1.intY).compareTo(bar2.intY);
+ case SIDE_BY_SIDE:
+ return bar1.series.getTitle().compareToIgnoreCase(bar2.series.getTitle());
+ case STACKED:
+ return bar1.series.getTitle().compareToIgnoreCase(bar2.series.getTitle());
+ default:
+ return 0;
+ }
+ }
+ }
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointFormatter.java
new file mode 100644
index 0000000..1935a83
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointFormatter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import com.androidplot.ui.SeriesRenderer;
+
+/**
+ * This is an experimental class and should not currently be used in production apps.
+ */
+public class BezierLineAndPointFormatter extends LineAndPointFormatter {
+
+ /**
+ * Should only be used in conjunction with calls to configure()...
+ */
+ public BezierLineAndPointFormatter() {
+ }
+
+ public BezierLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor) {
+ super(lineColor, vertexColor, fillColor, null);
+ }
+
+ public BezierLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor, FillDirection fillDir) {
+ super(lineColor, vertexColor, fillColor, null, fillDir);
+ }
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return BezierLineAndPointRenderer.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(XYPlot plot) {
+ return new BezierLineAndPointRenderer(plot);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointRenderer.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointRenderer.java
new file mode 100644
index 0000000..432abe5
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BezierLineAndPointRenderer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+
+/**
+ * WARNING: This is an unfinished class. Series drawn by this implementation may look nice
+ * but they are not yet accurate representations of the control points from which they are derived.
+ */
+public class BezierLineAndPointRenderer extends LineAndPointRenderer<BezierLineAndPointFormatter> {
+ public BezierLineAndPointRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ @Override
+ protected void appendToPath(Path path, PointF thisPoint, PointF lastPoint) {
+ //path.lineTo(thisPoint.x, thisPoint.y);
+
+ // bezier curve version:
+ PointF mid = new PointF();
+ mid.set((lastPoint.x + thisPoint.x) / 2, (lastPoint.y + thisPoint.y) / 2);
+ path.quadTo((lastPoint.x + mid.x) / 2, lastPoint.y, mid.x, mid.y);
+ //path.quadTo((mid.x + thisPoint.x) / 2, thisPoint.y, thisPoint.x, thisPoint.y);
+ path.quadTo((mid.x + thisPoint.x) / 2, lastPoint.y, thisPoint.x, thisPoint.y);
+
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/BoundaryMode.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BoundaryMode.java
new file mode 100644
index 0000000..4d717f8
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/BoundaryMode.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+public enum BoundaryMode {
+ FIXED,
+ AUTO,
+ GROW,
+ SHRINNK
+}
+
+
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/FillDirection.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/FillDirection.java
new file mode 100644
index 0000000..e1e1845
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/FillDirection.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+/**
+ * Defines which edge is used to close a fill path for drawing lines.
+ *
+ * TOP - Use the top edge of the plot.
+ * BOTTOM - Use the bottom edge of the plot.
+ * LEFT - (Not implemented) Use the left edge of the plot.
+ * RIGHT - (Not implemented) Use the right edge of the plot.
+ * DOMAIN_ORIGIN - (Not implemented) Use the domain origin line.
+ * RANGE_ORIGIN - Use the range origin line.
+ */
+public enum FillDirection {
+ TOP,
+ BOTTOM,
+ LEFT,
+ RIGHT,
+ DOMAIN_ORIGIN,
+ RANGE_ORIGIN
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointFormatter.java
new file mode 100644
index 0000000..9f9b377
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointFormatter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Paint;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.util.Configurator;
+import com.androidplot.util.PixelUtils;
+
+/**
+ * Defines the visual aesthetics of an XYSeries; outline color and width, fill style,
+ * vertex size and color, shadowing, etc.
+ */
+public class LineAndPointFormatter extends XYSeriesFormatter<XYRegionFormatter> {
+
+ private static final float DEFAULT_LINE_STROKE_WIDTH_DP = 1.5f;
+ private static final float DEFAULT_VERTEX_STROKE_WIDTH_DP = 4.5f;
+
+ // default implementation prints point's yVal:
+ private PointLabeler pointLabeler = new PointLabeler() {
+ @Override
+ public String getLabel(XYSeries series, int index) {
+ return series.getY(index) + "";
+ }
+ };
+
+ public FillDirection getFillDirection() {
+ return fillDirection;
+ }
+
+ /**
+ * Sets which edge to use to close the line's path for filling purposes.
+ * See {@link FillDirection}.
+ * @param fillDirection
+ */
+ public void setFillDirection(FillDirection fillDirection) {
+ this.fillDirection = fillDirection;
+ }
+
+ protected FillDirection fillDirection = FillDirection.BOTTOM;
+ protected Paint linePaint;
+ protected Paint vertexPaint;
+ protected Paint fillPaint;
+ private PointLabelFormatter pointLabelFormatter;
+
+ {
+ initLinePaint(Color.BLACK);
+ }
+
+ /**
+ * Should only be used in conjunction with calls to configure()...
+ */
+ public LineAndPointFormatter() {
+ this(Color.RED, Color.GREEN, Color.BLUE, null);
+ }
+
+ public LineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor, PointLabelFormatter plf) {
+ this(lineColor, vertexColor, fillColor, plf, FillDirection.BOTTOM);
+ }
+
+ public LineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor, PointLabelFormatter plf, FillDirection fillDir) {
+ initLinePaint(lineColor);
+ initVertexPaint(vertexColor);
+ initFillPaint(fillColor);
+ setFillDirection(fillDir);
+ this.setPointLabelFormatter(plf);
+ }
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return LineAndPointRenderer.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(XYPlot plot) {
+ return new LineAndPointRenderer(plot);
+ }
+
+ protected void initLinePaint(Integer lineColor) {
+ if (lineColor == null) {
+ linePaint = null;
+ } else {
+ linePaint = new Paint();
+ linePaint.setAntiAlias(true);
+ linePaint.setStrokeWidth(PixelUtils.dpToPix(DEFAULT_LINE_STROKE_WIDTH_DP));
+ linePaint.setColor(lineColor);
+ linePaint.setStyle(Paint.Style.STROKE);
+ }
+ }
+
+ protected void initVertexPaint(Integer vertexColor) {
+ if (vertexColor == null) {
+ vertexPaint = null;
+ } else {
+ vertexPaint = new Paint();
+ vertexPaint.setAntiAlias(true);
+ vertexPaint.setStrokeWidth(PixelUtils.dpToPix(DEFAULT_VERTEX_STROKE_WIDTH_DP));
+ vertexPaint.setColor(vertexColor);
+ vertexPaint.setStrokeCap(Paint.Cap.ROUND);
+ }
+ }
+
+ protected void initFillPaint(Integer fillColor) {
+ if (fillColor == null) {
+ fillPaint = null;
+ } else {
+ fillPaint = new Paint();
+ fillPaint.setAntiAlias(true);
+ fillPaint.setColor(fillColor);
+ }
+ }
+
+ /**
+ * Enables the shadow layer on linePaint and shadowPaint by calling
+ * setShadowLayer() with preset values.
+ */
+ public void enableShadows() {
+ linePaint.setShadowLayer(1, 3, 3, Color.BLACK);
+ vertexPaint.setShadowLayer(1, 3, 3, Color.BLACK);
+ }
+
+ public void disableShadows() {
+ linePaint.setShadowLayer(0, 0, 0, Color.BLACK);
+ vertexPaint.setShadowLayer(0, 0, 0, Color.BLACK);
+ }
+
+ public Paint getLinePaint() {
+ return linePaint;
+ }
+
+ public void setLinePaint(Paint linePaint) {
+ this.linePaint = linePaint;
+ }
+
+ public Paint getVertexPaint() {
+ return vertexPaint;
+ }
+
+ public void setVertexPaint(Paint vertexPaint) {
+ this.vertexPaint = vertexPaint;
+ }
+
+ public Paint getFillPaint() {
+ return fillPaint;
+ }
+
+ public void setFillPaint(Paint fillPaint) {
+ this.fillPaint = fillPaint;
+ }
+
+ public PointLabelFormatter getPointLabelFormatter() {
+ return pointLabelFormatter;
+ }
+
+ public void setPointLabelFormatter(PointLabelFormatter pointLabelFormatter) {
+ this.pointLabelFormatter = pointLabelFormatter;
+ }
+
+ public PointLabeler getPointLabeler() {
+ return pointLabeler;
+ }
+
+ public void setPointLabeler(PointLabeler pointLabeler) {
+ this.pointLabeler = pointLabeler;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java
new file mode 100644
index 0000000..14595c4
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.*;
+import android.util.Pair;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.util.ValPixConverter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Renders a point as a line with the vertices marked. Requires 2 or more points to
+ * be rendered.
+ */
+public class LineAndPointRenderer<FormatterType extends LineAndPointFormatter> extends XYSeriesRenderer<FormatterType> {
+
+ public LineAndPointRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ @Override
+ public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
+
+
+ List<XYSeries> seriesList = getPlot().getSeriesListForRenderer(this.getClass());
+ if (seriesList != null) {
+ for (XYSeries series : seriesList) {
+ //synchronized(series) {
+ drawSeries(canvas, plotArea, series, getFormatter(series));
+ //}
+ }
+ }
+ }
+
+ @Override
+ public void doDrawLegendIcon(Canvas canvas, RectF rect, LineAndPointFormatter formatter) {
+ // horizontal icon:
+ float centerY = rect.centerY();
+ float centerX = rect.centerX();
+
+ if(formatter.getFillPaint() != null) {
+ canvas.drawRect(rect, formatter.getFillPaint());
+ }
+ if(formatter.getLinePaint() != null) {
+ canvas.drawLine(rect.left, rect.bottom, rect.right, rect.top, formatter.getLinePaint());
+ }
+
+ if(formatter.getVertexPaint() != null) {
+ canvas.drawPoint(centerX, centerY, formatter.getVertexPaint());
+ }
+ }
+
+ /**
+ * This method exists for StepRenderer to override without having to duplicate any
+ * additional code.
+ */
+ protected void appendToPath(Path path, PointF thisPoint, PointF lastPoint) {
+
+ path.lineTo(thisPoint.x, thisPoint.y);
+ }
+
+
+ protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAndPointFormatter formatter) {
+ PointF thisPoint;
+ PointF lastPoint = null;
+ PointF firstPoint = null;
+ Paint linePaint = formatter.getLinePaint();
+
+ //PointF lastDrawn = null;
+ Path path = null;
+ ArrayList<Pair<PointF, Integer>> points = new ArrayList<Pair<PointF, Integer>>(series.size());
+ for (int i = 0; i < series.size(); i++) {
+ Number y = series.getY(i);
+ Number x = series.getX(i);
+
+ if (y != null && x != null) {
+ thisPoint = ValPixConverter.valToPix(
+ x,
+ y,
+ plotArea,
+ getPlot().getCalculatedMinX(),
+ getPlot().getCalculatedMaxX(),
+ getPlot().getCalculatedMinY(),
+ getPlot().getCalculatedMaxY());
+ points.add(new Pair<PointF, Integer>(thisPoint, i));
+ //appendToPath(path, thisPoint, lastPoint);
+ } else {
+ thisPoint = null;
+ }
+
+ if(linePaint != null && thisPoint != null) {
+
+ // record the first point of the new Path
+ if(firstPoint == null) {
+ path = new Path();
+ firstPoint = thisPoint;
+ // create our first point at the bottom/x position so filling
+ // will look good
+ path.moveTo(firstPoint.x, firstPoint.y);
+ }
+
+ if(lastPoint != null) {
+ appendToPath(path, thisPoint, lastPoint);
+ }
+
+ lastPoint = thisPoint;
+ } else {
+ if(lastPoint != null) {
+ renderPath(canvas, plotArea, path, firstPoint, lastPoint, formatter);
+ }
+ firstPoint = null;
+ lastPoint = null;
+ }
+ }
+ if(linePaint != null && firstPoint != null) {
+ renderPath(canvas, plotArea, path, firstPoint, lastPoint, formatter);
+ }
+
+ // TODO: benchmark this against drawPoints(float[]);
+ Paint vertexPaint = formatter.getVertexPaint();
+ PointLabelFormatter plf = formatter.getPointLabelFormatter();
+ if (vertexPaint != null || plf != null) {
+ for (Pair<PointF, Integer> p : points) {
+ PointLabeler pointLabeler = formatter.getPointLabeler();
+
+ // if vertexPaint is available, draw vertex:
+ if(vertexPaint != null) {
+ canvas.drawPoint(p.first.x, p.first.y, formatter.getVertexPaint());
+ }
+
+ // if textPaint and pointLabeler are available, draw point's text label:
+ if(plf != null && pointLabeler != null) {
+ canvas.drawText(pointLabeler.getLabel(series, p.second), p.first.x + plf.hOffset, p.first.y + plf.vOffset, plf.getTextPaint());
+ }
+ }
+ }
+ }
+
+ protected void renderPath(Canvas canvas, RectF plotArea, Path path, PointF firstPoint, PointF lastPoint, LineAndPointFormatter formatter) {
+ Path outlinePath = new Path(path);
+
+ // determine how to close the path for filling purposes:
+ // We always need to calculate this path because it is also used for
+ // masking off for region highlighting.
+ switch (formatter.getFillDirection()) {
+ case BOTTOM:
+ path.lineTo(lastPoint.x, plotArea.bottom);
+ path.lineTo(firstPoint.x, plotArea.bottom);
+ path.close();
+ break;
+ case TOP:
+ path.lineTo(lastPoint.x, plotArea.top);
+ path.lineTo(firstPoint.x, plotArea.top);
+ path.close();
+ break;
+ case RANGE_ORIGIN:
+ float originPix = ValPixConverter.valToPix(
+ getPlot().getRangeOrigin().doubleValue(),
+ getPlot().getCalculatedMinY().doubleValue(),
+ getPlot().getCalculatedMaxY().doubleValue(),
+ plotArea.height(),
+ true);
+ originPix += plotArea.top;
+
+ path.lineTo(lastPoint.x, originPix);
+ path.lineTo(firstPoint.x, originPix);
+ path.close();
+ break;
+ default:
+ throw new UnsupportedOperationException("Fill direction not yet implemented: " + formatter.getFillDirection());
+ }
+
+ if (formatter.getFillPaint() != null) {
+ canvas.drawPath(path, formatter.getFillPaint());
+ }
+
+
+ //}
+
+ // draw any visible regions on top of the base region:
+ double minX = getPlot().getCalculatedMinX().doubleValue();
+ double maxX = getPlot().getCalculatedMaxX().doubleValue();
+ double minY = getPlot().getCalculatedMinY().doubleValue();
+ double maxY = getPlot().getCalculatedMaxY().doubleValue();
+
+ // draw each region:
+ for (RectRegion r : RectRegion.regionsWithin(formatter.getRegions().elements(), minX, maxX, minY, maxY)) {
+ XYRegionFormatter f = formatter.getRegionFormatter(r);
+ RectF regionRect = r.getRectF(plotArea, minX, maxX, minY, maxY);
+ if (regionRect != null) {
+ try {
+ canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.clipPath(path);
+ canvas.drawRect(regionRect, f.getPaint());
+ } finally {
+ canvas.restore();
+ }
+ }
+ }
+
+ // finally we draw the outline path on top of everything else:
+ if(formatter.getLinePaint() != null) {
+ canvas.drawPath(outlinePath, formatter.getLinePaint());
+ }
+
+ path.rewind();
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabelFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabelFormatter.java
new file mode 100644
index 0000000..411a8fe
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabelFormatter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import com.androidplot.util.PixelUtils;
+
+public class PointLabelFormatter {
+ private static final float DEFAULT_H_OFFSET_DP = 0;
+ private static final float DEFAULT_V_OFFSET_DP = -4;
+ private static final float DEFAULT_TEXT_SIZE_SP = 12;
+ private Paint textPaint;
+ public float hOffset;
+ public float vOffset;
+
+ public PointLabelFormatter() {
+ this(Color.WHITE);
+ }
+
+ public PointLabelFormatter(int textColor) {
+ this(textColor, PixelUtils.dpToPix(DEFAULT_H_OFFSET_DP),
+ PixelUtils.dpToPix(DEFAULT_V_OFFSET_DP));
+ }
+
+ /**
+ *
+ * @param textColor
+ * @param hOffset Horizontal offset of text in pixels.
+ * @param vOffset Vertical offset of text in pixels. Offset is in screen coordinates;
+ * positive values shift the text further down the screen.
+ */
+ public PointLabelFormatter(int textColor, float hOffset, float vOffset) {
+ initTextPaint(textColor);
+ this.hOffset = hOffset;
+ this.vOffset = vOffset;
+ }
+
+ public Paint getTextPaint() {
+ return textPaint;
+ }
+
+ public void setTextPaint(Paint textPaint) {
+ this.textPaint = textPaint;
+ }
+
+ protected void initTextPaint(Integer textColor) {
+ if (textColor == null) {
+ setTextPaint(null);
+ } else {
+ setTextPaint(new Paint());
+ getTextPaint().setAntiAlias(true);
+ getTextPaint().setColor(textColor);
+ getTextPaint().setTextAlign(Paint.Align.CENTER);
+ getTextPaint().setTextSize(PixelUtils.spToPix(DEFAULT_TEXT_SIZE_SP));
+ //textPaint.setStyle(Paint.Style.STROKE);
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabeler.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabeler.java
new file mode 100644
index 0000000..8814ba7
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/PointLabeler.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+public interface PointLabeler {
+
+ public String getLabel(XYSeries series, int index);
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/RectRegion.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/RectRegion.java
new file mode 100644
index 0000000..bee4a4f
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/RectRegion.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import com.androidplot.LineRegion;
+import com.androidplot.util.ValPixConverter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * RectRegion is just a rectangle with additional methods for determining
+ * intersections with other RectRegion instances.
+ */
+public class RectRegion {
+
+ LineRegion xLineRegion;
+ LineRegion yLineRegion;
+ private String label;
+
+ /**
+ *
+ * @param minX
+ * @param maxX
+ * @param minY
+ * @param maxY
+ */
+ public RectRegion(Number minX, Number maxX, Number minY, Number maxY, String label) {
+ xLineRegion = new LineRegion(minX, maxX);
+ yLineRegion = new LineRegion(minY, maxY);
+ this.setLabel(label);
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ public RectRegion(Number minX, Number maxX, Number minY, Number maxY) {
+ this(minX, maxX, minY, maxY, null);
+ }
+
+ public boolean containsPoint(PointF point) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ public boolean containsValue(Number x, Number y) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ public boolean containsDomainValue(Number value) {
+ //return RectRegion.isBetween(value, minX, maxX);
+ return xLineRegion.contains(value);
+ }
+
+ public boolean containsRangeValue(Number value) {
+ //return RectRegion.isBetween(value, minY, maxY);
+ return yLineRegion.contains(value);
+ }
+
+ public boolean intersects(RectRegion region) {
+ return intersects(region.getMinX(), region.getMaxX(), region.getMinY(), region.getMaxY());
+ }
+
+
+ /**
+ * Tests whether this region intersects the region defined by params. Use
+ * null to represent infinity. Negative and positive infinity is implied by
+ * the boundary edge, ie. a maxX of null equals positive infinity while a
+ * minX of null equals negative infinity.
+ * @param minX
+ * @param maxX
+ * @param minY
+ * @param maxY
+ * @return
+ */
+ public boolean intersects(Number minX, Number maxX, Number minY, Number maxY) {
+ return xLineRegion.intersects(minX, maxX) && yLineRegion.intersects(minY, maxY);
+ }
+
+ public boolean intersects(RectF region, Number visMinX, Number visMaxX, Number visMinY, Number visMaxY) {
+
+ RectF thisRegion = getRectF(region, visMinX.doubleValue(), visMaxX.doubleValue(),
+ visMinY.doubleValue(), visMaxY.doubleValue());
+ return RectF.intersects(thisRegion, region);
+ }
+
+ public RectF getRectF(RectF plotRect, Number visMinX, Number visMaxX, Number visMinY, Number visMaxY) {
+ PointF topLeftPoint = ValPixConverter.valToPix(
+ xLineRegion.getMinVal().doubleValue() != Double.NEGATIVE_INFINITY ? xLineRegion.getMinVal() : visMinX,
+ yLineRegion.getMaxVal().doubleValue() != Double.POSITIVE_INFINITY ? yLineRegion.getMaxVal() : visMaxY,
+ plotRect,
+ visMinX,
+ visMaxX,
+ visMinY,
+ visMaxY);
+ PointF bottomRightPoint = ValPixConverter.valToPix(
+ xLineRegion.getMaxVal().doubleValue() != Double.POSITIVE_INFINITY ? xLineRegion.getMaxVal() : visMaxX,
+ yLineRegion.getMinVal().doubleValue() != Double.NEGATIVE_INFINITY ? yLineRegion.getMinVal() : visMinY,
+ plotRect,
+ visMinX,
+ visMaxX,
+ visMinY,
+ visMaxY);
+ // TODO: figure out why the y-values are inverted
+ return new RectF(topLeftPoint.x, topLeftPoint.y, bottomRightPoint.x, bottomRightPoint.y);
+ }
+
+ /**
+ * Returns a list of XYRegions that either completely or partially intersect the area
+ * defined by params. A null value for any parameter represents infinity / no boundary.
+ * @param regions The list of regions to search through
+ * @param minX
+ * @param maxX
+ * @param minY
+ * @param maxY
+ * @return
+ */
+ public static List<RectRegion> regionsWithin(List<RectRegion> regions, Number minX, Number maxX, Number minY, Number maxY) {
+ ArrayList<RectRegion> intersectingRegions = new ArrayList<RectRegion>();
+ for(RectRegion r : regions) {
+ if(r.intersects(minX, maxX, minY, maxY)) {
+ intersectingRegions.add(r);
+ }
+ }
+ return intersectingRegions;
+ }
+
+
+ public Number getMinX() {
+ return xLineRegion.getMinVal();
+ }
+
+ public void setMinX(double minX) {
+ xLineRegion.setMinVal(minX);
+ }
+
+ public Number getMaxX() {
+ return xLineRegion.getMaxVal();
+ }
+
+ public void setMaxX(Number maxX) {
+ xLineRegion.setMaxVal(maxX);
+ }
+
+ public Number getMinY() {
+ return yLineRegion.getMinVal();
+ }
+
+ public void setMinY(Number minY) {
+ yLineRegion.setMinVal(minY);
+ }
+
+ public Number getMaxY() {
+ return yLineRegion.getMaxVal();
+ }
+
+ public void setMaxY(Number maxY) {
+ yLineRegion.setMaxVal(maxY);
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/SimpleXYSeries.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/SimpleXYSeries.java
new file mode 100644
index 0000000..0416103
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/SimpleXYSeries.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Canvas;
+import android.util.Log;
+import android.util.Pair;
+import com.androidplot.Plot;
+import com.androidplot.PlotListener;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+
+/**
+ * A convenience class used to create instances of XYPlot generated from Lists of Numbers.
+ */
+public class SimpleXYSeries implements XYSeries, PlotListener {
+
+ private static final String TAG = SimpleXYSeries.class.getName();
+
+ @Override
+ public void onBeforeDraw(Plot source, Canvas canvas) {
+ lock.readLock().lock();
+ }
+
+ @Override
+ public void onAfterDraw(Plot source, Canvas canvas) {
+ lock.readLock().unlock();
+ }
+
+ public enum ArrayFormat {
+ Y_VALS_ONLY,
+ XY_VALS_INTERLEAVED
+ }
+
+ private volatile LinkedList<Number> xVals = new LinkedList<Number>();
+ private volatile LinkedList<Number> yVals = new LinkedList<Number>();
+ private volatile String title = null;
+ private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
+
+
+ public SimpleXYSeries(String title) {
+ this.title = title;
+ }
+ /**
+ * Generates an XYSeries instance from the List of numbers passed in. This is a convenience class
+ * and should only be used for static data models; it is not suitable for representing dynamically
+ * changing data.
+ *
+ * @param model A List of Number elements comprising the data model.
+ * @param format Format of the model. A format of Y_VALS_ONLY means that the array only contains y-values.
+ * For this format x values are autogenerated using values of 0 through n-1 where n is the size of the model.
+ * @param title Title of the series
+ */
+ public SimpleXYSeries(List<? extends Number> model, ArrayFormat format, String title) {
+ this(title);
+ setModel(model, format);
+ }
+
+ public SimpleXYSeries(List<? extends Number> xVals, List<? extends Number> yVals, String title) {
+ this(title);
+ if(xVals == null || yVals == null) {
+ throw new IllegalArgumentException("Neither the xVals nor the yVals parameters may be null.");
+ }
+
+ if(xVals.size() != yVals.size()) {
+ throw new IllegalArgumentException("xVals and yVals List parameters must be of the same size.");
+ }
+
+ this.xVals.addAll(xVals);
+ this.yVals.addAll(yVals);
+ }
+
+ /**
+ * Use index value as xVal, instead of explicit, user provided xVals.
+ */
+ public void useImplicitXVals() {
+ lock.writeLock().lock();
+ try {
+ xVals = null;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Use the provided list of Numbers as yVals and their corresponding indexes as xVals.
+ * @param model A List of Number elements comprising the data model.
+ * @param format Format of the model. A format of Y_VALS_ONLY means that the array only contains y-values.
+ * For this format x values are autogenerated using values of 0 through n-1 where n is the size of the model.
+ */
+ public void setModel(List<? extends Number> model, ArrayFormat format) {
+
+ lock.writeLock().lock();
+ try {
+ // empty the current values:
+ //xVals.clear();
+ xVals = null;
+ yVals.clear();
+
+ // make sure the new model has data:
+ if (model == null || model.size() == 0) {
+ return;
+ }
+
+ switch (format) {
+
+ // array containing only y-vals. assume x = index:
+ case Y_VALS_ONLY:
+ for(Number n : model) {
+ yVals.add(n);
+ }
+ /*for (int i = 0; i < model.size(); i++) {
+ //xVals.add(i);
+ yVals.add(model.get(i));
+ }*/
+ break;
+
+ // xy interleaved array:
+ case XY_VALS_INTERLEAVED:
+ if (xVals == null) {
+ xVals = new LinkedList<Number>();
+ }
+ if (model.size() % 2 != 0) {
+ throw new IndexOutOfBoundsException("Cannot auto-generate series from odd-sized xy List.");
+ }
+ // always need an x and y array so init them now:
+ int sz = model.size() / 2;
+ for (int i = 0, j = 0; i < sz; i++, j += 2) {
+ xVals.add(model.get(j));
+ yVals.add(model.get(j + 1));
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected enum value: " + format);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Sets individual x value based on index
+ * @param value
+ * @param index
+ */
+ public void setX(Number value, int index) {
+ lock.writeLock().lock();
+ try {
+ xVals.set(index, value);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Sets individual y value based on index
+ * @param value
+ * @param index
+ */
+ public void setY(Number value, int index) {
+ lock.writeLock().lock();
+ try {
+ yVals.set(index, value);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Sets xy values based on index
+ * @param xVal
+ * @param yVal
+ * @param index
+ */
+ public void setXY(Number xVal, Number yVal, int index) {
+ lock.writeLock().lock();
+ try {
+ yVals.set(index, yVal);
+ xVals.set(index, xVal);
+ } finally {lock.writeLock().unlock();}
+ }
+
+ public void addFirst(Number x, Number y) {
+ lock.writeLock().lock();
+ try {
+ if (xVals != null) {
+ xVals.addFirst(x);
+ }
+ yVals.addFirst(y);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ *
+ * @return Pair<Number, Number> with first equal to x-val and second equal to y-val.
+ */
+ public Pair<Number, Number> removeFirst() {
+ lock.writeLock().lock();
+ try {
+ if (size() <= 0) {
+ throw new NoSuchElementException();
+ }
+ return new Pair<Number, Number>(xVals != null ? xVals.removeFirst() : 0, yVals.removeFirst());
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ public void addLast(Number x, Number y) {
+ lock.writeLock().lock();
+ try {
+ if (xVals != null) {
+ xVals.addLast(x);
+ }
+ yVals.addLast(y);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ *
+ * @return Pair<Number, Number> with first equal to x-val and second equal to y-val.
+ */
+ public Pair<Number, Number> removeLast() {
+ lock.writeLock().lock();
+ try {
+ if (size() <= 0) {
+ throw new NoSuchElementException();
+ }
+ return new Pair<Number, Number>(xVals != null ? xVals.removeLast() : yVals.size() - 1, yVals.removeLast());
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ lock.writeLock().lock();
+ try {
+ this.title = title;
+ } finally {lock.writeLock().unlock();}
+ }
+
+ @Override
+ public int size() {
+ return yVals != null ? yVals.size() : 0;
+ }
+
+ @Override
+ public Number getX(int index) {
+ return xVals != null ? xVals.get(index) : index;
+ }
+
+ @Override
+ public Number getY(int index) {
+ return yVals.get(index);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/StepFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/StepFormatter.java
new file mode 100644
index 0000000..8c29277
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/StepFormatter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.util.Configurator;
+
+public class StepFormatter extends LineAndPointFormatter {
+
+ /**
+ * Should only be used in conjunction with calls to configure()...
+ */
+ public StepFormatter() {}
+
+ public StepFormatter(Integer lineColor, Integer fillColor) {
+ initLinePaint(lineColor);
+ initFillPaint(fillColor);
+ }
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return StepRenderer.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(XYPlot plot) {
+ return new StepRenderer(plot);
+ }
+
+} \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/StepRenderer.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/StepRenderer.java
new file mode 100644
index 0000000..17f01c3
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/StepRenderer.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+
+/**
+ * Renders a point as a line with the vertices marked. Requires 2 or more points to
+ * be rendered.
+ */
+
+public class StepRenderer extends LineAndPointRenderer<StepFormatter> {
+
+ public StepRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ @Override
+ protected void appendToPath(Path path, PointF thisPoint, PointF lastPoint) {
+ //path.lineTo(thisPoint.x, thisPoint.y);
+
+ path.lineTo(thisPoint.x, lastPoint.y);
+ path.lineTo(thisPoint.x, thisPoint.y);
+ //canvas.drawPoint(point.x, lastPoint.y, format.getVertexPaint());
+ // next the vertical:
+ //canvas.drawLine(point.x, lastPoint.y, point.x, point.y, format.getLinePaint());
+ }
+}
+/*
+public class StepRenderer extends XYSeriesRenderer<StepFormatter> {
+
+ private PointF lastPoint;
+
+ private boolean drawLinesEnabled = true;
+ private boolean drawPointsEnabled = true;
+
+ private XYAxisType stepAxis = XYAxisType.DOMAIN;
+
+
+
+ public StepRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ @Override
+ public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
+
+
+
+ for(XYSeries series : getPlot().getSeriesListForRenderer(this.getClass())) {
+
+ drawSeries(canvas, plotArea, series, getFormatter(series));
+ }
+ //foreach(this.)
+ //foreach()
+ }
+
+ @Override
+ public void doDrawLegendIcon(Canvas canvas, RectF rect, String text, StepFormatter formatter) {
+ // horizontal icon:
+ float centerY = rect.centerY();
+ float centerX = rect.centerX();
+ canvas.drawLine(rect.left, rect.top, rect.right, rect.bottom, formatter.getLinePaint());
+ canvas.drawPoint(centerX, centerY, formatter.getVertexPaint());
+ //canvas.drawRect(rect, formatter.getLinePaint());
+
+ }
+
+
+ private void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, StepFormatter formatter) throws PlotRenderException {
+ beginSeries(canvas, plotArea, formatter);
+ //XYDataset series = bundle.getDataset();
+ //int seriesIndex = bundle.getSeriesIndex();
+ PointF thisPoint;
+ for (int i = 0; i < series.size(); i++) {
+ Number y = series.getY(i);
+ Number x = series.getD(i);
+
+ if (y != null && x != null) {
+
+ thisPoint = ValPixConverter.valToPix(
+ x,
+ y,
+ plotArea,
+ getPlot().getCalculatedMinX(),
+ getPlot().getCalculatedMaxX(),
+ getPlot().getCalculatedMinY(),
+ getPlot().getCalculatedMaxY());
+ //float pixX = ValPixConverter.valToPix(x.doubleValue(), getPlot().getCalculatedMinX().doubleValue(), getPlot().getCalculatedMaxX().doubleValue(), plotArea.width(), false) + (plotArea.left);
+ //float pixY = ValPixConverter.valToPix(y.doubleValue(), getPlot().getCalculatedMinY().doubleValue(), getPlot().getCalculatedMaxY().doubleValue(), plotArea.height(), true) + plotArea.top;
+
+ //thisPoint = new PointF(pixX, pixY);
+ } else {
+ thisPoint = null;
+ }
+ drawPoint(canvas, thisPoint, plotArea, formatter);
+ }
+ endSeries(canvas, plotArea, formatter);
+ }
+
+ private void beginSeries(Canvas canvas, RectF plotArea, StepFormatter format) throws PlotRenderException {
+ lastPoint = null;
+ }
+
+ private void drawPoint(Canvas canvas, PointF point, RectF plotArea, StepFormatter format) throws PlotRenderException {
+ if (lastPoint != null) {
+ if (point != null) {
+
+ switch(stepAxis) {
+ case DOMAIN:
+ // first draw the horizontal line:
+ canvas.drawLine(lastPoint.x, lastPoint.y, point.x, lastPoint.y, format.getLinePaint());
+ canvas.drawPoint(point.x, lastPoint.y, format.getVertexPaint());
+ // next the vertical:
+ canvas.drawLine(point.x, lastPoint.y, point.x, point.y, format.getLinePaint());
+ break;
+ case RANGE:
+ break;
+ }
+ //doDrawLine(canvas, lastPoint, point, plotArea, format);
+
+
+ }
+ drawLastPoint(canvas, plotArea, format);
+ }
+
+ lastPoint = point;
+ }
+
+ private void endSeries(Canvas canvas, RectF plotArea, StepFormatter format) throws PlotRenderException {
+ if(lastPoint != null) {
+ drawLastPoint(canvas, plotArea, format);
+ }
+ }
+
+ protected void drawLastPoint(Canvas canvas, RectF plotArea, StepFormatter format) throws PlotRenderException {
+ canvas.drawPoint(lastPoint.x, lastPoint.y, format.getVertexPaint());
+ }
+
+
+ public XYAxisType getStepAxis() {
+ return stepAxis;
+ }
+
+ public void setStepAxis(XYAxisType stepAxis) {
+ this.stepAxis = stepAxis;
+ }
+}
+*/
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/ValueMarker.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/ValueMarker.java
new file mode 100644
index 0000000..648cf64
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/ValueMarker.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Color;
+import android.graphics.Paint;
+import com.androidplot.ui.PositionMetric;
+
+/**
+ * Encapsulates a single axis line marker drawn onto an XYPlot at a specified value.
+ * @param <PositionMetricType>
+ */
+public abstract class ValueMarker<PositionMetricType extends PositionMetric> {
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public enum TextOrientation {
+ HORIZONTAL,
+ VERTICAL
+ }
+ private Number value;
+ private Paint linePaint;
+ private Paint textPaint;
+ //private Paint backgroundPaint;
+ private TextOrientation textOrientation;
+ private int textMargin = 2;
+ private PositionMetricType textPosition;
+ private String text;
+
+ {
+ linePaint = new Paint();
+ linePaint.setColor(Color.RED);
+ linePaint.setAntiAlias(true);
+ linePaint.setStyle(Paint.Style.STROKE);
+ textPaint = new Paint();
+ textPaint.setAntiAlias(true);
+ textPaint.setColor(Color.RED);
+ //backgroundPaint = new Paint();
+ //backgroundPaint.setColor(Color.argb(100, 100, 100, 100));
+ //backgroundPaint.setColor(Color.DKGRAY);
+
+ }
+
+ public ValueMarker(Number value, String text, PositionMetricType textPosition) {
+ this.value = value;
+ this.textPosition = textPosition;
+ this.text = text;
+ }
+
+ /**
+ *
+ * @param value
+ * @param text
+ * @param textPosition
+ * @param linePaint
+ * @param textPaint
+ */
+ public ValueMarker(Number value, String text, PositionMetricType textPosition, Paint linePaint, Paint textPaint) {
+ this(value, text, textPosition);
+
+ this.linePaint = linePaint;
+ this.textPaint = textPaint;
+ //this.backgroundPaint = backgroundPaint;
+ }
+
+ public ValueMarker(Number value, String text, PositionMetricType textPosition, int linePaint, int textPaint) {
+ this(value, text, textPosition);
+ this.linePaint.setColor(linePaint);
+ this.textPaint.setColor(textPaint);
+ }
+
+ public Number getValue() {
+ return value;
+ }
+
+ public void setValue(Number value) {
+ this.value = value;
+ }
+
+ public Paint getLinePaint() {
+ return linePaint;
+ }
+
+ public void setLinePaint(Paint linePaint) {
+ this.linePaint = linePaint;
+ }
+
+ public Paint getTextPaint() {
+ return textPaint;
+ }
+
+ public void setTextPaint(Paint textPaint) {
+ this.textPaint = textPaint;
+ }
+
+ /*public Paint getBackgroundPaint() {
+ return backgroundPaint;
+ }
+
+ public void setBackgroundPaint(Paint backgroundPaint) {
+ this.backgroundPaint = backgroundPaint;
+ }*/
+
+ public TextOrientation getTextOrientation() {
+ return textOrientation;
+ }
+
+ /**
+ * Currently not implemented. Sets the orientation of the text portion of this
+ * ValueMarker.
+ * @param textOrientation
+ */
+ public void setTextOrientation(TextOrientation textOrientation) {
+ this.textOrientation = textOrientation;
+ }
+
+ /**
+ * Currently not implemented.
+ * @return
+ */
+ public int getTextMargin() {
+ return textMargin;
+ }
+
+ public void setTextMargin(int textMargin) {
+ this.textMargin = textMargin;
+ }
+
+ public PositionMetricType getTextPosition() {
+ return textPosition;
+ }
+
+ public void setTextPosition(PositionMetricType textPosition) {
+ this.textPosition = textPosition;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XValueMarker.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XValueMarker.java
new file mode 100644
index 0000000..84ddea4
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XValueMarker.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Paint;
+import com.androidplot.ui.YLayoutStyle;
+import com.androidplot.ui.YPositionMetric;
+
+public class XValueMarker extends ValueMarker<YPositionMetric> {
+
+ /**
+ *
+ * @param value
+ * @param text Set to null to use the plot's default formatter.
+ */
+ public XValueMarker(Number value, String text) {
+ super(value, text, new YPositionMetric(3, YLayoutStyle.ABSOLUTE_FROM_TOP));
+ }
+
+ /**
+ *
+ * @param value
+ * @param text Set to null to use the plot's default formatter.
+ * @param textPosition
+ * @param linePaint
+ * @param textPaint
+ */
+ public XValueMarker(Number value, String text, YPositionMetric textPosition, Paint linePaint, Paint textPaint) {
+ super(value, text, textPosition, linePaint, textPaint);
+ }
+
+
+ /**
+ *
+ * @param value
+ * @param text Set to null to use the plot's default formatter.
+ * @param textPosition
+ * @param linePaint
+ * @param textPaint
+ */
+ public XValueMarker(Number value, String text, YPositionMetric textPosition, int linePaint, int textPaint) {
+ super(value, text, textPosition, linePaint, textPaint);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYAxisType.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYAxisType.java
new file mode 100644
index 0000000..2077d6c
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYAxisType.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+public enum XYAxisType {
+ DOMAIN,
+ RANGE
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYFramingModel.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYFramingModel.java
new file mode 100644
index 0000000..7d1220c
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYFramingModel.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+public enum XYFramingModel {
+ ORIGIN,
+ EDGE,
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphBounds.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphBounds.java
new file mode 100644
index 0000000..8089185
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphBounds.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+public class XYGraphBounds {
+
+
+ private Number minX;
+ private Number maxX;
+ private Number minY;
+ private Number maxY;
+
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphWidget.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphWidget.java
new file mode 100644
index 0000000..c6ce620
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYGraphWidget.java
@@ -0,0 +1,1252 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.*;
+
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.LayoutManager;
+import com.androidplot.ui.SizeMetrics;
+import com.androidplot.ui.widget.Widget;
+import com.androidplot.util.FontUtils;
+import com.androidplot.util.ValPixConverter;
+import com.androidplot.util.ZHash;
+import com.androidplot.util.ZIndexable;
+
+import java.text.DecimalFormat;
+import java.text.Format;
+
+/**
+ * Displays graphical data annotated with domain and range tick markers.
+ */
+public class XYGraphWidget extends Widget {
+
+ public float getRangeLabelOrientation() {
+ return rangeLabelOrientation;
+ }
+
+ public void setRangeLabelOrientation(float rangeLabelOrientation) {
+ this.rangeLabelOrientation = rangeLabelOrientation;
+ }
+
+ public float getDomainLabelOrientation() {
+ return domainLabelOrientation;
+ }
+
+ public void setDomainLabelOrientation(float domainLabelOrientation) {
+ this.domainLabelOrientation = domainLabelOrientation;
+ }
+
+ /**
+ * Will be used in a future version.
+ */
+ public enum XYPlotOrientation {
+ HORIZONTAL, VERTICAL
+ }
+
+ private static final int MARKER_LABEL_SPACING = 2;
+ private static final int CURSOR_LABEL_SPACING = 2; // space between cursor
+ private static final String TAG = "AndroidPlot";
+ // lines and label in
+ // pixels
+ private float domainLabelWidth = 15; // how many pixels is the area
+ // allocated for domain labels
+ private float rangeLabelWidth = 41; // ...
+ private float domainLabelVerticalOffset = -5;
+ private float domainLabelHorizontalOffset = 0.0f;
+ private float rangeLabelHorizontalOffset = 1.0f; // allows tweaking of text position
+ private float rangeLabelVerticalOffset = 0.0f; // allows tweaking of text position
+
+ private int ticksPerRangeLabel = 1;
+ private int ticksPerDomainLabel = 1;
+ private float gridPaddingTop = 0;
+ private float gridPaddingBottom = 0;
+ private float gridPaddingLeft = 0;
+ private float gridPaddingRight = 0;
+ private int domainLabelTickExtension = 5;
+ private int rangeLabelTickExtension = 5;
+ private Paint gridBackgroundPaint;
+ private Paint rangeGridLinePaint;
+ private Paint rangeSubGridLinePaint;
+ private Paint domainGridLinePaint;
+ private Paint domainSubGridLinePaint;
+ private Paint domainLabelPaint;
+ private Paint rangeLabelPaint;
+ private Paint domainCursorPaint;
+ private Paint rangeCursorPaint;
+ private Paint cursorLabelPaint;
+ private Paint cursorLabelBackgroundPaint;
+ private XYPlot plot;
+ private Format rangeValueFormat;
+ private Format domainValueFormat;
+ private Paint domainOriginLinePaint;
+ private Paint rangeOriginLinePaint;
+ private Paint domainOriginLabelPaint;
+ private Paint rangeOriginLabelPaint;
+ private RectF gridRect;
+ private RectF paddedGridRect;
+ private float domainCursorPosition;
+ private float rangeCursorPosition;
+ @SuppressWarnings("FieldCanBeLocal")
+ private boolean drawCursorLabelEnabled = true;
+ private boolean drawMarkersEnabled = true;
+
+ private boolean rangeAxisLeft = true;
+ private boolean domainAxisBottom = true;
+
+ private float rangeLabelOrientation;
+ private float domainLabelOrientation;
+
+ // TODO: consider typing this manager with a special
+ // axisLabelRegionFormatter
+ // private ZHash<LineRegion, AxisValueLabelFormatter> domainLabelRegions;
+ // private ZHash<LineRegion, AxisValueLabelFormatter> rangeLabelRegions;
+ private ZHash<RectRegion, AxisValueLabelFormatter> axisValueLabelRegions;
+
+ {
+ gridBackgroundPaint = new Paint();
+ gridBackgroundPaint.setColor(Color.rgb(140, 140, 140));
+ gridBackgroundPaint.setStyle(Paint.Style.FILL);
+ rangeGridLinePaint = new Paint();
+ rangeGridLinePaint.setColor(Color.rgb(180, 180, 180));
+ rangeGridLinePaint.setAntiAlias(true);
+ rangeGridLinePaint.setStyle(Paint.Style.STROKE);
+ domainGridLinePaint = new Paint(rangeGridLinePaint);
+ domainSubGridLinePaint = new Paint(domainGridLinePaint);
+ rangeSubGridLinePaint = new Paint(rangeGridLinePaint);
+ domainOriginLinePaint = new Paint();
+ domainOriginLinePaint.setColor(Color.WHITE);
+ domainOriginLinePaint.setAntiAlias(true);
+ rangeOriginLinePaint = new Paint();
+ rangeOriginLinePaint.setColor(Color.WHITE);
+ rangeOriginLinePaint.setAntiAlias(true);
+ domainOriginLabelPaint = new Paint();
+ domainOriginLabelPaint.setColor(Color.WHITE);
+ domainOriginLabelPaint.setAntiAlias(true);
+ domainOriginLabelPaint.setTextAlign(Paint.Align.CENTER);
+ rangeOriginLabelPaint = new Paint();
+ rangeOriginLabelPaint.setColor(Color.WHITE);
+ rangeOriginLabelPaint.setAntiAlias(true);
+ rangeOriginLabelPaint.setTextAlign(Paint.Align.RIGHT);
+ domainLabelPaint = new Paint();
+ domainLabelPaint.setColor(Color.LTGRAY);
+ domainLabelPaint.setAntiAlias(true);
+ domainLabelPaint.setTextAlign(Paint.Align.CENTER);
+ rangeLabelPaint = new Paint();
+ rangeLabelPaint.setColor(Color.LTGRAY);
+ rangeLabelPaint.setAntiAlias(true);
+ rangeLabelPaint.setTextAlign(Paint.Align.RIGHT);
+ domainCursorPaint = new Paint();
+ domainCursorPaint.setColor(Color.YELLOW);
+ rangeCursorPaint = new Paint();
+ rangeCursorPaint.setColor(Color.YELLOW);
+ cursorLabelPaint = new Paint();
+ cursorLabelPaint.setColor(Color.YELLOW);
+ cursorLabelBackgroundPaint = new Paint();
+ cursorLabelBackgroundPaint.setColor(Color.argb(100, 50, 50, 50));
+ setMarginTop(7);
+ setMarginRight(4);
+ setMarginBottom(4);
+ rangeValueFormat = new DecimalFormat("0.0");
+ domainValueFormat = new DecimalFormat("0.0");
+ // domainLabelRegions = new ZHash<LineRegion,
+ // AxisValueLabelFormatter>();
+ // rangeLabelRegions = new ZHash<LineRegion, AxisValueLabelFormatter>();
+ axisValueLabelRegions = new ZHash<RectRegion, AxisValueLabelFormatter>();
+ }
+
+ public XYGraphWidget(LayoutManager layoutManager, XYPlot plot, SizeMetrics sizeMetrics) {
+ super(layoutManager, sizeMetrics);
+ this.plot = plot;
+ }
+
+ public ZIndexable<RectRegion> getAxisValueLabelRegions() {
+ return axisValueLabelRegions;
+ }
+
+ /**
+ * Add a new Region used for rendering axis valuelabels. Note that it is
+ * possible to add multiple Region instances which overlap, in which cast
+ * the last region to be added will be used. It is up to the developer to
+ * guard against this often undesireable situation.
+ *
+ * @param region
+ * @param formatter
+ */
+ public void addAxisValueLabelRegion(RectRegion region,
+ AxisValueLabelFormatter formatter) {
+ axisValueLabelRegions.addToTop(region, formatter);
+ }
+
+ /**
+ * Convenience method - wraps addAxisValueLabelRegion, using
+ * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off range
+ * axis value labels.
+ *
+ * @param min
+ * @param max
+ * @param formatter
+ *
+ */
+ public void addDomainAxisValueLabelRegion(double min, double max,
+ AxisValueLabelFormatter formatter) {
+ addAxisValueLabelRegion(new RectRegion(min, max,
+ Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, null),
+ formatter);
+ }
+
+ /**
+ * Convenience method - wraps addAxisValueLabelRegion, using
+ * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off domain
+ * axis value labels.
+ *
+ * @param min
+ * @param max
+ * @param formatter
+ */
+ public void addRangeAxisValueLabelRegion(double min, double max,
+ AxisValueLabelFormatter formatter) {
+ addAxisValueLabelRegion(new RectRegion(Double.POSITIVE_INFINITY,
+ Double.NEGATIVE_INFINITY, min, max, null), formatter);
+ }
+
+ /*
+ * public void addRangeLabelRegion(LineRegion region,
+ * AxisValueLabelFormatter formatter) { rangeLabelRegions.addToTop(region,
+ * formatter); }
+ *
+ * public boolean removeRangeLabelRegion(LineRegion region) { return
+ * rangeLabelRegions.remove(region); }
+ */
+
+ /**
+ * Returns the formatter associated with the first (bottom) Region
+ * containing x and y.
+ *
+ * @param x
+ * @param y
+ * @return the formatter associated with the first (bottom) region
+ * containing x and y. null otherwise.
+ */
+ public AxisValueLabelFormatter getAxisValueLabelFormatterForVal(double x,
+ double y) {
+ for (RectRegion r : axisValueLabelRegions.elements()) {
+ if (r.containsValue(x, y)) {
+ return axisValueLabelRegions.get(r);
+ }
+ }
+ return null;
+ }
+
+ public AxisValueLabelFormatter getAxisValueLabelFormatterForDomainVal(
+ double val) {
+ for (RectRegion r : axisValueLabelRegions.elements()) {
+ if (r.containsDomainValue(val)) {
+ return axisValueLabelRegions.get(r);
+ }
+ }
+ return null;
+ }
+
+ public AxisValueLabelFormatter getAxisValueLabelFormatterForRangeVal(
+ double val) {
+ for (RectRegion r : axisValueLabelRegions.elements()) {
+ if (r.containsRangeValue(val)) {
+ return axisValueLabelRegions.get(r);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the formatter associated with the first (bottom-most) Region
+ * containing value.
+ *
+ * @param value
+ * @return
+ */
+ /*
+ * public AxisValueLabelFormatter getXYAxisFormatterForRangeVal(double
+ * value) { return getRegionContainingVal(rangeLabelRegions, value); }
+ *//**
+ * Returns the formatter associated with the first (bottom-most) Region
+ * containing value.
+ *
+ * @param value
+ * @return
+ */
+ /*
+ * public AxisValueLabelFormatter getXYAxisFormatterForDomainVal(double
+ * value) { return getRegionContainingVal(domainLabelRegions, value); }
+ */
+
+ /*
+ * private AxisValueLabelFormatter getRegionContainingVal(ZHash<LineRegion,
+ * AxisValueLabelFormatter> zhash, double val) { for (LineRegion r :
+ * zhash.elements()) { if (r.contains(val)) { return
+ * rangeLabelRegions.get(r); } } // nothing found return null; }
+ */
+
+ /**
+ * Returns a RectF representing the grid area last drawn by this plot.
+ *
+ * @return
+ */
+ public RectF getGridRect() {
+ return paddedGridRect;
+ }
+ private String getFormattedRangeValue(Number value) {
+ return rangeValueFormat.format(value);
+ }
+
+ private String getFormattedDomainValue(Number value) {
+ return domainValueFormat.format(value);
+ }
+
+ /**
+ * Convenience method. Wraps getYVal(float)
+ *
+ * @param point
+ * @return
+ */
+ public Double getYVal(PointF point) {
+ return getYVal(point.y);
+ }
+
+ /**
+ * Converts a y pixel to a y value.
+ *
+ * @param yPix
+ * @return
+ */
+ public Double getYVal(float yPix) {
+ if (plot.getCalculatedMinY() == null
+ || plot.getCalculatedMaxY() == null) {
+ return null;
+ }
+ return ValPixConverter.pixToVal(yPix - paddedGridRect.top, plot
+ .getCalculatedMinY().doubleValue(), plot.getCalculatedMaxY()
+ .doubleValue(), paddedGridRect.height(), true);
+ }
+
+ /**
+ * Convenience method. Wraps getXVal(float)
+ *
+ * @param point
+ * @return
+ */
+ public Double getXVal(PointF point) {
+ return getXVal(point.x);
+ }
+
+ /**
+ * Converts an x pixel into an x value.
+ *
+ * @param xPix
+ * @return
+ */
+ public Double getXVal(float xPix) {
+ if (plot.getCalculatedMinX() == null
+ || plot.getCalculatedMaxX() == null) {
+ return null;
+ }
+ return ValPixConverter.pixToVal(xPix - paddedGridRect.left, plot
+ .getCalculatedMinX().doubleValue(), plot.getCalculatedMaxX()
+ .doubleValue(), paddedGridRect.width(), false);
+ }
+
+ @Override
+ protected void doOnDraw(Canvas canvas, RectF widgetRect)
+ throws PlotRenderException {
+ gridRect = getGridRect(widgetRect); // used for drawing the background
+ // of the grid
+ paddedGridRect = getPaddedGridRect(gridRect); // used for drawing lines
+ // etc.
+ //Log.v(TAG, "gridRect :" + gridRect);
+ //Log.v(TAG, "paddedGridRect :" + paddedGridRect);
+ // if (!plot.isEmpty()) {
+ // don't draw if we have no space to draw into
+ if ((paddedGridRect.height() > 0.0f) && (paddedGridRect.width() > 0.0f)) {
+ if (plot.getCalculatedMinX() != null
+ && plot.getCalculatedMaxX() != null
+ && plot.getCalculatedMinY() != null
+ && plot.getCalculatedMaxY() != null) {
+ drawGrid(canvas);
+ drawData(canvas);
+ drawCursors(canvas);
+ if (isDrawMarkersEnabled()) {
+ drawMarkers(canvas);
+ }
+ }
+ }
+ // }
+ }
+
+ private RectF getGridRect(RectF widgetRect) {
+ return new RectF(widgetRect.left + ((rangeAxisLeft)?rangeLabelWidth:1),
+ widgetRect.top + ((domainAxisBottom)?1:domainLabelWidth),
+ widgetRect.right - ((rangeAxisLeft)?1:rangeLabelWidth),
+ widgetRect.bottom - ((domainAxisBottom)?domainLabelWidth:1));
+ }
+
+ private RectF getPaddedGridRect(RectF gridRect) {
+ return new RectF(gridRect.left + gridPaddingLeft, gridRect.top
+ + gridPaddingTop, gridRect.right - gridPaddingRight,
+ gridRect.bottom - gridPaddingBottom);
+ }
+
+ private void drawTickText(Canvas canvas, XYAxisType axis, Number value,
+ float xPix, float yPix, Paint labelPaint) {
+ AxisValueLabelFormatter rf = null;
+ String txt = null;
+ double v = value.doubleValue();
+
+ int canvasState = canvas.save();
+ try {
+ switch (axis) {
+ case DOMAIN:
+ rf = getAxisValueLabelFormatterForDomainVal(v);
+ txt = getFormattedDomainValue(value);
+ canvas.rotate(getDomainLabelOrientation(), xPix, yPix);
+ break;
+ case RANGE:
+ rf = getAxisValueLabelFormatterForRangeVal(v);
+ txt = getFormattedRangeValue(value);
+ canvas.rotate(getRangeLabelOrientation(), xPix, yPix);
+ break;
+ }
+
+ // if a matching region formatter was found, create a clone
+ // of labelPaint and use the formatter's color. Otherwise
+ // just use labelPaint:
+ Paint p;
+ if (rf != null) {
+ // p = rf.getPaint();
+ p = new Paint(labelPaint);
+ p.setColor(rf.getColor());
+ // p.setColor(Color.RED);
+ } else {
+ p = labelPaint;
+ }
+ canvas.drawText(txt, xPix, yPix, p);
+ } finally {
+ canvas.restoreToCount(canvasState);
+ }
+ }
+
+ private void drawDomainTick(Canvas canvas, float xPix, Number xVal,
+ Paint labelPaint, Paint linePaint, boolean drawLineOnly) {
+ if (!drawLineOnly) {
+ if (linePaint != null) {
+ if (domainAxisBottom){
+ canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom
+ + domainLabelTickExtension, linePaint);
+ } else {
+ canvas.drawLine(xPix, gridRect.top - domainLabelTickExtension, xPix,
+ gridRect.bottom , linePaint);
+ }
+ }
+ if (labelPaint != null) {
+ float fontHeight = FontUtils.getFontHeight(labelPaint);
+ float yPix;
+ if (domainAxisBottom){
+ yPix = gridRect.bottom + domainLabelTickExtension
+ + domainLabelVerticalOffset + fontHeight;
+ } else {
+ yPix = gridRect.top - domainLabelTickExtension
+ - domainLabelVerticalOffset;
+ }
+ drawTickText(canvas, XYAxisType.DOMAIN, xVal, xPix + domainLabelHorizontalOffset, yPix,
+ labelPaint);
+ }
+ } else if (linePaint != null) {
+
+ canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom,
+ linePaint);
+
+ }
+ }
+
+ public void drawRangeTick(Canvas canvas, float yPix, Number yVal,
+ Paint labelPaint, Paint linePaint, boolean drawLineOnly) {
+ if (!drawLineOnly) {
+ if (linePaint != null) {
+ if (rangeAxisLeft){
+ canvas.drawLine(gridRect.left - rangeLabelTickExtension, yPix,
+ gridRect.right, yPix, linePaint);
+ } else {
+ canvas.drawLine(gridRect.left, yPix,
+ gridRect.right + rangeLabelTickExtension, yPix, linePaint);
+ }
+ }
+ if (labelPaint != null) {
+ float xPix;
+ if (rangeAxisLeft){
+ xPix = gridRect.left
+ - (rangeLabelTickExtension + rangeLabelHorizontalOffset);
+ } else {
+ xPix = gridRect.right
+ + (rangeLabelTickExtension + rangeLabelHorizontalOffset);
+ }
+ drawTickText(canvas, XYAxisType.RANGE, yVal, xPix, yPix - rangeLabelVerticalOffset,
+ labelPaint);
+ }
+ } else if (linePaint != null) {
+ canvas.drawLine(gridRect.left, yPix, gridRect.right, yPix,
+ linePaint);
+ }
+ }
+
+ /**
+ * Draws the drid and domain/range labels for the plot.
+ *
+ * @param canvas
+ */
+ protected void drawGrid(Canvas canvas) {
+
+ if (gridBackgroundPaint != null) {
+ canvas.drawRect(gridRect, gridBackgroundPaint);
+ }
+
+ float domainOriginF;
+ if (plot.getDomainOrigin() != null) {
+ double domainOriginVal = plot.getDomainOrigin().doubleValue();
+ domainOriginF = ValPixConverter.valToPix(domainOriginVal, plot
+ .getCalculatedMinX().doubleValue(), plot
+ .getCalculatedMaxX().doubleValue(), paddedGridRect.width(),
+ false);
+ domainOriginF += paddedGridRect.left;
+ // if no origin is set, use the leftmost value visible on the grid:
+ } else {
+ domainOriginF = paddedGridRect.left;
+ }
+
+ XYStep domainStep = XYStepCalculator.getStep(plot, XYAxisType.DOMAIN,
+ paddedGridRect, plot.getCalculatedMinX().doubleValue(), plot
+ .getCalculatedMaxX().doubleValue());
+
+ // draw domain origin:
+ if (domainOriginF >= paddedGridRect.left
+ && domainOriginF <= paddedGridRect.right) {
+ if (domainOriginLinePaint != null){
+ domainOriginLinePaint.setTextAlign(Paint.Align.CENTER);
+ }
+ drawDomainTick(canvas, domainOriginF, plot.getDomainOrigin()
+ .doubleValue(), domainOriginLabelPaint,
+ domainOriginLinePaint, false);
+ }
+
+ // draw ticks LEFT of origin:
+ {
+ int i = 1;
+ double xVal;
+ float xPix = domainOriginF - domainStep.getStepPix();
+ for (; xPix >= paddedGridRect.left; xPix = domainOriginF
+ - (i * domainStep.getStepPix())) {
+ xVal = plot.getDomainOrigin().doubleValue() - i
+ * domainStep.getStepVal();
+ if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) {
+ if (i % getTicksPerDomainLabel() == 0) {
+ drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
+ domainGridLinePaint, false);
+ } else {
+ drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
+ domainSubGridLinePaint, true);
+ }
+ }
+ i++;
+ }
+ }
+
+ // draw ticks RIGHT of origin:
+ {
+ int i = 1;
+ double xVal;
+ float xPix = domainOriginF + domainStep.getStepPix();
+ for (; xPix <= paddedGridRect.right; xPix = domainOriginF
+ + (i * domainStep.getStepPix())) {
+ xVal = plot.getDomainOrigin().doubleValue() + i
+ * domainStep.getStepVal();
+ if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) {
+
+ if (i % getTicksPerDomainLabel() == 0) {
+ drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
+ domainGridLinePaint, false);
+ } else {
+ drawDomainTick(canvas, xPix, xVal, domainLabelPaint,
+ domainSubGridLinePaint, true);
+ }
+ }
+ i++;
+ }
+ }
+
+ // draw range origin:
+
+ float rangeOriginF;
+ if (plot.getRangeOrigin() != null) {
+ // --------- NEW WAY ------
+ double rangeOriginD = plot.getRangeOrigin().doubleValue();
+ rangeOriginF = ValPixConverter.valToPix(rangeOriginD, plot
+ .getCalculatedMinY().doubleValue(), plot
+ .getCalculatedMaxY().doubleValue(),
+ paddedGridRect.height(), true);
+ rangeOriginF += paddedGridRect.top;
+ // if no origin is set, use the leftmost value visible on the grid
+ } else {
+ rangeOriginF = paddedGridRect.bottom;
+ }
+
+ XYStep rangeStep = XYStepCalculator.getStep(plot, XYAxisType.RANGE,
+ paddedGridRect, plot.getCalculatedMinY().doubleValue(), plot
+ .getCalculatedMaxY().doubleValue());
+
+ // draw range origin:
+ if (rangeOriginF >= paddedGridRect.top
+ && rangeOriginF <= paddedGridRect.bottom) {
+ if (rangeOriginLinePaint != null){
+ rangeOriginLinePaint.setTextAlign(Paint.Align.RIGHT);
+ }
+ drawRangeTick(canvas, rangeOriginF, plot.getRangeOrigin()
+ .doubleValue(), rangeOriginLabelPaint,
+ rangeOriginLinePaint, false);
+ }
+ // draw ticks ABOVE origin:
+ {
+ int i = 1;
+ double yVal;
+ float yPix = rangeOriginF - rangeStep.getStepPix();
+ for (; yPix >= paddedGridRect.top; yPix = rangeOriginF
+ - (i * rangeStep.getStepPix())) {
+ yVal = plot.getRangeOrigin().doubleValue() + i
+ * rangeStep.getStepVal();
+ if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) {
+ if (i % getTicksPerRangeLabel() == 0) {
+ drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
+ rangeGridLinePaint, false);
+ } else {
+ drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
+ rangeSubGridLinePaint, true);
+ }
+ }
+ i++;
+ }
+ }
+
+ // draw ticks BENEATH origin:
+ {
+ int i = 1;
+ double yVal;
+ float yPix = rangeOriginF + rangeStep.getStepPix();
+ for (; yPix <= paddedGridRect.bottom; yPix = rangeOriginF
+ + (i * rangeStep.getStepPix())) {
+ yVal = plot.getRangeOrigin().doubleValue() - i
+ * rangeStep.getStepVal();
+ if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) {
+ if (i % getTicksPerRangeLabel() == 0) {
+ drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
+ rangeGridLinePaint, false);
+ } else {
+ drawRangeTick(canvas, yPix, yVal, rangeLabelPaint,
+ rangeSubGridLinePaint, true);
+ }
+ }
+ i++;
+ }
+ }
+ }
+
+ /**
+ * Renders the text associated with user defined markers
+ *
+ * @param canvas
+ * @param text
+ * @param marker
+ * @param x
+ * @param y
+ */
+ private void drawMarkerText(Canvas canvas, String text, ValueMarker marker,
+ float x, float y) {
+ x += MARKER_LABEL_SPACING;
+ y -= MARKER_LABEL_SPACING;
+ RectF textRect = new RectF(FontUtils.getStringDimensions(text,
+ marker.getTextPaint()));
+ textRect.offsetTo(x, y - textRect.height());
+
+ if (textRect.right > paddedGridRect.right) {
+ textRect.offset(-(textRect.right - paddedGridRect.right), 0);
+ }
+
+ if (textRect.top < paddedGridRect.top) {
+ textRect.offset(0, paddedGridRect.top - textRect.top);
+ }
+
+ canvas.drawText(text, textRect.left, textRect.bottom,
+ marker.getTextPaint());
+
+ }
+
+ protected void drawMarkers(Canvas canvas) {
+ for (YValueMarker marker : plot.getYValueMarkers()) {
+
+ if (marker.getValue() != null) {
+ double yVal = marker.getValue().doubleValue();
+ float yPix = ValPixConverter.valToPix(yVal, plot
+ .getCalculatedMinY().doubleValue(), plot
+ .getCalculatedMaxY().doubleValue(), paddedGridRect
+ .height(), true);
+ yPix += paddedGridRect.top;
+ canvas.drawLine(paddedGridRect.left, yPix,
+ paddedGridRect.right, yPix, marker.getLinePaint());
+
+ // String text = getFormattedRangeValue(yVal);
+ float xPix = marker.getTextPosition().getPixelValue(
+ paddedGridRect.width());
+ xPix += paddedGridRect.left;
+
+ if (marker.getText() != null) {
+ drawMarkerText(canvas, marker.getText(), marker, xPix, yPix);
+ } else {
+ drawMarkerText(canvas,
+ getFormattedRangeValue(marker.getValue()), marker,
+ xPix, yPix);
+ }
+ }
+ }
+
+ for (XValueMarker marker : plot.getXValueMarkers()) {
+ if (marker.getValue() != null) {
+ double xVal = marker.getValue().doubleValue();
+ float xPix = ValPixConverter.valToPix(xVal, plot
+ .getCalculatedMinX().doubleValue(), plot
+ .getCalculatedMaxX().doubleValue(), paddedGridRect
+ .width(), false);
+ xPix += paddedGridRect.left;
+ canvas.drawLine(xPix, paddedGridRect.top, xPix,
+ paddedGridRect.bottom, marker.getLinePaint());
+
+ // String text = getFormattedDomainValue(xVal);
+ float yPix = marker.getTextPosition().getPixelValue(
+ paddedGridRect.height());
+ yPix += paddedGridRect.top;
+ if (marker.getText() != null) {
+ drawMarkerText(canvas, marker.getText(), marker, xPix, yPix);
+ } else {
+ drawMarkerText(canvas,
+ getFormattedDomainValue(marker.getValue()), marker,
+ xPix, yPix);
+ }
+ }
+ }
+ }
+
+ protected void drawCursors(Canvas canvas) {
+ boolean hasDomainCursor = false;
+ // draw the domain cursor:
+ if (domainCursorPaint != null
+ && domainCursorPosition <= paddedGridRect.right
+ && domainCursorPosition >= paddedGridRect.left) {
+ hasDomainCursor = true;
+ canvas.drawLine(domainCursorPosition, paddedGridRect.top,
+ domainCursorPosition, paddedGridRect.bottom,
+ domainCursorPaint);
+ }
+
+ boolean hasRangeCursor = false;
+ // draw the range cursor:
+ if (rangeCursorPaint != null
+ && rangeCursorPosition >= paddedGridRect.top
+ && rangeCursorPosition <= paddedGridRect.bottom) {
+ hasRangeCursor = true;
+ canvas.drawLine(paddedGridRect.left, rangeCursorPosition,
+ paddedGridRect.right, rangeCursorPosition, rangeCursorPaint);
+ }
+
+ if (drawCursorLabelEnabled && cursorLabelPaint != null
+ && hasRangeCursor && hasDomainCursor) {
+
+ String label = "X="
+ + getDomainValueFormat().format(getDomainCursorVal());
+ label += " Y=" + getRangeValueFormat().format(getRangeCursorVal());
+
+ // convert the label dimensions rect into floating-point:
+ RectF cursorRect = new RectF(FontUtils.getPackedStringDimensions(
+ label, cursorLabelPaint));
+ cursorRect.offsetTo(domainCursorPosition, rangeCursorPosition
+ - cursorRect.height());
+
+ // if we are too close to the right edge of the plot, we will move
+ // the
+ // label to the left side of our cursor:
+ if (cursorRect.right >= paddedGridRect.right) {
+ cursorRect.offsetTo(domainCursorPosition - cursorRect.width(),
+ cursorRect.top);
+ }
+
+ // same thing for the top edge of the plot:
+ // dunno why but these rects can have negative values for top and
+ // bottom.
+ if (cursorRect.top <= paddedGridRect.top) {
+ cursorRect.offsetTo(cursorRect.left, rangeCursorPosition);
+ }
+
+ if (cursorLabelBackgroundPaint != null) {
+ canvas.drawRect(cursorRect, cursorLabelBackgroundPaint);
+ }
+
+ canvas.drawText(label, cursorRect.left, cursorRect.bottom,
+ cursorLabelPaint);
+ }
+ }
+
+ /**
+ * Draws lines and points for each element in the series.
+ *
+ * @param canvas
+ * @throws PlotRenderException
+ */
+ protected void drawData(Canvas canvas) throws PlotRenderException {
+ // TODO: iterate through a XYSeriesRenderer list
+
+ // int canvasState = canvas.save();
+ try {
+ canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.clipRect(gridRect, android.graphics.Region.Op.INTERSECT);
+ for (XYSeriesRenderer renderer : plot.getRendererList()) {
+ renderer.render(canvas, paddedGridRect);
+ }
+ // canvas.restoreToCount(canvasState);
+ } finally {
+ canvas.restore();
+ }
+ }
+
+ protected void drawPoint(Canvas canvas, PointF point, Paint paint) {
+ canvas.drawPoint(point.x, point.y, paint);
+ }
+
+ public float getDomainLabelWidth() {
+ return domainLabelWidth;
+ }
+
+ public void setDomainLabelWidth(float domainLabelWidth) {
+ this.domainLabelWidth = domainLabelWidth;
+ }
+
+ public float getRangeLabelWidth() {
+ return rangeLabelWidth;
+ }
+
+ public void setRangeLabelWidth(float rangeLabelWidth) {
+ this.rangeLabelWidth = rangeLabelWidth;
+ }
+
+ public float getDomainLabelVerticalOffset() {
+ return domainLabelVerticalOffset;
+ }
+
+ public void setDomainLabelVerticalOffset(float domainLabelVerticalOffset) {
+ this.domainLabelVerticalOffset = domainLabelVerticalOffset;
+ }
+
+ public float getDomainLabelHorizontalOffset() {
+ return domainLabelHorizontalOffset;
+ }
+
+ public void setDomainLabelHorizontalOffset(float domainLabelHorizontalOffset) {
+ this.domainLabelHorizontalOffset = domainLabelHorizontalOffset;
+ }
+
+ public float getRangeLabelHorizontalOffset() {
+ return rangeLabelHorizontalOffset;
+ }
+
+ public void setRangeLabelHorizontalOffset(float rangeLabelHorizontalOffset) {
+ this.rangeLabelHorizontalOffset = rangeLabelHorizontalOffset;
+ }
+
+ public float getRangeLabelVerticalOffset() {
+ return rangeLabelVerticalOffset;
+ }
+
+ public void setRangeLabelVerticalOffset(float rangeLabelVerticalOffset) {
+ this.rangeLabelVerticalOffset = rangeLabelVerticalOffset;
+ }
+
+ public Paint getGridBackgroundPaint() {
+ return gridBackgroundPaint;
+ }
+
+ public void setGridBackgroundPaint(Paint gridBackgroundPaint) {
+ this.gridBackgroundPaint = gridBackgroundPaint;
+ }
+
+ public Paint getDomainLabelPaint() {
+ return domainLabelPaint;
+ }
+
+ public void setDomainLabelPaint(Paint domainLabelPaint) {
+ this.domainLabelPaint = domainLabelPaint;
+ }
+
+ public Paint getRangeLabelPaint() {
+ return rangeLabelPaint;
+ }
+
+ public void setRangeLabelPaint(Paint rangeLabelPaint) {
+ this.rangeLabelPaint = rangeLabelPaint;
+ }
+
+ /**
+ * Get the paint used to draw the domain grid line.
+ */
+ public Paint getDomainGridLinePaint() {
+ return domainGridLinePaint;
+ }
+
+ /**
+ * Set the paint used to draw the domain grid line.
+ * @param gridLinePaint
+ */
+ public void setDomainGridLinePaint(Paint gridLinePaint) {
+ this.domainGridLinePaint = gridLinePaint;
+ }
+
+ /**
+ * Get the paint used to draw the range grid line.
+ */
+ public Paint getRangeGridLinePaint() {
+ return rangeGridLinePaint;
+ }
+
+ /**
+ * Get the paint used to draw the domain grid line.
+ */
+ public Paint getDomainSubGridLinePaint() {
+ return domainSubGridLinePaint;
+ }
+
+ /**
+ * Set the paint used to draw the domain grid line.
+ * @param gridLinePaint
+ */
+ public void setDomainSubGridLinePaint(Paint gridLinePaint) {
+ this.domainSubGridLinePaint = gridLinePaint;
+ }
+
+ /**
+ * Set the Paint used to draw the range grid line.
+ * @param gridLinePaint
+ */
+ public void setRangeGridLinePaint(Paint gridLinePaint) {
+ this.rangeGridLinePaint = gridLinePaint;
+ }
+
+ /**
+ * Get the paint used to draw the range grid line.
+ */
+ public Paint getRangeSubGridLinePaint() {
+ return rangeSubGridLinePaint;
+ }
+
+ /**
+ * Set the Paint used to draw the range grid line.
+ * @param gridLinePaint
+ */
+ public void setRangeSubGridLinePaint(Paint gridLinePaint) {
+ this.rangeSubGridLinePaint = gridLinePaint;
+ }
+
+ // TODO: make a generic renderer queue.
+
+ public Format getRangeValueFormat() {
+ return rangeValueFormat;
+ }
+
+ public void setRangeValueFormat(Format rangeValueFormat) {
+ this.rangeValueFormat = rangeValueFormat;
+ }
+
+ public Format getDomainValueFormat() {
+ return domainValueFormat;
+ }
+
+ public void setDomainValueFormat(Format domainValueFormat) {
+ this.domainValueFormat = domainValueFormat;
+ }
+
+ public int getDomainLabelTickExtension() {
+ return domainLabelTickExtension;
+ }
+
+ public void setDomainLabelTickExtension(int domainLabelTickExtension) {
+ this.domainLabelTickExtension = domainLabelTickExtension;
+ }
+
+ public int getRangeLabelTickExtension() {
+ return rangeLabelTickExtension;
+ }
+
+ public void setRangeLabelTickExtension(int rangeLabelTickExtension) {
+ this.rangeLabelTickExtension = rangeLabelTickExtension;
+ }
+
+ public int getTicksPerRangeLabel() {
+ return ticksPerRangeLabel;
+ }
+
+ public void setTicksPerRangeLabel(int ticksPerRangeLabel) {
+ this.ticksPerRangeLabel = ticksPerRangeLabel;
+ }
+
+ public int getTicksPerDomainLabel() {
+ return ticksPerDomainLabel;
+ }
+
+ public void setTicksPerDomainLabel(int ticksPerDomainLabel) {
+ this.ticksPerDomainLabel = ticksPerDomainLabel;
+ }
+
+ public void setGridPaddingTop(float gridPaddingTop) {
+ this.gridPaddingTop = gridPaddingTop;
+ }
+
+ public float getGridPaddingBottom() {
+ return gridPaddingBottom;
+ }
+
+ public void setGridPaddingBottom(float gridPaddingBottom) {
+ this.gridPaddingBottom = gridPaddingBottom;
+ }
+
+ public float getGridPaddingLeft() {
+ return gridPaddingLeft;
+ }
+
+ public void setGridPaddingLeft(float gridPaddingLeft) {
+ this.gridPaddingLeft = gridPaddingLeft;
+ }
+
+ public float getGridPaddingRight() {
+ return gridPaddingRight;
+ }
+
+ public void setGridPaddingRight(float gridPaddingRight) {
+ this.gridPaddingRight = gridPaddingRight;
+ }
+
+ public float getGridPaddingTop() {
+ return gridPaddingTop;
+ }
+
+ public void setGridPadding(float left, float top, float right, float bottom) {
+ setGridPaddingLeft(left);
+ setGridPaddingTop(top);
+ setGridPaddingRight(right);
+ setGridPaddingBottom(bottom);
+ }
+
+ public Paint getDomainOriginLinePaint() {
+ return domainOriginLinePaint;
+ }
+
+ public void setDomainOriginLinePaint(Paint domainOriginLinePaint) {
+ this.domainOriginLinePaint = domainOriginLinePaint;
+ }
+
+ public Paint getRangeOriginLinePaint() {
+ return rangeOriginLinePaint;
+ }
+
+ public void setRangeOriginLinePaint(Paint rangeOriginLinePaint) {
+ this.rangeOriginLinePaint = rangeOriginLinePaint;
+ }
+
+ public Paint getDomainOriginLabelPaint() {
+ return domainOriginLabelPaint;
+ }
+
+ public void setDomainOriginLabelPaint(Paint domainOriginLabelPaint) {
+ this.domainOriginLabelPaint = domainOriginLabelPaint;
+ }
+
+ public Paint getRangeOriginLabelPaint() {
+ return rangeOriginLabelPaint;
+ }
+
+ public void setRangeOriginLabelPaint(Paint rangeOriginLabelPaint) {
+ this.rangeOriginLabelPaint = rangeOriginLabelPaint;
+ }
+
+ public void setCursorPosition(float x, float y) {
+ setDomainCursorPosition(x);
+ setRangeCursorPosition(y);
+ }
+
+ public void setCursorPosition(PointF point) {
+ setCursorPosition(point.x, point.y);
+ }
+
+ public float getDomainCursorPosition() {
+ return domainCursorPosition;
+ }
+
+ public Double getDomainCursorVal() {
+ return getXVal(getDomainCursorPosition());
+ }
+
+ public void setDomainCursorPosition(float domainCursorPosition) {
+ this.domainCursorPosition = domainCursorPosition;
+ }
+
+ public float getRangeCursorPosition() {
+ return rangeCursorPosition;
+ }
+
+ public Double getRangeCursorVal() {
+ return getYVal(getRangeCursorPosition());
+ }
+
+ public void setRangeCursorPosition(float rangeCursorPosition) {
+ this.rangeCursorPosition = rangeCursorPosition;
+ }
+
+ public Paint getCursorLabelPaint() {
+ return cursorLabelPaint;
+ }
+
+ public void setCursorLabelPaint(Paint cursorLabelPaint) {
+ this.cursorLabelPaint = cursorLabelPaint;
+ }
+
+ public Paint getCursorLabelBackgroundPaint() {
+ return cursorLabelBackgroundPaint;
+ }
+
+ public void setCursorLabelBackgroundPaint(Paint cursorLabelBackgroundPaint) {
+ this.cursorLabelBackgroundPaint = cursorLabelBackgroundPaint;
+ }
+
+ public boolean isDrawMarkersEnabled() {
+ return drawMarkersEnabled;
+ }
+
+ public void setDrawMarkersEnabled(boolean drawMarkersEnabled) {
+ this.drawMarkersEnabled = drawMarkersEnabled;
+ }
+
+ public boolean isRangeAxisLeft() {
+ return rangeAxisLeft;
+ }
+
+ public void setRangeAxisLeft(boolean rangeAxisLeft) {
+ this.rangeAxisLeft = rangeAxisLeft;
+ }
+
+ public boolean isDomainAxisBottom() {
+ return domainAxisBottom;
+ }
+
+ public void setDomainAxisBottom(boolean domainAxisBottom) {
+ this.domainAxisBottom = domainAxisBottom;
+ }
+
+ /*
+ * set the position of the range axis labels. Set the labelPaint textSizes before setting this.
+ * This call sets the various vertical and horizontal offsets and widths to good defaults.
+ *
+ * @param rangeAxisLeft axis labels are on the left hand side not the right hand side.
+ * @param rangeAxisOverlay axis labels are overlaid on the plot, not external to it.
+ * @param tickSize the size of the tick extensions for none overlaid axis.
+ * @param maxLableString Sample label representing the biggest size space needs to be allocated for.
+ */
+ public void setRangeAxisPosition(boolean rangeAxisLeft, boolean rangeAxisOverlay, int tickSize, String maxLableString){
+ setRangeAxisLeft(rangeAxisLeft);
+
+ if (rangeAxisOverlay) {
+ setRangeLabelWidth(1); // needs to be at least 1 to display grid line.
+ setRangeLabelHorizontalOffset(-2.0f);
+ setRangeLabelVerticalOffset(2.0f); // get above the line
+ Paint p = getRangeLabelPaint();
+ if (p != null) {
+ p.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
+ }
+ Paint po = getRangeOriginLabelPaint();
+ if (po != null) {
+ po.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
+ }
+ setRangeLabelTickExtension(0);
+ } else {
+ setRangeLabelWidth(1); // needs to be at least 1 to display grid line.
+ // if we have a paint this gets bigger.
+ setRangeLabelHorizontalOffset(1.0f);
+ setRangeLabelTickExtension(tickSize);
+ Paint p = getRangeLabelPaint();
+ if (p != null) {
+ p.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
+ Rect r = FontUtils.getPackedStringDimensions(maxLableString,p);
+ setRangeLabelVerticalOffset(r.top/2);
+ setRangeLabelWidth(r.right + getRangeLabelTickExtension());
+ }
+ Paint po = getRangeOriginLabelPaint();
+ if (po != null) {
+ po.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT));
+ }
+ }
+ }
+
+ /*
+ * set the position of the domain axis labels. Set the labelPaint textSizes before setting this.
+ * This call sets the various vertical and horizontal offsets and widths to good defaults.
+ *
+ * @param domainAxisBottom axis labels are on the bottom not the top of the plot.
+ * @param domainAxisOverlay axis labels are overlaid on the plot, not external to it.
+ * @param tickSize the size of the tick extensions for non overlaid axis.
+ * @param maxLableString Sample label representing the biggest size space needs to be allocated for.
+ */
+ public void setDomainAxisPosition(boolean domainAxisBottom, boolean domainAxisOverlay, int tickSize, String maxLabelString){
+ setDomainAxisBottom(domainAxisBottom);
+ if (domainAxisOverlay) {
+ setDomainLabelWidth(1); // needs to be at least 1 to display grid line.
+ setDomainLabelVerticalOffset(2.0f); // get above the line
+ setDomainLabelTickExtension(0);
+ Paint p = getDomainLabelPaint();
+ if (p != null) {
+ Rect r = FontUtils.getPackedStringDimensions(maxLabelString,p);
+ if (domainAxisBottom){
+ setDomainLabelVerticalOffset(2 * r.top);
+ } else {
+ setDomainLabelVerticalOffset(r.top - 1.0f);
+ }
+ }
+ } else {
+ setDomainLabelWidth(1); // needs to be at least 1 to display grid line.
+ // if we have a paint this gets bigger.
+ setDomainLabelTickExtension(tickSize);
+ Paint p = getDomainLabelPaint();
+ if (p != null) {
+ float fontHeight = FontUtils.getFontHeight(p);
+ if (domainAxisBottom){
+ setDomainLabelVerticalOffset(-4.0f);
+ } else {
+ setDomainLabelVerticalOffset(+1.0f);
+ }
+ setDomainLabelWidth(fontHeight + getDomainLabelTickExtension());
+ }
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYLegendWidget.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYLegendWidget.java
new file mode 100644
index 0000000..3350406
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYLegendWidget.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.*;
+import com.androidplot.ui.LayoutManager;
+import com.androidplot.ui.SeriesAndFormatterList;
+import com.androidplot.ui.SizeMetrics;
+import com.androidplot.ui.TableModel;
+import com.androidplot.ui.widget.Widget;
+import com.androidplot.util.FontUtils;
+
+import java.util.*;
+
+public class XYLegendWidget extends Widget {
+
+ /**
+ * This class is of no use outside of XYLegendWidget. It's just used to alphabetically sort
+ * Region legend entries.
+ */
+ private static class RegionEntryComparator implements Comparator<Map.Entry<XYRegionFormatter, String>> {
+ @Override
+ public int compare(Map.Entry<XYRegionFormatter, String> o1, Map.Entry<XYRegionFormatter, String> o2) {
+ return o1.getValue().compareTo(o2.getValue());
+ }
+ }
+
+ private enum CellType {
+ SERIES,
+ REGION
+ }
+
+ private XYPlot plot;
+ //private float iconWidth = 12;
+ private Paint textPaint;
+ private Paint iconBorderPaint;
+ private TableModel tableModel;
+ private boolean drawIconBackgroundEnabled = true;
+ private boolean drawIconBorderEnabled = true;
+
+ private SizeMetrics iconSizeMetrics;
+ private static final RegionEntryComparator regionEntryComparator = new RegionEntryComparator();
+ //private RectF iconRect = new RectF(0, 0, ICON_WIDTH_DEFAULT, ICON_HEIGHT_DEFAULT);
+
+ {
+ textPaint = new Paint();
+ textPaint.setColor(Color.LTGRAY);
+ textPaint.setAntiAlias(true);
+
+ iconBorderPaint = new Paint();
+ iconBorderPaint.setStyle(Paint.Style.STROKE);
+ //regionEntryComparator = new RegionEntryComparator();
+ }
+
+ public XYLegendWidget(LayoutManager layoutManager, XYPlot plot,
+ SizeMetrics widgetSizeMetrics,
+ TableModel tableModel,
+ SizeMetrics iconSizeMetrics) {
+ super(layoutManager, widgetSizeMetrics);
+ this.plot = plot;
+ setTableModel(tableModel);
+ this.iconSizeMetrics = iconSizeMetrics;
+ }
+
+ public synchronized void setTableModel(TableModel tableModel) {
+ this.tableModel = tableModel;
+ }
+
+ private RectF getIconRect(RectF cellRect) {
+ float cellRectCenterY = cellRect.top + (cellRect.height()/2);
+ RectF iconRect = iconSizeMetrics.getRectF(cellRect);
+
+ // center the icon rect vertically
+ float centeredIconOriginY = cellRectCenterY - (iconRect.height()/2);
+ iconRect.offsetTo(cellRect.left + 1, centeredIconOriginY);
+ return iconRect;
+ }
+
+ private static float getRectCenterY(RectF cellRect) {
+ return cellRect.top + (cellRect.height()/2);
+ }
+
+ private void beginDrawingCell(Canvas canvas, RectF iconRect) {
+
+ Paint bgPaint = plot.getGraphWidget().getGridBackgroundPaint();
+ if(drawIconBackgroundEnabled && bgPaint != null) {
+ canvas.drawRect(iconRect, bgPaint);
+ }
+ }
+
+ private void finishDrawingCell(Canvas canvas, RectF cellRect, RectF iconRect, String text) {
+
+ Paint bgPaint = plot.getGraphWidget().getGridBackgroundPaint();
+ if(drawIconBorderEnabled && bgPaint != null) {
+ iconBorderPaint.setColor(bgPaint.getColor());
+ canvas.drawRect(iconRect, iconBorderPaint);
+ }
+
+ float centeredTextOriginY = getRectCenterY(cellRect) + (FontUtils.getFontHeight(textPaint)/2);
+ canvas.drawText(text, iconRect.right + 2, centeredTextOriginY, textPaint);
+ }
+
+ protected void drawRegionLegendIcon(Canvas canvas, RectF rect, XYRegionFormatter formatter) {
+ canvas.drawRect(rect, formatter.getPaint());
+ }
+
+ private void drawRegionLegendCell(Canvas canvas, XYRegionFormatter formatter, RectF cellRect, String text) {
+ RectF iconRect = getIconRect(cellRect);
+ beginDrawingCell(canvas, iconRect);
+
+ drawRegionLegendIcon(
+ canvas,
+ iconRect,
+ formatter
+ );
+ finishDrawingCell(canvas, cellRect, iconRect, text);
+ }
+
+ private void drawSeriesLegendCell(Canvas canvas, XYSeriesRenderer renderer, XYSeriesFormatter formatter, RectF cellRect, String seriesTitle) {
+ RectF iconRect = getIconRect(cellRect);
+ beginDrawingCell(canvas, iconRect);
+
+ renderer.drawSeriesLegendIcon(
+ canvas,
+ iconRect,
+ formatter);
+ finishDrawingCell(canvas, cellRect, iconRect, seriesTitle);
+ }
+
+ @Override
+ protected synchronized void doOnDraw(Canvas canvas, RectF widgetRect) {
+ // TODO: a good amount of iterating could be avoided if
+ // we create a temporary list of all the legend items up here.
+ if(plot.isEmpty()) {
+ return;
+ }
+
+ //Hashtable<XYRegionFormatter, XYSeriesRenderer> regionRendererLookup = new Hashtable<XYRegionFormatter, XYSeriesRenderer>();
+
+ // Keep an alphabetically sorted list of regions:
+ TreeSet<Map.Entry<XYRegionFormatter, String>> sortedRegions = new TreeSet<Map.Entry<XYRegionFormatter, String>>(new RegionEntryComparator());
+
+ // Calculate the number of cells needed to draw the Legend:
+ int seriesCount = 0;
+ for(XYSeriesRenderer renderer : plot.getRendererList()) {
+
+ SeriesAndFormatterList sfl = plot.getSeriesAndFormatterListForRenderer(renderer.getClass());
+ if(sfl != null) {
+ seriesCount += sfl.size();
+ }
+
+ // Figure out how many regions need to be added to the legend:
+ Hashtable<XYRegionFormatter, String> urf = renderer.getUniqueRegionFormatters();
+ /*for(XYRegionFormatter xyf : urf.keySet()) {
+ regionRendererLookup.put(xyf, renderer);
+ }*/
+ sortedRegions.addAll(urf.entrySet());
+ //sortedRegions.addAll(renderer.getUniqueRegionFormatters().entrySet());
+ }
+ seriesCount += sortedRegions.size();
+
+ // Create an iterator specially created to draw the number of cells we calculated:
+ Iterator<RectF> it = tableModel.getIterator(widgetRect, seriesCount);
+
+ RectF cellRect;
+
+ // draw each series legend item:
+ for(XYSeriesRenderer renderer : plot.getRendererList()) {
+ SeriesAndFormatterList<XYSeries,XYSeriesFormatter> sfl = plot.getSeriesAndFormatterListForRenderer(renderer.getClass());
+
+ if (sfl != null) {
+ // maxIndex is only used if it has been determined.
+ // if it is 0 then it could not be determined.
+ for (int i = 0; i < sfl.size() && it.hasNext(); i++) {
+ cellRect = it.next();
+ XYSeriesFormatter formatter = sfl.getFormatter(i);
+ drawSeriesLegendCell(canvas, renderer, formatter, cellRect, sfl.getSeries(i).getTitle());
+ }
+ }
+ }
+
+ // draw each region legend item:
+ for(Map.Entry<XYRegionFormatter, String> entry : sortedRegions) {
+ if(!it.hasNext()) {
+ break;
+ }
+ cellRect = it.next();
+ XYRegionFormatter formatter = entry.getKey();
+ drawRegionLegendCell(canvas, formatter, cellRect, entry.getValue());
+ }
+ }
+
+
+ public Paint getTextPaint() {
+ return textPaint;
+ }
+
+ public void setTextPaint(Paint textPaint) {
+ this.textPaint = textPaint;
+ }
+
+ public boolean isDrawIconBackgroundEnabled() {
+ return drawIconBackgroundEnabled;
+ }
+
+ public void setDrawIconBackgroundEnabled(boolean drawIconBackgroundEnabled) {
+ this.drawIconBackgroundEnabled = drawIconBackgroundEnabled;
+ }
+
+ public boolean isDrawIconBorderEnabled() {
+ return drawIconBorderEnabled;
+ }
+
+ public void setDrawIconBorderEnabled(boolean drawIconBorderEnabled) {
+ this.drawIconBorderEnabled = drawIconBorderEnabled;
+ }
+
+ public TableModel getTableModel() {
+ return tableModel;
+ }
+
+ public SizeMetrics getIconSizeMetrics() {
+ return iconSizeMetrics;
+ }
+
+ /**
+ * Set the size of each legend's icon. Note that when using relative sizing,
+ * the size is calculated against the countaining cell's size, not the plot's size.
+ * @param iconSizeMetrics
+ */
+ public void setIconSizeMetrics(SizeMetrics iconSizeMetrics) {
+ this.iconSizeMetrics = iconSizeMetrics;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlot.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlot.java
new file mode 100644
index 0000000..d3c286c
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlot.java
@@ -0,0 +1,1344 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.util.AttributeSet;
+import com.androidplot.Plot;
+import com.androidplot.ui.*;
+import com.androidplot.ui.TextOrientationType;
+import com.androidplot.ui.widget.TextLabelWidget;
+import com.androidplot.util.PixelUtils;
+
+import java.text.Format;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A View to graphically display x/y coordinates.
+ */
+public class XYPlot extends Plot<XYSeries, XYSeriesFormatter, XYSeriesRenderer> {
+
+ private BoundaryMode domainOriginBoundaryMode;
+ private BoundaryMode rangeOriginBoundaryMode;
+
+ // widgets
+ private XYLegendWidget legendWidget;
+ private XYGraphWidget graphWidget;
+ private TextLabelWidget domainLabelWidget;
+ private TextLabelWidget rangeLabelWidget;
+
+ private XYStepMode domainStepMode = XYStepMode.SUBDIVIDE;
+ private double domainStepValue = 10;
+
+ private XYStepMode rangeStepMode = XYStepMode.SUBDIVIDE;
+ private double rangeStepValue = 10;
+
+ // user settable min/max values
+ private Number userMinX;
+ private Number userMaxX;
+ private Number userMinY;
+ private Number userMaxY;
+
+ // these are the final min/max used for dispplaying data
+ private Number calculatedMinX;
+ private Number calculatedMaxX;
+ private Number calculatedMinY;
+ private Number calculatedMaxY;
+
+ // previous calculated min/max vals.
+ // primarily used for GROW/SHRINK operations.
+ private Number prevMinX;
+ private Number prevMaxX;
+ private Number prevMinY;
+ private Number prevMaxY;
+
+ // uses set boundary min and max values
+ // should be null if not used.
+ private Number rangeTopMin = null;
+ private Number rangeTopMax = null;
+ private Number rangeBottomMin = null;
+ private Number rangeBottomMax = null;
+ private Number domainLeftMin = null;
+ private Number domainLeftMax = null;
+ private Number domainRightMin = null;
+ private Number domainRightMax = null;
+
+ // used for calculating the domain/range extents that will be displayed on the plot.
+ // using boundaries and origins are mutually exclusive. because of this,
+ // setting one will disable the other. when only setting the FramingModel,
+ // the origin or boundary is set to the current value of the plot.
+ private XYFramingModel domainFramingModel = XYFramingModel.EDGE;
+ private XYFramingModel rangeFramingModel = XYFramingModel.EDGE;
+
+ private Number userDomainOrigin;
+ private Number userRangeOrigin;
+
+ private Number calculatedDomainOrigin;
+ private Number calculatedRangeOrigin;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private Number domainOriginExtent = null;
+ @SuppressWarnings("FieldCanBeLocal")
+ private Number rangeOriginExtent = null;
+
+ private BoundaryMode domainUpperBoundaryMode = BoundaryMode.AUTO;
+ private BoundaryMode domainLowerBoundaryMode = BoundaryMode.AUTO;
+ private BoundaryMode rangeUpperBoundaryMode = BoundaryMode.AUTO;
+ private BoundaryMode rangeLowerBoundaryMode = BoundaryMode.AUTO;
+
+ private boolean drawDomainOriginEnabled = true;
+ private boolean drawRangeOriginEnabled = true;
+
+ private ArrayList<YValueMarker> yValueMarkers;
+ private ArrayList<XValueMarker> xValueMarkers;
+
+ private RectRegion defaultBounds;
+
+
+ private static final int DEFAULT_LEGEND_WIDGET_H_DP = 10;
+ private static final int DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP = 7;
+ private static final int DEFAULT_GRAPH_WIDGET_H_DP = 18;
+ private static final int DEFAULT_GRAPH_WIDGET_W_DP = 10;
+ private static final int DEFAULT_DOMAIN_LABEL_WIDGET_H_DP = 10;
+ private static final int DEFAULT_DOMAIN_LABEL_WIDGET_W_DP = 80;
+ private static final int DEFAULT_RANGE_LABEL_WIDGET_H_DP = 50;
+ private static final int DEFAULT_RANGE_LABEL_WIDGET_W_DP = 10;
+
+ private static final int DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP = 0;
+ private static final int DEFAULT_LEGEND_WIDGET_X_OFFSET_DP = 40;
+ private static final int DEFAULT_GRAPH_WIDGET_Y_OFFSET_DP = 0;
+ private static final int DEFAULT_GRAPH_WIDGET_X_OFFSET_DP = 0;
+ private static final int DEFAULT_DOMAIN_LABEL_WIDGET_Y_OFFSET_DP = 0;
+ private static final int DEFAULT_DOMAIN_LABEL_WIDGET_X_OFFSET_DP = 20;
+ private static final int DEFAULT_RANGE_LABEL_WIDGET_Y_OFFSET_DP = 0;
+ private static final int DEFAULT_RANGE_LABEL_WIDGET_X_OFFSET_DP = 0;
+
+ private static final int DEFAULT_GRAPH_WIDGET_TOP_MARGIN_DP = 3;
+ private static final int DEFAULT_GRAPH_WIDGET_RIGHT_MARGIN_DP = 3;
+ private static final int DEFAULT_PLOT_LEFT_MARGIN_DP = 2;
+ private static final int DEFAULT_PLOT_RIGHT_MARGIN_DP = 2;
+ private static final int DEFAULT_PLOT_BOTTOM_MARGIN_DP = 2;
+
+ public XYPlot(Context context, String title) {
+ super(context, title);
+ }
+
+ public XYPlot(Context context, String title, RenderMode mode) {
+ super(context, title, mode);
+ }
+
+ public XYPlot(Context context, AttributeSet attributes) {
+ super(context, attributes);
+ }
+
+ public XYPlot(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ }
+
+ @Override
+ protected void onPreInit() {
+ legendWidget = new XYLegendWidget(
+ getLayoutManager(),
+ this,
+ new SizeMetrics(
+ PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_H_DP),
+ SizeLayoutType.ABSOLUTE, 0.5f, SizeLayoutType.RELATIVE),
+ new DynamicTableModel(0, 1),
+ new SizeMetrics(
+ PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP),
+ SizeLayoutType.ABSOLUTE,
+ PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP),
+ SizeLayoutType.ABSOLUTE));
+
+ graphWidget = new XYGraphWidget(
+ getLayoutManager(),
+ this,
+ new SizeMetrics(
+ PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_H_DP),
+ SizeLayoutType.FILL,
+ PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_W_DP),
+ SizeLayoutType.FILL));
+
+ Paint backgroundPaint = new Paint();
+ backgroundPaint.setColor(Color.DKGRAY);
+ backgroundPaint.setStyle(Paint.Style.FILL);
+ graphWidget.setBackgroundPaint(backgroundPaint);
+
+
+ domainLabelWidget = new TextLabelWidget(
+ getLayoutManager(),
+ new SizeMetrics(
+ PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_H_DP),
+ SizeLayoutType.ABSOLUTE,
+ PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_W_DP),
+ SizeLayoutType.ABSOLUTE),
+ TextOrientationType.HORIZONTAL);
+ rangeLabelWidget = new TextLabelWidget(
+ getLayoutManager(),
+ new SizeMetrics(
+ PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_H_DP),
+ SizeLayoutType.ABSOLUTE,
+ PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_W_DP),
+ SizeLayoutType.ABSOLUTE),
+ TextOrientationType.VERTICAL_ASCENDING);
+
+ legendWidget.position(
+ PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_X_OFFSET_DP),
+ XLayoutStyle.ABSOLUTE_FROM_RIGHT,
+ PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP),
+ YLayoutStyle.ABSOLUTE_FROM_BOTTOM,
+ AnchorPosition.RIGHT_BOTTOM);
+
+ graphWidget.position(
+ PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_X_OFFSET_DP),
+ XLayoutStyle.ABSOLUTE_FROM_RIGHT,
+ PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_Y_OFFSET_DP),
+ YLayoutStyle.ABSOLUTE_FROM_CENTER,
+ AnchorPosition.RIGHT_MIDDLE);
+
+ domainLabelWidget.position(
+ PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_X_OFFSET_DP),
+ XLayoutStyle.ABSOLUTE_FROM_LEFT,
+ PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_Y_OFFSET_DP),
+ YLayoutStyle.ABSOLUTE_FROM_BOTTOM,
+ AnchorPosition.LEFT_BOTTOM);
+
+ rangeLabelWidget.position(
+ PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_X_OFFSET_DP),
+ XLayoutStyle.ABSOLUTE_FROM_LEFT,
+ PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_Y_OFFSET_DP),
+ YLayoutStyle.ABSOLUTE_FROM_CENTER,
+ AnchorPosition.LEFT_MIDDLE);
+
+ getLayoutManager().moveToTop(getTitleWidget());
+ getLayoutManager().moveToTop(getLegendWidget());
+ graphWidget.setMarginTop(PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_TOP_MARGIN_DP));
+ graphWidget.setMarginRight(PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_RIGHT_MARGIN_DP));
+
+ getDomainLabelWidget().pack();
+ getRangeLabelWidget().pack();
+ setPlotMarginLeft(PixelUtils.dpToPix(DEFAULT_PLOT_LEFT_MARGIN_DP));
+ setPlotMarginRight(PixelUtils.dpToPix(DEFAULT_PLOT_RIGHT_MARGIN_DP));
+ setPlotMarginBottom(PixelUtils.dpToPix(DEFAULT_PLOT_BOTTOM_MARGIN_DP));
+
+ xValueMarkers = new ArrayList<XValueMarker>();
+ yValueMarkers = new ArrayList<YValueMarker>();
+
+ setDefaultBounds(new RectRegion(-1, 1, -1, 1));
+ }
+
+
+ public void setGridPadding(float left, float top, float right, float bottom) {
+ getGraphWidget().setGridPaddingTop(top);
+ getGraphWidget().setGridPaddingBottom(bottom);
+ getGraphWidget().setGridPaddingLeft(left);
+ getGraphWidget().setGridPaddingRight(right);
+ }
+
+ @Override
+ protected void notifyListenersBeforeDraw(Canvas canvas) {
+ super.notifyListenersBeforeDraw(canvas);
+
+ // this call must be AFTER the notify so that if the listener
+ // is a synchronized series, it has the opportunity to
+ // place a read lock on it's data.
+ calculateMinMaxVals();
+ }
+
+ /**
+ * Checks whether the point is within the plot's graph area.
+ *
+ * @param x
+ * @param y
+ * @return
+ */
+ public boolean containsPoint(float x, float y) {
+ if (getGraphWidget().getGridRect() != null) {
+ return getGraphWidget().getGridRect().contains(x, y);
+ }
+ return false;
+ }
+
+
+ /**
+ * Convenience method - wraps containsPoint(PointF).
+ *
+ * @param point
+ * @return
+ */
+ public boolean containsPoint(PointF point) {
+ return containsPoint(point.x, point.y);
+ }
+
+ public void setCursorPosition(PointF point) {
+ getGraphWidget().setCursorPosition(point);
+ }
+
+ public void setCursorPosition(float x, float y) {
+ getGraphWidget().setCursorPosition(x, y);
+ }
+
+ public Number getYVal(PointF point) {
+ return getGraphWidget().getYVal(point);
+ }
+
+ public Number getXVal(PointF point) {
+ return getGraphWidget().getXVal(point);
+ }
+
+ private boolean isXValWithinView(double xVal) {
+ return (userMinY == null || xVal >= userMinY.doubleValue()) &&
+ userMaxY == null || xVal <= userMaxY.doubleValue();
+ }
+
+ private boolean isPointVisible(Number x, Number y) {
+ // values without both an x and y val arent visible
+ if (x == null || y == null) {
+ return false;
+ }
+ return isValWithinRange(y.doubleValue(), userMinY, userMaxY) &&
+ isValWithinRange(x.doubleValue(), userMinX, userMaxX);
+ }
+
+ private boolean isValWithinRange(double val, Number min, Number max) {
+ boolean isAboveMinThreshold = min == null || val >= min.doubleValue();
+ boolean isBelowMaxThreshold = max == null || val <= max.doubleValue();
+ return isAboveMinThreshold &&
+ isBelowMaxThreshold;
+ }
+
+ public void calculateMinMaxVals() {
+ prevMinX = calculatedMinX;
+ prevMaxX = calculatedMaxX;
+ prevMinY = calculatedMinY;
+ prevMaxY = calculatedMaxY;
+
+ calculatedMinX = userMinX;
+ calculatedMaxX = userMaxX;
+ calculatedMinY = userMinY;
+ calculatedMaxY = userMaxY;
+
+ // next we go through each series to update our min/max values:
+ for (final XYSeries series : getSeriesSet()) {
+ // step through each point in each series:
+ for (int i = 0; i < series.size(); i++) {
+ Number thisX = series.getX(i);
+ Number thisY = series.getY(i);
+ if (isPointVisible(thisX, thisY)) {
+ // only calculate if a static value has not been set:
+ if (userMinX == null) {
+ if (thisX != null && (calculatedMinX == null ||
+ thisX.doubleValue() < calculatedMinX.doubleValue())) {
+ calculatedMinX = thisX;
+ }
+ }
+
+ if (userMaxX == null) {
+ if (thisX != null && (calculatedMaxX == null ||
+ thisX.doubleValue() > calculatedMaxX.doubleValue())) {
+ calculatedMaxX = thisX;
+ }
+ }
+
+ if (userMinY == null) {
+ if (thisY != null && (calculatedMinY == null ||
+ thisY.doubleValue() < calculatedMinY.doubleValue())) {
+ calculatedMinY = thisY;
+ }
+ }
+
+ if (userMaxY == null) {
+ if (thisY != null && (calculatedMaxY == null || thisY.doubleValue() > calculatedMaxY.doubleValue())) {
+ calculatedMaxY = thisY;
+ }
+ }
+ }
+ }
+ }
+
+ // at this point we now know what points are going to be visible on our
+ // plot, but we still need to make corrections based on modes being used:
+ // (grow, shrink etc.)
+ switch (domainFramingModel) {
+ case ORIGIN:
+ updateDomainMinMaxForOriginModel();
+ break;
+ case EDGE:
+ updateDomainMinMaxForEdgeModel();
+ calculatedMinX = ApplyUserMinMax(calculatedMinX, domainLeftMin,
+ domainLeftMax);
+ calculatedMaxX = ApplyUserMinMax(calculatedMaxX,
+ domainRightMin, domainRightMax);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Domain Framing Model not yet supported: " + domainFramingModel);
+ }
+
+ switch (rangeFramingModel) {
+ case ORIGIN:
+ updateRangeMinMaxForOriginModel();
+ break;
+ case EDGE:
+ if (getSeriesSet().size() > 0) {
+ updateRangeMinMaxForEdgeModel();
+ calculatedMinY = ApplyUserMinMax(calculatedMinY,
+ rangeBottomMin, rangeBottomMax);
+ calculatedMaxY = ApplyUserMinMax(calculatedMaxY, rangeTopMin,
+ rangeTopMax);
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Range Framing Model not yet supported: " + domainFramingModel);
+ }
+
+ calculatedDomainOrigin = userDomainOrigin != null ? userDomainOrigin : getCalculatedMinX();
+ calculatedRangeOrigin = this.userRangeOrigin != null ? userRangeOrigin : getCalculatedMinY();
+ }
+
+ /**
+ * Should ONLY be called from updateMinMax.
+ * Results are undefined otherwise.
+ */
+ private void updateDomainMinMaxForEdgeModel() {
+ switch (domainUpperBoundaryMode) {
+ case FIXED:
+ break;
+ case AUTO:
+ break;
+ case GROW:
+ if (!(prevMaxX == null || (calculatedMaxX.doubleValue() > prevMaxX.doubleValue()))) {
+ calculatedMaxX = prevMaxX;
+ }
+ break;
+ case SHRINNK:
+ if (!(prevMaxX == null || calculatedMaxX.doubleValue() < prevMaxX.doubleValue())) {
+ calculatedMaxX = prevMaxX;
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "DomainUpperBoundaryMode not yet implemented: " + domainUpperBoundaryMode);
+ }
+
+ switch (domainLowerBoundaryMode) {
+ case FIXED:
+ break;
+ case AUTO:
+ break;
+ case GROW:
+ if (!(prevMinX == null || calculatedMinX.doubleValue() < prevMinX.doubleValue())) {
+ calculatedMinX = prevMinX;
+ }
+ break;
+ case SHRINNK:
+ if (!(prevMinX == null || calculatedMinX.doubleValue() > prevMinX.doubleValue())) {
+ calculatedMinX = prevMinX;
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "DomainLowerBoundaryMode not supported: " + domainLowerBoundaryMode);
+ }
+ }
+
+ public void updateRangeMinMaxForEdgeModel() {
+ switch (rangeUpperBoundaryMode) {
+ case FIXED:
+ break;
+ case AUTO:
+ break;
+ case GROW:
+ if (!(prevMaxY == null || calculatedMaxY.doubleValue() > prevMaxY.doubleValue())) {
+ calculatedMaxY = prevMaxY;
+ }
+ break;
+ case SHRINNK:
+ if (!(prevMaxY == null || calculatedMaxY.doubleValue() < prevMaxY.doubleValue())) {
+ calculatedMaxY = prevMaxY;
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "RangeUpperBoundaryMode not supported: " + rangeUpperBoundaryMode);
+ }
+
+ switch (rangeLowerBoundaryMode) {
+ case FIXED:
+ break;
+ case AUTO:
+ break;
+ case GROW:
+ if (!(prevMinY == null || calculatedMinY.doubleValue() < prevMinY.doubleValue())) {
+ calculatedMinY = prevMinY;
+ }
+ break;
+ case SHRINNK:
+ if (!(prevMinY == null || calculatedMinY.doubleValue() > prevMinY.doubleValue())) {
+ calculatedMinY = prevMinY;
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "RangeLowerBoundaryMode not supported: " + rangeLowerBoundaryMode);
+ }
+ }
+
+ /**
+ * Apply user supplied min and max to the calculated boundary value.
+ *
+ * @param value
+ * @param min
+ * @param max
+ */
+ private Number ApplyUserMinMax(Number value, Number min, Number max) {
+ value = (((min == null) || (value.doubleValue() > min.doubleValue()))
+ ? value
+ : min);
+ value = (((max == null) || (value.doubleValue() < max.doubleValue()))
+ ? value
+ : max);
+ return value;
+ }
+
+ /**
+ * Centers the domain axis on origin.
+ *
+ * @param origin
+ */
+ public void centerOnDomainOrigin(Number origin) {
+ centerOnDomainOrigin(origin, null, BoundaryMode.AUTO);
+ }
+
+ /**
+ * Centers the domain on origin, calculating the upper and lower boundaries of the axis
+ * using mode and extent.
+ *
+ * @param origin
+ * @param extent
+ * @param mode
+ */
+ public void centerOnDomainOrigin(Number origin, Number extent, BoundaryMode mode) {
+ if (origin == null) {
+ throw new NullPointerException("Origin param cannot be null.");
+ }
+ domainFramingModel = XYFramingModel.ORIGIN;
+ setUserDomainOrigin(origin);
+ domainOriginExtent = extent;
+ domainOriginBoundaryMode = mode;
+
+ if (domainOriginBoundaryMode == BoundaryMode.FIXED) {
+ double domO = userDomainOrigin.doubleValue();
+ double domE = domainOriginExtent.doubleValue();
+ userMaxX = domO + domE;
+ userMinX = domO - domE;
+ } else {
+ userMaxX = null;
+ userMinX = null;
+ }
+ }
+
+ /**
+ * Centers the range axis on origin.
+ *
+ * @param origin
+ */
+ public void centerOnRangeOrigin(Number origin) {
+ centerOnRangeOrigin(origin, null, BoundaryMode.AUTO);
+ }
+
+ /**
+ * Centers the domain on origin, calculating the upper and lower boundaries of the axis
+ * using mode and extent.
+ *
+ * @param origin
+ * @param extent
+ * @param mode
+ */
+ @SuppressWarnings("SameParameterValue")
+ public void centerOnRangeOrigin(Number origin, Number extent, BoundaryMode mode) {
+ if (origin == null) {
+ throw new NullPointerException("Origin param cannot be null.");
+ }
+ rangeFramingModel = XYFramingModel.ORIGIN;
+ setUserRangeOrigin(origin);
+ rangeOriginExtent = extent;
+ rangeOriginBoundaryMode = mode;
+
+ if (rangeOriginBoundaryMode == BoundaryMode.FIXED) {
+ double raO = userRangeOrigin.doubleValue();
+ double raE = rangeOriginExtent.doubleValue();
+ userMaxY = raO + raE;
+ userMinY = raO - raE;
+ } else {
+ userMaxY = null;
+ userMinY = null;
+ }
+ }
+
+ /**
+ * Returns the distance between x and y.
+ * Result is never a negative number.
+ *
+ * @param x
+ * @param y
+ * @return
+ */
+ private double distance(double x, double y) {
+ if (x > y) {
+ return x - y;
+ } else {
+ return y - x;
+ }
+ }
+
+ public void updateDomainMinMaxForOriginModel() {
+ double origin = userDomainOrigin.doubleValue();
+ double maxXDelta = distance(calculatedMaxX.doubleValue(), origin);
+ double minXDelta = distance(calculatedMinX.doubleValue(), origin);
+ double delta = maxXDelta > minXDelta ? maxXDelta : minXDelta;
+ double dlb = origin - delta;
+ double dub = origin + delta;
+ switch (domainOriginBoundaryMode) {
+ case AUTO:
+ calculatedMinX = dlb;
+ calculatedMaxX = dub;
+
+ break;
+ // if fixed, then the value already exists within "user" vals.
+ case FIXED:
+ break;
+ case GROW: {
+
+ if (prevMinX == null || dlb < prevMinX.doubleValue()) {
+ calculatedMinX = dlb;
+ } else {
+ calculatedMinX = prevMinX;
+ }
+
+ if (prevMaxX == null || dub > prevMaxX.doubleValue()) {
+ calculatedMaxX = dub;
+ } else {
+ calculatedMaxX = prevMaxX;
+ }
+ }
+ break;
+ case SHRINNK:
+ if (prevMinX == null || dlb > prevMinX.doubleValue()) {
+ calculatedMinX = dlb;
+ } else {
+ calculatedMinX = prevMinX;
+ }
+
+ if (prevMaxX == null || dub < prevMaxX.doubleValue()) {
+ calculatedMaxX = dub;
+ } else {
+ calculatedMaxX = prevMaxX;
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException("Domain Origin Boundary Mode not yet supported: " + domainOriginBoundaryMode);
+ }
+ }
+
+ public void updateRangeMinMaxForOriginModel() {
+ switch (rangeOriginBoundaryMode) {
+ case AUTO:
+ double origin = userRangeOrigin.doubleValue();
+ double maxYDelta = distance(calculatedMaxY.doubleValue(), origin);
+ double minYDelta = distance(calculatedMinY.doubleValue(), origin);
+ if (maxYDelta > minYDelta) {
+ calculatedMinY = origin - maxYDelta;
+ calculatedMaxY = origin + maxYDelta;
+ } else {
+ calculatedMinY = origin - minYDelta;
+ calculatedMaxY = origin + minYDelta;
+ }
+ break;
+ case FIXED:
+ case GROW:
+ case SHRINNK:
+ default:
+ throw new UnsupportedOperationException(
+ "Range Origin Boundary Mode not yet supported: " + rangeOriginBoundaryMode);
+ }
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.getTicksPerRangeLabel().
+ * Equivalent to getGraphWidget().getTicksPerRangeLabel().
+ *
+ * @return
+ */
+ public int getTicksPerRangeLabel() {
+ return graphWidget.getTicksPerRangeLabel();
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.setTicksPerRangeLabel().
+ * Equivalent to getGraphWidget().setTicksPerRangeLabel().
+ *
+ * @param ticksPerRangeLabel
+ */
+ public void setTicksPerRangeLabel(int ticksPerRangeLabel) {
+ graphWidget.setTicksPerRangeLabel(ticksPerRangeLabel);
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.getTicksPerDomainLabel().
+ * Equivalent to getGraphWidget().getTicksPerDomainLabel().
+ *
+ * @return
+ */
+ public int getTicksPerDomainLabel() {
+ return graphWidget.getTicksPerDomainLabel();
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.setTicksPerDomainLabel().
+ * Equivalent to getGraphWidget().setTicksPerDomainLabel().
+ *
+ * @param ticksPerDomainLabel
+ */
+ public void setTicksPerDomainLabel(int ticksPerDomainLabel) {
+ graphWidget.setTicksPerDomainLabel(ticksPerDomainLabel);
+ }
+
+ public XYStepMode getDomainStepMode() {
+ return domainStepMode;
+ }
+
+ public void setDomainStepMode(XYStepMode domainStepMode) {
+ this.domainStepMode = domainStepMode;
+ }
+
+ public double getDomainStepValue() {
+ return domainStepValue;
+ }
+
+ public void setDomainStepValue(double domainStepValue) {
+ this.domainStepValue = domainStepValue;
+ }
+
+ public void setDomainStep(XYStepMode mode, double value) {
+ setDomainStepMode(mode);
+ setDomainStepValue(value);
+ }
+
+ public XYStepMode getRangeStepMode() {
+ return rangeStepMode;
+ }
+
+ public void setRangeStepMode(XYStepMode rangeStepMode) {
+ this.rangeStepMode = rangeStepMode;
+ }
+
+ public double getRangeStepValue() {
+ return rangeStepValue;
+ }
+
+ public void setRangeStepValue(double rangeStepValue) {
+ this.rangeStepValue = rangeStepValue;
+ }
+
+ public void setRangeStep(XYStepMode mode, double value) {
+ setRangeStepMode(mode);
+ setRangeStepValue(value);
+ }
+
+ public String getDomainLabel() {
+ return getDomainLabelWidget().getText();
+ }
+
+ public void setDomainLabel(String domainLabel) {
+ getDomainLabelWidget().setText(domainLabel);
+ }
+
+ public String getRangeLabel() {
+ return getRangeLabelWidget().getText();
+ }
+
+ public void setRangeLabel(String rangeLabel) {
+ getRangeLabelWidget().setText(rangeLabel);
+ }
+
+ public XYLegendWidget getLegendWidget() {
+ return legendWidget;
+ }
+
+ public void setLegendWidget(XYLegendWidget legendWidget) {
+ this.legendWidget = legendWidget;
+ }
+
+ public XYGraphWidget getGraphWidget() {
+ return graphWidget;
+ }
+
+ public void setGraphWidget(XYGraphWidget graphWidget) {
+ this.graphWidget = graphWidget;
+ }
+
+ public TextLabelWidget getDomainLabelWidget() {
+ return domainLabelWidget;
+ }
+
+ public void setDomainLabelWidget(TextLabelWidget domainLabelWidget) {
+ this.domainLabelWidget = domainLabelWidget;
+ }
+
+ public TextLabelWidget getRangeLabelWidget() {
+ return rangeLabelWidget;
+ }
+
+ public void setRangeLabelWidget(TextLabelWidget rangeLabelWidget) {
+ this.rangeLabelWidget = rangeLabelWidget;
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.getRangeValueFormat().
+ *
+ * @return
+ */
+ public Format getRangeValueFormat() {
+ return graphWidget.getRangeValueFormat();
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.setRangeValueFormat().
+ *
+ * @param rangeValueFormat
+ */
+ public void setRangeValueFormat(Format rangeValueFormat) {
+ graphWidget.setRangeValueFormat(rangeValueFormat);
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.getDomainValueFormat().
+ *
+ * @return
+ */
+ public Format getDomainValueFormat() {
+ return graphWidget.getDomainValueFormat();
+ }
+
+ /**
+ * Convenience method - wraps XYGraphWidget.setDomainValueFormat().
+ *
+ * @param domainValueFormat
+ */
+ public void setDomainValueFormat(Format domainValueFormat) {
+ graphWidget.setDomainValueFormat(domainValueFormat);
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param lowerBoundary
+ * @param upperBoundary
+ * @param mode
+ */
+ public synchronized void setDomainBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) {
+ setDomainBoundaries(lowerBoundary, mode, upperBoundary, mode);
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param lowerBoundary
+ * @param lowerBoundaryMode
+ * @param upperBoundary
+ * @param upperBoundaryMode
+ */
+ public synchronized void setDomainBoundaries(Number lowerBoundary, BoundaryMode lowerBoundaryMode,
+ Number upperBoundary, BoundaryMode upperBoundaryMode) {
+ setDomainLowerBoundary(lowerBoundary, lowerBoundaryMode);
+ setDomainUpperBoundary(upperBoundary, upperBoundaryMode);
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param lowerBoundary
+ * @param upperBoundary
+ * @param mode
+ */
+ public synchronized void setRangeBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) {
+ setRangeBoundaries(lowerBoundary, mode, upperBoundary, mode);
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param lowerBoundary
+ * @param lowerBoundaryMode
+ * @param upperBoundary
+ * @param upperBoundaryMode
+ */
+ public synchronized void setRangeBoundaries(Number lowerBoundary, BoundaryMode lowerBoundaryMode,
+ Number upperBoundary, BoundaryMode upperBoundaryMode) {
+ setRangeLowerBoundary(lowerBoundary, lowerBoundaryMode);
+ setRangeUpperBoundary(upperBoundary, upperBoundaryMode);
+ }
+
+ protected synchronized void setDomainUpperBoundaryMode(BoundaryMode mode) {
+ this.domainUpperBoundaryMode = mode;
+ }
+
+ protected synchronized void setUserMaxX(Number boundary) {
+ // Ifor 12/10/2011
+ // you want null for auto grow and shrink
+ //if(boundary == null) {
+ // throw new NullPointerException("Boundary value cannot be null.");
+ //}
+ this.userMaxX = boundary;
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param boundary
+ * @param mode
+ */
+ public synchronized void setDomainUpperBoundary(Number boundary, BoundaryMode mode) {
+ setUserMaxX((mode == BoundaryMode.FIXED) ? boundary : null);
+ setDomainUpperBoundaryMode(mode);
+ setDomainFramingModel(XYFramingModel.EDGE);
+ }
+
+ protected synchronized void setDomainLowerBoundaryMode(BoundaryMode mode) {
+ this.domainLowerBoundaryMode = mode;
+ }
+
+ protected synchronized void setUserMinX(Number boundary) {
+ // Ifor 12/10/2011
+ // you want null for auto grow and shrink
+ //if(boundary == null) {
+ // throw new NullPointerException("Boundary value cannot be null.");
+ //}
+ this.userMinX = boundary;
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param boundary
+ * @param mode
+ */
+ public synchronized void setDomainLowerBoundary(Number boundary, BoundaryMode mode) {
+ setUserMinX((mode == BoundaryMode.FIXED) ? boundary : null);
+ setDomainLowerBoundaryMode(mode);
+ setDomainFramingModel(XYFramingModel.EDGE);
+ //updateMinMaxVals();
+ }
+
+ protected synchronized void setRangeUpperBoundaryMode(BoundaryMode mode) {
+ this.rangeUpperBoundaryMode = mode;
+ }
+
+ protected synchronized void setUserMaxY(Number boundary) {
+ // Ifor 12/10/2011
+ // you want null for auto grow and shrink
+ //if(boundary == null) {
+ // throw new NullPointerException("Boundary value cannot be null.");
+ //}
+ this.userMaxY = boundary;
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param boundary
+ * @param mode
+ */
+ public synchronized void setRangeUpperBoundary(Number boundary, BoundaryMode mode) {
+ setUserMaxY((mode == BoundaryMode.FIXED) ? boundary : null);
+ setRangeUpperBoundaryMode(mode);
+ setRangeFramingModel(XYFramingModel.EDGE);
+ }
+
+ protected synchronized void setRangeLowerBoundaryMode(BoundaryMode mode) {
+ this.rangeLowerBoundaryMode = mode;
+ }
+
+ protected synchronized void setUserMinY(Number boundary) {
+ // Ifor 12/10/2011
+ // you want null for auto grow and shrink
+ //if(boundary == null) {
+ // throw new NullPointerException("Boundary value cannot be null.");
+ //}
+ this.userMinY = boundary;
+ }
+
+ /**
+ * Setup the boundary mode, boundary values only applicable in FIXED mode.
+ *
+ * @param boundary
+ * @param mode
+ */
+ public synchronized void setRangeLowerBoundary(Number boundary, BoundaryMode mode) {
+ setUserMinY((mode == BoundaryMode.FIXED) ? boundary : null);
+ setRangeLowerBoundaryMode(mode);
+ setRangeFramingModel(XYFramingModel.EDGE);
+ }
+
+ private Number getUserMinX() {
+ return userMinX;
+ }
+
+ private Number getUserMaxX() {
+ return userMaxX;
+ }
+
+ private Number getUserMinY() {
+ return userMinY;
+ }
+
+ private Number getUserMaxY() {
+ return userMaxY;
+ }
+
+ public Number getDomainOrigin() {
+ return calculatedDomainOrigin;
+ }
+
+ public Number getRangeOrigin() {
+ return calculatedRangeOrigin;
+ }
+
+ protected BoundaryMode getDomainUpperBoundaryMode() {
+ return domainUpperBoundaryMode;
+ }
+
+ protected BoundaryMode getDomainLowerBoundaryMode() {
+ return domainLowerBoundaryMode;
+ }
+
+ protected BoundaryMode getRangeUpperBoundaryMode() {
+ return rangeUpperBoundaryMode;
+ }
+
+ protected BoundaryMode getRangeLowerBoundaryMode() {
+ return rangeLowerBoundaryMode;
+ }
+
+ public synchronized void setUserDomainOrigin(Number origin) {
+ if (origin == null) {
+ throw new NullPointerException("Origin value cannot be null.");
+ }
+ this.userDomainOrigin = origin;
+ }
+
+ public synchronized void setUserRangeOrigin(Number origin) {
+ if (origin == null) {
+ throw new NullPointerException("Origin value cannot be null.");
+ }
+ this.userRangeOrigin = origin;
+ }
+
+ public XYFramingModel getDomainFramingModel() {
+ return domainFramingModel;
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ protected void setDomainFramingModel(XYFramingModel domainFramingModel) {
+ this.domainFramingModel = domainFramingModel;
+ }
+
+ public XYFramingModel getRangeFramingModel() {
+
+ return rangeFramingModel;
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ protected void setRangeFramingModel(XYFramingModel rangeFramingModel) {
+ this.rangeFramingModel = rangeFramingModel;
+ }
+
+ /**
+ * CalculatedMinX value after the the framing model has been applied.
+ *
+ * @return
+ */
+ public Number getCalculatedMinX() {
+ return calculatedMinX != null ? calculatedMinX : getDefaultBounds().getMinX();
+ }
+
+ /**
+ * CalculatedMaxX value after the the framing model has been applied.
+ *
+ * @return
+ */
+ public Number getCalculatedMaxX() {
+ return calculatedMaxX != null ? calculatedMaxX : getDefaultBounds().getMaxX();
+ }
+
+ /**
+ * CalculatedMinY value after the the framing model has been applied.
+ *
+ * @return
+ */
+ public Number getCalculatedMinY() {
+ return calculatedMinY != null ? calculatedMinY : getDefaultBounds().getMinY();
+ }
+
+ /**
+ * CalculatedMaxY value after the the framing model has been applied.
+ *
+ * @return
+ */
+ public Number getCalculatedMaxY() {
+ return calculatedMaxY != null ? calculatedMaxY : getDefaultBounds().getMaxY();
+ }
+
+ public boolean isDrawDomainOriginEnabled() {
+ return drawDomainOriginEnabled;
+ }
+
+ public void setDrawDomainOriginEnabled(boolean drawDomainOriginEnabled) {
+ this.drawDomainOriginEnabled = drawDomainOriginEnabled;
+ }
+
+ public boolean isDrawRangeOriginEnabled() {
+ return drawRangeOriginEnabled;
+ }
+
+ public void setDrawRangeOriginEnabled(boolean drawRangeOriginEnabled) {
+ this.drawRangeOriginEnabled = drawRangeOriginEnabled;
+ }
+
+ /**
+ * Appends the specified marker to the end of plot's yValueMarkers list.
+ *
+ * @param marker The YValueMarker to be added.
+ * @return true if the object was successfully added, false otherwise.
+ */
+ public boolean addMarker(YValueMarker marker) {
+ if (yValueMarkers.contains(marker)) {
+ return false;
+ } else {
+ return yValueMarkers.add(marker);
+ }
+ }
+
+ /**
+ * Removes the specified marker from the plot.
+ *
+ * @param marker
+ * @return The YValueMarker removed if successfull, null otherwise.
+ */
+ public YValueMarker removeMarker(YValueMarker marker) {
+ int markerIndex = yValueMarkers.indexOf(marker);
+ if (markerIndex == -1) {
+ return null;
+ } else {
+ return yValueMarkers.remove(markerIndex);
+ }
+ }
+
+ /**
+ * Convenience method - combines removeYMarkers() and removeXMarkers().
+ *
+ * @return
+ */
+ public int removeMarkers() {
+ int removed = removeXMarkers();
+ removed += removeYMarkers();
+ return removed;
+ }
+
+ /**
+ * Removes all YValueMarker instances from the plot.
+ *
+ * @return
+ */
+ public int removeYMarkers() {
+ int numMarkersRemoved = yValueMarkers.size();
+ yValueMarkers.clear();
+ return numMarkersRemoved;
+ }
+
+ /**
+ * Appends the specified marker to the end of plot's xValueMarkers list.
+ *
+ * @param marker The XValueMarker to be added.
+ * @return true if the object was successfully added, false otherwise.
+ */
+ public boolean addMarker(XValueMarker marker) {
+ return !xValueMarkers.contains(marker) && xValueMarkers.add(marker);
+ }
+
+ /**
+ * Removes the specified marker from the plot.
+ *
+ * @param marker
+ * @return The XValueMarker removed if successfull, null otherwise.
+ */
+ public XValueMarker removeMarker(XValueMarker marker) {
+ int markerIndex = xValueMarkers.indexOf(marker);
+ if (markerIndex == -1) {
+ return null;
+ } else {
+ return xValueMarkers.remove(markerIndex);
+ }
+ }
+
+ /**
+ * Removes all XValueMarker instances from the plot.
+ *
+ * @return
+ */
+ public int removeXMarkers() {
+ int numMarkersRemoved = xValueMarkers.size();
+ xValueMarkers.clear();
+ return numMarkersRemoved;
+ }
+
+ protected List<YValueMarker> getYValueMarkers() {
+ return yValueMarkers;
+ }
+
+ protected List<XValueMarker> getXValueMarkers() {
+ return xValueMarkers;
+ }
+
+ public RectRegion getDefaultBounds() {
+ return defaultBounds;
+ }
+
+ public void setDefaultBounds(RectRegion defaultBounds) {
+ this.defaultBounds = defaultBounds;
+ }
+
+ /**
+ * @return the rangeTopMin
+ */
+ public Number getRangeTopMin() {
+ return rangeTopMin;
+ }
+
+ /**
+ * @param rangeTopMin the rangeTopMin to set
+ */
+ public synchronized void setRangeTopMin(Number rangeTopMin) {
+ this.rangeTopMin = rangeTopMin;
+ }
+
+ /**
+ * @return the rangeTopMax
+ */
+ public Number getRangeTopMax() {
+ return rangeTopMax;
+ }
+
+ /**
+ * @param rangeTopMax the rangeTopMax to set
+ */
+ public synchronized void setRangeTopMax(Number rangeTopMax) {
+ this.rangeTopMax = rangeTopMax;
+ }
+
+ /**
+ * @return the rangeBottomMin
+ */
+ public Number getRangeBottomMin() {
+ return rangeBottomMin;
+ }
+
+ /**
+ * @param rangeBottomMin the rangeBottomMin to set
+ */
+ public synchronized void setRangeBottomMin(Number rangeBottomMin) {
+ this.rangeBottomMin = rangeBottomMin;
+ }
+
+ /**
+ * @return the rangeBottomMax
+ */
+ public Number getRangeBottomMax() {
+ return rangeBottomMax;
+ }
+
+ /**
+ * @param rangeBottomMax the rangeBottomMax to set
+ */
+ public synchronized void setRangeBottomMax(Number rangeBottomMax) {
+ this.rangeBottomMax = rangeBottomMax;
+ }
+
+ /**
+ * @return the domainLeftMin
+ */
+ public Number getDomainLeftMin() {
+ return domainLeftMin;
+ }
+
+ /**
+ * @param domainLeftMin the domainLeftMin to set
+ */
+ public synchronized void setDomainLeftMin(Number domainLeftMin) {
+ this.domainLeftMin = domainLeftMin;
+ }
+
+ /**
+ * @return the domainLeftMax
+ */
+ public Number getDomainLeftMax() {
+ return domainLeftMax;
+ }
+
+ /**
+ * @param domainLeftMax the domainLeftMax to set
+ */
+ public synchronized void setDomainLeftMax(Number domainLeftMax) {
+ this.domainLeftMax = domainLeftMax;
+ }
+
+ /**
+ * @return the domainRightMin
+ */
+ public Number getDomainRightMin() {
+ return domainRightMin;
+ }
+
+ /**
+ * @param domainRightMin the domainRightMin to set
+ */
+ public synchronized void setDomainRightMin(Number domainRightMin) {
+ this.domainRightMin = domainRightMin;
+ }
+
+ /**
+ * @return the domainRightMax
+ */
+ public Number getDomainRightMax() {
+ return domainRightMax;
+ }
+
+ /**
+ * @param domainRightMax the domainRightMax to set
+ */
+ public synchronized void setDomainRightMax(Number domainRightMax) {
+ this.domainRightMax = domainRightMax;
+ }
+} \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlotZoomPan.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlotZoomPan.java
new file mode 100644
index 0000000..e1064dc
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYPlotZoomPan.java
@@ -0,0 +1,384 @@
+package com.androidplot.xy;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+public class XYPlotZoomPan extends XYPlot implements OnTouchListener {
+ private static final float MIN_DIST_2_FING = 5f;
+
+ // Definition of the touch states
+ private enum State
+ {
+ NONE,
+ ONE_FINGER_DRAG,
+ TWO_FINGERS_DRAG
+ }
+
+ private State mode = State.NONE;
+ private float minXLimit = Float.MAX_VALUE;
+ private float maxXLimit = Float.MAX_VALUE;
+ private float minYLimit = Float.MAX_VALUE;
+ private float maxYLimit = Float.MAX_VALUE;
+ private float lastMinX = Float.MAX_VALUE;
+ private float lastMaxX = Float.MAX_VALUE;
+ private float lastMinY = Float.MAX_VALUE;
+ private float lastMaxY = Float.MAX_VALUE;
+ private PointF firstFingerPos;
+ private float mDistX;
+ private boolean mZoomEnabled; //default is enabled
+ private boolean mZoomVertically;
+ private boolean mZoomHorizontally;
+ private boolean mCalledBySelf;
+ private boolean mZoomEnabledInit;
+ private boolean mZoomVerticallyInit;
+ private boolean mZoomHorizontallyInit;
+
+ public XYPlotZoomPan(Context context, String title, RenderMode mode) {
+ super(context, title, mode);
+ setZoomEnabled(true); //Default is ZoomEnabled if instantiated programmatically
+ }
+
+ public XYPlotZoomPan(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ if(mZoomEnabled || !mZoomEnabledInit) {
+ setZoomEnabled(true);
+ }
+ if(!mZoomHorizontallyInit) {
+ mZoomHorizontally = true;
+ }
+ if(!mZoomVerticallyInit) {
+ mZoomVertically = true;
+ }
+ }
+
+ public XYPlotZoomPan(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ if(mZoomEnabled || !mZoomEnabledInit) {
+ setZoomEnabled(true);
+ }
+ if(!mZoomHorizontallyInit) {
+ mZoomHorizontally = true;
+ }
+ if(!mZoomVerticallyInit) {
+ mZoomVertically = true;
+ }
+ }
+
+ public XYPlotZoomPan(final Context context, final String title) {
+ super(context, title);
+ }
+
+ @Override
+ public void setOnTouchListener(OnTouchListener l) {
+ if(l != this) {
+ mZoomEnabled = false;
+ }
+ super.setOnTouchListener(l);
+ }
+
+ public boolean getZoomVertically() {
+ return mZoomVertically;
+ }
+
+ public void setZoomVertically(boolean zoomVertically) {
+ mZoomVertically = zoomVertically;
+ mZoomVerticallyInit = true;
+ }
+
+ public boolean getZoomHorizontally() {
+ return mZoomHorizontally;
+ }
+
+ public void setZoomHorizontally(boolean zoomHorizontally) {
+ mZoomHorizontally = zoomHorizontally;
+ mZoomHorizontallyInit = true;
+ }
+
+ public void setZoomEnabled(boolean enabled) {
+ if(enabled) {
+ setOnTouchListener(this);
+ } else {
+ setOnTouchListener(null);
+ }
+ mZoomEnabled = enabled;
+ mZoomEnabledInit = true;
+ }
+
+ public boolean getZoomEnabled() {
+ return mZoomEnabled;
+ }
+
+ private float getMinXLimit() {
+ if(minXLimit == Float.MAX_VALUE) {
+ minXLimit = getCalculatedMinX().floatValue();
+ lastMinX = minXLimit;
+ }
+ return minXLimit;
+ }
+
+ private float getMaxXLimit() {
+ if(maxXLimit == Float.MAX_VALUE) {
+ maxXLimit = getCalculatedMaxX().floatValue();
+ lastMaxX = maxXLimit;
+ }
+ return maxXLimit;
+ }
+
+ private float getMinYLimit() {
+ if(minYLimit == Float.MAX_VALUE) {
+ minYLimit = getCalculatedMinY().floatValue();
+ lastMinY = minYLimit;
+ }
+ return minYLimit;
+ }
+
+ private float getMaxYLimit() {
+ if(maxYLimit == Float.MAX_VALUE) {
+ maxYLimit = getCalculatedMaxY().floatValue();
+ lastMaxY = maxYLimit;
+ }
+ return maxYLimit;
+ }
+
+ private float getLastMinX() {
+ if(lastMinX == Float.MAX_VALUE) {
+ lastMinX = getCalculatedMinX().floatValue();
+ }
+ return lastMinX;
+ }
+
+ private float getLastMaxX() {
+ if(lastMaxX == Float.MAX_VALUE) {
+ lastMaxX = getCalculatedMaxX().floatValue();
+ }
+ return lastMaxX;
+ }
+
+ private float getLastMinY() {
+ if(lastMinY == Float.MAX_VALUE) {
+ lastMinY = getCalculatedMinY().floatValue();
+ }
+ return lastMinY;
+ }
+
+ private float getLastMaxY() {
+ if(lastMaxY == Float.MAX_VALUE) {
+ lastMaxY = getCalculatedMaxY().floatValue();
+ }
+ return lastMaxY;
+ }
+
+ @Override
+ public synchronized void setDomainBoundaries(final Number lowerBoundary, final BoundaryMode lowerBoundaryMode, final Number upperBoundary, final BoundaryMode upperBoundaryMode) {
+ super.setDomainBoundaries(lowerBoundary, lowerBoundaryMode, upperBoundary, upperBoundaryMode);
+ if(mCalledBySelf) {
+ mCalledBySelf = false;
+ } else {
+ minXLimit = lowerBoundaryMode == BoundaryMode.FIXED ? lowerBoundary.floatValue() : getCalculatedMinX().floatValue();
+ maxXLimit = upperBoundaryMode == BoundaryMode.FIXED ? upperBoundary.floatValue() : getCalculatedMaxX().floatValue();
+ lastMinX = minXLimit;
+ lastMaxX = maxXLimit;
+ }
+ }
+
+ @Override
+ public synchronized void setRangeBoundaries(final Number lowerBoundary, final BoundaryMode lowerBoundaryMode, final Number upperBoundary, final BoundaryMode upperBoundaryMode) {
+ super.setRangeBoundaries(lowerBoundary, lowerBoundaryMode, upperBoundary, upperBoundaryMode);
+ if(mCalledBySelf) {
+ mCalledBySelf = false;
+ } else {
+ minYLimit = lowerBoundaryMode == BoundaryMode.FIXED ? lowerBoundary.floatValue() : getCalculatedMinY().floatValue();
+ maxYLimit = upperBoundaryMode == BoundaryMode.FIXED ? upperBoundary.floatValue() : getCalculatedMaxY().floatValue();
+ lastMinY = minYLimit;
+ lastMaxY = maxYLimit;
+ }
+ }
+
+ @Override
+ public synchronized void setDomainBoundaries(final Number lowerBoundary, final Number upperBoundary, final BoundaryMode mode) {
+ super.setDomainBoundaries(lowerBoundary, upperBoundary, mode);
+ if(mCalledBySelf) {
+ mCalledBySelf = false;
+ } else {
+ minXLimit = mode == BoundaryMode.FIXED ? lowerBoundary.floatValue() : getCalculatedMinX().floatValue();
+ maxXLimit = mode == BoundaryMode.FIXED ? upperBoundary.floatValue() : getCalculatedMaxX().floatValue();
+ lastMinX = minXLimit;
+ lastMaxX = maxXLimit;
+ }
+ }
+
+ @Override
+ public synchronized void setRangeBoundaries(final Number lowerBoundary, final Number upperBoundary, final BoundaryMode mode) {
+ super.setRangeBoundaries(lowerBoundary, upperBoundary, mode);
+ if(mCalledBySelf) {
+ mCalledBySelf = false;
+ } else {
+ minYLimit = mode == BoundaryMode.FIXED ? lowerBoundary.floatValue() : getCalculatedMinY().floatValue();
+ maxYLimit = mode == BoundaryMode.FIXED ? upperBoundary.floatValue() : getCalculatedMaxY().floatValue();
+ lastMinY = minYLimit;
+ lastMaxY = maxYLimit;
+ }
+ }
+
+ @Override
+ public boolean onTouch(final View view, final MotionEvent event) {
+ switch (event.getAction() & MotionEvent.ACTION_MASK)
+ {
+ case MotionEvent.ACTION_DOWN: // start gesture
+ firstFingerPos = new PointF(event.getX(), event.getY());
+ mode = State.ONE_FINGER_DRAG;
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN: // second finger
+ {
+ mDistX = getXDistance(event);
+ // the distance check is done to avoid false alarms
+ if(mDistX > MIN_DIST_2_FING || mDistX < -MIN_DIST_2_FING) {
+ mode = State.TWO_FINGERS_DRAG;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: // end zoom
+ mode = State.NONE;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if(mode == State.ONE_FINGER_DRAG) {
+ pan(event);
+ } else if(mode == State.TWO_FINGERS_DRAG) {
+ zoom(event);
+ }
+ break;
+ }
+ return true;
+ }
+
+ private float getXDistance(final MotionEvent event) {
+ return event.getX(0) - event.getX(1);
+ }
+
+ private void pan(final MotionEvent motionEvent) {
+ final PointF oldFirstFinger = firstFingerPos; //save old position of finger
+ firstFingerPos = new PointF(motionEvent.getX(), motionEvent.getY()); //update finger position
+ PointF newX = new PointF();
+ if(mZoomHorizontally) {
+ calculatePan(oldFirstFinger, newX, true);
+ mCalledBySelf = true;
+ super.setDomainBoundaries(newX.x, newX.y, BoundaryMode.FIXED);
+ lastMinX = newX.x;
+ lastMaxX = newX.y;
+ }
+ if(mZoomVertically) {
+ calculatePan(oldFirstFinger, newX, false);
+ mCalledBySelf = true;
+ super.setRangeBoundaries(newX.x, newX.y, BoundaryMode.FIXED);
+ lastMinY = newX.x;
+ lastMaxY = newX.y;
+ }
+ redraw();
+ }
+
+ private void calculatePan(final PointF oldFirstFinger, PointF newX, final boolean horizontal) {
+ final float offset;
+ // multiply the absolute finger movement for a factor.
+ // the factor is dependent on the calculated min and max
+ if(horizontal) {
+ newX.x = getLastMinX();
+ newX.y = getLastMaxX();
+ offset = (oldFirstFinger.x - firstFingerPos.x) * ((newX.y - newX.x) / getWidth());
+ } else {
+ newX.x = getLastMinY();
+ newX.y = getLastMaxY();
+ offset = -(oldFirstFinger.y - firstFingerPos.y) * ((newX.y - newX.x) / getHeight());
+ }
+ // move the calculated offset
+ newX.x = newX.x + offset;
+ newX.y = newX.y + offset;
+ //get the distance between max and min
+ final float diff = newX.y - newX.x;
+ //check if we reached the limit of panning
+ if(horizontal) {
+ if(newX.x < getMinXLimit()) {
+ newX.x = getMinXLimit();
+ newX.y = newX.x + diff;
+ }
+ if(newX.y > getMaxXLimit()) {
+ newX.y = getMaxXLimit();
+ newX.x = newX.y - diff;
+ }
+ } else {
+ if(newX.x < getMinYLimit()) {
+ newX.x = getMinYLimit();
+ newX.y = newX.x + diff;
+ }
+ if(newX.y > getMaxYLimit()) {
+ newX.y = getMaxYLimit();
+ newX.x = newX.y - diff;
+ }
+ }
+ }
+
+ private void zoom(final MotionEvent motionEvent) {
+ final float oldDist = mDistX;
+ final float newDist = getXDistance(motionEvent);
+ // sign change! Fingers have crossed ;-)
+ if(oldDist > 0 && newDist < 0 || oldDist < 0 && newDist > 0) {
+ return;
+ }
+ mDistX = newDist;
+ float scale = (oldDist / mDistX);
+ // sanity check
+ if(Float.isInfinite(scale) || Float.isNaN(scale) || scale > -0.001 && scale < 0.001) {
+ return;
+ }
+ PointF newX = new PointF();
+ if(mZoomHorizontally) {
+ calculateZoom(scale, newX, true);
+ mCalledBySelf = true;
+ super.setDomainBoundaries(newX.x, newX.y, BoundaryMode.FIXED);
+ lastMinX = newX.x;
+ lastMaxX = newX.y;
+ }
+ if(mZoomVertically) {
+ calculateZoom(scale, newX, false);
+ mCalledBySelf = true;
+ super.setRangeBoundaries(newX.x, newX.y, BoundaryMode.FIXED);
+ lastMinY = newX.x;
+ lastMaxY = newX.y;
+ }
+ redraw();
+ }
+
+ private void calculateZoom(float scale, PointF newX, final boolean horizontal) {
+ final float calcMax;
+ final float span;
+ if(horizontal) {
+ calcMax = getLastMaxX();
+ span = calcMax - getLastMinX();
+ } else {
+ calcMax = getLastMaxY();
+ span = calcMax - getLastMinY();
+ }
+ final float midPoint = calcMax - (span / 2.0f);
+ final float offset = span * scale / 2.0f;
+ newX.x = midPoint - offset;
+ newX.y = midPoint + offset;
+ if(horizontal) {
+ if(newX.x < getMinXLimit()) {
+ newX.x = getMinXLimit();
+ }
+ if(newX.y > getMaxXLimit()) {
+ newX.y = getMaxXLimit();
+ }
+ } else {
+ if(newX.x < getMinYLimit()) {
+ newX.x = getMinYLimit();
+ }
+ if(newX.y > getMaxYLimit()) {
+ newX.y = getMaxYLimit();
+ }
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYRegionFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYRegionFormatter.java
new file mode 100644
index 0000000..9566d5a
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYRegionFormatter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import android.graphics.Paint;
+import com.androidplot.util.Configurator;
+
+/**
+ * Base class of all XYRegionFormatters.
+ */
+public class XYRegionFormatter {
+
+ //private int color;
+ private Paint paint = new Paint();
+
+ {
+ paint.setStyle(Paint.Style.FILL);
+ paint.setAntiAlias(true);
+ }
+
+ /**
+ * Provided as a convenience to users; allows instantiation and xml configuration
+ * to take place in a single line
+ *
+ * @param ctx
+ * @param xmlCfgId Id of the xml config file within /res/xml
+ */
+ public XYRegionFormatter(Context ctx, int xmlCfgId) {
+ // prevent configuration of classes derived from this one:
+ if (getClass().equals(XYRegionFormatter.class)) {
+ Configurator.configure(ctx, this, xmlCfgId);
+ }
+ }
+
+ public XYRegionFormatter(int color) {
+ //paint = new Paint();
+ paint.setColor(color);
+ //paint.setStyle(Paint.Style.FILL);
+ //paint.setAntiAlias(true);
+ //this.color = color;
+ }
+
+ public int getColor() {
+ return paint.getColor();
+ }
+
+ public void setColor(int color) {
+ paint.setColor(color);
+ }
+
+ /**
+ * Advanced users can use this method to access the Paint instance to add transparency etc.
+ * @return
+ */
+ public Paint getPaint() {
+ return paint;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeries.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeries.java
new file mode 100644
index 0000000..22b7c19
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeries.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.util.Pair;
+import com.androidplot.Series;
+
+/**
+ * Represents a two dimensional series of data represented as xy values.
+ */
+public interface XYSeries extends Series<Pair<Number, Number>> {
+
+ /**
+ * @return Number of elements in this Series.
+ */
+ public int size();
+
+ /**
+ * Returns the x-value for an index within a series.
+ *
+ * @param index the index index (in the range <code>0</code> to
+ * <code>size()-1</code>).
+ *
+ * @return The x-value.
+ */
+ public Number getX(int index);
+
+ /**
+ * Returns the y-value for an index within a series.
+ *
+ * @param index the index index (in the range <code>0</code> to
+ * <code>size()-1</code>).
+ *
+ * @return The y-value.
+ */
+ public Number getY(int index);
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java
new file mode 100644
index 0000000..465f5bd
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.ui.Formatter;
+import com.androidplot.util.ZHash;
+import com.androidplot.util.ZIndexable;
+
+public abstract class XYSeriesFormatter<XYRegionFormatterType extends XYRegionFormatter> extends Formatter<XYPlot> {
+ ZHash<RectRegion, XYRegionFormatterType> regions;
+
+ {
+ regions = new ZHash<RectRegion, XYRegionFormatterType>();
+ }
+
+ public void addRegion(RectRegion region, XYRegionFormatterType regionFormatter) {
+ regions.addToBottom(region, regionFormatter);
+ }
+
+ public void removeRegion(RectRegion region) {
+ regions.remove(region);
+ }
+
+ /**
+ * Can be used to access z-index manipulation methods of ZIndexable.
+ * @return
+ */
+ public ZIndexable<RectRegion> getRegions() {
+ return regions;
+ }
+
+ /**
+ * @param region
+ * @return
+ */
+ public XYRegionFormatterType getRegionFormatter(RectRegion region) {
+ return regions.get(region);
+ }
+
+ /**
+ * Not completely sure why this is necessary, but if it's not here then
+ * subclasses are forced to take a Plot instead of an XYPlot as a parameter,
+ * which in turn breaks the pattern.
+ * @param plot
+ * @return
+ */
+ @Override
+ public abstract SeriesRenderer getRendererInstance(XYPlot plot);
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java
new file mode 100644
index 0000000..14f1ca9
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.SeriesAndFormatterList;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.util.ZIndexable;
+
+import java.util.Hashtable;
+
+public abstract class XYSeriesRenderer<XYFormatterType extends XYSeriesFormatter>
+ extends SeriesRenderer<XYPlot, XYSeries, XYFormatterType> {
+
+ public XYSeriesRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ public Hashtable<XYRegionFormatter, String> getUniqueRegionFormatters() {
+
+ Hashtable<XYRegionFormatter, String> found = new Hashtable<XYRegionFormatter, String>();
+ SeriesAndFormatterList<XYSeries, XYFormatterType> sfl = getSeriesAndFormatterList();
+
+ if (sfl != null) {
+ for (XYFormatterType xyf : sfl.getFormatterList()) {
+ ZIndexable<RectRegion> regionIndexer = xyf.getRegions();
+ for (RectRegion region : regionIndexer.elements()) {
+ XYRegionFormatter f = xyf.getRegionFormatter(region);
+ found.put(f, region.getLabel());
+ }
+ }
+ }
+
+ return found;
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStep.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStep.java
new file mode 100644
index 0000000..a4dacd5
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStep.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+/**
+ * An immutable object generated by XYStepCalculator representing
+ * a stepping model to be used by an XYPlot.
+ */
+public class XYStep {
+
+ private final float stepCount;
+ private final float stepPix;
+ private final double stepVal;
+
+ //public XYStep() {}
+
+ public XYStep(float stepCount, float stepPix, double stepVal) {
+ this.stepCount = stepCount;
+ this.stepPix = stepPix;
+ this.stepVal = stepVal;
+ }
+
+ public double getStepCount() {
+ return stepCount;
+ }
+
+ /*public void setStepCount(double stepCount) {
+ this.stepCount = stepCount;
+ }*/
+
+ public float getStepPix() {
+ return stepPix;
+ }
+
+ /*public void setStepPix(float stepPix) {
+ this.stepPix = stepPix;
+ }*/
+
+ public double getStepVal() {
+ return stepVal;
+ }
+
+ /*public void setStepVal(double stepVal) {
+ this.stepVal = stepVal;
+ }*/
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepCalculator.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepCalculator.java
new file mode 100644
index 0000000..fedc201
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepCalculator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.RectF;
+import com.androidplot.util.ValPixConverter;
+
+/**
+ * Calculates "stepping" values for a plot. These values are most commonly used for
+ * drawing grid lines on a graph.
+ */
+public class XYStepCalculator {
+
+
+ /**
+ * Convenience method - wraps other form of getStep().
+ * @param plot
+ * @param axisType
+ * @param rect
+ * @param minVal
+ * @param maxVal
+ * @return
+ */
+ public static XYStep getStep(XYPlot plot, XYAxisType axisType, RectF rect, Number minVal, Number maxVal) {
+ XYStep step = null;
+ switch(axisType) {
+ case DOMAIN:
+ step = getStep(plot.getDomainStepMode(), rect.width(), plot.getDomainStepValue(), minVal, maxVal);
+ break;
+ case RANGE:
+ step = getStep(plot.getRangeStepMode(), rect.height(), plot.getRangeStepValue(), minVal, maxVal);
+ break;
+ }
+ return step;
+ }
+
+ public static XYStep getStep(XYStepMode typeXY, float plotPixelSize, double stepValue, Number minVal, Number maxVal) {
+ //XYStep step = new XYStep();
+ double stepVal = 0;
+ float stepPix = 0;
+ float stepCount = 0;
+ switch(typeXY) {
+ case INCREMENT_BY_VAL:
+ stepVal = stepValue;
+ stepPix = (float)(stepValue/ ValPixConverter.valPerPix(minVal.doubleValue(), maxVal.doubleValue(), plotPixelSize));
+ stepCount = plotPixelSize /stepPix;
+ break;
+ case INCREMENT_BY_PIXELS:
+ stepPix = new Double(stepValue).floatValue();
+ stepCount = plotPixelSize /stepPix;
+ stepVal = ValPixConverter.valPerPix(minVal.doubleValue(), maxVal.doubleValue(), plotPixelSize)*stepPix;
+ break;
+ case SUBDIVIDE:
+ stepCount = new Double(stepValue).floatValue();
+ stepPix = (plotPixelSize /(stepCount-1));
+ stepVal = ValPixConverter.valPerPix(minVal.doubleValue(), maxVal.doubleValue(), plotPixelSize)*stepPix;
+ break;
+ }
+ return new XYStep(stepCount, stepPix, stepVal);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepMode.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepMode.java
new file mode 100644
index 0000000..005ec5f
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/XYStepMode.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+/**
+ * INCREMENTAL_VALUE - (default) draw a tick every n values.
+ * INCREMENTAL_PIXEL - draw a tick every n pixels.
+ * SUBDIVIDE - draw n number of evenly spaced ticks.
+ */
+public enum XYStepMode {
+ SUBDIVIDE, // default
+ INCREMENT_BY_VAL,
+ INCREMENT_BY_PIXELS
+}
diff --git a/AndroidPlot-Core/src/main/java/com/androidplot/xy/YValueMarker.java b/AndroidPlot-Core/src/main/java/com/androidplot/xy/YValueMarker.java
new file mode 100644
index 0000000..d1fbde5
--- /dev/null
+++ b/AndroidPlot-Core/src/main/java/com/androidplot/xy/YValueMarker.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.graphics.Paint;
+import com.androidplot.ui.XLayoutStyle;
+import com.androidplot.ui.XPositionMetric;
+
+public class YValueMarker extends ValueMarker<XPositionMetric> {
+
+
+ /**
+ *
+ * @param value
+ * @param text Set to null to use the plot's default formatter.
+ */
+ public YValueMarker(Number value, String text) {
+ super(value, text, new XPositionMetric(3, XLayoutStyle.ABSOLUTE_FROM_LEFT));
+ }
+
+ /**
+ *
+ * @param value
+ * @param text Set to null to use the plot's default formatter.
+ * @param textPosition
+ * @param linePaint
+ * @param textPaint
+ */
+ public YValueMarker(Number value, String text, XPositionMetric textPosition, Paint linePaint, Paint textPaint) {
+ super(value, text, textPosition, linePaint, textPaint);
+ }
+
+ /**
+ *
+ * @param value
+ * @param text Set to null to use the plot's default formatter.
+ * @param textPosition
+ * @param linePaint
+ * @param textPaint
+ */
+ public YValueMarker(Number value, String text, XPositionMetric textPosition, int linePaint, int textPaint) {
+ super(value, text, textPosition, linePaint, textPaint);
+ }
+}
diff --git a/AndroidPlot-Core/src/main/javadoc/com/androidplot/series/package.html b/AndroidPlot-Core/src/main/javadoc/com/androidplot/series/package.html
new file mode 100644
index 0000000..1dc055f
--- /dev/null
+++ b/AndroidPlot-Core/src/main/javadoc/com/androidplot/series/package.html
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title></title>
+</head>
+<body>
+Series interface definitions.
+</body>
+</html> \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/javadoc/com/androidplot/ui/package.html b/AndroidPlot-Core/src/main/javadoc/com/androidplot/ui/package.html
new file mode 100644
index 0000000..dc47c84
--- /dev/null
+++ b/AndroidPlot-Core/src/main/javadoc/com/androidplot/ui/package.html
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title></title>
+</head>
+<body>
+Visual components of AndroidPlot
+</body>
+</html> \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/javadoc/com/androidplot/util/package.html b/AndroidPlot-Core/src/main/javadoc/com/androidplot/util/package.html
new file mode 100644
index 0000000..95a1a39
--- /dev/null
+++ b/AndroidPlot-Core/src/main/javadoc/com/androidplot/util/package.html
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title></title>
+</head>
+<body>
+General use classes and interfaces.
+</body>
+</html> \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/javadoc/com/androidplot/xy/package.html b/AndroidPlot-Core/src/main/javadoc/com/androidplot/xy/package.html
new file mode 100644
index 0000000..c894bab
--- /dev/null
+++ b/AndroidPlot-Core/src/main/javadoc/com/androidplot/xy/package.html
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title></title>
+</head>
+<body>
+Classes for creating XYPlots.
+</body>
+</html> \ No newline at end of file
diff --git a/AndroidPlot-Core/src/main/javadoc/doc-files/barplot1.jpg b/AndroidPlot-Core/src/main/javadoc/doc-files/barplot1.jpg
new file mode 100644
index 0000000..c2f4fa9
--- /dev/null
+++ b/AndroidPlot-Core/src/main/javadoc/doc-files/barplot1.jpg
Binary files differ
diff --git a/AndroidPlot-Core/src/main/javadoc/doc-files/lineplot1.jpg b/AndroidPlot-Core/src/main/javadoc/doc-files/lineplot1.jpg
new file mode 100644
index 0000000..f1917b2
--- /dev/null
+++ b/AndroidPlot-Core/src/main/javadoc/doc-files/lineplot1.jpg
Binary files differ
diff --git a/AndroidPlot-Core/src/main/javadoc/overview.html b/AndroidPlot-Core/src/main/javadoc/overview.html
new file mode 100644
index 0000000..d882141
--- /dev/null
+++ b/AndroidPlot-Core/src/main/javadoc/overview.html
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<body>
+A library for drawing plots on the Android platform.
+Visit <a href="http://androidplot.com/wiki/Docs">http://androidplot.com/wiki/Docs</a> for documentation and tutorials.
+</body> \ No newline at end of file
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/LineRegionTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/LineRegionTest.java
new file mode 100644
index 0000000..a524313
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/LineRegionTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class LineRegionTest {
+ @Before
+ public void setUp() throws Exception {
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testConstructor() throws Exception {
+ LineRegion lr = new LineRegion(0d, 0d);
+ assertEquals(0d, lr.getMinVal());
+ assertEquals(0d, lr.getMaxVal());
+
+ lr = new LineRegion(1.5d, -2d);
+ assertEquals(-2d, lr.getMinVal());
+ assertEquals(1.5d, lr.getMaxVal());
+
+ lr = new LineRegion(10d, 20d);
+ assertEquals(10d, lr.getMinVal());
+ assertEquals(20d, lr.getMaxVal());
+ }
+
+
+ @Test
+ public void testContains() throws Exception {
+
+ }
+
+ @Test
+ public void testIntersects() throws Exception {
+ LineRegion line1 = new LineRegion(1, 10);
+ LineRegion line2 = new LineRegion(11, 20);
+ assertFalse(line1.intersects(line2));
+
+ line1.setMaxVal(15);
+ assertTrue(line1.intersects(line2));
+
+ //l1end = 30;
+ line1.setMaxVal(30);
+ assertTrue(line1.intersects(line2));
+
+ //l1start = 21;
+ line1.setMinVal(21);
+ assertFalse(line1.intersects(line2));
+ }
+
+ @Test
+ public void testLength() throws Exception {
+ LineRegion lr = new LineRegion(0, 10);
+ assertEquals(10d, lr.length().doubleValue(), 0);
+
+ lr = new LineRegion(-5, 5);
+ assertEquals(10d, lr.length().doubleValue(), 0);
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/PlotTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/PlotTest.java
new file mode 100644
index 0000000..3dc8aeb
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/PlotTest.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot;
+
+import android.content.Context;
+import android.graphics.*;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import com.androidplot.mock.MockContext;
+import com.androidplot.mock.MockPaint;
+import com.androidplot.ui.SeriesAndFormatterList;
+import com.androidplot.exception.PlotRenderException;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.ui.Formatter;
+//import mockit.*;
+import com.androidplot.ui.widget.TextLabelWidget;
+import com.androidplot.util.Configurator;
+import com.androidplot.util.FontUtils;
+import com.androidplot.util.PixelUtils;
+import mockit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@UsingMocksAndStubs({Log.class, View.class,Handler.class,Paint.class,Color.class,
+ RectF.class, Rect.class, FontUtils.class, Canvas.class,
+ PixelUtils.class,Context.class})
+
+public class PlotTest {
+
+ static class MockPlotListener implements PlotListener {
+
+ @Override
+ public void onBeforeDraw(Plot source, Canvas canvas) {}
+
+ @Override
+ public void onAfterDraw(Plot source, Canvas canvas) {}
+ }
+
+ static class MockSeries implements Series {
+ @Override
+ public String getTitle() {
+ return null;
+ }
+
+ }
+
+ static class MockSeries2 implements Series {
+ @Override
+ public String getTitle() {
+ return null;
+ }
+ }
+
+ static class MockSeries3 implements Series {
+ @Override
+ public String getTitle() {
+ return null;
+ }
+ }
+
+ static class MockRenderer1 extends SeriesRenderer {
+
+ public MockRenderer1(Plot plot) {
+ super(plot);
+ }
+
+ @Override
+ public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
+
+ }
+
+ @Override
+ public void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) {
+
+ }
+ }
+ static class MockRenderer2 extends SeriesRenderer {
+
+ public MockRenderer2(Plot plot) {
+ super(plot);
+ }
+
+ @Override
+ public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
+
+ }
+
+ @Override
+ public void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) {
+
+ }
+ }
+
+ static class MockFormatter1 extends Formatter<MockPlot> {
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return MockRenderer1.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(MockPlot plot) {
+ return new MockRenderer1(plot);
+ }
+ }
+
+ static class MockFormatter2 extends Formatter<MockPlot> {
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return MockRenderer2.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(MockPlot plot) {
+ return new MockRenderer2(plot);
+ }
+ }
+
+ //@MockClass(realClass = Plot.class)
+ public static class MockPlot extends Plot<MockSeries, Formatter, SeriesRenderer> {
+ public MockPlot(Context context, String title) {
+ super(context, title);
+ }
+
+ @Override
+ protected void onPreInit() {
+
+ }
+
+ /*@Override
+ protected SeriesRenderer doGetRendererInstance(Class clazz) {
+ if(clazz == MockRenderer1.class) {
+ return new MockRenderer1(this);
+ } else if(clazz == MockRenderer2.class) {
+ return new MockRenderer2(this);
+ } else {
+ return null;
+ }
+ }*/
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ Mockit.setUpMocks(MockPaint.class,MockContext.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testAddSeries() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ //Plot plot = Mockit.setUpMock(Plot.class, new MockPlot(context, "MockPlot"));
+ //Plot plot = Mockit.setUpMock(new MockPlot());
+ Plot plot = new MockPlot(context, "MockPlot");
+
+ MockSeries m1 = new MockSeries();
+ Class cl = MockRenderer1.class;
+
+
+
+ plot.addSeries(m1, new MockFormatter1());
+
+ LinkedHashMap<Class<SeriesRenderer>, SeriesAndFormatterList<MockSeries,MockFormatter1>> registry = Deencapsulation.getField(plot, "seriesRegistry");
+ assertEquals(1, registry.size());
+ assertEquals(1, registry.get(cl).size());
+
+ plot.addSeries(m1, new MockFormatter1());
+
+ // duplicate Renderer added, registry size should not grow:
+ assertEquals(1, registry.size());
+ assertEquals(1, registry.get(cl).size());
+
+ MockSeries m2 = new MockSeries();
+
+ plot.addSeries(m2, new MockFormatter1());
+
+ // still should only be one renderer type:
+ assertEquals(1, registry.size());
+
+ // we added a new instance of cl to the renderer so there should be 2 in the subregistry:
+ assertEquals(2, registry.get(cl).size());
+
+
+ // lets add another renderer:
+ plot.addSeries(m1, new MockFormatter2());
+
+ assertEquals(2, registry.size());
+ }
+
+ @Test
+ public void testRemoveSeries() throws Exception {
+
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+ LinkedHashMap<Class<SeriesRenderer>, SeriesAndFormatterList<MockSeries,MockFormatter1>> registry = Deencapsulation.getField(plot, "seriesRegistry");
+
+ MockSeries m1 = new MockSeries();
+ MockSeries m2 = new MockSeries();
+ MockSeries m3 = new MockSeries();
+
+ plot.addSeries(m1, new MockFormatter1());
+ plot.addSeries(m2, new MockFormatter1());
+ plot.addSeries(m3, new MockFormatter1());
+
+ plot.addSeries(m1, new MockFormatter2());
+ plot.addSeries(m2, new MockFormatter2());
+ plot.addSeries(m3, new MockFormatter2());
+
+
+ // a quick sanity check:
+ assertEquals(2, registry.size());
+ assertEquals(3, registry.get(MockRenderer1.class).size());
+ assertEquals(3, registry.get(MockRenderer2.class).size());
+
+ plot.removeSeries(m1, MockRenderer1.class);
+ assertEquals(2, registry.get(MockRenderer1.class).size());
+
+ plot.removeSeries(m2, MockRenderer1.class);
+ assertEquals(1, registry.get(MockRenderer1.class).size());
+
+ plot.removeSeries(m2, MockRenderer1.class);
+ assertEquals(1, registry.get(MockRenderer1.class).size());
+
+ plot.removeSeries(m3, MockRenderer1.class);
+
+ // all the elements should be gone from MockRenderer1, thus the renderer should
+ // also be gone:
+ assertNull(registry.get(MockRenderer1.class));
+
+
+ // add em all back
+ plot.addSeries(m1, new MockFormatter1());
+ plot.addSeries(m2, new MockFormatter1());
+ plot.addSeries(m3, new MockFormatter1());
+
+ plot.addSeries(m1, new MockFormatter1());
+ plot.addSeries(m2, new MockFormatter1());
+ plot.addSeries(m3, new MockFormatter1());
+
+
+ // a quick sanity check:
+ assertEquals(2, registry.size());
+ assertEquals(3, registry.get(MockRenderer1.class).size());
+ assertEquals(3, registry.get(MockRenderer2.class).size());
+
+ // now lets try removing a series from all renderers:
+ plot.removeSeries(m1);
+ assertEquals(2, registry.get(MockRenderer1.class).size());
+ assertEquals(2, registry.get(MockRenderer2.class).size());
+
+ // and now lets remove the remaining series:
+ plot.removeSeries(m2);
+ plot.removeSeries(m3);
+
+ // nothing should be left:
+ assertNull(registry.get(MockRenderer1.class));
+ assertNull(registry.get(MockRenderer2.class));
+ }
+
+
+ @Test
+ public void testGetFormatter() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+ LinkedHashMap<Class<SeriesRenderer>, SeriesAndFormatterList<MockSeries,MockFormatter1>> registry = Deencapsulation.getField(plot, "seriesRegistry");
+
+ MockSeries m1 = new MockSeries();
+ MockSeries m2 = new MockSeries();
+ MockSeries m3 = new MockSeries();
+
+ MockFormatter1 f1 = new MockFormatter1();
+ MockFormatter1 f2 = new MockFormatter1();
+ MockFormatter2 f3 = new MockFormatter2();
+
+ plot.addSeries(m1, f1);
+ plot.addSeries(m2, f2);
+ plot.addSeries(m3, new MockFormatter1());
+
+ plot.addSeries(m1, new MockFormatter1());
+ plot.addSeries(m2, f3);
+ plot.addSeries(m3, new MockFormatter1());
+
+ assertEquals(registry.get(MockRenderer1.class).getFormatter(m1), f1);
+ assertEquals(registry.get(MockRenderer1.class).getFormatter(m2), f2);
+ assertEquals(registry.get(MockRenderer2.class).getFormatter(m2), f3);
+
+ assertNotSame(registry.get(MockRenderer2.class).getFormatter(m2), f1);
+
+ }
+
+ @Test
+ public void testGetSeriesListForRenderer() throws Exception {
+
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+ //LinkedHashMap<Class<SeriesRenderer>, SeriesAndFormatterList<MockSeries,MockFormatter1>> registry = Deencapsulation.getField(plot, "seriesRegistry");
+
+ MockSeries m1 = new MockSeries();
+ MockSeries m2 = new MockSeries();
+ MockSeries m3 = new MockSeries();
+
+ plot.addSeries(m1, new MockFormatter1());
+ plot.addSeries(m2, new MockFormatter1());
+ plot.addSeries(m3, new MockFormatter1());
+
+ plot.addSeries(m1, new MockFormatter1());
+ plot.addSeries(m2, new MockFormatter1());
+ plot.addSeries(m3, new MockFormatter1());
+
+ List<MockSeries> m1List = plot.getSeriesListForRenderer(MockRenderer1.class);
+ assertEquals(3, m1List.size());
+ assertEquals(m1, m1List.get(0));
+ assertNotSame(m2, m1List.get(0));
+ assertEquals(m2, m1List.get(1));
+ assertEquals(m3, m1List.get(2));
+ }
+
+ @Test
+ public void testGetRendererList() throws Exception {
+
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+ //LinkedHashMap<Class<SeriesRenderer>, SeriesAndFormatterList<MockSeries,MockFormatter1>> registry = Deencapsulation.getField(plot, "seriesRegistry");
+
+ MockSeries m1 = new MockSeries();
+ MockSeries m2 = new MockSeries();
+ MockSeries m3 = new MockSeries();
+
+ plot.addSeries(m1, new MockFormatter1());
+ plot.addSeries(m2, new MockFormatter1());
+ plot.addSeries(m3, new MockFormatter1());
+
+ plot.addSeries(m1, new MockFormatter2());
+ plot.addSeries(m2, new MockFormatter2());
+ plot.addSeries(m3, new MockFormatter2());
+
+ List<SeriesRenderer> rList = plot.getRendererList();
+ assertEquals(2, rList.size());
+
+ assertEquals(MockRenderer1.class, rList.get(0).getClass());
+ assertEquals(MockRenderer2.class, rList.get(1).getClass());
+ }
+
+ @Test
+ public void testAddListener() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+ ArrayList<PlotListener> listeners = Deencapsulation.getField(plot, "listeners");
+ //LinkedHashMap<Class<SeriesRenderer>, SeriesAndFormatterList<MockSeries,MockFormatter1>> registry = Deencapsulation.getField(plot, "seriesRegistry");
+
+ assertEquals(0, listeners.size());
+
+ MockPlotListener pl1 = new MockPlotListener();
+ MockPlotListener pl2 = new MockPlotListener();
+
+ plot.addListener(pl1);
+
+ assertEquals(1, listeners.size());
+
+ // should return false on a double entry attempt
+ assertFalse(plot.addListener(pl1));
+
+ // make sure the listener wasnt added anyway:
+ assertEquals(1, listeners.size());
+
+ plot.addListener(pl2);
+
+ assertEquals(2, listeners.size());
+
+ }
+
+ @Test
+ public void testRemoveListener() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+ ArrayList<PlotListener> listeners = Deencapsulation.getField(plot, "listeners");
+ //LinkedHashMap<Class<SeriesRenderer>, SeriesAndFormatterList<MockSeries,MockFormatter1>> registry = Deencapsulation.getField(plot, "seriesRegistry");
+
+ assertEquals(0, listeners.size());
+
+ MockPlotListener pl1 = new MockPlotListener();
+ MockPlotListener pl2 = new MockPlotListener();
+ MockPlotListener pl3 = new MockPlotListener();
+
+ plot.addListener(pl1);
+ plot.addListener(pl2);
+
+ assertEquals(2, listeners.size());
+
+ assertFalse(plot.removeListener(pl3));
+
+ assertTrue(plot.removeListener(pl1));
+
+ assertEquals(1, listeners.size());
+
+ assertFalse(plot.removeListener(pl1));
+
+ assertEquals(1, listeners.size());
+
+ assertTrue(plot.removeListener(pl2));
+
+ assertEquals(0, listeners.size());
+
+ }
+
+ /*@Test
+ public void testGuessGetterName() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+
+ Method m = Plot.class.getDeclaredMethod("guessGetterMethod", Object.class, String.class);
+ assertNotNull(m);
+ }
+
+ @Test
+ public void testGuessSetterName() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ Plot plot = new MockPlot(context, "MockPlot");
+
+ Method m = Plot.class.getDeclaredMethod("guessSetterMethod", Object.class, String.class, Class.class);
+ assertNotNull(m);
+ }*/
+
+
+
+ @Test
+ public void testConfigure() throws Exception {
+ //Context context = Mockit.setUpMock(new MockContext.MockContext2());
+ Context context = new MockContext.MockContext2();
+ Plot plot = new MockPlot(context, "MockPlot");
+
+ HashMap<String, String> params = new HashMap<String, String>();
+ String param1 = "this is a test.";
+ //String param2 = Plot.RenderMode.USE_BACKGROUND_THREAD.toString();
+ String param2 = "use_background_thread";
+ String param3 = "#FF0000";
+ params.put("title", param1);
+ params.put("renderMode", param2);
+ params.put("backgroundPaint.color", param3);
+
+
+ //Method m = Plot.class.getDeclaredMethod("configure", params.getClass());
+ //m.setAccessible(true);
+ //m.invoke(plot, params);
+ Configurator.configure(context, plot, params);
+
+ assertEquals(param1, plot.getTitle());
+ assertEquals(Plot.RenderMode.USE_BACKGROUND_THREAD, plot.getRenderMode());
+ assertEquals(Color.parseColor(param3), plot.getBackgroundPaint().getColor());
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/RegionTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/RegionTest.java
new file mode 100644
index 0000000..dcf4148
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/RegionTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class RegionTest {
+ @Before
+ public void setUp() throws Exception {
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testContains() throws Exception {
+
+ }
+
+ @Test
+ public void testIntersects() throws Exception {
+ LineRegion line1 = new LineRegion(1, 10);
+ LineRegion line2 = new LineRegion(11, 20);
+ assertFalse(line1.intersects(line2));
+
+ line1.setMaxVal(15);
+ assertTrue(line1.intersects(line2));
+
+ //l1end = 30;
+ line1.setMaxVal(30);
+ assertTrue(line1.intersects(line2));
+
+ //l1start = 21;
+ line1.setMinVal(21);
+ assertFalse(line1.intersects(line2));
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockCanvas.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockCanvas.java
new file mode 100644
index 0000000..44fca82
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockCanvas.java
@@ -0,0 +1,34 @@
+package com.androidplot.mock;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import mockit.Mock;
+import mockit.MockClass;
+import mockit.MockUp;
+import mockit.Mocked;
+
+@MockClass(realClass = Canvas.class, stubs="", inverse=true)
+public class MockCanvas {
+
+ @Mock
+ public int getHeight() {
+ return 100;
+ }
+
+ @Mock
+ public int getWidth() {
+ return 100;
+ }
+
+ @Mock
+ public void restore() {}
+
+ @Mock
+ public int save(int flags) {
+ return 1;
+ }
+
+ @Mock
+ public void drawPoint(float x, float y, Paint paint) {}
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockContext.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockContext.java
new file mode 100644
index 0000000..0251c77
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockContext.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.mock;
+
+import android.content.*;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Bitmap;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import mockit.Instantiation;
+import mockit.Mock;
+import mockit.MockClass;
+
+import java.io.*;
+
+@MockClass(realClass = Context.class)
+public class MockContext {
+
+ /**
+ * Useful for when methods are going to actually be called on a Context instance.
+ * See {@link com.androidplot.PlotTest#testConfigure()} for an example.
+ */
+ public static final class MockContext2 extends Context {
+
+ public MockContext2() {}
+
+ @Override
+ public AssetManager getAssets() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Mock
+ public android.content.res.Resources getResources() { throw new IllegalArgumentException();}
+
+ @Override
+ public PackageManager getPackageManager() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Looper getMainLooper() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void setTheme(int i) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public String getPackageName() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public String getPackageResourcePath() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public String getPackageCodePath() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String s, int i) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public FileInputStream openFileInput(String s) throws FileNotFoundException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String s, int i) throws FileNotFoundException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public boolean deleteFile(String s) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getFileStreamPath(String s) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getFilesDir() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getExternalFilesDir(String s) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getObbDir() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getCacheDir() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getExternalCacheDir() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public String[] fileList() {
+ return new String[0]; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getDir(String s, int i) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String s, int i, SQLiteDatabase.CursorFactory cursorFactory) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String s, int i, SQLiteDatabase.CursorFactory cursorFactory, DatabaseErrorHandler databaseErrorHandler) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public boolean deleteDatabase(String s) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public File getDatabasePath(String s) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public String[] databaseList() {
+ return new String[0]; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Drawable getWallpaper() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Drawable peekWallpaper() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumWidth() {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumHeight() {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void setWallpaper(Bitmap bitmap) throws IOException {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void setWallpaper(InputStream inputStream) throws IOException {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void clearWallpaper() throws IOException {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ //@Override
+ public void startActivity(Intent intent, Bundle bundle) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void startActivities(Intent[] intents) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ //@Override
+ public void startActivities(Intent[] intents, Bundle bundle) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intentSender, Intent intent, int i, int i1, int i2) throws IntentSender.SendIntentException {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ //@Override
+ public void startIntentSender(IntentSender intentSender, Intent intent, int i, int i1, int i2, Bundle bundle) throws IntentSender.SendIntentException {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String s) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String s) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String s, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s1, Bundle bundle) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent intent) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s, Bundle bundle) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent intent) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter, String s, Handler handler) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public ComponentName startService(Intent intent) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public boolean stopService(Intent intent) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void unbindService(ServiceConnection serviceConnection) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public boolean startInstrumentation(ComponentName componentName, String s, Bundle bundle) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Object getSystemService(String s) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int checkPermission(String s, int i, int i1) {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int checkCallingPermission(String s) {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String s) {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void enforcePermission(String s, int i, int i1, String s1) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void enforceCallingPermission(String s, String s1) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String s, String s1) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void grantUriPermission(String s, Uri uri, int i) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void revokeUriPermission(Uri uri, int i) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, int i, int i1, int i2) {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int checkCallingUriPermission(Uri uri, int i) {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int checkCallingOrSelfUriPermission(Uri uri, int i) {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int checkUriPermission(Uri uri, String s, String s1, int i, int i1, int i2) {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void enforceUriPermission(Uri uri, int i, int i1, int i2, String s) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void enforceCallingUriPermission(Uri uri, int i, String s) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void enforceCallingOrSelfUriPermission(Uri uri, int i, String s) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void enforceUriPermission(Uri uri, String s, String s1, int i, int i1, int i2, String s2) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Context createPackageContext(String s, int i) throws PackageManager.NameNotFoundException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockLooper.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockLooper.java
new file mode 100644
index 0000000..5a4649e
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockLooper.java
@@ -0,0 +1,25 @@
+package com.androidplot.mock;
+
+import android.os.Looper;
+import mockit.Mock;
+import mockit.MockClass;
+
+
+/**
+ * myLooper and getMainLooper will always be equal. The implication is that any calling
+ * entity will assume that it is running on the main thread. Simulation of background mode
+ * is not supported.
+ */
+@MockClass(realClass = Looper.class)
+public class MockLooper {
+
+ @Mock
+ public static Looper myLooper() {
+ return null;
+ }
+
+ @Mock
+ public static Looper getMainLooper() {
+ return null;
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPaint.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPaint.java
new file mode 100644
index 0000000..f0209ee
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPaint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.mock;
+
+import android.graphics.Paint;
+import android.graphics.RectF;
+import mockit.Instantiation;
+import mockit.Mock;
+import mockit.MockClass;
+
+@MockClass(realClass = Paint.class)
+public final class MockPaint {
+ int color = 0;
+
+ @Mock
+ public void setColor(int color) {
+ this.color = color;
+ }
+
+ @Mock
+ public int getColor() {
+ return color;
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPixelUtils.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPixelUtils.java
new file mode 100644
index 0000000..45cb25c
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPixelUtils.java
@@ -0,0 +1,22 @@
+package com.androidplot.mock;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import com.androidplot.util.PixelUtils;
+import mockit.Mock;
+import mockit.MockClass;
+
+@MockClass(realClass = PixelUtils.class, stubs="", inverse=true)
+public class MockPixelUtils {
+
+ @Mock
+ public static void init(Context ctx) {
+
+ }
+
+ @Mock
+ public static PointF sub(PointF lhs, PointF rhs) {
+ return new PointF(0, 0);
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPointF.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPointF.java
new file mode 100644
index 0000000..0970c35
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockPointF.java
@@ -0,0 +1,25 @@
+package com.androidplot.mock;
+
+import android.graphics.PointF;
+import mockit.Mock;
+import mockit.MockClass;
+
+@MockClass(realClass = PointF.class)
+public class MockPointF {
+ float x;
+ float y;
+
+ @Mock
+ public void $init() {}
+
+ @Mock
+ public void $init(float x, float y) {
+ set(x, y);
+ }
+
+ @Mock
+ public void set(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockRectF.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockRectF.java
new file mode 100644
index 0000000..022788c
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockRectF.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.mock;
+
+import android.graphics.RectF;
+import mockit.Instantiation;
+import mockit.Mock;
+import mockit.MockClass;
+
+@MockClass(realClass = RectF.class, stubs="", inverse=true)
+public final class MockRectF {
+ //public float left;
+ //public float top;
+ //public float right;
+ //public float bottom;
+
+ public RectF it;
+
+ @Mock
+ public void $init() {
+
+ }
+
+ @Mock
+ public void $init(RectF rhs) {
+ it.left = rhs.left;
+ it.top = rhs.top;
+ it.right = rhs.right;
+ it.bottom = rhs.bottom;
+ }
+
+ @Mock
+ public void $init(float left, float top, float right, float bottom) {
+ it.left = left;
+ it.top = top;
+ it.right = right;
+ it.bottom = bottom;
+ // do anything here
+
+ }
+
+ @Mock
+ public void offset(float dx, float dy) {
+ float w = width();
+ float h = height();
+
+ it.left = it.left + dx;
+ it.right = it.right + dx;
+
+ it.top = it.top + dy;
+ it.bottom = it.bottom + dy;
+ }
+
+ @Mock
+ public void offsetTo(float left, float top) {
+
+ it.right = left + width();
+ it.bottom = top + height();
+
+ it.left = left;
+ it.top = top;
+
+ // do anything here
+ }
+
+ @Mock
+ public float height() {
+ return it.bottom - it.top;
+ }
+
+ @Mock
+ public float width() {
+ return it.right - it.left;
+ }
+
+ @Mock
+ public String toString() {
+ return null;
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockSizeMetrics.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockSizeMetrics.java
new file mode 100644
index 0000000..389b1d9
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockSizeMetrics.java
@@ -0,0 +1,21 @@
+package com.androidplot.mock;
+
+import com.androidplot.ui.SizeLayoutType;
+import com.androidplot.ui.SizeMetric;
+import com.androidplot.ui.SizeMetrics;
+import mockit.Mock;
+import mockit.MockClass;
+
+@MockClass(realClass = SizeMetrics.class)
+public class MockSizeMetrics {
+
+ @Mock
+ public SizeMetric getWidthMetric() {
+ return new SizeMetric(0, SizeLayoutType.ABSOLUTE);
+ }
+
+ @Mock
+ public SizeMetric getHeightMetric() {
+ return new SizeMetric(0, SizeLayoutType.ABSOLUTE);
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockWidget.java b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockWidget.java
new file mode 100644
index 0000000..53dc6b8
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/mock/MockWidget.java
@@ -0,0 +1,20 @@
+package com.androidplot.mock;
+
+import com.androidplot.ui.widget.Widget;
+import mockit.Mock;
+import mockit.MockClass;
+
+@MockClass(realClass = Widget.class, inverse = false)
+public class MockWidget {
+
+ @Mock
+ public float getWidthPix(float f) {
+ return 100;
+ }
+
+ @Mock
+ public float getHeightPix(float f) {
+ return 100;
+ }
+
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/ui/DynamicTableModelTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/ui/DynamicTableModelTest.java
new file mode 100644
index 0000000..2585ec8
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/ui/DynamicTableModelTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+import com.androidplot.mock.MockRectF;
+import com.androidplot.ui.DynamicTableModel;
+import com.androidplot.ui.TableModel;
+import com.androidplot.ui.TableOrder;
+import mockit.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static junit.framework.Assert.assertEquals;
+
+public class DynamicTableModelTest {
+ @Before
+ public void setUp() throws Exception {
+ Mockit.setUpMocks(MockRectF.class);
+ }
+
+ @Test
+ public void testConstructor() throws Exception {
+ TableModel model = new DynamicTableModel(5, 5, TableOrder.COLUMN_MAJOR);
+ // TODO
+ }
+
+ @Test
+ public void testGetCellRect() throws Exception {
+
+ // square table, both rows and columns defined:
+ DynamicTableModel model = new DynamicTableModel(5, 5);
+ RectF tableRect = new RectF(0, 0, 1000, 2000);
+ RectF cellRect = model.getCellRect(tableRect, 10);
+ assertEquals(200f, cellRect.width());
+
+ // only rows defined:
+ model = new DynamicTableModel(5, 0);
+ cellRect = model.getCellRect(tableRect, 10);
+ assertEquals(200f, cellRect.width());
+
+ // only columns defined:
+ model = new DynamicTableModel(0, 5);
+ cellRect = model.getCellRect(tableRect, 10);
+ assertEquals(400f, cellRect.height());
+ }
+
+ @Test public void testIterator() throws Exception {
+ TableModel model = new DynamicTableModel(2, 2);
+
+ RectF tableRect = new RectF(0, 0, 1000, 2000);
+
+ // should stop at 4 iterations since the table can only hold that many:
+ Iterator<RectF> it = model.getIterator(tableRect, 10);
+ int iterations = 0;
+ while(it.hasNext()) {
+ it.next();
+ iterations++;
+ }
+ assertEquals(4, iterations);
+
+ // now set a dynamic number of columns. iterations should equal however
+ // many elements we throw at it:
+ model = new DynamicTableModel(2, 0);
+ it = model.getIterator(tableRect, 10);
+ iterations = 0;
+ while(it.hasNext()) {
+ it.next();
+ iterations++;
+ }
+ assertEquals(10, iterations);
+
+
+ }
+
+ @Test
+ public void testRowMajorIteration() throws Exception {
+
+ // square table, both rows and columns defined:
+ TableModel model = new DynamicTableModel(2, 2);
+ RectF tableRect = new RectF(0, 0, 1000, 2000);
+ int createdCells = 4;
+ Iterator<RectF> it = model.getIterator(tableRect, createdCells);
+
+
+ // 2x2:
+ // cell 0 (top-left
+ RectF cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(0f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(500f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ // cell 1 (top-right)
+ cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(500f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(1000f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ // cell 2 (bottom-left)
+ cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(0f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(500f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+
+ // cell 3 (bottom-right)
+ cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(500f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(1000f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+
+ // 2xN:
+ /*model = new DynamicTableModel(2, 0);
+ tableRect = new RectF(0, 0, 1000, 2000);
+ createdCells = 4;
+ it = model.getIterator(tableRect, createdCells);*/
+
+
+
+ }
+
+
+ @Test
+ public void testColumnMajorIteration() throws Exception {
+
+ // square table, both rows and columns defined:
+ TableModel model = new DynamicTableModel(2, 2, TableOrder.COLUMN_MAJOR);
+ RectF tableRect = new RectF(0, 0, 1000, 2000);
+ int createdCells = 4;
+ Iterator<RectF> it = model.getIterator(tableRect, createdCells);
+
+
+ // 2x2 fixed:
+ // cell 0 (top-left
+ RectF cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(0f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(500f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ // cell 1 (bottom-left)
+ cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(0f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(500f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+
+ // cell 2 (bottom-left)
+ cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(500f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(1000f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ // cell 3 (bottom-right)
+ cellRect = it.next();
+ assertEquals(500f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(500f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(1000f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+ }
+
+ @Test
+ public void testSingleRowIteration() throws Exception {
+ // square table, both rows and columns defined:
+ TableModel model = new DynamicTableModel(0, 1);
+ RectF tableRect = new RectF(0, 0, 1000, 1000);
+ int createdCells = 4;
+ Iterator<RectF> it = model.getIterator(tableRect, createdCells);
+
+
+
+ // 2x2 fixed:
+ // cell 0 (top-left
+ RectF cellRect = it.next();
+ assertEquals(250f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(0f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(250f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ // cell 1
+ cellRect = it.next();
+ assertEquals(250f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(250f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(500f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ // cell 2
+ cellRect = it.next();
+ assertEquals(250f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(500f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(750f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ // cell 3
+ cellRect = it.next();
+ assertEquals(250f, cellRect.width());
+ assertEquals(1000f, cellRect.height());
+ assertEquals(750f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(1000f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/ui/FixedTableModelTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/ui/FixedTableModelTest.java
new file mode 100644
index 0000000..42453b6
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/ui/FixedTableModelTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.ui;
+
+import android.graphics.RectF;
+import com.androidplot.mock.MockRectF;
+import com.androidplot.ui.FixedTableModel;
+import com.androidplot.ui.TableOrder;
+import mockit.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static junit.framework.Assert.*;
+
+public class FixedTableModelTest {
+ @Before
+ public void setUp() throws Exception {
+ Mockit.setUpMocks(MockRectF.class);
+ }
+
+ @Test
+ public void testConstructor() throws Exception {
+ FixedTableModel model = new FixedTableModel(100, 100, null);
+ // TODO
+ }
+
+ @Test
+ public void testIterator() throws Exception {
+ FixedTableModel model = new FixedTableModel(100, 100, TableOrder.COLUMN_MAJOR);
+
+ RectF tableRect = new RectF(0, 0, 1000, 2000);
+
+ Iterator<RectF> it = model.getIterator(tableRect, 5);
+
+ assertTrue(it.hasNext());
+ RectF cellRect = it.next();
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+
+ assertFalse(it.hasNext());
+ try {
+ cellRect = it.next();
+ fail("Expected IndexOutOfBoundsException");
+ } catch(IndexOutOfBoundsException ex) {
+ // this was expected
+ }
+ }
+
+ @Test
+ public void testColumnMajor() throws Exception {
+ FixedTableModel model = new FixedTableModel(300, 500, TableOrder.COLUMN_MAJOR);
+
+ RectF tableRect = new RectF(0, 0, 1000, 2000);
+
+ // test the numElement limit:
+ Iterator<RectF> it = model.getIterator(tableRect, 5);
+ assertTrue(it.hasNext());
+ RectF cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(300f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(600f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(600f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(900f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(500f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(300f, cellRect.left);
+ assertEquals(500f, cellRect.top);
+ assertEquals(600f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ assertFalse(it.hasNext());
+
+ // test border limit:
+ it = model.getIterator(tableRect, 25);
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(300f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(600f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(600f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(900f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(500f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(300f, cellRect.left);
+ assertEquals(500f, cellRect.top);
+ assertEquals(600f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(600f, cellRect.left);
+ assertEquals(500f, cellRect.top);
+ assertEquals(900f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(1500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(300f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(600f, cellRect.right);
+ assertEquals(1500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(600f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(900f, cellRect.right);
+ assertEquals(1500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(1500f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(300f, cellRect.left);
+ assertEquals(1500f, cellRect.top);
+ assertEquals(600f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(600f, cellRect.left);
+ assertEquals(1500f, cellRect.top);
+ assertEquals(900f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+
+ //we've reached the limit
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void testRowMajor() throws Exception {
+ FixedTableModel model = new FixedTableModel(300, 500, TableOrder.ROW_MAJOR);
+
+ RectF tableRect = new RectF(0, 0, 1000, 2000);
+
+ // test the numElement limit:
+ Iterator<RectF> it = model.getIterator(tableRect, 5);
+ assertTrue(it.hasNext());
+ RectF cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(500f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(1000f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(1000f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(1500f, cellRect.bottom);
+
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(0f, cellRect.left);
+ assertEquals(1500f, cellRect.top);
+ assertEquals(300f, cellRect.right);
+ assertEquals(2000f, cellRect.bottom);
+
+ // next column over
+ assertTrue(it.hasNext());
+ cellRect = it.next();
+ assertEquals(300f, cellRect.left);
+ assertEquals(0f, cellRect.top);
+ assertEquals(600f, cellRect.right);
+ assertEquals(500f, cellRect.bottom);
+ }
+} \ No newline at end of file
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/util/ConfiguratorTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/util/ConfiguratorTest.java
new file mode 100644
index 0000000..b5790e8
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/util/ConfiguratorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import android.content.Context;
+import android.util.Log;
+import com.androidplot.mock.MockContext;
+import mockit.Mockit;
+import mockit.UsingMocksAndStubs;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@UsingMocksAndStubs({Log.class})
+public class ConfiguratorTest {
+
+ class A {
+ int d = 0;
+
+ public int getD() {
+ return d;
+ }
+
+ public void setD(int d) {
+ this.d = d;
+ }
+ }
+
+ class B {
+ A a = new A();
+
+ public A getA() {
+ return a;
+ }
+
+ public void setA(A a) {
+ this.a = a;
+ }
+ }
+
+ class C {
+ B b = new B();
+
+ public B getB() {
+ return b;
+ }
+
+ public void setB(B a) {
+ this.b = b;
+ }
+ }
+
+ @org.junit.Before
+ public void setUp() throws Exception {
+
+ }
+
+ @org.junit.After
+ public void tearDown() throws Exception {
+
+ }
+
+
+ @Test
+ public void testGetFieldAt() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ C c = new C();
+ assertEquals(c, Configurator.getObjectContaining(c, "b"));
+ assertEquals(c.getB(), Configurator.getObjectContaining(c, "b.a"));
+ assertEquals(c.getB().getA(), Configurator.getObjectContaining(c, "b.a.d"));
+ }
+
+ @Test
+ public void testGetSetter() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ C c = new C();
+
+ Method m = Configurator.getSetter(c.getClass(), "b");
+ assertEquals(1, m.getParameterTypes().length);
+ assertEquals(B.class, m.getParameterTypes()[0]);
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/util/ListOrganizerTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/util/ListOrganizerTest.java
new file mode 100644
index 0000000..5f4e240
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/util/ListOrganizerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedList;
+
+import static junit.framework.Assert.assertEquals;
+
+public class ListOrganizerTest {
+ @Before
+ public void setUp() throws Exception {
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testMoveToTop() throws Exception {
+
+ }
+
+ @Test
+ public void testMoveAbove() throws Exception {
+
+ }
+
+ @Test
+ public void testMoveBeneath() throws Exception {
+
+ }
+
+ @Test
+ public void testMoveToBottom() throws Exception {
+ Object obj1 = new Object();
+ Object obj2 = new Object();
+ Object obj3 = new Object();
+ LinkedList list = new LinkedList();
+
+ list.add(obj1);
+ list.add(obj2);
+ list.add(obj3);
+
+ assertEquals(obj1, list.getFirst());
+ assertEquals(obj3, list.getLast());
+
+ ListOrganizer organizer = new ListOrganizer(list);
+
+ organizer.moveToBottom(obj3);
+
+ assertEquals(obj2, list.getLast());
+ assertEquals(obj3, list.getFirst());
+
+ }
+
+ @Test
+ public void testMoveUp() throws Exception {
+
+ }
+
+ @Test
+ public void testMoveDown() throws Exception {
+
+ }
+
+ @Test
+ public void testAddFirst() throws Exception {
+
+ }
+
+ @Test
+ public void testAddLast() throws Exception {
+
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/util/ValPixConverterTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/util/ValPixConverterTest.java
new file mode 100644
index 0000000..030e30c
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/util/ValPixConverterTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.util;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ValPixConverterTest {
+ @org.junit.Before
+ public void setUp() throws Exception {
+
+ }
+
+ @org.junit.After
+ public void tearDown() throws Exception {
+
+ }
+
+ /*
+ @org.junit.Test
+ public void testIndexToPix() throws Exception {
+ int sizeInPix = 100;
+ int itemCount = 10;
+ assertEquals(10.0f, ValPixConverter.indexToPix(1, itemCount, sizeInPix));
+
+ try {
+ ValPixConverter.indexToPix(100, 10, 100);
+ fail("IndexOutOfBoundsException expected.");
+ } catch(IndexOutOfBoundsException ex) {
+
+ }
+
+ }
+ */
+
+ @org.junit.Test
+ public void testValToPix() throws Exception {
+ int sizeInPix = 100;
+ int min = 0;
+ int max = 100;
+
+ int value = 50;
+
+ assertEquals(50.0f, ValPixConverter.valToPix(value, min, max, sizeInPix, true));
+
+ // should be closer to the top:
+ // (remember that 0,0 is the top left pixel)
+ value = 75;
+ assertEquals(25.0f, ValPixConverter.valToPix(value, min, max, sizeInPix, true));
+
+ // should be at the very top:
+ value = 100;
+ assertEquals(0.0f, ValPixConverter.valToPix(value, min, max, sizeInPix, true));
+
+ // should be at the very top:
+ value = 0;
+ assertEquals(100.0f, ValPixConverter.valToPix(value, min, max, sizeInPix, true));
+
+ // should be smack in the middle:
+ min = -100;
+ value = 0;
+ assertEquals(50.0f, ValPixConverter.valToPix(value, min, max, sizeInPix, true));
+
+ // should be at the very bottom:
+ value = 100;
+ assertEquals(0.0f, ValPixConverter.valToPix(value, min, max, sizeInPix, true));
+
+ // should be in the middle:
+ value = 0;
+ assertEquals(50.0f, ValPixConverter.valToPix(value, min, max, sizeInPix, true));
+
+ min = -100;
+ max = 100;
+ sizeInPix = 200;
+ assertEquals(0f, ValPixConverter.valToPix(-100, min, max, sizeInPix, false));
+ assertEquals(200f, ValPixConverter.valToPix(100, min, max, sizeInPix, false));
+ }
+
+ @org.junit.Test
+ public void testpixToVal() throws Exception {
+ int sizeInPix = 100;
+ int min = 0;
+ int max = 100;
+
+ double value = 50;
+
+ float pixel = ValPixConverter.valToPix(value, min, max, sizeInPix, true);
+ assertEquals(value, ValPixConverter.pixToVal(pixel, min, max, sizeInPix, true));
+
+ value = 75;
+ pixel = ValPixConverter.valToPix(value, min, max, sizeInPix, true);
+ assertEquals(value, ValPixConverter.pixToVal(pixel, min, max, sizeInPix, true));
+
+
+ min = -100;
+ value = 50;
+ pixel = ValPixConverter.valToPix(value, min, max, sizeInPix, true);
+ assertEquals(value, ValPixConverter.pixToVal(pixel, min, max, sizeInPix, true));
+
+ min = -100;
+ value = 60;
+ pixel = ValPixConverter.valToPix(value, min, max, sizeInPix, true);
+ assertEquals(value, ValPixConverter.pixToVal(pixel, min, max, sizeInPix, true));
+
+ min = 20;
+ value = 50;
+ pixel = ValPixConverter.valToPix(value, min, max, sizeInPix, true);
+ assertEquals(value, ValPixConverter.pixToVal(pixel, min, max, sizeInPix, true));
+
+ try {
+ ValPixConverter.pixToVal(-5, 0, 0, 0, true);
+ fail("IllegalArgumentException expected.");
+ } catch(IllegalArgumentException ex) {
+
+ }
+
+
+ }
+
+
+ @Test
+ public void testValPerPix() {
+ //double result = ;
+ assertEquals(1.0, ValPixConverter.valPerPix(0, 100, 100));
+ double expected = 200d/100;
+ assertEquals(expected, ValPixConverter.valPerPix(100, 300, 100));
+ expected = 50d/100;
+ assertEquals(expected, ValPixConverter.valPerPix(0, 50, 100));
+ expected = 200d/100;
+ assertEquals(expected, ValPixConverter.valPerPix(-100, 100, 100));
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/xy/RectRegionTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/xy/RectRegionTest.java
new file mode 100644
index 0000000..9ae3877
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/xy/RectRegionTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class RectRegionTest {
+ @Before
+ public void setUp() throws Exception {
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testContainsPoint() throws Exception {
+
+ }
+
+ @Test
+ public void testContainsValue() throws Exception {
+
+ }
+
+ @Test
+ public void testContainsDomainValue() throws Exception {
+
+ }
+
+ @Test
+ public void testContainsRangeValue() throws Exception {
+
+ }
+
+
+
+ @Test
+ public void testIsWithin() throws Exception {
+
+ RectRegion region1 = new RectRegion(0, 100, 0, 100, "");
+
+ RectRegion region2 = new RectRegion(5, 10, 5, 10, "");
+
+ assertTrue(region1.intersects(region2));
+ assertTrue(region2.intersects(region1));
+
+ RectRegion region3 = new RectRegion(101, 200, 101, 200, "");
+ assertFalse(region1.intersects(region3));
+ assertFalse(region3.intersects(region1));
+
+ RectRegion region4 = new RectRegion(99, 109, 99, 109, "");
+ assertTrue(region1.intersects(region4));
+ assertTrue(region4.intersects(region1));
+
+ // negative numbers:
+ RectRegion region5 = new RectRegion(-100, 1, -100, 1, "");
+ assertTrue(region1.intersects(region5));
+ assertTrue(region5.intersects(region1));
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java
new file mode 100644
index 0000000..3179f6d
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.util.Pair;
+import mockit.UsingMocksAndStubs;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static junit.framework.Assert.assertEquals;
+
+@UsingMocksAndStubs({Pair.class})
+
+public class SimpleXYSeriesTest {
+
+ @Test
+ public void testYValsOnlyConstructor() throws Exception {
+ Number[] yVals = {5, 6, 7, 8, 9};
+ SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(yVals), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "test");
+
+ assertEquals(yVals[0], series.getY(0));
+ assertEquals(yVals[1], series.getY(1));
+ assertEquals(yVals[2], series.getY(2));
+ assertEquals(yVals[3], series.getY(3));
+ assertEquals(yVals[4], series.getY(4));
+
+ assertEquals(0, series.getX(0));
+ assertEquals(1, series.getX(1));
+ assertEquals(2, series.getX(2));
+ assertEquals(3, series.getX(3));
+ assertEquals(4, series.getX(4));
+ }
+
+ @Test
+ public void testXYInterleavedConstructor() throws Exception {
+ Number[] yVals = {55, 5, 66, 6, 77, 7, 88, 8, 99, 9};
+ SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(yVals), SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED, "test");
+
+ assertEquals(5, series.getY(0));
+ assertEquals(6, series.getY(1));
+ assertEquals(7, series.getY(2));
+ assertEquals(8, series.getY(3));
+ assertEquals(9, series.getY(4));
+
+ assertEquals(55, series.getX(0));
+ assertEquals(66, series.getX(1));
+ assertEquals(77, series.getX(2));
+ assertEquals(88, series.getX(3));
+ assertEquals(99, series.getX(4));
+ }
+
+ @Test
+ public void testTwoListConstructor() throws Exception {
+ Number[] yVals = {5, 6, 7, 8, 9};
+ Number[] xVals = {1, 2, 3, 4, 5};
+ SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(xVals), Arrays.asList(yVals), "test");
+
+ assertEquals(5, series.getY(0));
+ assertEquals(6, series.getY(1));
+ assertEquals(7, series.getY(2));
+ assertEquals(8, series.getY(3));
+ assertEquals(9, series.getY(4));
+
+ assertEquals(1, series.getX(0));
+ assertEquals(2, series.getX(1));
+ assertEquals(3, series.getX(2));
+ assertEquals(4, series.getX(3));
+ assertEquals(5, series.getX(4));
+ }
+
+ @Test
+ public void testPushPopStuff() throws Exception {
+ Number[] yVals = {5, 6, 7, 8, 9};
+ Number[] xVals = {1, 2, 3, 4, 5};
+ SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(xVals), Arrays.asList(yVals), "test");
+
+ // chop off the tail:
+ series.removeLast();
+ assertEquals(8, series.getY(series.size()-1));
+ assertEquals(4, series.getX(series.size()-1));
+
+ // chop off the head:
+ series.removeFirst();
+ assertEquals(6, series.getY(0));
+ assertEquals(2, series.getX(0));
+
+ // add to the tail:
+ series.addLast(22, 33);
+ assertEquals(33, series.getY(series.size()-1));
+ assertEquals(22, series.getX(series.size()-1));
+
+ // add to the head:
+ series.addFirst(55, 66);
+ assertEquals(66, series.getY(0));
+ assertEquals(55, series.getX(0));
+ }
+
+ @Test
+ public void testSet() throws Exception {
+ Number[] yVals = {5, 6, 7, 8, 9};
+ Number[] xVals = {1, 2, 3, 4, 5};
+ SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(xVals), Arrays.asList(yVals), "test");
+
+ int size = series.size();
+
+ series.setX(22, 2);
+ assertEquals(22, series.getX(2));
+
+ // make sure size has not changed:
+ assertEquals(size, series.size());
+
+ series.setY(23, 2);
+ assertEquals(23, series.getY(2));
+
+ // make sure size has not changed:
+ assertEquals(size, series.size());
+ }
+
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java
new file mode 100644
index 0000000..35f091d
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import android.graphics.*;
+import android.os.Handler;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
+import com.androidplot.Plot;
+import com.androidplot.mock.*;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.ui.SizeMetric;
+import com.androidplot.ui.SizeMetrics;
+import com.androidplot.ui.widget.TextLabelWidget;
+import com.androidplot.ui.widget.Widget;
+import com.androidplot.util.FontUtils;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.util.ValPixConverter;
+import mockit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@UsingMocksAndStubs({Log.class,View.class,Handler.class,Paint.class,Color.class, Rect.class,
+ FontUtils.class, Paint.FontMetrics.class,Bitmap.class, Pair.class})
+
+public class XYLegendWidgetTest {
+
+ @MockClass(realClass = Context.class)
+ public static class MockContext {}
+
+ @MockClass(realClass = TextLabelWidget.class)
+ public static class MockTextLabelWidget {
+ @Mock
+ public void doOnDraw(Canvas canvas, RectF widgetRect) {}
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ Mockit.setUpMocks(MockCanvas.class, MockRectF.class, MockSizeMetrics.class,
+ MockPointF.class, MockLooper.class, MockPixelUtils.class, MockTextLabelWidget.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+
+ @Test
+ public void testDoOnDraw() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ XYPlot plot = new XYPlot(context, "Test", Plot.RenderMode.USE_MAIN_THREAD);
+
+ SimpleXYSeries s1 = new SimpleXYSeries((Arrays.asList(1, 2, 3)),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1");
+
+ plot.addSeries(s1, new LineAndPointFormatter(
+ Color.RED, Color.GREEN, Color.BLUE, (PointLabelFormatter)null));
+
+ assertEquals(1, plot.getSeriesSet().size());
+
+ Deencapsulation.invoke(plot, "onSizeChanged", 100, 100, 100, 100);
+ plot.redraw();
+ // have to manually invoke this because the invalidate()
+ // invoked by redraw() is a stub and will not result in onDraw being called.
+ Deencapsulation.invoke(plot, "onDraw", new Canvas());
+
+ plot.removeSeries(s1);
+ assertEquals(0, plot.getSeriesSet().size());
+ plot.addSeries(s1, new BarFormatter(Color.RED, Color.GREEN));
+ plot.redraw();
+
+ // throws NullPointerException before fix
+ // for ANDROIDPLOT-166 was applied.
+ Deencapsulation.invoke(plot, "onDraw", new Canvas());
+
+ }
+
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYPlotTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYPlotTest.java
new file mode 100644
index 0000000..04d5945
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYPlotTest.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import android.graphics.*;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import com.androidplot.Plot;
+import com.androidplot.PlotTest;
+import com.androidplot.mock.MockContext;
+import com.androidplot.mock.MockPaint;
+import com.androidplot.ui.widget.TextLabelWidget;
+import com.androidplot.util.Configurator;
+import com.androidplot.util.FontUtils;
+import com.androidplot.util.PixelUtils;
+import mockit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+
+@UsingMocksAndStubs({Log.class,View.class,Context.class,Handler.class,Paint.class,Color.class,
+ Rect.class, RectF.class,FontUtils.class, PixelUtils.class, Canvas.class})
+
+public class XYPlotTest {
+
+ XYPlot plot; // testing
+
+ List<Integer> numList1;
+ List<Integer> numList2;
+ SimpleXYSeries series1;
+
+ @Before
+ public void setUp() throws Exception {
+ Mockit.setUpMocks(MockPaint.class,MockContext.class);
+ new MockUp<View>() {
+ @Mock int getWidth() { return 100;}
+ @Mock int getHeight() { return 100;}
+
+ };
+
+ plot = new XYPlot(null, "test");
+ numList1 = Arrays.asList(0, 1, 3, 5, 10, 15, 25, 50, 75, 100); // 10 elements
+ numList2 = Arrays.asList(-100, 0, 1, 3, 5, 10, 15, 25, 50, 75, 100, 200); // 12 elements
+ series1 = new SimpleXYSeries(numList1, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testOriginFixedMode() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.centerOnDomainOrigin(5, 2, BoundaryMode.FIXED);
+ plot.calculateMinMaxVals();
+
+
+ assertEquals(3.0, plot.getCalculatedMinX());
+ assertEquals(7.0, plot.getCalculatedMaxX());
+ }
+
+ @Test
+ public void testOriginAutoMode() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.centerOnDomainOrigin(5);
+ plot.calculateMinMaxVals();
+ //plot.updateMinMaxVals();
+
+ assertEquals(10.0, plot.getCalculatedMaxX()); // symmetry is @ 10, not 9
+ assertEquals(0.0, plot.getCalculatedMinX());
+
+ plot.centerOnRangeOrigin(50);
+ plot.calculateMinMaxVals();
+
+ assertEquals(100.0, plot.getCalculatedMaxY());
+ assertEquals(0.0, plot.getCalculatedMinY());
+
+ }
+
+ @Test
+ public void testOriginGrowMode() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.centerOnDomainOrigin(5, null, BoundaryMode.GROW);
+ plot.calculateMinMaxVals();
+
+ assertEquals(0.0, plot.getCalculatedMinX());
+ assertEquals(10.0, plot.getCalculatedMaxX());
+
+ // introduce a larger domain set. boundaries should change
+ series1.setModel(numList2, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ assertEquals(-1.0, plot.getCalculatedMinX());
+ assertEquals(11.0, plot.getCalculatedMaxX());
+
+ // revert series model back to the previous set. boundaries should remain the same
+ series1.setModel(numList1, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ assertEquals(-1.0, plot.getCalculatedMinX());
+ assertEquals(11.0, plot.getCalculatedMaxX());
+ }
+
+ @Test
+ public void testOriginShrinkMode() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.centerOnDomainOrigin(5, null, BoundaryMode.SHRINNK);
+ plot.calculateMinMaxVals();
+
+ assertEquals(0.0, plot.getCalculatedMinX());
+ assertEquals(10.0, plot.getCalculatedMaxX());
+
+ // update with more extreme values...nothing should change in shrink mode:
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+
+ assertEquals(0.0, plot.getCalculatedMinX());
+ assertEquals(10.0, plot.getCalculatedMaxX());
+
+ }
+
+
+ // Ifor not sure about filling in test stubs just going to do my own stuff instead.
+ @Test
+ public void testsetDomainBoundaries() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.calculateMinMaxVals();
+
+ // default to auto so check them
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+
+ plot.setDomainBoundaries(2, BoundaryMode.FIXED, 8, BoundaryMode.FIXED);
+ plot.calculateMinMaxVals();
+
+ // fixed
+ assertEquals(2, plot.getCalculatedMinX());
+ assertEquals(8, plot.getCalculatedMaxX());
+
+ // back to auto
+ plot.setDomainBoundaries(2, BoundaryMode.AUTO, 8, BoundaryMode.AUTO);
+ plot.calculateMinMaxVals();
+
+ // check again
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+
+ // we are not testing MinY well with this dataset.
+ // try grow
+ plot.setDomainBoundaries(2, BoundaryMode.GROW, 8, BoundaryMode.GROW);
+ plot.calculateMinMaxVals();
+
+ // check inital
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+
+ // update with more extreme values...
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // after growing
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(11, plot.getCalculatedMaxX());
+
+ // back to previous
+ series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // should not of changed.
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(11, plot.getCalculatedMaxX());
+
+ // back to big
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+
+ plot.setDomainBoundaries(2, BoundaryMode.SHRINNK, 8, BoundaryMode.SHRINNK);
+ plot.calculateMinMaxVals();
+
+ // check inital
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(11, plot.getCalculatedMaxX());
+
+ // now small
+ series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // after shrinking
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+
+ // back to previous
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // should not of changed.
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+
+ // back to auto
+ plot.setDomainBoundaries(2, BoundaryMode.AUTO, 8, BoundaryMode.AUTO);
+ plot.calculateMinMaxVals();
+
+ // should of changed.
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(11, plot.getCalculatedMaxX());
+ }
+
+ @Test
+ public void testsetRangeBoundaries() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.calculateMinMaxVals();
+
+ // default to auto so check them
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+
+ plot.setRangeBoundaries(5, BoundaryMode.FIXED, 80, BoundaryMode.FIXED);
+ plot.calculateMinMaxVals();
+
+ // fixed
+ assertEquals(5, plot.getCalculatedMinY());
+ assertEquals(80, plot.getCalculatedMaxY());
+
+ // back to auto
+ plot.setRangeBoundaries(2, BoundaryMode.AUTO, 8, BoundaryMode.AUTO);
+ plot.calculateMinMaxVals();
+
+ // check again
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+
+ // try grow
+ plot.setRangeBoundaries(2, BoundaryMode.GROW, 8, BoundaryMode.GROW);
+ plot.calculateMinMaxVals();
+
+ // check inital
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+
+ // update with more extreme values...
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // after growing
+ assertEquals(-100, plot.getCalculatedMinY());
+ assertEquals(200, plot.getCalculatedMaxY());
+
+ // back to previous
+ series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // should not of changed.
+ assertEquals(-100, plot.getCalculatedMinY());
+ assertEquals(200, plot.getCalculatedMaxY());
+
+ // back to big
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+
+ plot.setRangeBoundaries(2, BoundaryMode.SHRINNK, 8, BoundaryMode.SHRINNK);
+ plot.calculateMinMaxVals();
+
+ // check inital
+ assertEquals(-100, plot.getCalculatedMinY());
+ assertEquals(200, plot.getCalculatedMaxY());
+
+ // now small
+ series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // after shrinking
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+
+ // back to previous
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // should not of changed.
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+
+ // back to auto
+ plot.setRangeBoundaries(2, BoundaryMode.AUTO, 8, BoundaryMode.AUTO);
+ plot.calculateMinMaxVals();
+
+ // should of changed.
+ assertEquals(-100, plot.getCalculatedMinY());
+ assertEquals(200, plot.getCalculatedMaxY());
+ }
+
+ @Test
+ public void testSetDomainRightMinMax() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.calculateMinMaxVals();
+
+ // default to auto so check them
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+
+ plot.setDomainRightMax(10);
+ plot.calculateMinMaxVals();
+
+ // same values.
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // on RightMax
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(10, plot.getCalculatedMaxX());
+
+ plot.setDomainRightMax(null);
+ plot.calculateMinMaxVals();
+
+ // back to full
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(11, plot.getCalculatedMaxX());
+
+ // now the RightMin
+ plot.setDomainRightMin(10);
+ plot.calculateMinMaxVals();
+
+ // still to full
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(11, plot.getCalculatedMaxX());
+
+ // small list
+ series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // on RightMin
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(10, plot.getCalculatedMaxX());
+
+ // now off again
+ plot.setDomainRightMin(null);
+ plot.calculateMinMaxVals();
+
+ // small values.
+ assertEquals(0, plot.getCalculatedMinX());
+ assertEquals(9, plot.getCalculatedMaxX());
+ }
+
+ @Test
+ public void testSetRangeTopBottomMinMax() throws Exception {
+ plot.addSeries(series1, new LineAndPointFormatter());
+ plot.calculateMinMaxVals();
+
+ // default to auto so check them
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+
+ plot.setRangeTopMax(110);
+ plot.setRangeBottomMin(-50);
+ plot.calculateMinMaxVals();
+
+ // same values.
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+
+ series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // on Limits
+ assertEquals(-50, plot.getCalculatedMinY());
+ assertEquals(110, plot.getCalculatedMaxY());
+
+ plot.setRangeTopMax(null);
+ plot.setRangeBottomMin(null);
+ plot.calculateMinMaxVals();
+
+ // back to full
+ assertEquals(-100, plot.getCalculatedMinY());
+ assertEquals(200, plot.getCalculatedMaxY());
+
+ // now the Min
+ plot.setRangeTopMin(150);
+ plot.setRangeBottomMax(-60);
+ plot.calculateMinMaxVals();
+
+ // still to full
+ assertEquals(-100, plot.getCalculatedMinY());
+ assertEquals(200, plot.getCalculatedMaxY());
+
+ // small list
+ series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+ plot.calculateMinMaxVals();
+
+ // on Limits
+ assertEquals(-60, plot.getCalculatedMinY());
+ assertEquals(150, plot.getCalculatedMaxY());
+
+ // now off again
+ plot.setRangeTopMin(null);
+ plot.setRangeBottomMax(null);
+ plot.calculateMinMaxVals();
+
+ // small values.
+ assertEquals(0, plot.getCalculatedMinY());
+ assertEquals(100, plot.getCalculatedMaxY());
+ }
+
+ @Test
+ public void testSetDomainUpperBoundary() throws Exception {
+
+ }
+
+ @Test
+ public void testSetDomainLowerBoundary() throws Exception {
+
+ }
+
+ @Test
+ public void testSetRangeUpperBoundary() throws Exception {
+
+ }
+
+ @Test
+ public void testSetRangeLowerBoundary() throws Exception {
+
+ }
+
+ @Test
+ public void testSetDomainOrigin() throws Exception {
+
+ }
+
+ @Test
+ public void testSetRangeOrigin() throws Exception {
+
+ }
+
+ @Test
+ public void testConfigure() throws Exception {
+ //Context context = Mockit.setUpMock(new MockContext());
+ Context context = new MockContext.MockContext2();
+ HashMap<String, String> params = new HashMap<String, String>();
+ String param1 = "this is a test.";
+ String param2 = Plot.RenderMode.USE_BACKGROUND_THREAD.toString();
+ String param3 = "#FF0000";
+ params.put("title", param1);
+ params.put("renderMode", param2);
+ params.put("backgroundPaint.color", param3);
+ params.put("graphWidget.domainLabelPaint.color", param3);
+
+ Configurator.configure(context, plot, params);
+ assertEquals(param1, plot.getTitle());
+ assertEquals(Plot.RenderMode.USE_BACKGROUND_THREAD, plot.getRenderMode());
+ assertEquals(Color.parseColor(param3), plot.getBackgroundPaint().getColor());
+ assertEquals(Color.parseColor(param3), plot.getGraphWidget().getDomainLabelPaint().getColor());
+ }
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYSeriesRendererTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYSeriesRendererTest.java
new file mode 100644
index 0000000..c412e08
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYSeriesRendererTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import android.content.Context;
+import android.graphics.*;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import com.androidplot.util.FontUtils;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.util.ValPixConverter;
+import mockit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+@UsingMocksAndStubs({Log.class,View.class,Handler.class,Paint.class,Color.class, Rect.class, RectF.class,
+ FontUtils.class, PixelUtils.class, Canvas.class})
+
+public class XYSeriesRendererTest {
+
+ @MockClass(realClass = Context.class)
+ public static class MockContext {}
+
+ @Before
+ public void setUp() throws Exception {
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+
+ @Test
+ public void testDataToGridCorrelation() throws Exception {
+ Context context = Mockit.setUpMock(new MockContext());
+ //RectF gridRect = Mockit.setUpMock(new MockRectF());
+ //Mockit.setUpMock(RectF.class, new MockRectF());
+ //@MockClass(realClass = RectF.class)
+ new MockUp<RectF>()
+ {
+ final float left = 5;
+ final float top = 5;
+ final float right = 104;
+ final float bottom = 104;
+
+ @Mock void $init() {}
+
+ @Mock float width() {return 100;}
+ @Mock float height() {return 100;}
+
+ };
+ RectF gridRect = new RectF();
+ XYPlot plot = new XYPlot(context, "Test");
+ plot.setDomainStepMode(XYStepMode.SUBDIVIDE);
+ plot.setDomainStepValue(10);
+ plot.setDomainBoundaries(0, 100, BoundaryMode.FIXED);
+ plot.setRangeBoundaries(0, 100, BoundaryMode.FIXED);
+ plot.calculateMinMaxVals();
+ XYStep domainStep = XYStepCalculator.getStep(plot, XYAxisType.DOMAIN, gridRect, plot.getCalculatedMinX().doubleValue(), plot.getCalculatedMaxX().doubleValue());
+
+ float width = gridRect.width();
+
+ int x = 0;
+ float val = ValPixConverter.valToPix(x, 0, 9, gridRect.width(), false) + (gridRect.left);
+
+ assertEquals(val, domainStep.getStepPix()*x);
+
+ x = 1;
+ val = ValPixConverter.valToPix(x, 0, 9, gridRect.width(), false) + (gridRect.left);
+
+ assertEquals(val, domainStep.getStepPix()*x);
+
+ x = 9;
+ val = ValPixConverter.valToPix(x, 0, 9, gridRect.width(), false) + (gridRect.left);
+
+ assertEquals(val, domainStep.getStepPix()*x);
+ }
+
+}
diff --git a/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYStepCalculatorTest.java b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYStepCalculatorTest.java
new file mode 100644
index 0000000..1f93977
--- /dev/null
+++ b/AndroidPlot-Core/src/test/java/com/androidplot/xy/XYStepCalculatorTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.xy;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+public class XYStepCalculatorTest {
+ @Before
+ public void setUp() throws Exception {
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void testGetStep() throws Exception {
+
+ }
+
+ @Test
+ public void testSubdivide() throws Exception {
+ int numSegments = 10;
+ float plotSize = 100;
+ double minVal = 0;
+ double maxVal = 100;
+ XYStep step = XYStepCalculator.getStep(XYStepMode.SUBDIVIDE, plotSize, numSegments, minVal, maxVal);
+
+ assertEquals(plotSize/(numSegments-1), step.getStepPix());
+ //assertEquals(10, step.getStepVal());
+
+ // make sure large values dont break anything:
+ minVal = 1000000000;
+ maxVal = 2000000000;
+ step = XYStepCalculator.getStep(XYStepMode.SUBDIVIDE, plotSize, numSegments, minVal, maxVal);
+ assertEquals(plotSize/(numSegments-1), step.getStepPix());
+
+ }
+
+ @Test
+ public void testIncrementByVal() throws Exception {
+
+ }
+
+ @Test
+ public void testIncrementByPixels() throws Exception {
+
+ }
+}
diff --git a/DynamicXYPlotExample.iml b/DynamicXYPlotExample.iml
new file mode 100644
index 0000000..cde54cf
--- /dev/null
+++ b/DynamicXYPlotExample.iml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="android" name="Android">
+ <configuration>
+ <option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/target/generated-sources/r" />
+ <option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/target/generated-sources/aidl" />
+ <option name="MANIFEST_FILE_RELATIVE_PATH" value="/Examples/DynamicXYPlotExample/AndroidManifest.xml" />
+ <option name="RES_FOLDER_RELATIVE_PATH" value="/Examples/DynamicXYPlotExample/res" />
+ <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/Examples/DynamicXYPlotExample/assets" />
+ <option name="LIBS_FOLDER_RELATIVE_PATH" value="/Examples/DynamicXYPlotExample/libs" />
+ <option name="REGENERATE_R_JAVA" value="true" />
+ <option name="REGENERATE_JAVA_BY_AIDL" value="true" />
+ <option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" />
+ <option name="CUSTOM_APK_RESOURCE_FOLDER" value="" />
+ <option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
+ <option name="CUSTOM_COMPILER_MANIFEST" value="" />
+ <option name="APK_PATH" value="/target/AndroidPlot.apk" />
+ <option name="LIBRARY_PROJECT" value="false" />
+ <option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="true" />
+ <option name="GENERATE_UNSIGNED_APK" value="false" />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <output url="file://$MODULE_DIR$/Examples/DynamicXYPlotExample" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
+
diff --git a/Examples/AndroidPlot-Examples.iml b/Examples/AndroidPlot-Examples.iml
new file mode 100644
index 0000000..0cfe50e
--- /dev/null
+++ b/Examples/AndroidPlot-Examples.iml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.0.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.0.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.3" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: xerces:xmlParserAPIs:2.6.2" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: xpp3:xpp3:1.1.4c" level="project" />
+ </component>
+</module>
+
diff --git a/Examples/DemoApp/.classpath b/Examples/DemoApp/.classpath
new file mode 100644
index 0000000..c64ee78
--- /dev/null
+++ b/Examples/DemoApp/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry kind="src" output="bin/classes" path="src"/>
+ <classpathentry kind="src" output="bin/classes" path="gen"/>
+ <classpathentry combineaccessrules="false" exported="true" kind="src" path="/AndroidPlot-Core"/>
+ <classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/Examples/DemoApp/.project b/Examples/DemoApp/.project
new file mode 100644
index 0000000..185fcd4
--- /dev/null
+++ b/Examples/DemoApp/.project
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>AndroidPlotDemo</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ </natures>
+</projectDescription>
diff --git a/Examples/DemoApp/AndroidManifest.xml b/Examples/DemoApp/AndroidManifest.xml
new file mode 100644
index 0000000..765336b
--- /dev/null
+++ b/Examples/DemoApp/AndroidManifest.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.androidplot.demos"
+ android:versionCode="8"
+ android:versionName="0.6.0.1">
+ <uses-sdk android:minSdkVersion="6" android:targetSdkVersion="16"/>
+
+ <!-- We must disable hardware acceleration otherwise some lines will not appear in our plots. -->
+ <application android:label="AndroidPlot API DemoApp"
+ android:icon="@drawable/icon">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".SimplePieChartActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".SimpleXYPlotActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".BarPlotExampleActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".DynamicXYPlotActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".OrientationSensorExampleActivity"
+ android:label="@string/app_name"
+ android:screenOrientation="landscape">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".StepChartExampleActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".TouchZoomExampleActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ListViewActivity" android:label="ListView Example"/>
+ <activity android:name=".XYRegionExampleActivity" android:label="XYRegion Example">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".TimeSeriesActivity"/>
+ <activity android:name=".DualScaleXYPlotExampleActivity"/>
+ <activity android:name=".XYPlotWithBgImgActivity"/>
+
+ <!-- receiver for demo app widget -->
+ <receiver
+ android:icon="@drawable/icon"
+ android:label="Example Widget"
+ android:name="com.androidplot.demos.widget.DemoAppWidgetProvider" >
+ <intent-filter >
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/demo_app_widget_provider_info" />
+ </receiver>
+ </application>
+</manifest>
diff --git a/Examples/DemoApp/DemoApp.iml b/Examples/DemoApp/DemoApp.iml
new file mode 100644
index 0000000..c4b2db2
--- /dev/null
+++ b/Examples/DemoApp/DemoApp.iml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="android" name="Android">
+ <configuration>
+ <option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="false" />
+ <includeSystemProguardFile>false</includeSystemProguardFile>
+ <includeAssetsFromLibraries>true</includeAssetsFromLibraries>
+ <resOverlayFolders />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/target/generated-sources/r" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/target/classes" />
+ <excludeFolder url="file://$MODULE_DIR$/target/generated-sources/combined-assets" />
+ <excludeFolder url="file://$MODULE_DIR$/target/generated-sources/combined-resources" />
+ <excludeFolder url="file://$MODULE_DIR$/target/generated-sources/extracted-dependencies" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.android:android:4.1.1.4" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.0.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.0.1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.3" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.khronos:opengl-api:gl1.1-android-2.1_r1" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: xerces:xmlParserAPIs:2.6.2" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: xpp3:xpp3:1.1.4c" level="project" />
+ <orderEntry type="library" scope="PROVIDED" name="Maven: org.json:json:20080701" level="project" />
+ <orderEntry type="module" module-name="androidplot-core" />
+ </component>
+</module>
+
diff --git a/Examples/DemoApp/ant.properties b/Examples/DemoApp/ant.properties
new file mode 100644
index 0000000..87c820a
--- /dev/null
+++ b/Examples/DemoApp/ant.properties
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2012 AndroidPlot.com. All rights reserved.
+#
+# Redistribution and use of source without modification and derived binaries with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY ANDROIDPLOT.COM ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANDROIDPLOT.COM OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and documentation are those of the
+# authors and should not be interpreted as representing official policies, either expressed
+# or implied, of AndroidPlot.com.
+#
+
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked into Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+# 'source.dir' for the location of your java source folder and
+# 'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+# 'key.store' for the location of your keystore and
+# 'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+jar.libs.dir=..\\..\\AndroidPlot-Core\\target\\obfuscated
+
diff --git a/Examples/DemoApp/build.xml b/Examples/DemoApp/build.xml
new file mode 100644
index 0000000..45987d1
--- /dev/null
+++ b/Examples/DemoApp/build.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="testapp" default="help">
+
+ <!-- The local.properties file is created and updated by the 'android' tool.
+ It contains the path to the SDK. It should *NOT* be checked into
+ Version Control Systems. -->
+ <property file="local.properties" />
+
+ <!-- The ant.properties file can be created by you. It is only edited by the
+ 'android' tool to add properties to it.
+ This is the place to change some Ant specific build properties.
+ Here are some properties you may want to change/update:
+
+ source.dir
+ The name of the source directory. Default is 'src'.
+ out.dir
+ The name of the output directory. Default is 'bin'.
+
+ For other overridable properties, look at the beginning of the rules
+ files in the SDK, at tools/ant/build.xml
+
+ Properties related to the SDK location or the project target should
+ be updated using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems.
+
+ -->
+ <property file="ant.properties" />
+
+ <!-- if sdk.dir was not set from one of the property file, then
+ get it from the ANDROID_HOME env var.
+ This must be done before we load project.properties since
+ the proguard config can use sdk.dir -->
+ <property environment="env" />
+ <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+ <isset property="env.ANDROID_HOME" />
+ </condition>
+
+ <!-- The project.properties file is created and updated by the 'android'
+ tool, as well as ADT.
+
+ This contains project specific properties such as project target, and library
+ dependencies. Lower level build properties are stored in ant.properties
+ (or in .classpath for Eclipse projects).
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems. -->
+ <loadproperties srcFile="project.properties" />
+
+ <!-- quick check on sdk.dir -->
+ <fail
+ message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+ unless="sdk.dir"
+ />
+
+ <!--
+ Import per project custom build rules if present at the root of the project.
+ This is the place to put custom intermediary targets such as:
+ -pre-build
+ -pre-compile
+ -post-compile (This is typically used for code obfuscation.
+ Compiled code location: ${out.classes.absolute.dir}
+ If this is not done in place, override ${out.dex.input.absolute.dir})
+ -post-package
+ -post-build
+ -pre-clean
+ -->
+ <import file="custom_rules.xml" optional="true" />
+
+ <!-- Import the actual build file.
+
+ To customize existing targets, there are two options:
+ - Customize only one target:
+ - copy/paste the target into this file, *before* the
+ <import> task.
+ - customize it to your needs.
+ - Customize the whole content of build.xml
+ - copy/paste the content of the rules files (minus the top node)
+ into this file, replacing the <import> task.
+ - customize to your needs.
+
+ ***********************
+ ****** IMPORTANT ******
+ ***********************
+ In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+ in order to avoid having your file be overridden by tools such as "android update project"
+ -->
+ <!-- version-tag: 1 -->
+ <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>
diff --git a/Examples/DemoApp/pom.xml b/Examples/DemoApp/pom.xml
new file mode 100644
index 0000000..3ce1b3f
--- /dev/null
+++ b/Examples/DemoApp/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.androidplot</groupId>
+ <artifactId>androidplot</artifactId>
+ <version>0.6.0</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+
+ <groupId>com.androidplot</groupId>
+ <artifactId>androidplot-demoapp</artifactId>
+ <packaging>apk</packaging>
+ <name>AndroidPlot-DemoApp</name>
+ <!--<version>1.0-SNAPSHOT</version>-->
+
+ <dependencies>
+ <!--<dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <version>4.1.1.4</version>
+ <scope>provided</scope>
+ </dependency>-->
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.androidplot</groupId>
+ <artifactId>androidplot-core</artifactId>
+ <version>0.6.0</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <sourceDirectory>src</sourceDirectory>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <version>3.6.0</version>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <configuration>
+ <sdk>
+ <!-- platform or api level (api level 4 = platform 1.6)-->
+ <platform>16</platform>
+ </sdk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project> \ No newline at end of file
diff --git a/Examples/DemoApp/proguard-project.txt b/Examples/DemoApp/proguard-project.txt
new file mode 100644
index 0000000..2c0da18
--- /dev/null
+++ b/Examples/DemoApp/proguard-project.txt
@@ -0,0 +1,26 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# remove all debug and verbose level logging statements:
+-assumenosideeffects class android.util.Log {
+ public static *** d(...);
+ public static *** v(...);
+}
diff --git a/Examples/DemoApp/project.properties b/Examples/DemoApp/project.properties
new file mode 100644
index 0000000..29656f2
--- /dev/null
+++ b/Examples/DemoApp/project.properties
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2012 AndroidPlot.com. All rights reserved.
+#
+# Redistribution and use of source without modification and derived binaries with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY ANDROIDPLOT.COM ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANDROIDPLOT.COM OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and documentation are those of the
+# authors and should not be interpreted as representing official policies, either expressed
+# or implied, of AndroidPlot.com.
+#
+
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt
+# using config with optimization enabled so that things like log statement removal will work:
+proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt
+# Project target.
+target=android-16
diff --git a/Examples/DemoApp/res/drawable/graph_background.png b/Examples/DemoApp/res/drawable/graph_background.png
new file mode 100755
index 0000000..47523c2
--- /dev/null
+++ b/Examples/DemoApp/res/drawable/graph_background.png
Binary files differ
diff --git a/Examples/DemoApp/res/drawable/icon.png b/Examples/DemoApp/res/drawable/icon.png
new file mode 100644
index 0000000..6557d3a
--- /dev/null
+++ b/Examples/DemoApp/res/drawable/icon.png
Binary files differ
diff --git a/Examples/DemoApp/res/layout/bar_plot_example.xml b/Examples/DemoApp/res/layout/bar_plot_example.xml
new file mode 100644
index 0000000..76ac666
--- /dev/null
+++ b/Examples/DemoApp/res/layout/bar_plot_example.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity"
+ android:orientation="vertical">
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/mySimpleXYPlot"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidPlot.title="Growth"
+ androidPlot.domainLabel="Month"
+ androidPlot.rangeLabel="Revenue (millions)"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="15dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.gridBackgroundPaint.color="#000000"
+ androidPlot.graphWidget.domainGridLinePaint.alpha="0"
+ androidPlot.graphWidget.domainOriginLinePaint.alpha="0"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.heightMetric.value="25dp"
+ androidPlot.legendWidget.positionMetrics.anchor="right_bottom"
+ android:layout_weight="1"
+ androidPlot.graphWidget.gridLinePaint.color="#000000"/>
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+ <Spinner
+ android:id="@+id/spRenderStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <Spinner
+ android:id="@+id/spSeriesSize"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+ <Spinner
+ android:id="@+id/spWidthStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <RelativeLayout
+ android:id="@+id/sectionGraph"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <SeekBar
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:max="300"
+ android:progress="10"
+ android:id="@+id/sbFixed"/>
+ <SeekBar
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:max="50"
+ android:progress="1"
+ android:id="@+id/sbVariable"/>
+ </RelativeLayout>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+ <CheckBox android:id="@+id/s1CheckBox"
+ android:text="Series 1"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/s2CheckBox"
+ android:text="Series 2"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/demo_app_widget.xml b/Examples/DemoApp/res/layout/demo_app_widget.xml
new file mode 100644
index 0000000..817e0a4
--- /dev/null
+++ b/Examples/DemoApp/res/layout/demo_app_widget.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity"
+ android:orientation="vertical">
+
+ <ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/imgView"
+ android:background="@android:drawable/alert_dark_frame"
+ android:scaleType="centerInside"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ </ImageView>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/dual_scale_xy_plot_example.xml b/Examples/DemoApp/res/layout/dual_scale_xy_plot_example.xml
new file mode 100644
index 0000000..f21eac2
--- /dev/null
+++ b/Examples/DemoApp/res/layout/dual_scale_xy_plot_example.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:id="@+id/sectionGraph"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginTop="0px"
+ android:layout_marginBottom="0px"
+ android:layout_marginLeft="0px"
+ android:layout_marginRight="0px">
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/mySimpleXYPlot_L"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidPlot.title="A Simple XY Plot"
+ androidPlot.graphWidget.gridLinePaint.color="#000000"/>
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/mySimpleXYPlot_R"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidPlot.title="A Simple XY Plot"
+ androidPlot.graphWidget.gridLinePaint.color="#000000"/>
+
+ </RelativeLayout>
+
+ <Button android:id="@+id/toggleSeries2"
+ style="@style/toc_button"
+ android:text="Toggle position of Series2"
+ android:enabled="true"/>
+
+</LinearLayout>
diff --git a/Examples/DemoApp/res/layout/dynamicxyplot_example.xml b/Examples/DemoApp/res/layout/dynamicxyplot_example.xml
new file mode 100644
index 0000000..0276fce
--- /dev/null
+++ b/Examples/DemoApp/res/layout/dynamicxyplot_example.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity">
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/dynamicXYPlot"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidplot.renderMode="use_background_thread"
+ androidPlot.title="A Dynamic XY Plot"
+ androidPlot.domainLabel="Domain"
+ androidPlot.rangeLabel="Range"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="15dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.heightMetric.value="25dp"
+ androidPlot.legendWidget.positionMetrics.anchor="right_bottom"/>
+
+</LinearLayout>
diff --git a/Examples/DemoApp/res/layout/listview_example.xml b/Examples/DemoApp/res/layout/listview_example.xml
new file mode 100644
index 0000000..5ba50bc
--- /dev/null
+++ b/Examples/DemoApp/res/layout/listview_example.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout android:id="@+id/LinearLayout01"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ListView android:id="@+id/listView1"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/listview_example_item.xml b/Examples/DemoApp/res/layout/listview_example_item.xml
new file mode 100644
index 0000000..d1aa023
--- /dev/null
+++ b/Examples/DemoApp/res/layout/listview_example_item.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/xyplot"
+ android:layout_width="fill_parent"
+ android:layout_height="250dp"
+ title="an xy plot"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/main.xml b/Examples/DemoApp/res/layout/main.xml
new file mode 100644
index 0000000..779b2f3
--- /dev/null
+++ b/Examples/DemoApp/res/layout/main.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ScrollView android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <Button android:id="@+id/startSimplePieExButton"
+ style="@style/toc_button"
+ android:text="A Pie Chart"/>
+ <Button android:id="@+id/startSimpleXYExButton"
+ style="@style/toc_button"
+ android:text="A Simple XY Plot"/>
+ <Button android:id="@+id/startDynamicXYExButton"
+ style="@style/toc_button"
+ android:text="A Dynamic XY Plot"/>
+ <Button android:id="@+id/startOrSensorExButton"
+ style="@style/toc_button"
+ android:text="Realtime Orientation Sensor Plot"/>
+ <Button android:id="@+id/startTimeSeriesExButton"
+ style="@style/toc_button"
+ android:text="@string/ts_title"/>
+ <Button android:id="@+id/startStepChartExButton"
+ style="@style/toc_button"
+ android:text="Step Chart"
+ />
+ <Button android:id="@+id/startScrollZoomButton"
+ style="@style/toc_button"
+ android:text="Scroll and Zoom"
+ />
+ <Button android:id="@+id/startBarPlotExButton"
+ style="@style/toc_button"
+ android:text="Bar Plot"/>
+ <Button android:id="@+id/startXyScatterExButton"
+ style="@style/toc_button"
+ android:text="XY Scatter"
+ android:enabled="false"/>
+ <Button android:id="@+id/startXyScatterLineExButton"
+ style="@style/toc_button"
+ android:text="XY Scatter With Lines"
+ android:enabled="false"/>
+ <Button android:id="@+id/startXyRegionExampleButton"
+ style="@style/toc_button"
+ android:text="XYRegion Example"
+ android:enabled="true"/>
+ <Button android:id="@+id/startXyListViewExButton"
+ style="@style/toc_button"
+ android:text="ListView of XYPlots"
+ android:enabled="true"/>
+ <Button android:id="@+id/startDualScaleExampleButton"
+ style="@style/toc_button"
+ android:text="Dual Scale Example"
+ android:enabled="true"/>
+ <Button android:id="@+id/startXYPlotWithBgImgExample"
+ style="@style/toc_button"
+ android:text="XY Background Example"
+ android:enabled="true"/>
+ </LinearLayout>
+ </ScrollView>
+ <TextView android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="web"
+ android:gravity="center"
+ android:text="http://androidplot.com"/>
+</LinearLayout>
+
diff --git a/Examples/DemoApp/res/layout/orientation_sensor_example.xml b/Examples/DemoApp/res/layout/orientation_sensor_example.xml
new file mode 100644
index 0000000..85fa45f
--- /dev/null
+++ b/Examples/DemoApp/res/layout/orientation_sensor_example.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <LinearLayout android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_weight="1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/aprLevelsPlot"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_margin="0dp"
+ android:layout_weight="3"
+ android:layout_marginTop="10px"
+ android:layout_marginLeft="10px"
+ android:layout_marginRight="10px"
+ androidPlot.backgroundPaint.color="#000000"
+ androidPlot.borderPaint.color="#000000"
+ androidplot.renderMode="use_background_thread"
+ androidPlot.title="Levels"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.backgroundPaint.color="#000000"
+ androidPlot.graphWidget.gridBackgroundPaint.color="#000000"
+ androidPlot.graphWidget.domainGridLinePaint.color="#00000000"
+ androidPlot.graphWidget.domainOriginLinePaint.color="#00000000"
+ androidPlot.graphWidget.domainOriginLabelPaint.color="#00000000"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="15dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="10dp"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.heightMetric.value="25dp"
+ />
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/aprHistoryPlot"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_margin="0dp"
+ android:layout_weight="1"
+ android:layout_marginTop="10px"
+ android:layout_marginLeft="10px"
+ android:layout_marginRight="10px"
+ androidPlot.backgroundPaint.color="#000000"
+ androidPlot.borderPaint.color="#000000"
+ androidplot.renderMode="use_background_thread"
+ androidPlot.title="History"
+ androidPlot.domainLabel="Domain"
+ androidPlot.rangeLabel="Range"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.backgroundPaint.color="#000000"
+ androidPlot.graphWidget.gridBackgroundPaint.color="#000000"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="15dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.heightMetric.value="25dp"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <CheckBox android:id="@+id/hwAccelerationCb"
+ android:visibility="gone"
+ android:text="HW Acceleration"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <CheckBox android:id="@+id/showFpsCb"
+ android:text="Show FPS"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/pie_chart.xml b/Examples/DemoApp/res/layout/pie_chart.xml
new file mode 100644
index 0000000..010bcfc
--- /dev/null
+++ b/Examples/DemoApp/res/layout/pie_chart.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity"
+ android:orientation="vertical">
+
+ <com.androidplot.pie.PieChart
+ android:id="@+id/mySimplePieChart"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ androidPlot.title="A Simple Pie Chart"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"/>
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center"
+ android:text="Donut Size"
+ android:id="@+id/donutSizeSeekLabel"/>
+ <SeekBar
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:max="90"
+ android:progress="50"
+ android:id="@+id/donutSizeSeekBar"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center"
+ android:text="unknown"
+ android:id="@+id/donutSizeTextView"/>
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/simple_xy_plot_example.xml b/Examples/DemoApp/res/layout/simple_xy_plot_example.xml
new file mode 100644
index 0000000..fe54c57
--- /dev/null
+++ b/Examples/DemoApp/res/layout/simple_xy_plot_example.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity">
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/mySimpleXYPlot"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidPlot.title="A Simple XY Plot"
+ androidPlot.domainLabel="Domain"
+ androidPlot.rangeLabel="Range"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="15dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.heightMetric.value="25dp"
+ androidPlot.legendWidget.positionMetrics.anchor="right_bottom"
+ androidPlot.graphWidget.gridLinePaint.color="#000000"/>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/step_chart_example.xml b/Examples/DemoApp/res/layout/step_chart_example.xml
new file mode 100644
index 0000000..fadc79b
--- /dev/null
+++ b/Examples/DemoApp/res/layout/step_chart_example.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ style="@style/sample_activity"
+ >
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/stepChartExamplePlot"
+ style="@style/plotStyle1"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidplot.title="HTTP Server State (15 Sec)"
+ androidplot.domainLabel="time (secs)"
+ androidplot.rangeLabel="server state"
+ androidplot.markupEnabled="false"
+ renderMode="use_main_thread"
+ />
+</LinearLayout>
+
diff --git a/Examples/DemoApp/res/layout/time_series_example.xml b/Examples/DemoApp/res/layout/time_series_example.xml
new file mode 100644
index 0000000..847b381
--- /dev/null
+++ b/Examples/DemoApp/res/layout/time_series_example.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity"
+ android:orientation="vertical">
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/plot1"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ title="@string/ts_plot1_title"
+ renderMode="use_main_thread"/>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/touch_zoom_example.xml b/Examples/DemoApp/res/layout/touch_zoom_example.xml
new file mode 100644
index 0000000..71d0e33
--- /dev/null
+++ b/Examples/DemoApp/res/layout/touch_zoom_example.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/sample_activity"
+ android:orientation="vertical">
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/mySimpleXYPlot"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidPlot.title="ScrollZoom Example"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="15dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.widthMetric.value="1"
+ androidPlot.legendWidget.heightMetric.value="25dp"
+ androidPlot.legendWidget.positionMetrics.xPositionMetric.value="0"
+ androidPlot.legendWidget.positionMetrics.anchor="right_bottom"
+ android:layout_weight="1"/>
+
+ <Button android:id="@+id/resetButton"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Reset"/>
+</LinearLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/xy_plot_with_bq_img_example.xml b/Examples/DemoApp/res/layout/xy_plot_with_bq_img_example.xml
new file mode 100644
index 0000000..04e4315
--- /dev/null
+++ b/Examples/DemoApp/res/layout/xy_plot_with_bq_img_example.xml
@@ -0,0 +1,48 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".GraphMetricsActivity" >
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/graph_metrics"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true"
+ androidPlot.title="BG Image Example"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="15dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.heightMetric.value="25dp"
+ androidPlot.legendWidget.positionMetrics.xPositionMetric.value="0"
+ androidPlot.legendWidget.positionMetrics.anchor="right_bottom"
+ android:layout_centerVertical="true"/>
+
+ <ToggleButton
+ android:id="@+id/style_toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@id/graph_metrics"
+ android:layout_alignRight="@id/graph_metrics"
+ android:layout_marginTop="15dp"
+ android:layout_marginRight="15dp"
+ android:textOn="bg on"
+ android:textOff="bg off"
+ android:onClick="onGraphStyleToggle" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/Examples/DemoApp/res/layout/xyregion_example.xml b/Examples/DemoApp/res/layout/xyregion_example.xml
new file mode 100644
index 0000000..db0bbf8
--- /dev/null
+++ b/Examples/DemoApp/res/layout/xyregion_example.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+
+ <com.androidplot.xy.XYPlot
+ android:id="@+id/xyRegionExamplePlot"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ androidPlot.title="Batting Practice Stats"
+ androidPlot.domainLabel="Pitch #"
+ androidPlot.rangeLabel="Distance"
+ androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
+ androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
+ androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
+ androidPlot.graphWidget.marginTop="20dp"
+ androidPlot.graphWidget.marginLeft="35dp"
+ androidPlot.graphWidget.marginBottom="25dp"
+ androidPlot.graphWidget.marginRight="10dp"
+ androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
+ androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
+ androidPlot.legendWidget.textPaint.textSize="12dp"
+ androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
+ androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
+ androidPlot.legendWidget.heightMetric.value="40dp"
+ androidPlot.legendWidget.widthMetric.value="40dp"
+ android:layout_weight="1"
+ />
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+ <CheckBox android:id="@+id/s1CheckBox"
+ android:text="Tim"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/s2CheckBox"
+ android:text="Nick"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/s3CheckBox"
+ android:text="Joe"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/s4CheckBox"
+ android:text="James"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="horizontal">
+ <CheckBox android:id="@+id/r1CheckBox"
+ android:text="R1"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/r2CheckBox"
+ android:text="R2"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/r3CheckBox"
+ android:text="R3"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/r4CheckBox"
+ android:text="R4"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ <CheckBox android:id="@+id/r5CheckBox"
+ android:text="R5"
+ android:checked="true"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"/>
+ </LinearLayout>
+
+</LinearLayout>
+
diff --git a/Examples/DemoApp/res/values-hdpi/dimens.xml b/Examples/DemoApp/res/values-hdpi/dimens.xml
new file mode 100644
index 0000000..79fac2c
--- /dev/null
+++ b/Examples/DemoApp/res/values-hdpi/dimens.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- HDPI DIMENSIONS -->
+<resources>
+ <dimen name="title_font_size">30dp</dimen>
+ <dimen name="pie_segment_label_font_size">20dp</dimen>
+</resources> \ No newline at end of file
diff --git a/Examples/DemoApp/res/values-ldpi/dimens.xml b/Examples/DemoApp/res/values-ldpi/dimens.xml
new file mode 100644
index 0000000..81033f3
--- /dev/null
+++ b/Examples/DemoApp/res/values-ldpi/dimens.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- LDPI DIMENSIONS -->
+<resources>
+</resources> \ No newline at end of file
diff --git a/Examples/DemoApp/res/values/attrs.xml b/Examples/DemoApp/res/values/attrs.xml
new file mode 100644
index 0000000..b88cddf
--- /dev/null
+++ b/Examples/DemoApp/res/values/attrs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <declare-styleable name="XYPlot">
+ <attr name="androidplot.title" format="string"/>
+ </declare-styleable>
+</resources> \ No newline at end of file
diff --git a/Examples/DemoApp/res/values/dimens.xml b/Examples/DemoApp/res/values/dimens.xml
new file mode 100644
index 0000000..379e32b
--- /dev/null
+++ b/Examples/DemoApp/res/values/dimens.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- DEFAULT DIMENSIONS -->
+<resources>
+
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="pie_segment_label_font_size">10dp</dimen>
+ <dimen name="title_font_size">20dp</dimen>
+ <dimen name="domain_label_font_size">13dp</dimen>
+ <dimen name="range_label_font_size">13dp</dimen>
+ <dimen name="range_tick_label_font_size">15dp</dimen>
+ <dimen name="domain_tick_label_font_size">15dp</dimen>
+ <dimen name="legend_text_font_size">20dp</dimen>
+</resources> \ No newline at end of file
diff --git a/Examples/DemoApp/res/values/strings.xml b/Examples/DemoApp/res/values/strings.xml
new file mode 100644
index 0000000..0bde0cd
--- /dev/null
+++ b/Examples/DemoApp/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">AndroidPlot Demo</string>
+
+ <string name="sxy_title">A Simple XY Plot</string>
+
+ <string name="ts_title">Time Series</string>
+ <string name="ts_plot1_title">Yearly UFO Sightings</string>
+</resources>
diff --git a/Examples/DemoApp/res/values/style.xml b/Examples/DemoApp/res/values/style.xml
new file mode 100644
index 0000000..d465715
--- /dev/null
+++ b/Examples/DemoApp/res/values/style.xml
@@ -0,0 +1,34 @@
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns="http://schemas.android.com/apk/lib/com.androidplot.xy">
+ <style name="sample_activity">
+ <item name="android:layout_width">fill_parent</item>
+ <item name="android:layout_height">fill_parent</item>
+ </style>
+
+ <style name="toc_button">
+ <item name="android:layout_width">fill_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ </style>
+
+ <style name="plotStyle1">
+ <!--<item name="androidplot.title">Default Title</item>
+ <item name="androidplot.domainLabel">my domain</item>-->
+ <!--<item name="XYPlot:androidplot.title">my domain</item>-->
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/Examples/DemoApp/res/xml/demo_app_widget_provider_info.xml b/Examples/DemoApp/res/xml/demo_app_widget_provider_info.xml
new file mode 100644
index 0000000..771c246
--- /dev/null
+++ b/Examples/DemoApp/res/xml/demo_app_widget_provider_info.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="294dp"
+ android:minHeight="72dp"
+ android:updatePeriodMillis="86400000"
+ android:previewImage="@drawable/icon"
+ android:initialLayout="@layout/demo_app_widget"
+ android:resizeMode="horizontal|vertical">
+</appwidget-provider> \ No newline at end of file
diff --git a/Examples/DemoApp/res/xml/line_point_formatter_with_plf1.xml b/Examples/DemoApp/res/xml/line_point_formatter_with_plf1.xml
new file mode 100644
index 0000000..47bf5f5
--- /dev/null
+++ b/Examples/DemoApp/res/xml/line_point_formatter_with_plf1.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<config
+ linePaint.strokeWidth="3dp"
+ linePaint.color="#00AA00"
+ vertexPaint.color="#007700"
+ fillPaint.color="#00000000"
+ pointLabelFormatter.textPaint.color="#FFFFFF"/> \ No newline at end of file
diff --git a/Examples/DemoApp/res/xml/line_point_formatter_with_plf2.xml b/Examples/DemoApp/res/xml/line_point_formatter_with_plf2.xml
new file mode 100644
index 0000000..31894cd
--- /dev/null
+++ b/Examples/DemoApp/res/xml/line_point_formatter_with_plf2.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<config
+ linePaint.strokeWidth="3dp"
+ linePaint.color="#0000AA"
+ vertexPaint.color="#000077"
+ fillPaint.color="#00000000"
+ pointLabelFormatter.textPaint.color="#FFFFFF"/> \ No newline at end of file
diff --git a/Examples/DemoApp/res/xml/pie_segment_formatter1.xml b/Examples/DemoApp/res/xml/pie_segment_formatter1.xml
new file mode 100644
index 0000000..1b0c7e0
--- /dev/null
+++ b/Examples/DemoApp/res/xml/pie_segment_formatter1.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<config
+ fillPaint.color="#FF0000"
+ labelPaint.textSize="@dimen/pie_segment_label_font_size"/> \ No newline at end of file
diff --git a/Examples/DemoApp/res/xml/pie_segment_formatter2.xml b/Examples/DemoApp/res/xml/pie_segment_formatter2.xml
new file mode 100644
index 0000000..ad7a225
--- /dev/null
+++ b/Examples/DemoApp/res/xml/pie_segment_formatter2.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<config
+ fillPaint.color="#00FF00"
+ labelPaint.textSize="@dimen/pie_segment_label_font_size"/> \ No newline at end of file
diff --git a/Examples/DemoApp/res/xml/pie_segment_formatter3.xml b/Examples/DemoApp/res/xml/pie_segment_formatter3.xml
new file mode 100644
index 0000000..5e02a39
--- /dev/null
+++ b/Examples/DemoApp/res/xml/pie_segment_formatter3.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<config
+ fillPaint.color="#0000FF"
+ labelPaint.textSize="@dimen/pie_segment_label_font_size"/> \ No newline at end of file
diff --git a/Examples/DemoApp/res/xml/pie_segment_formatter4.xml b/Examples/DemoApp/res/xml/pie_segment_formatter4.xml
new file mode 100644
index 0000000..2cf1171
--- /dev/null
+++ b/Examples/DemoApp/res/xml/pie_segment_formatter4.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<config
+ fillPaint.color="#FF00FF"
+ labelPaint.textSize="@dimen/pie_segment_label_font_size"/> \ No newline at end of file
diff --git a/Examples/DemoApp/src/com/androidplot/demos/BarPlotExampleActivity.java b/Examples/DemoApp/src/com/androidplot/demos/BarPlotExampleActivity.java
new file mode 100644
index 0000000..4c79dd8
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/BarPlotExampleActivity.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import java.text.DateFormatSymbols;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+
+import com.androidplot.LineRegion;
+import com.androidplot.ui.AnchorPosition;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.ui.SizeLayoutType;
+import com.androidplot.ui.SizeMetrics;
+import com.androidplot.ui.TextOrientationType;
+import com.androidplot.ui.widget.TextLabelWidget;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.xy.*;
+import com.androidplot.ui.XLayoutStyle;
+import com.androidplot.ui.YLayoutStyle;
+
+/**
+ * The simplest possible example of using AndroidPlot to plot some data.
+ */
+public class BarPlotExampleActivity extends Activity
+{
+
+ private static final String NO_SELECTION_TXT = "Touch bar to select.";
+ private XYPlot plot;
+
+ private CheckBox series1CheckBox;
+ private CheckBox series2CheckBox;
+ private Spinner spRenderStyle, spWidthStyle, spSeriesSize;
+ private SeekBar sbFixedWidth, sbVariableWidth;
+
+ private XYSeries series1;
+ private XYSeries series2;
+ private enum SeriesSize {
+ TEN,
+ TWENTY,
+ SIXTY
+ }
+
+ // Create a couple arrays of y-values to plot:
+ Number[] series1Numbers10 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5};
+ Number[] series2Numbers10 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4};
+ Number[] series1Numbers20 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3};
+ Number[] series2Numbers20 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9};
+ Number[] series1Numbers60 = {2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3, 2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3, 2, null, 5, 2, 7, 4, 3, 7, 4, 5, 7, 4, 5, 8, 5, 3, 6, 3, 9, 3};
+ Number[] series2Numbers60 = {4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9, 4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9, 4, 6, 3, null, 2, 0, 7, 4, 5, 4, 9, 6, 2, 8, 4, 0, 7, 4, 7, 9};
+ Number[] series1Numbers = series1Numbers10;
+ Number[] series2Numbers = series2Numbers10;
+
+ private MyBarFormatter formatter1 =
+ new MyBarFormatter(Color.argb(200, 100, 150, 100), Color.LTGRAY);
+
+ private MyBarFormatter formatter2 =
+ new MyBarFormatter(Color.argb(200, 100, 100, 150), Color.LTGRAY);
+
+ private MyBarFormatter selectionFormatter =
+ new MyBarFormatter(Color.YELLOW, Color.WHITE);
+
+ private TextLabelWidget selectionWidget;
+
+ private Pair<Integer, XYSeries> selection;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.bar_plot_example);
+
+ // initialize our XYPlot reference:
+ plot = (XYPlot) findViewById(R.id.mySimpleXYPlot);
+
+ selectionWidget = new TextLabelWidget(plot.getLayoutManager(), NO_SELECTION_TXT,
+ new SizeMetrics(
+ PixelUtils.dpToPix(100), SizeLayoutType.ABSOLUTE,
+ PixelUtils.dpToPix(100), SizeLayoutType.ABSOLUTE),
+ TextOrientationType.HORIZONTAL);
+
+ selectionWidget.getLabelPaint().setTextSize(PixelUtils.dpToPix(16));
+
+ // add a dark, semi-transparent background to the selection label widget:
+ Paint p = new Paint();
+ p.setARGB(100, 0, 0, 0);
+ selectionWidget.setBackgroundPaint(p);
+
+ selectionWidget.position(
+ 0, XLayoutStyle.RELATIVE_TO_CENTER,
+ PixelUtils.dpToPix(45), YLayoutStyle.ABSOLUTE_FROM_TOP,
+ AnchorPosition.TOP_MIDDLE);
+ selectionWidget.pack();
+
+
+ // reduce the number of range labels
+ plot.setTicksPerRangeLabel(3);
+ plot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
+ plot.getGraphWidget().setGridPadding(30, 10, 30, 0);
+
+ plot.setTicksPerDomainLabel(2);
+
+
+ // setup checkbox listers:
+ series1CheckBox = (CheckBox) findViewById(R.id.s1CheckBox);
+ series1CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onS1CheckBoxClicked(b);
+ }
+ });
+
+ series2CheckBox = (CheckBox) findViewById(R.id.s2CheckBox);
+ series2CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {onS2CheckBoxClicked(b);
+ }
+ });
+
+ plot.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ if(motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+ onPlotClicked(new PointF(motionEvent.getX(), motionEvent.getY()));
+ }
+ return true;
+ }
+ });
+
+ spRenderStyle = (Spinner) findViewById(R.id.spRenderStyle);
+ ArrayAdapter <BarRenderer.BarRenderStyle> adapter = new ArrayAdapter <BarRenderer.BarRenderStyle> (this, android.R.layout.simple_spinner_item, BarRenderer.BarRenderStyle.values() );
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spRenderStyle.setAdapter(adapter);
+ spRenderStyle.setSelection(BarRenderer.BarRenderStyle.OVERLAID.ordinal());
+ spRenderStyle.setOnItemSelectedListener(new OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) {
+ updatePlot();
+ }
+ @Override
+ public void onNothingSelected(AdapterView<?> arg0) {
+ }
+ });
+
+ spWidthStyle = (Spinner) findViewById(R.id.spWidthStyle);
+ ArrayAdapter <BarRenderer.BarWidthStyle> adapter1 = new ArrayAdapter <BarRenderer.BarWidthStyle> (this, android.R.layout.simple_spinner_item, BarRenderer.BarWidthStyle.values() );
+ adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spWidthStyle.setAdapter(adapter1);
+ spWidthStyle.setSelection(BarRenderer.BarWidthStyle.FIXED_WIDTH.ordinal());
+ spWidthStyle.setOnItemSelectedListener(new OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) {
+ if (BarRenderer.BarWidthStyle.FIXED_WIDTH.equals(spWidthStyle.getSelectedItem())) {
+ sbFixedWidth.setVisibility(View.VISIBLE);
+ sbVariableWidth.setVisibility(View.INVISIBLE);
+ } else {
+ sbFixedWidth.setVisibility(View.INVISIBLE);
+ sbVariableWidth.setVisibility(View.VISIBLE);
+ }
+ updatePlot();
+ }
+ @Override
+ public void onNothingSelected(AdapterView<?> arg0) {
+ }
+ });
+
+ spSeriesSize = (Spinner) findViewById(R.id.spSeriesSize);
+ ArrayAdapter <SeriesSize> adapter11 = new ArrayAdapter <SeriesSize> (this, android.R.layout.simple_spinner_item, SeriesSize.values() );
+ adapter11.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spSeriesSize.setAdapter(adapter11);
+ spSeriesSize.setSelection(SeriesSize.TEN.ordinal());
+ spSeriesSize.setOnItemSelectedListener(new OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> arg0, View arg1,int arg2, long arg3) {
+ switch ((SeriesSize)arg0.getSelectedItem()) {
+ case TEN:
+ series1Numbers = series1Numbers10;
+ series2Numbers = series2Numbers10;
+ break;
+ case TWENTY:
+ series1Numbers = series1Numbers20;
+ series2Numbers = series2Numbers20;
+ break;
+ case SIXTY:
+ series1Numbers = series1Numbers60;
+ series2Numbers = series2Numbers60;
+ break;
+ default:
+ break;
+ }
+ updatePlot();
+ }
+ @Override
+ public void onNothingSelected(AdapterView<?> arg0) {
+ }
+ });
+
+
+ sbFixedWidth = (SeekBar) findViewById(R.id.sbFixed);
+ sbFixedWidth.setProgress(50);
+ sbFixedWidth.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int i, boolean b) {updatePlot();}
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {}
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {}
+ });
+
+
+ sbVariableWidth = (SeekBar) findViewById(R.id.sbVariable);
+ sbVariableWidth.setProgress(1);
+ sbVariableWidth.setVisibility(View.INVISIBLE);
+ sbVariableWidth.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int i, boolean b) {updatePlot();}
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {}
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {}
+ });
+
+ plot.setDomainValueFormat(new NumberFormat() {
+ @Override
+ public StringBuffer format(double value, StringBuffer buffer, FieldPosition field) {
+ int year = (int) (value + 0.5d) / 12;
+ int month = (int) ((value + 0.5d) % 12);
+ return new StringBuffer(DateFormatSymbols.getInstance().getShortMonths()[month] + " '0" + year);
+ }
+
+ @Override
+ public StringBuffer format(long value, StringBuffer buffer, FieldPosition field) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ @Override
+ public Number parse(String string, ParsePosition position) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+ });
+ updatePlot();
+
+ }
+
+ private void updatePlot() {
+
+ // Remove all current series from each plot
+ Iterator<XYSeries> iterator1 = plot.getSeriesSet().iterator();
+ while(iterator1.hasNext()) {
+ XYSeries setElement = iterator1.next();
+ plot.removeSeries(setElement);
+ }
+
+ // Setup our Series with the selected number of elements
+ series1 = new SimpleXYSeries(Arrays.asList(series1Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Us");
+ series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Them");
+
+ // add a new series' to the xyplot:
+ if (series1CheckBox.isChecked()) plot.addSeries(series1, formatter1);
+ if (series2CheckBox.isChecked()) plot.addSeries(series2, formatter2);
+
+ // Setup the BarRenderer with our selected options
+ MyBarRenderer renderer = ((MyBarRenderer)plot.getRenderer(MyBarRenderer.class));
+ renderer.setBarRenderStyle((BarRenderer.BarRenderStyle)spRenderStyle.getSelectedItem());
+ renderer.setBarWidthStyle((BarRenderer.BarWidthStyle)spWidthStyle.getSelectedItem());
+ renderer.setBarWidth(sbFixedWidth.getProgress());
+ renderer.setBarGap(sbVariableWidth.getProgress());
+
+ if (BarRenderer.BarRenderStyle.STACKED.equals(spRenderStyle.getSelectedItem())) {
+ plot.setRangeTopMin(15);
+ } else {
+ plot.setRangeTopMin(0);
+ }
+
+ plot.redraw();
+
+ }
+
+ private void onPlotClicked(PointF point) {
+
+ // make sure the point lies within the graph area. we use gridrect
+ // because it accounts for margins and padding as well.
+ if (plot.getGraphWidget().getGridRect().contains(point.x, point.y)) {
+ Number x = plot.getXVal(point);
+ Number y = plot.getYVal(point);
+
+
+ selection = null;
+ double xDistance = 0;
+ double yDistance = 0;
+
+ // find the closest value to the selection:
+ for (XYSeries series : plot.getSeriesSet()) {
+ for (int i = 0; i < series.size(); i++) {
+ Number thisX = series.getX(i);
+ Number thisY = series.getY(i);
+ if (thisX != null && thisY != null) {
+ double thisXDistance =
+ LineRegion.measure(x, thisX).doubleValue();
+ double thisYDistance =
+ LineRegion.measure(y, thisY).doubleValue();
+ if (selection == null) {
+ selection = new Pair<Integer, XYSeries>(i, series);
+ xDistance = thisXDistance;
+ yDistance = thisYDistance;
+ } else if (thisXDistance < xDistance) {
+ selection = new Pair<Integer, XYSeries>(i, series);
+ xDistance = thisXDistance;
+ yDistance = thisYDistance;
+ } else if (thisXDistance == xDistance &&
+ thisYDistance < yDistance &&
+ thisY.doubleValue() >= y.doubleValue()) {
+ selection = new Pair<Integer, XYSeries>(i, series);
+ xDistance = thisXDistance;
+ yDistance = thisYDistance;
+ }
+ }
+ }
+ }
+
+ } else {
+ // if the press was outside the graph area, deselect:
+ selection = null;
+ }
+
+ if(selection == null) {
+ selectionWidget.setText(NO_SELECTION_TXT);
+ } else {
+ selectionWidget.setText("Selected: " + selection.second.getTitle() +
+ " Value: " + selection.second.getY(selection.first));
+ }
+ plot.redraw();
+ }
+
+ private void onS1CheckBoxClicked(boolean checked) {
+ if (checked) {
+ plot.addSeries(series1, formatter1);
+ } else {
+ plot.removeSeries(series1);
+ }
+ plot.redraw();
+ }
+
+ private void onS2CheckBoxClicked(boolean checked) {
+ if (checked) {
+ plot.addSeries(series2, formatter2);
+ } else {
+ plot.removeSeries(series2);
+ }
+ plot.redraw();
+ }
+
+ class MyBarFormatter extends BarFormatter {
+ public MyBarFormatter(int fillColor, int borderColor) {
+ super(fillColor, borderColor);
+ }
+
+ @Override
+ public Class<? extends SeriesRenderer> getRendererClass() {
+ return MyBarRenderer.class;
+ }
+
+ @Override
+ public SeriesRenderer getRendererInstance(XYPlot plot) {
+ return new MyBarRenderer(plot);
+ }
+ }
+
+ class MyBarRenderer extends BarRenderer<MyBarFormatter> {
+
+ public MyBarRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ /**
+ * Implementing this method to allow us to inject our
+ * special selection formatter.
+ * @param index index of the point being rendered.
+ * @param series XYSeries to which the point being rendered belongs.
+ * @return
+ */
+ //@Override
+ // TODO: figure out why using @Override screws up the Maven builds
+ protected MyBarFormatter getFormatter(int index, XYSeries series) {
+ if(selection != null &&
+ selection.second == series &&
+ selection.first == index) {
+ return selectionFormatter;
+ } else {
+ return getFormatter(series);
+ }
+ }
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/DualScaleXYPlotExampleActivity.java b/Examples/DemoApp/src/com/androidplot/demos/DualScaleXYPlotExampleActivity.java
new file mode 100644
index 0000000..d06581e
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/DualScaleXYPlotExampleActivity.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import com.androidplot.ui.AnchorPosition;
+import com.androidplot.ui.DynamicTableModel;
+import com.androidplot.ui.SizeLayoutType;
+import com.androidplot.ui.SizeMetrics;
+import com.androidplot.xy.LineAndPointFormatter;
+import com.androidplot.xy.PointLabelFormatter;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.ui.XLayoutStyle;
+import com.androidplot.xy.XYGraphWidget;
+import com.androidplot.xy.XYLegendWidget;
+import com.androidplot.xy.XYPlot;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.ui.YLayoutStyle;
+
+/**
+ * The simplest possible example of using AndroidPlot to plot some data.
+ */
+public class DualScaleXYPlotExampleActivity extends Activity implements OnClickListener
+{
+
+ private XYPlot myXYPlot_LEFT, myXYPlot_RIGHT;
+ private Boolean series2_onRight = true;
+ private LineAndPointFormatter series1Format, series2Format;
+ private Button button;
+
+ // Declare and enable buttons to toggle whether the 2nd series is on left or right.
+
+ // Create a couple arrays of y-values to plot:
+ private Number[] series1Numbers = {1, 8, 5, 2, 7, 4};
+ private Number[] series2Numbers = {444, 613, 353, 876, 924, 1004};
+ XYSeries series1, series2;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.dual_scale_xy_plot_example);
+
+ // Setup the LEFT Plot as normal
+ myXYPlot_LEFT = (XYPlot) findViewById(R.id.mySimpleXYPlot_L);
+ myXYPlot_RIGHT = (XYPlot) findViewById(R.id.mySimpleXYPlot_R);
+
+ // Disable Hardware Acceleration on the xyPlot view object.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ myXYPlot_LEFT.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ myXYPlot_RIGHT.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ XYGraphWidget graphWidget_LEFT = myXYPlot_LEFT.getGraphWidget();
+ graphWidget_LEFT.setRangeAxisPosition(true, false, 4, "10");
+ graphWidget_LEFT.setMarginRight(0);
+ graphWidget_LEFT.setPaddingRight(30);
+ graphWidget_LEFT.setRangeLabelVerticalOffset(-3);
+ graphWidget_LEFT.setRangeLabelWidth(50);
+
+ // Setup the second Plot with Right-hand Scale and otherwise invisible.
+ myXYPlot_RIGHT.getDomainLabelWidget().setVisible(false);
+ myXYPlot_RIGHT.getRangeLabelWidget().setVisible(false);
+ myXYPlot_RIGHT.getTitleWidget().setVisible(false);
+ myXYPlot_RIGHT.getBorderPaint().setAlpha(0);
+ myXYPlot_RIGHT.getBackgroundPaint().setAlpha(0);
+ XYGraphWidget graphWidget_RIGHT = myXYPlot_RIGHT.getGraphWidget();
+ graphWidget_RIGHT.getBackgroundPaint().setAlpha(0);
+ graphWidget_RIGHT.getDomainLabelPaint().setAlpha(0);
+ graphWidget_RIGHT.getGridBackgroundPaint().setAlpha(0);
+ graphWidget_RIGHT.getDomainOriginLabelPaint().setAlpha(0);
+ graphWidget_RIGHT.getRangeOriginLinePaint().setAlpha(0);
+ graphWidget_RIGHT.getDomainGridLinePaint().setAlpha(0);
+ graphWidget_RIGHT.getRangeGridLinePaint().setAlpha(0);
+ graphWidget_RIGHT.setRangeAxisPosition(false, false, 4, "10");
+
+ // Copy where possible from the LEFT plot
+ graphWidget_RIGHT.setRangeLabelVerticalOffset(graphWidget_LEFT.getRangeLabelVerticalOffset());
+ graphWidget_RIGHT.setMarginRight(graphWidget_LEFT.getMarginRight());
+ graphWidget_RIGHT.setPaddingRight(graphWidget_LEFT.getPaddingRight());
+ graphWidget_RIGHT.setRangeLabelWidth(graphWidget_LEFT.getRangeLabelWidth());
+
+ // Position the Graphs
+ myXYPlot_LEFT.getGraphWidget().position(
+ 0 ,XLayoutStyle.ABSOLUTE_FROM_LEFT,10,YLayoutStyle.ABSOLUTE_FROM_TOP,AnchorPosition.LEFT_TOP);
+ myXYPlot_RIGHT.getGraphWidget().position(
+ 49,XLayoutStyle.ABSOLUTE_FROM_LEFT,10,YLayoutStyle.ABSOLUTE_FROM_TOP,AnchorPosition.LEFT_TOP);
+
+ // Setup and Position the LEFT Legend
+ XYLegendWidget legendWidget_LEFT = myXYPlot_LEFT.getLegendWidget();
+ legendWidget_LEFT.setTableModel(new DynamicTableModel(1, 3));
+ legendWidget_LEFT.getTextPaint().setTextSize(20);
+ legendWidget_LEFT.setSize(new SizeMetrics(100, SizeLayoutType.ABSOLUTE, 75, SizeLayoutType.FILL));
+ legendWidget_LEFT.setPadding(1, 1, 1, 1);
+ myXYPlot_LEFT.getGraphWidget().position(
+ 55, XLayoutStyle.ABSOLUTE_FROM_LEFT, 15, YLayoutStyle.ABSOLUTE_FROM_TOP, AnchorPosition.LEFT_TOP);
+
+ // Setup and Position the RIGHT Legend
+ XYLegendWidget legendWidget_RIGHT = myXYPlot_RIGHT.getLegendWidget();
+ legendWidget_RIGHT.setTableModel(new DynamicTableModel(1, 3));
+ legendWidget_RIGHT.getTextPaint().setTextSize(20);
+ legendWidget_RIGHT.setSize(new SizeMetrics(100, SizeLayoutType.ABSOLUTE, 110, SizeLayoutType.ABSOLUTE));
+ legendWidget_RIGHT.setPadding(1, 1, 1, 1);
+ myXYPlot_RIGHT.getGraphWidget().position(
+ 25, XLayoutStyle.ABSOLUTE_FROM_RIGHT, 15, YLayoutStyle.ABSOLUTE_FROM_TOP, AnchorPosition.RIGHT_TOP);
+
+ // Setup the Series
+ series1 = new SimpleXYSeries(Arrays.asList(series1Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series1");
+ series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series2");
+
+ // Setup the formatters
+ series1Format = new LineAndPointFormatter(Color.rgb(0, 200, 0), Color.rgb(0, 100, 0), null, new PointLabelFormatter(Color.WHITE));
+ series2Format = new LineAndPointFormatter(Color.rgb(0, 0, 200), Color.rgb(0, 0, 100), null, new PointLabelFormatter(Color.WHITE));
+
+ // Setup the Button
+ button = (Button)findViewById(R.id.toggleSeries2);
+ button.setOnClickListener(this);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ updateView();
+
+ }
+
+ private void updateView() {
+
+ // Remove all current series from each plot
+ Iterator<XYSeries> iterator1 = myXYPlot_LEFT.getSeriesSet().iterator();
+ while(iterator1.hasNext()) {
+ XYSeries setElement = iterator1.next();
+ myXYPlot_LEFT.removeSeries(setElement);
+ }
+ Iterator<XYSeries> iterator2 = myXYPlot_RIGHT.getSeriesSet().iterator();
+ while(iterator2.hasNext()) {
+ XYSeries setElement = iterator2.next();
+ myXYPlot_RIGHT.removeSeries(setElement);
+ }
+
+ // Add series to each plot as needed.
+ myXYPlot_LEFT.addSeries(series1, series1Format);
+ if (series2_onRight) {
+ myXYPlot_RIGHT.addSeries(series2, series2Format);
+ } else {
+ myXYPlot_LEFT.addSeries(series2, series2Format);
+ }
+
+ // Finalise each Plot based on whether they have any series or not.
+ if (! myXYPlot_RIGHT.getSeriesSet().isEmpty()) {
+ myXYPlot_RIGHT.setVisibility(XYPlot.VISIBLE);
+ myXYPlot_RIGHT.redraw();
+ } else {
+ myXYPlot_RIGHT.setVisibility(XYPlot.INVISIBLE);
+ }
+
+ if (! myXYPlot_LEFT.getSeriesSet().isEmpty()) {
+ myXYPlot_LEFT.setVisibility(XYPlot.VISIBLE);
+ myXYPlot_LEFT.redraw();
+ } else {
+ myXYPlot_LEFT.setVisibility(XYPlot.INVISIBLE);
+ }
+
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (series2_onRight) {
+ series2_onRight = false;
+ } else {
+ series2_onRight = true;
+ }
+ updateView();
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/DynamicXYPlotActivity.java b/Examples/DemoApp/src/com/androidplot/demos/DynamicXYPlotActivity.java
new file mode 100644
index 0000000..b4e6456
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/DynamicXYPlotActivity.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2013 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.os.Bundle;
+import com.androidplot.Plot;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.xy.*;
+
+import java.text.DecimalFormat;
+import java.util.Observable;
+import java.util.Observer;
+
+public class DynamicXYPlotActivity extends Activity {
+
+ // redraws a plot whenever an update is received:
+ private class MyPlotUpdater implements Observer {
+ Plot plot;
+
+ public MyPlotUpdater(Plot plot) {
+ this.plot = plot;
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ plot.redraw();
+ }
+ }
+
+ private XYPlot dynamicPlot;
+ private MyPlotUpdater plotUpdater;
+ SampleDynamicXYDatasource data;
+ private Thread myThread;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ // android boilerplate stuff
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.dynamicxyplot_example);
+
+ // get handles to our View defined in layout.xml:
+ dynamicPlot = (XYPlot) findViewById(R.id.dynamicXYPlot);
+
+ plotUpdater = new MyPlotUpdater(dynamicPlot);
+
+ // only display whole numbers in domain labels
+ dynamicPlot.getGraphWidget().setDomainValueFormat(new DecimalFormat("0"));
+
+ // getInstance and position datasets:
+ data = new SampleDynamicXYDatasource();
+ SampleDynamicSeries sine1Series = new SampleDynamicSeries(data, 0, "Sine 1");
+ SampleDynamicSeries sine2Series = new SampleDynamicSeries(data, 1, "Sine 2");
+
+ LineAndPointFormatter formatter1 = new LineAndPointFormatter(
+ Color.rgb(0, 0, 0), null, null, null);
+ formatter1.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
+ formatter1.getLinePaint().setStrokeWidth(10);
+ dynamicPlot.addSeries(sine1Series,
+ formatter1);
+
+ LineAndPointFormatter formatter2 =
+ new LineAndPointFormatter(Color.rgb(0, 0, 200), null, null, null);
+ formatter2.getLinePaint().setStrokeWidth(10);
+ formatter2.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
+
+ //formatter2.getFillPaint().setAlpha(220);
+ dynamicPlot.addSeries(sine2Series, formatter2);
+
+ // hook up the plotUpdater to the data model:
+ data.addObserver(plotUpdater);
+
+ // thin out domain tick labels so they dont overlap each other:
+ dynamicPlot.setDomainStepMode(XYStepMode.INCREMENT_BY_VAL);
+ dynamicPlot.setDomainStepValue(5);
+
+ dynamicPlot.setRangeStepMode(XYStepMode.INCREMENT_BY_VAL);
+ dynamicPlot.setRangeStepValue(10);
+
+ dynamicPlot.setRangeValueFormat(new DecimalFormat("###.#"));
+
+ // uncomment this line to freeze the range boundaries:
+ dynamicPlot.setRangeBoundaries(-100, 100, BoundaryMode.FIXED);
+
+ // create a dash effect for domain and range grid lines:
+ DashPathEffect dashFx = new DashPathEffect(
+ new float[] {PixelUtils.dpToPix(3), PixelUtils.dpToPix(3)}, 0);
+ dynamicPlot.getGraphWidget().getDomainGridLinePaint().setPathEffect(dashFx);
+ dynamicPlot.getGraphWidget().getRangeGridLinePaint().setPathEffect(dashFx);
+
+
+ }
+
+ @Override
+ public void onResume() {
+ // kick off the data generating thread:
+ myThread = new Thread(data);
+ myThread.start();
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ data.stopThread();
+ super.onPause();
+ }
+
+ class SampleDynamicXYDatasource implements Runnable {
+
+ // encapsulates management of the observers watching this datasource for update events:
+ class MyObservable extends Observable {
+ @Override
+ public void notifyObservers() {
+ setChanged();
+ super.notifyObservers();
+ }
+ }
+
+ private static final double FREQUENCY = 5; // larger is lower frequency
+ private static final int MAX_AMP_SEED = 100;
+ private static final int MIN_AMP_SEED = 10;
+ private static final int AMP_STEP = 1;
+ public static final int SINE1 = 0;
+ public static final int SINE2 = 1;
+ private static final int SAMPLE_SIZE = 30;
+ private int phase = 0;
+ private int sinAmp = 1;
+ private MyObservable notifier;
+ private boolean keepRunning = false;
+
+ {
+ notifier = new MyObservable();
+ }
+
+ public void stopThread() {
+ keepRunning = false;
+ }
+
+ //@Override
+ public void run() {
+ try {
+ keepRunning = true;
+ boolean isRising = true;
+ while (keepRunning) {
+
+ Thread.sleep(10); // decrease or remove to speed up the refresh rate.
+ phase++;
+ if (sinAmp >= MAX_AMP_SEED) {
+ isRising = false;
+ } else if (sinAmp <= MIN_AMP_SEED) {
+ isRising = true;
+ }
+
+ if (isRising) {
+ sinAmp += AMP_STEP;
+ } else {
+ sinAmp -= AMP_STEP;
+ }
+ notifier.notifyObservers();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public int getItemCount(int series) {
+ return SAMPLE_SIZE;
+ }
+
+ public Number getX(int series, int index) {
+ if (index >= SAMPLE_SIZE) {
+ throw new IllegalArgumentException();
+ }
+ return index;
+ }
+
+ public Number getY(int series, int index) {
+ if (index >= SAMPLE_SIZE) {
+ throw new IllegalArgumentException();
+ }
+ double angle = (index + (phase))/FREQUENCY;
+ double amp = sinAmp * Math.sin(angle);
+ switch (series) {
+ case SINE1:
+ return amp;
+ case SINE2:
+ return -amp;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public void addObserver(Observer observer) {
+ notifier.addObserver(observer);
+ }
+
+ public void removeObserver(Observer observer) {
+ notifier.deleteObserver(observer);
+ }
+
+ }
+
+ class SampleDynamicSeries implements XYSeries {
+ private SampleDynamicXYDatasource datasource;
+ private int seriesIndex;
+ private String title;
+
+ public SampleDynamicSeries(SampleDynamicXYDatasource datasource, int seriesIndex, String title) {
+ this.datasource = datasource;
+ this.seriesIndex = seriesIndex;
+ this.title = title;
+ }
+
+ @Override
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public int size() {
+ return datasource.getItemCount(seriesIndex);
+ }
+
+ @Override
+ public Number getX(int index) {
+ return datasource.getX(seriesIndex, index);
+ }
+
+ @Override
+ public Number getY(int index) {
+ return datasource.getY(seriesIndex, index);
+ }
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/GlobalDefs.java b/Examples/DemoApp/src/com/androidplot/demos/GlobalDefs.java
new file mode 100644
index 0000000..28b74d9
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/GlobalDefs.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+public interface GlobalDefs {
+ static final float PLOT_TITLE_FONT_SIZE_DP = 15;
+ static final float PLOT_DOMAIN_LABEL_FONT_SIZE_DP = 10;
+ static final float PLOT_RANGE_LABEL_FONT_SIZE_DP = 10;
+ static final float PLOT_TICK_LABEL_FONT_SIZE_DP = 10;
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/ListViewActivity.java b/Examples/DemoApp/src/com/androidplot/demos/ListViewActivity.java
new file mode 100644
index 0000000..d80ac50
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/ListViewActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import com.androidplot.Plot;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.xy.LineAndPointFormatter;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYPlot;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class ListViewActivity extends Activity {
+ private static final int NUM_PLOTS = 10;
+ private static final int NUM_POINTS_PER_SERIES = 10;
+ private static final int NUM_SERIES_PER_PLOT = 5;
+ private ListView lv;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.listview_example);
+ lv = (ListView) findViewById(R.id.listView1);
+ lv.setAdapter(new MyViewAdapter(getApplicationContext(), R.layout.listview_example_item, null));
+ }
+
+ class MyViewAdapter extends ArrayAdapter<View> {
+ public MyViewAdapter(Context context, int resId, List<View> views) {
+ super(context, resId, views);
+ }
+
+ @Override
+ public int getCount() {
+ return 5;
+ }
+
+ @Override
+ public View getView(int pos, View convertView, ViewGroup parent) {
+ LayoutInflater inf = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ View v = convertView;
+ if (v == null) {
+ v = inf.inflate(R.layout.listview_example_item, parent, false);
+ }
+
+ Plot p = (XYPlot) v.findViewById(R.id.xyplot);
+ Random generator = new Random();
+
+ for (int k = 0; k < NUM_SERIES_PER_PLOT; k++) {
+ ArrayList<Number> nums = new ArrayList<Number>();
+ for (int j = 0; j < NUM_POINTS_PER_SERIES; j++) {
+ nums.add(generator.nextFloat());
+ }
+
+ double rl = Math.random();
+ double gl = Math.random();
+ double bl = Math.random();
+
+ double rp = Math.random();
+ double gp = Math.random();
+ double bp = Math.random();
+
+ XYSeries series = new SimpleXYSeries(nums, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "S" + k);
+ p.addSeries(series, new LineAndPointFormatter(
+ Color.rgb(new Double(rl * 255).intValue(), new Double(gl * 255).intValue(), new Double(bl * 255).intValue()),
+ Color.rgb(new Double(rp * 255).intValue(), new Double(gp * 255).intValue(), new Double(bp * 255).intValue()),
+ null, null));
+ }
+ return v;
+ }
+ }
+} \ No newline at end of file
diff --git a/Examples/DemoApp/src/com/androidplot/demos/MainActivity.java b/Examples/DemoApp/src/com/androidplot/demos/MainActivity.java
new file mode 100644
index 0000000..51c1441
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/MainActivity.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+public class MainActivity extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ Button startSimplePieExButton = (Button) findViewById(R.id.startSimplePieExButton);
+ startSimplePieExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, SimplePieChartActivity.class));
+ }
+ });
+
+ Button startDynamicXYExButton = (Button)findViewById(R.id.startDynamicXYExButton);
+ startDynamicXYExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, DynamicXYPlotActivity.class));
+ }
+ });
+
+ Button startSimpleXYExButton = (Button) findViewById(R.id.startSimpleXYExButton);
+ startSimpleXYExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, SimpleXYPlotActivity.class));
+ }
+ });
+
+ Button startBarPlotExButton = (Button) findViewById(R.id.startBarPlotExButton);
+ startBarPlotExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, BarPlotExampleActivity.class));
+ }
+ });
+
+ Button startOrSensorExButton = (Button) findViewById(R.id.startOrSensorExButton);
+ startOrSensorExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, OrientationSensorExampleActivity.class));
+ }
+ });
+
+ Button startTimeSeriesExButon = (Button)findViewById(R.id.startTimeSeriesExButton);
+ startTimeSeriesExButon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, TimeSeriesActivity.class));
+ }
+ });
+
+ Button startStepChartExButton = (Button)findViewById(R.id.startStepChartExButton);
+ startStepChartExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, StepChartExampleActivity.class));
+ }
+ });
+
+ Button startScrollZoomExButton = (Button)findViewById(R.id.startScrollZoomButton);
+ startScrollZoomExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, TouchZoomExampleActivity.class));
+ }
+ });
+
+ Button startXyRegionExampleButton = (Button)findViewById(R.id.startXyRegionExampleButton);
+ startXyRegionExampleButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, XYRegionExampleActivity.class));
+ }
+ });
+
+
+ Button listViewExButton = (Button)findViewById(R.id.startXyListViewExButton);
+ listViewExButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, ListViewActivity.class));
+ }
+ });
+
+ Button startDualScaleExampleButton = (Button)findViewById(R.id.startDualScaleExampleButton);
+ startDualScaleExampleButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, DualScaleXYPlotExampleActivity.class));
+ }
+ });
+
+ Button startXYPlotWithBgImgExampleButton = (Button)findViewById(R.id.startXYPlotWithBgImgExample);
+ startXYPlotWithBgImgExampleButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, XYPlotWithBgImgActivity.class));
+ }
+ });
+
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/OrientationSensorExampleActivity.java b/Examples/DemoApp/src/com/androidplot/demos/OrientationSensorExampleActivity.java
new file mode 100644
index 0000000..23012bd
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/OrientationSensorExampleActivity.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import com.androidplot.Plot;
+import com.androidplot.util.PlotStatistics;
+import com.androidplot.util.Redrawer;
+import com.androidplot.xy.*;
+import java.text.DecimalFormat;
+import java.util.Arrays;
+
+// Monitor the phone's orientation sensor and plot the resulting azimuth pitch and roll values.
+// See: http://developer.android.com/reference/android/hardware/SensorEvent.html
+public class OrientationSensorExampleActivity extends Activity implements SensorEventListener
+{
+
+ private static final int HISTORY_SIZE = 300; // number of points to plot in history
+ private SensorManager sensorMgr = null;
+ private Sensor orSensor = null;
+
+ private XYPlot aprLevelsPlot = null;
+ private XYPlot aprHistoryPlot = null;
+
+ private CheckBox hwAcceleratedCb;
+ private CheckBox showFpsCb;
+ //private SimpleXYSeries aprLevelsSeries = null;
+ private SimpleXYSeries aLvlSeries;
+ private SimpleXYSeries pLvlSeries;
+ private SimpleXYSeries rLvlSeries;
+ private SimpleXYSeries azimuthHistorySeries = null;
+ private SimpleXYSeries pitchHistorySeries = null;
+ private SimpleXYSeries rollHistorySeries = null;
+
+ private Redrawer redrawer;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.orientation_sensor_example);
+
+ // setup the APR Levels plot:
+ aprLevelsPlot = (XYPlot) findViewById(R.id.aprLevelsPlot);
+ aprLevelsPlot.setDomainBoundaries(-1, 1, BoundaryMode.FIXED);
+ aprLevelsPlot.getGraphWidget().getDomainLabelPaint().setColor(Color.TRANSPARENT);
+
+ aLvlSeries = new SimpleXYSeries("A");
+ pLvlSeries = new SimpleXYSeries("P");
+ rLvlSeries = new SimpleXYSeries("R");
+
+ aprLevelsPlot.addSeries(aLvlSeries,
+ new BarFormatter(Color.rgb(0, 200, 0), Color.rgb(0, 80, 0)));
+ aprLevelsPlot.addSeries(pLvlSeries,
+ new BarFormatter(Color.rgb(200, 0, 0), Color.rgb(0, 80, 0)));
+ aprLevelsPlot.addSeries(rLvlSeries,
+ new BarFormatter(Color.rgb(0, 0, 200), Color.rgb(0, 80, 0)));
+
+ aprLevelsPlot.setDomainStepValue(3);
+ aprLevelsPlot.setTicksPerRangeLabel(3);
+
+ // per the android documentation, the minimum and maximum readings we can get from
+ // any of the orientation sensors is -180 and 359 respectively so we will fix our plot's
+ // boundaries to those values. If we did not do this, the plot would auto-range which
+ // can be visually confusing in the case of dynamic plots.
+ aprLevelsPlot.setRangeBoundaries(-180, 359, BoundaryMode.FIXED);
+
+ // update our domain and range axis labels:
+ aprLevelsPlot.setDomainLabel("");
+ aprLevelsPlot.getDomainLabelWidget().pack();
+ aprLevelsPlot.setRangeLabel("Angle (Degs)");
+ aprLevelsPlot.getRangeLabelWidget().pack();
+ aprLevelsPlot.setGridPadding(15, 0, 15, 0);
+ aprLevelsPlot.setRangeValueFormat(new DecimalFormat("#"));
+
+ // setup the APR History plot:
+ aprHistoryPlot = (XYPlot) findViewById(R.id.aprHistoryPlot);
+
+ azimuthHistorySeries = new SimpleXYSeries("Az.");
+ azimuthHistorySeries.useImplicitXVals();
+ pitchHistorySeries = new SimpleXYSeries("Pitch");
+ pitchHistorySeries.useImplicitXVals();
+ rollHistorySeries = new SimpleXYSeries("Roll");
+ rollHistorySeries.useImplicitXVals();
+
+ aprHistoryPlot.setRangeBoundaries(-180, 359, BoundaryMode.FIXED);
+ aprHistoryPlot.setDomainBoundaries(0, HISTORY_SIZE, BoundaryMode.FIXED);
+ aprHistoryPlot.addSeries(azimuthHistorySeries,
+ new LineAndPointFormatter(
+ Color.rgb(100, 100, 200), null, null, null));
+ aprHistoryPlot.addSeries(pitchHistorySeries,
+ new LineAndPointFormatter(
+ Color.rgb(100, 200, 100), null, null, null));
+ aprHistoryPlot.addSeries(rollHistorySeries,
+ new LineAndPointFormatter(
+ Color.rgb(200, 100, 100), null, null, null));
+ aprHistoryPlot.setDomainStepMode(XYStepMode.INCREMENT_BY_VAL);
+ aprHistoryPlot.setDomainStepValue(HISTORY_SIZE/10);
+ aprHistoryPlot.setTicksPerRangeLabel(3);
+ aprHistoryPlot.setDomainLabel("Sample Index");
+ aprHistoryPlot.getDomainLabelWidget().pack();
+ aprHistoryPlot.setRangeLabel("Angle (Degs)");
+ aprHistoryPlot.getRangeLabelWidget().pack();
+
+ aprHistoryPlot.setRangeValueFormat(new DecimalFormat("#"));
+ aprHistoryPlot.setDomainValueFormat(new DecimalFormat("#"));
+
+ // setup checkboxes:
+ hwAcceleratedCb = (CheckBox) findViewById(R.id.hwAccelerationCb);
+ final PlotStatistics levelStats = new PlotStatistics(1000, false);
+ final PlotStatistics histStats = new PlotStatistics(1000, false);
+
+ aprLevelsPlot.addListener(levelStats);
+ aprHistoryPlot.addListener(histStats);
+ hwAcceleratedCb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ if(b) {
+ aprLevelsPlot.setLayerType(View.LAYER_TYPE_NONE, null);
+ aprHistoryPlot.setLayerType(View.LAYER_TYPE_NONE, null);
+ } else {
+ aprLevelsPlot.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ aprHistoryPlot.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+ }
+ });
+
+ showFpsCb = (CheckBox) findViewById(R.id.showFpsCb);
+ showFpsCb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ levelStats.setAnnotatePlotEnabled(b);
+ histStats.setAnnotatePlotEnabled(b);
+ }
+ });
+
+ // get a ref to the BarRenderer so we can make some changes to it:
+ BarRenderer barRenderer = (BarRenderer) aprLevelsPlot.getRenderer(BarRenderer.class);
+ if(barRenderer != null) {
+ // make our bars a little thicker than the default so they can be seen better:
+ barRenderer.setBarWidth(25);
+ }
+
+ // register for orientation sensor events:
+ sensorMgr = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
+ for (Sensor sensor : sensorMgr.getSensorList(Sensor.TYPE_ORIENTATION)) {
+ if (sensor.getType() == Sensor.TYPE_ORIENTATION) {
+ orSensor = sensor;
+ }
+ }
+
+ // if we can't access the orientation sensor then exit:
+ if (orSensor == null) {
+ System.out.println("Failed to attach to orSensor.");
+ cleanup();
+ }
+
+ sensorMgr.registerListener(this, orSensor, SensorManager.SENSOR_DELAY_UI);
+
+ redrawer = new Redrawer(
+ Arrays.asList(new Plot[]{aprHistoryPlot, aprLevelsPlot}),
+ 100, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ redrawer.start();
+ }
+
+ @Override
+ public void onPause() {
+ redrawer.pause();
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy() {
+ redrawer.finish();
+ super.onDestroy();
+ }
+
+ private void cleanup() {
+ // aunregister with the orientation sensor before exiting:
+ sensorMgr.unregisterListener(this);
+ finish();
+ }
+
+
+ // Called whenever a new orSensor reading is taken.
+ @Override
+ public synchronized void onSensorChanged(SensorEvent sensorEvent) {
+
+ // update level data:
+ aLvlSeries.setModel(Arrays.asList(
+ new Number[]{sensorEvent.values[0]}),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+
+ pLvlSeries.setModel(Arrays.asList(
+ new Number[]{sensorEvent.values[1]}),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+
+ rLvlSeries.setModel(Arrays.asList(
+ new Number[]{sensorEvent.values[2]}),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
+
+ // get rid the oldest sample in history:
+ if (rollHistorySeries.size() > HISTORY_SIZE) {
+ rollHistorySeries.removeFirst();
+ pitchHistorySeries.removeFirst();
+ azimuthHistorySeries.removeFirst();
+ }
+
+ // add the latest history sample:
+ azimuthHistorySeries.addLast(null, sensorEvent.values[0]);
+ pitchHistorySeries.addLast(null, sensorEvent.values[1]);
+ rollHistorySeries.addLast(null, sensorEvent.values[2]);
+ }
+
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int i) {
+ // Not interested in this event
+ }
+} \ No newline at end of file
diff --git a/Examples/DemoApp/src/com/androidplot/demos/SimplePieChartActivity.java b/Examples/DemoApp/src/com/androidplot/demos/SimplePieChartActivity.java
new file mode 100644
index 0000000..f90160b
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/SimplePieChartActivity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import android.app.Activity;
+import android.graphics.*;
+import android.os.Bundle;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import com.androidplot.pie.PieChart;
+import com.androidplot.pie.PieRenderer;
+import com.androidplot.pie.Segment;
+import com.androidplot.pie.SegmentFormatter;
+
+/**
+ * The simplest possible example of using AndroidPlot to plot some data.
+ */
+public class SimplePieChartActivity extends Activity
+{
+
+ private TextView donutSizeTextView;
+ private SeekBar donutSizeSeekBar;
+
+ private PieChart pie;
+
+ private Segment s1;
+ private Segment s2;
+ private Segment s3;
+ private Segment s4;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.pie_chart);
+
+ // initialize our XYPlot reference:
+ pie = (PieChart) findViewById(R.id.mySimplePieChart);
+
+
+ donutSizeSeekBar = (SeekBar) findViewById(R.id.donutSizeSeekBar);
+
+ donutSizeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int i, boolean b) {}
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {}
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ pie.getRenderer(PieRenderer.class).setDonutSize(seekBar.getProgress()/100f,
+ PieRenderer.DonutMode.PERCENT);
+ pie.redraw();
+ updateDonutText();
+ }
+ });
+
+ donutSizeTextView = (TextView) findViewById(R.id.donutSizeTextView);
+ updateDonutText();
+
+ s1 = new Segment("s1", 10);
+ s2 = new Segment("s2", 1);
+ s3 = new Segment("s3", 10);
+ s4 = new Segment("s4", 10);
+
+ EmbossMaskFilter emf = new EmbossMaskFilter(
+ new float[]{1, 1, 1}, 0.4f, 10, 8.2f);
+
+ SegmentFormatter sf1 = new SegmentFormatter();
+ sf1.configure(getApplicationContext(), R.xml.pie_segment_formatter1);
+
+ sf1.getFillPaint().setMaskFilter(emf);
+
+ SegmentFormatter sf2 = new SegmentFormatter();
+ sf2.configure(getApplicationContext(), R.xml.pie_segment_formatter2);
+
+ sf2.getFillPaint().setMaskFilter(emf);
+
+ SegmentFormatter sf3 = new SegmentFormatter();
+ sf3.configure(getApplicationContext(), R.xml.pie_segment_formatter3);
+
+ sf3.getFillPaint().setMaskFilter(emf);
+
+ SegmentFormatter sf4 = new SegmentFormatter();
+ sf4.configure(getApplicationContext(), R.xml.pie_segment_formatter4);
+
+ sf4.getFillPaint().setMaskFilter(emf);
+
+ pie.addSeries(s1, sf1);
+ pie.addSeries(s2, sf2);
+ pie.addSeries(s3, sf3);
+ pie.addSeries(s4, sf4);
+
+ pie.getBorderPaint().setColor(Color.TRANSPARENT);
+ pie.getBackgroundPaint().setColor(Color.TRANSPARENT);
+ }
+
+ protected void updateDonutText() {
+ donutSizeTextView.setText(donutSizeSeekBar.getProgress() + "%");
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/SimpleXYPlotActivity.java b/Examples/DemoApp/src/com/androidplot/demos/SimpleXYPlotActivity.java
new file mode 100644
index 0000000..985e5ff
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/SimpleXYPlotActivity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.xy.*;
+import java.util.Arrays;
+
+/**
+ * A straightforward example of using AndroidPlot to plot some data.
+ */
+public class SimpleXYPlotActivity extends Activity
+{
+
+ private XYPlot plot;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // fun little snippet that prevents users from taking screenshots
+ // on ICS+ devices :-)
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE);
+ setContentView(R.layout.simple_xy_plot_example);
+
+ // initialize our XYPlot reference:
+ plot = (XYPlot) findViewById(R.id.mySimpleXYPlot);
+
+ // Create a couple arrays of y-values to plot:
+ Number[] series1Numbers = {1, 8, 5, 2, 7, 4};
+ Number[] series2Numbers = {4, 6, 3, 8, 2, 10};
+
+ // Turn the above arrays into XYSeries':
+ XYSeries series1 = new SimpleXYSeries(
+ Arrays.asList(series1Numbers), // SimpleXYSeries takes a List so turn our array into a List
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, // Y_VALS_ONLY means use the element index as the x value
+ "Series1"); // Set the display title of the series
+
+ // same as above
+ XYSeries series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series2");
+
+ // Create a formatter to use for drawing a series using LineAndPointRenderer
+ // and configure it from xml:
+ LineAndPointFormatter series1Format = new LineAndPointFormatter();
+ series1Format.setPointLabelFormatter(new PointLabelFormatter());
+ series1Format.configure(getApplicationContext(),
+ R.xml.line_point_formatter_with_plf1);
+
+ // add a new series' to the xyplot:
+ plot.addSeries(series1, series1Format);
+
+ // same as above:
+ LineAndPointFormatter series2Format = new LineAndPointFormatter();
+ series2Format.setPointLabelFormatter(new PointLabelFormatter());
+ series2Format.configure(getApplicationContext(),
+ R.xml.line_point_formatter_with_plf2);
+ plot.addSeries(series2, series2Format);
+
+ // reduce the number of range labels
+ plot.setTicksPerRangeLabel(3);
+ plot.getGraphWidget().setDomainLabelOrientation(-45);
+
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/StepChartExampleActivity.java b/Examples/DemoApp/src/com/androidplot/demos/StepChartExampleActivity.java
new file mode 100644
index 0000000..660d03a
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/StepChartExampleActivity.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+
+import android.app.Activity;
+import android.graphics.*;
+import android.os.Bundle;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.xy.*;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.Arrays;
+
+public class StepChartExampleActivity extends Activity
+{
+
+ private XYPlot mySimpleXYPlot;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.step_chart_example);
+
+ // initialize our XYPlot reference:
+ mySimpleXYPlot = (XYPlot) findViewById(R.id.stepChartExamplePlot);
+
+ // y-vals to plot:
+ Number[] series1Numbers = {1, 2, 3, 4, 2, 3, 4, 2, 2, 2, 3, 4, 2, 3, 2, 2};
+ // create our series from our array of nums:
+ XYSeries series2 = new SimpleXYSeries(
+ Arrays.asList(series1Numbers),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,
+ "Thread #1");
+
+ //mySimpleXYPlot.getLayoutManager().remove(mySimpleXYPlot.getLegendWidget());
+
+
+ mySimpleXYPlot.getGraphWidget().getGridBackgroundPaint().setColor(Color.WHITE);
+ mySimpleXYPlot.getGraphWidget().getDomainGridLinePaint().setColor(Color.BLACK);
+ mySimpleXYPlot.getGraphWidget().getDomainGridLinePaint().setPathEffect(new DashPathEffect(new float[]{1, 1}, 1));
+ mySimpleXYPlot.getGraphWidget().getRangeGridLinePaint().setColor(Color.BLACK);
+ mySimpleXYPlot.getGraphWidget().getRangeGridLinePaint().setPathEffect(new DashPathEffect(new float[]{1, 1}, 1));
+ mySimpleXYPlot.getGraphWidget().getDomainOriginLinePaint().setColor(Color.BLACK);
+ mySimpleXYPlot.getGraphWidget().getRangeOriginLinePaint().setColor(Color.BLACK);
+ mySimpleXYPlot.getGraphWidget().setMarginRight(5);
+
+ // Create a formatter to use for drawing a series using LineAndPointRenderer:
+ LineAndPointFormatter series1Format = new LineAndPointFormatter(
+ Color.rgb(0, 100, 0), // line color
+ Color.rgb(0, 100, 0), // point color
+ Color.rgb(100, 200, 0), null); // fill color
+
+
+ // setup our line fill paint to be a slightly transparent gradient:
+ Paint lineFill = new Paint();
+ lineFill.setAlpha(200);
+ lineFill.setShader(new LinearGradient(0, 0, 0, 250, Color.WHITE, Color.BLUE, Shader.TileMode.MIRROR));
+
+ StepFormatter stepFormatter = new StepFormatter(Color.rgb(0, 0,0), Color.BLUE);
+ stepFormatter.getLinePaint().setStrokeWidth(1);
+
+ stepFormatter.getLinePaint().setAntiAlias(false);
+ stepFormatter.setFillPaint(lineFill);
+ mySimpleXYPlot.addSeries(series2, stepFormatter);
+
+ // adjust the domain/range ticks to make more sense; label per tick for range and label per 5 ticks domain:
+ mySimpleXYPlot.setRangeStep(XYStepMode.INCREMENT_BY_VAL, 1);
+ mySimpleXYPlot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 1);
+ mySimpleXYPlot.setTicksPerRangeLabel(1);
+ mySimpleXYPlot.setTicksPerDomainLabel(5);
+
+ // customize our domain/range labels
+ //mySimpleXYPlot.setDomainLabel("Time (Secs)");
+ //mySimpleXYPlot.setRangeLabel("Server State");
+
+ //mySimpleXYPlot.getGraphWidget().getGridLinePaint().setAlpha(0);
+
+
+
+ // get rid of decimal points in our domain labels:
+ mySimpleXYPlot.setDomainValueFormat(new DecimalFormat("0"));
+
+ // create a custom formatter to draw our state names as range tick labels:
+ mySimpleXYPlot.setRangeValueFormat(new Format() {
+ @Override
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ Number num = (Number) obj;
+ switch(num.intValue()) {
+ case 1:
+ toAppendTo.append("Init");
+ break;
+ case 2:
+ toAppendTo.append("Idle");
+ break;
+ case 3:
+ toAppendTo.append("Recv");
+ break;
+ case 4:
+ toAppendTo.append("Send");
+ break;
+ default:
+ toAppendTo.append("Unknown");
+ break;
+ }
+ return toAppendTo;
+ }
+
+ @Override
+ public Object parseObject(String source, ParsePosition pos) {
+ return null;
+ }
+ });
+
+ // by default, AndroidPlot displays developer guides to aid in laying out your plot.
+ // To get rid of them call disableAllMarkup():
+ //mySimpleXYPlot.disableAllMarkup();
+
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/TimeSeriesActivity.java b/Examples/DemoApp/src/com/androidplot/demos/TimeSeriesActivity.java
new file mode 100644
index 0000000..0e7904c
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/TimeSeriesActivity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import android.app.Activity;
+import android.graphics.*;
+import android.os.Bundle;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.xy.*;
+
+import java.text.*;
+import java.util.Arrays;
+import java.util.Date;
+
+public class TimeSeriesActivity extends Activity
+{
+
+ private XYPlot plot1;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.time_series_example);
+
+ plot1 = (XYPlot) findViewById(R.id.plot1);
+ Number[] numSightings = {5, 8, 9, 2, 5};
+
+ // an array of years in milliseconds:
+ Number[] years = {
+ 978307200, // 2001
+ 1009843200, // 2002
+ 1041379200, // 2003
+ 1072915200, // 2004
+ 1104537600 // 2005
+ };
+ // create our series from our array of nums:
+ XYSeries series2 = new SimpleXYSeries(
+ Arrays.asList(years),
+ Arrays.asList(numSightings),
+ "Sightings in USA");
+
+ plot1.getGraphWidget().getGridBackgroundPaint().setColor(Color.WHITE);
+ plot1.getGraphWidget().getDomainGridLinePaint().setColor(Color.BLACK);
+ plot1.getGraphWidget().getDomainGridLinePaint().
+ setPathEffect(new DashPathEffect(new float[]{1, 1}, 1));
+ plot1.getGraphWidget().getRangeGridLinePaint().setColor(Color.BLACK);
+ plot1.getGraphWidget().getRangeGridLinePaint().
+ setPathEffect(new DashPathEffect(new float[]{1, 1}, 1));
+ plot1.getGraphWidget().getDomainOriginLinePaint().setColor(Color.BLACK);
+ plot1.getGraphWidget().getRangeOriginLinePaint().setColor(Color.BLACK);
+
+ // Create a formatter to use for drawing a series using LineAndPointRenderer:
+ LineAndPointFormatter series1Format = new LineAndPointFormatter(
+ Color.rgb(0, 100, 0), // line color
+ Color.rgb(0, 100, 0), // point color
+ Color.rgb(100, 200, 0), null); // fill color
+
+
+ // setup our line fill paint to be a slightly transparent gradient:
+ Paint lineFill = new Paint();
+ lineFill.setAlpha(200);
+
+ // ugly usage of LinearGradient. unfortunately there's no way to determine the actual size of
+ // a View from within onCreate. one alternative is to specify a dimension in resources
+ // and use that accordingly. at least then the values can be customized for the device type and orientation.
+ lineFill.setShader(new LinearGradient(0, 0, 200, 200, Color.WHITE, Color.GREEN, Shader.TileMode.CLAMP));
+
+ LineAndPointFormatter formatter =
+ new LineAndPointFormatter(Color.rgb(0, 0,0), Color.BLUE, Color.RED, null);
+ formatter.setFillPaint(lineFill);
+ plot1.getGraphWidget().setPaddingRight(2);
+ plot1.addSeries(series2, formatter);
+
+ // draw a domain tick for each year:
+ plot1.setDomainStep(XYStepMode.SUBDIVIDE, years.length);
+
+ // customize our domain/range labels
+ plot1.setDomainLabel("Year");
+ plot1.setRangeLabel("# of Sightings");
+
+ // get rid of decimal points in our range labels:
+ plot1.setRangeValueFormat(new DecimalFormat("0"));
+
+ plot1.setDomainValueFormat(new Format() {
+
+ // create a simple date format that draws on the year portion of our timestamp.
+ // see http://download.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html
+ // for a full description of SimpleDateFormat.
+ private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy");
+
+ @Override
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+
+ // because our timestamps are in seconds and SimpleDateFormat expects milliseconds
+ // we multiply our timestamp by 1000:
+ long timestamp = ((Number) obj).longValue() * 1000;
+ Date date = new Date(timestamp);
+ return dateFormat.format(date, toAppendTo, pos);
+ }
+
+ @Override
+ public Object parseObject(String source, ParsePosition pos) {
+ return null;
+
+ }
+ });
+
+ // by default, AndroidPlot displays developer guides to aid in laying out your plot.
+ // To get rid of them call disableAllMarkup():
+ //plot1.disableAllMarkup();
+ }
+} \ No newline at end of file
diff --git a/Examples/DemoApp/src/com/androidplot/demos/TouchZoomExampleActivity.java b/Examples/DemoApp/src/com/androidplot/demos/TouchZoomExampleActivity.java
new file mode 100644
index 0000000..65c5c71
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/TouchZoomExampleActivity.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import java.text.DecimalFormat;
+import java.util.Random;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.Button;
+
+import com.androidplot.Plot;
+import com.androidplot.xy.BoundaryMode;
+import com.androidplot.xy.LineAndPointFormatter;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYPlot;
+
+/***********************************
+ * @author David Buezas (david.buezas at gmail.com)
+ * Feel free to copy, modify and use the source as it fits you.
+ * 09/27/2012 nfellows - updated for 0.5.1 and made a few simplifications
+ */
+public class TouchZoomExampleActivity extends Activity implements OnTouchListener {
+ private static final int SERIES_SIZE = 200;
+ private XYPlot mySimpleXYPlot;
+ private Button resetButton;
+ private SimpleXYSeries[] series = null;
+ private PointF minXY;
+ private PointF maxXY;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.touch_zoom_example);
+ resetButton = (Button) findViewById(R.id.resetButton);
+ resetButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ minXY.x = series[0].getX(0).floatValue();
+ maxXY.x = series[3].getX(series[3].size() - 1).floatValue();
+ mySimpleXYPlot.setDomainBoundaries(minXY.x, maxXY.x, BoundaryMode.FIXED);
+
+ // pre 0.5.1 users should use postRedraw() instead.
+ mySimpleXYPlot.redraw();
+ }
+ });
+ mySimpleXYPlot = (XYPlot) findViewById(R.id.mySimpleXYPlot);
+ mySimpleXYPlot.setOnTouchListener(this);
+ mySimpleXYPlot.getGraphWidget().setTicksPerRangeLabel(2);
+ mySimpleXYPlot.getGraphWidget().setTicksPerDomainLabel(2);
+ mySimpleXYPlot.getGraphWidget().getBackgroundPaint().setColor(Color.TRANSPARENT);
+ mySimpleXYPlot.getGraphWidget().setRangeValueFormat(
+ new DecimalFormat("#####"));
+ mySimpleXYPlot.getGraphWidget().setDomainValueFormat(
+ new DecimalFormat("#####.#"));
+ mySimpleXYPlot.getGraphWidget().setRangeLabelWidth(25);
+ mySimpleXYPlot.setRangeLabel("");
+ mySimpleXYPlot.setDomainLabel("");
+
+ mySimpleXYPlot.setBorderStyle(Plot.BorderStyle.NONE, null, null);
+ //mySimpleXYPlot.disableAllMarkup();
+ series = new SimpleXYSeries[4];
+ int scale = 1;
+ for (int i = 0; i < 4; i++, scale *= 5) {
+ series[i] = new SimpleXYSeries("S" + i);
+ populateSeries(series[i], scale);
+ }
+ mySimpleXYPlot.addSeries(series[3],
+ new LineAndPointFormatter(Color.rgb(50, 0, 0), null,
+ Color.rgb(100, 0, 0), null));
+ mySimpleXYPlot.addSeries(series[2],
+ new LineAndPointFormatter(Color.rgb(50, 50, 0), null,
+ Color.rgb(100, 100, 0), null));
+ mySimpleXYPlot.addSeries(series[1],
+ new LineAndPointFormatter(Color.rgb(0, 50, 0), null,
+ Color.rgb(0, 100, 0), null));
+ mySimpleXYPlot.addSeries(series[0],
+ new LineAndPointFormatter(Color.rgb(0, 0, 0), null,
+ Color.rgb(0, 0, 150), null));
+ mySimpleXYPlot.redraw();
+ mySimpleXYPlot.calculateMinMaxVals();
+ minXY = new PointF(mySimpleXYPlot.getCalculatedMinX().floatValue(),
+ mySimpleXYPlot.getCalculatedMinY().floatValue());
+ maxXY = new PointF(mySimpleXYPlot.getCalculatedMaxX().floatValue(),
+ mySimpleXYPlot.getCalculatedMaxY().floatValue());
+ }
+
+ private void populateSeries(SimpleXYSeries series, int max) {
+ Random r = new Random();
+ for(int i = 0; i < SERIES_SIZE; i++) {
+ series.addLast(i, r.nextInt(max));
+ }
+ }
+
+ // Definition of the touch states
+ static final int NONE = 0;
+ static final int ONE_FINGER_DRAG = 1;
+ static final int TWO_FINGERS_DRAG = 2;
+ int mode = NONE;
+
+ PointF firstFinger;
+ float distBetweenFingers;
+ boolean stopThread = false;
+
+ @Override
+ public boolean onTouch(View arg0, MotionEvent event) {
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: // Start gesture
+ firstFinger = new PointF(event.getX(), event.getY());
+ mode = ONE_FINGER_DRAG;
+ stopThread = true;
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ mode = NONE;
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN: // second finger
+ distBetweenFingers = spacing(event);
+ // the distance check is done to avoid false alarms
+ if (distBetweenFingers > 5f) {
+ mode = TWO_FINGERS_DRAG;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mode == ONE_FINGER_DRAG) {
+ PointF oldFirstFinger = firstFinger;
+ firstFinger = new PointF(event.getX(), event.getY());
+ scroll(oldFirstFinger.x - firstFinger.x);
+ mySimpleXYPlot.setDomainBoundaries(minXY.x, maxXY.x,
+ BoundaryMode.FIXED);
+ mySimpleXYPlot.redraw();
+
+ } else if (mode == TWO_FINGERS_DRAG) {
+ float oldDist = distBetweenFingers;
+ distBetweenFingers = spacing(event);
+ zoom(oldDist / distBetweenFingers);
+ mySimpleXYPlot.setDomainBoundaries(minXY.x, maxXY.x,
+ BoundaryMode.FIXED);
+ mySimpleXYPlot.redraw();
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void zoom(float scale) {
+ float domainSpan = maxXY.x - minXY.x;
+ float domainMidPoint = maxXY.x - domainSpan / 2.0f;
+ float offset = domainSpan * scale / 2.0f;
+
+ minXY.x = domainMidPoint - offset;
+ maxXY.x = domainMidPoint + offset;
+
+ minXY.x = Math.min(minXY.x, series[3].getX(series[3].size() - 3)
+ .floatValue());
+ maxXY.x = Math.max(maxXY.x, series[0].getX(1).floatValue());
+ clampToDomainBounds(domainSpan);
+ }
+
+ private void scroll(float pan) {
+ float domainSpan = maxXY.x - minXY.x;
+ float step = domainSpan / mySimpleXYPlot.getWidth();
+ float offset = pan * step;
+ minXY.x = minXY.x + offset;
+ maxXY.x = maxXY.x + offset;
+ clampToDomainBounds(domainSpan);
+ }
+
+ private void clampToDomainBounds(float domainSpan) {
+ float leftBoundary = series[0].getX(0).floatValue();
+ float rightBoundary = series[3].getX(series[3].size() - 1).floatValue();
+ // enforce left scroll boundary:
+ if (minXY.x < leftBoundary) {
+ minXY.x = leftBoundary;
+ maxXY.x = leftBoundary + domainSpan;
+ } else if (maxXY.x > series[3].getX(series[3].size() - 1).floatValue()) {
+ maxXY.x = rightBoundary;
+ minXY.x = rightBoundary - domainSpan;
+ }
+ }
+
+ private float spacing(MotionEvent event) {
+ float x = event.getX(0) - event.getX(1);
+ float y = event.getY(0) - event.getY(1);
+ return FloatMath.sqrt(x * x + y * y);
+ }
+}
+
diff --git a/Examples/DemoApp/src/com/androidplot/demos/XYPlotWithBgImgActivity.java b/Examples/DemoApp/src/com/androidplot/demos/XYPlotWithBgImgActivity.java
new file mode 100644
index 0000000..2f9de9c
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/XYPlotWithBgImgActivity.java
@@ -0,0 +1,131 @@
+package com.androidplot.demos;
+
+import android.app.Activity;
+import android.graphics.*;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ToggleButton;
+import com.androidplot.xy.*;
+
+import java.text.DecimalFormat;
+import java.util.Arrays;
+
+public class XYPlotWithBgImgActivity extends Activity {
+ private static final String TAG = XYPlotWithBgImgActivity.class.getName();
+
+ private int SERIES_LEN = 50;
+ private Shader WHITE_SHADER = new LinearGradient(1, 1, 1, 1, Color.WHITE, Color.WHITE, Shader.TileMode.REPEAT);
+
+ private XYPlot plot;
+ private SimpleXYSeries series;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.xy_plot_with_bq_img_example);
+
+ plot = (XYPlot) findViewById(R.id.graph_metrics);
+
+ //For debugging.
+ //plot.setMarkupEnabled(true);
+
+ // Format Graph
+ plot.getGraphWidget().getBackgroundPaint().setColor(Color.TRANSPARENT);
+ plot.getGraphWidget().getGridBackgroundPaint().setShader(WHITE_SHADER);
+ plot.getGraphWidget().getDomainGridLinePaint().setColor(Color.BLACK);
+ plot.getGraphWidget().getDomainGridLinePaint().setPathEffect(new DashPathEffect(new float[]{3, 3}, 1));
+ plot.getGraphWidget().getRangeGridLinePaint().setColor(Color.BLACK);
+ plot.getGraphWidget().getRangeGridLinePaint().setPathEffect(new DashPathEffect(new float[]{3, 3}, 1));
+ plot.getGraphWidget().getDomainOriginLinePaint().setColor(Color.BLACK);
+ plot.getGraphWidget().getRangeOriginLinePaint().setColor(Color.BLACK);
+ //plot.getGraphWidget().setMarginTop(10);
+
+ // Customize domain and range labels.
+ plot.setDomainLabel("x-vals");
+ plot.setRangeLabel("y-vals");
+ plot.setRangeValueFormat(new DecimalFormat("0"));
+
+ // Make the domain and range step correctly
+ plot.setRangeBoundaries(40, 160, BoundaryMode.FIXED);
+ plot.setRangeStep(XYStepMode.INCREMENT_BY_VAL, 20);
+ plot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 60);
+ plot.setTicksPerDomainLabel(2);
+
+ series = (SimpleXYSeries) getSeries();
+ LineAndPointFormatter lpFormat = new LineAndPointFormatter(
+ Color.BLACK,
+ Color.BLACK,
+ null, // No fill
+ new PointLabelFormatter(Color.TRANSPARENT) // Don't show text at points
+ );
+ plot.addSeries(series, lpFormat);
+ plot.redraw();
+ }
+
+ private SimpleXYSeries getSeries() {
+ Integer[] xVals = new Integer[SERIES_LEN];
+ Integer[] yVals = new Integer[SERIES_LEN];
+
+ xVals[0] = 0;
+ yVals[0] = 0;
+
+ for (int i = 1; i < SERIES_LEN; i += 1){
+ xVals[i] = xVals[i-1] + (int)(Math.random() * i);
+ yVals[i] = (int)(Math.random() * 140);
+ }
+
+ return new SimpleXYSeries(
+ Arrays.asList(xVals),
+ Arrays.asList(yVals),
+ "Sample Series");
+ }
+
+ public void onGraphStyleToggle(View v) {
+ boolean styleOn = ((ToggleButton) v).isChecked();
+
+ /*RectF graphRect = plot.getGraphWidget().getGridRect();
+ float segmentSize = 1.0f/6.0f;
+ LinearGradient lg = new LinearGradient(
+ 0,
+ graphRect.top,
+ 0,
+ graphRect.bottom,
+ new int[]{
+ Color.RED,
+ Color.YELLOW,
+ Color.GREEN,
+ Color.WHITE},
+ new float[]{
+ 0,
+ segmentSize*2,
+ segmentSize*3,
+ segmentSize*5
+ },
+ Shader.TileMode.REPEAT
+ );
+ plot.getGraphWidget().getGridBackgroundPaint().setShader(lg);*/
+
+ RectF rect = plot.getGraphWidget().getGridRect();
+ BitmapShader myShader = new BitmapShader(
+ Bitmap.createScaledBitmap(
+ BitmapFactory.decodeResource(
+ getResources(),
+ R.drawable.graph_background),
+ 1,
+ (int) rect.height(),
+ false),
+ Shader.TileMode.REPEAT,
+ Shader.TileMode.REPEAT);
+ Matrix m = new Matrix();
+ m.setTranslate(rect.left, rect.top);
+ myShader.setLocalMatrix(m);
+ if (styleOn)
+ plot.getGraphWidget().getGridBackgroundPaint().setShader(
+ myShader);
+ else
+ plot.getGraphWidget().getGridBackgroundPaint().setShader(WHITE_SHADER);
+
+ plot.redraw();
+
+ }
+}
diff --git a/Examples/DemoApp/src/com/androidplot/demos/XYRegionExampleActivity.java b/Examples/DemoApp/src/com/androidplot/demos/XYRegionExampleActivity.java
new file mode 100644
index 0000000..67e1b1b
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/XYRegionExampleActivity.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.ui.*;
+import com.androidplot.xy.*;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Arrays;
+
+/**
+ * Demonstration of the usage of Marker and RectRegion.
+ */
+public class XYRegionExampleActivity extends Activity {
+
+ private static final float HOME_RUN_DIST = 325;
+ private static final int LINE_THICKNESS_DP = 2;
+ private static final int POINT_SIZE_DP = 6;
+ private XYPlot plot;
+ private final Number[] timHits = {105, 252, 220, 350, 12, 250, 353};
+ private final Number[] nickHits = {110, 191, 61, 371, 289, 101, 10};
+ private final Number[] joeHits = {25, 375, 364, 128, 178, 289, 346};
+ private final Number[] jamesHits = {250, 285, 295, 211, 311, 365, 241};
+ private LineAndPointFormatter timFormatter;
+ private LineAndPointFormatter nickFormatter;
+ private LineAndPointFormatter joeFormatter;
+ private LineAndPointFormatter jamesFormatter;
+
+ private XYSeries timSeries;
+ private XYSeries nickSeries;
+ private XYSeries joeSeries;
+ private XYSeries jamesSeries;
+
+ private RectRegion shortRegion;
+ private RectRegion warmupRegion;
+ private RectRegion homeRunRegion;
+
+ //private XYRegionFormatter rf1;
+ private XYRegionFormatter shortRegionFormatter;
+ private XYRegionFormatter warmupRegionFormatter;
+ private XYRegionFormatter homeRunRegionFormatter;
+ //private XYRegionFormatter rf5;
+
+ private CheckBox timCB;
+ private CheckBox nickCB;
+ private CheckBox joeCB;
+ private CheckBox jamesCB;
+
+ private CheckBox r2CheckBox;
+ private CheckBox r3CheckBox;
+ private CheckBox r4CheckBox;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.xyregion_example);
+ plot = (XYPlot) findViewById(R.id.xyRegionExamplePlot);
+ timCB = (CheckBox) findViewById(R.id.s1CheckBox);
+ timCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onS1CheckBoxClicked();
+ }
+ });
+
+ nickCB = (CheckBox) findViewById(R.id.s2CheckBox);
+ nickCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onS2CheckBoxClicked();
+ }
+ });
+
+ joeCB = (CheckBox) findViewById(R.id.s3CheckBox);
+ joeCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onS3CheckBoxClicked();
+ }
+ });
+
+ jamesCB = (CheckBox) findViewById(R.id.s4CheckBox);
+ jamesCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onS4CheckBoxClicked();
+ }
+ });
+
+
+
+ r2CheckBox = (CheckBox) findViewById(R.id.r2CheckBox);
+ r2CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onCheckBoxClicked(r2CheckBox, timFormatter, shortRegionFormatter, shortRegion);
+ }
+ });
+
+ r3CheckBox = (CheckBox) findViewById(R.id.r3CheckBox);
+ r3CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onCheckBoxClicked(r3CheckBox, nickFormatter, warmupRegionFormatter, warmupRegion);
+ }
+ });
+
+ r4CheckBox = (CheckBox) findViewById(R.id.r4CheckBox);
+ r4CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ onCheckBoxClicked(r4CheckBox, nickFormatter, homeRunRegionFormatter, homeRunRegion);
+ }
+ });
+
+ seriesSetup();
+ markerSetup();
+ axisLabelSetup();
+ regionSetup();
+ makePlotPretty();
+ }
+
+ private void onS1CheckBoxClicked() {
+ if(timCB.isChecked()) {
+ plot.addSeries(timSeries, timFormatter);
+ r2CheckBox.setEnabled(true);
+ } else {
+ plot.removeSeries(timSeries);
+ r2CheckBox.setEnabled(false);
+ }
+ plot.redraw();
+ }
+
+ private void onS2CheckBoxClicked() {
+ if(nickCB.isChecked()) {
+ plot.addSeries(nickSeries, nickFormatter);
+ r3CheckBox.setEnabled(true);
+ r4CheckBox.setEnabled(true);
+ } else {
+ plot.removeSeries(nickSeries);
+ r3CheckBox.setEnabled(false);
+ r4CheckBox.setEnabled(false);
+ }
+ plot.redraw();
+ }
+
+ private void onS3CheckBoxClicked() {
+ if(joeCB.isChecked()) {
+ plot.addSeries(joeSeries, joeFormatter);
+ } else {
+ plot.removeSeries(joeSeries);
+ }
+ plot.redraw();
+ }
+
+ private void onS4CheckBoxClicked() {
+ if(jamesCB.isChecked()) {
+ plot.addSeries(jamesSeries, jamesFormatter);
+ } else {
+ plot.removeSeries(jamesSeries);
+ }
+ plot.redraw();
+ }
+
+ /**
+ * Processes a check box event
+ * @param cb The checkbox event origin
+ * @param lpf LineAndPointFormatter with which rr and rf are to be added/removed
+ * @param rf The XYRegionFormatter with which rr should be rendered
+ * @param rr The RectRegion to add/remove
+ */
+ private void onCheckBoxClicked(CheckBox cb, LineAndPointFormatter lpf,
+ XYRegionFormatter rf, RectRegion rr) {
+ if(cb.isChecked()) {
+ lpf.removeRegion(rr);
+ } else {
+ lpf.addRegion(rr, rf);
+ }
+ }
+
+ /**
+ * Cleans up the plot's general layout and color scheme
+ */
+ private void makePlotPretty() {
+ // use a 2x5 grid with room for 10 items:
+ plot.getLegendWidget().setTableModel(new DynamicTableModel(4, 2));
+
+ // add a semi-transparent black background to the legend
+ // so it's easier to see overlaid on top of our plot:
+ Paint bgPaint = new Paint();
+ bgPaint.setColor(Color.BLACK);
+ bgPaint.setStyle(Paint.Style.FILL);
+ bgPaint.setAlpha(40);
+
+ plot.getLegendWidget().setBackgroundPaint(bgPaint);
+
+ // adjust the padding of the legend widget to look a little nicer:
+ plot.getLegendWidget().setPadding(5, 5, 5, 5);
+
+ plot.setRangeValueFormat(new NumberFormat() {
+ @Override
+ public StringBuffer format(double value, StringBuffer buffer, FieldPosition field) {
+ return new StringBuffer(value + "'");
+ }
+
+ @Override
+ public StringBuffer format(long value, StringBuffer buffer, FieldPosition field) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ @Override
+ public Number parse(String string, ParsePosition position) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+ });
+
+ plot.setDomainValueFormat(new DecimalFormat("#"));
+
+ plot.getLegendWidget().setWidth(PixelUtils.dpToPix(100), SizeLayoutType.FILL);
+
+
+ // adjust the legend size so there is enough room
+ // to draw the new legend grid:
+ //plot.getLegendWidget().getHeightMetric().setLayoutType(SizeLayoutType.ABSOLUTE);
+ //plot.getLegendWidget().getWidthMetric().setLayoutType(SizeLayoutType.ABSOLUTE);
+ //plot.getLegendWidget().setSize(
+ // new SizeMetrics(70, SizeLayoutType.ABSOLUTE, 80, SizeLayoutType.ABSOLUTE));
+
+ // reposition the grid so that it rests above the bottom-left
+ // edge of the graph widget:
+
+ plot.getLegendWidget().position(
+ 125,
+ XLayoutStyle.ABSOLUTE_FROM_LEFT,
+ 65,
+ YLayoutStyle.ABSOLUTE_FROM_TOP,
+ AnchorPosition.LEFT_TOP);
+
+ plot.getGraphWidget().setRangeLabelHorizontalOffset(-1);
+
+ // add enough space to ensure range value labels arent cut off on the left/right:
+ plot.getGraphWidget().setRangeLabelWidth(25);
+
+ // add enough space to make sure domain value labels arent cut off on the bottom:
+ plot.getGraphWidget().setDomainLabelWidth(15);
+
+ plot.getGraphWidget().setDomainLabelVerticalOffset(-6);
+
+ plot.setRangeBoundaries(0, BoundaryMode.FIXED, 500, BoundaryMode.FIXED);
+ }
+
+ /**
+ * Create 4 XYSeries from the values defined above add add them to the plot.
+ * Also add some arbitrary regions.
+ */
+ private void seriesSetup() {
+
+
+ // TIM
+ timFormatter = new LineAndPointFormatter(
+ Color.rgb(100, 25, 20),
+ Color.rgb(100, 25, 20),
+ null, null);
+ timFormatter.getLinePaint().setStrokeWidth(PixelUtils.dpToPix(LINE_THICKNESS_DP));
+ timFormatter.getVertexPaint().setStrokeWidth(PixelUtils.dpToPix(POINT_SIZE_DP));
+
+ timSeries = new SimpleXYSeries(Arrays.asList(timHits),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Tim");
+
+ plot.addSeries(timSeries, timFormatter);
+
+ // SERIES #2:
+ nickFormatter = new LineAndPointFormatter(
+ Color.rgb(100, 25, 200),
+ Color.rgb(100, 25, 200),
+ null, null);
+ nickFormatter.getLinePaint().setStrokeWidth(PixelUtils.dpToPix(LINE_THICKNESS_DP));
+ nickFormatter.getVertexPaint().setStrokeWidth(PixelUtils.dpToPix(POINT_SIZE_DP));
+
+
+
+ nickSeries = new SimpleXYSeries(Arrays.asList(nickHits),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Nick");
+
+ plot.addSeries(nickSeries, nickFormatter);
+
+ // SERIES #3:
+ joeFormatter = new LineAndPointFormatter(
+ Color.rgb(200, 25, 200),
+ Color.rgb(200, 25, 200),
+ null, null);
+ joeFormatter.getLinePaint().setStrokeWidth(PixelUtils.dpToPix(LINE_THICKNESS_DP));
+ joeFormatter.getVertexPaint().setStrokeWidth(PixelUtils.dpToPix(POINT_SIZE_DP));
+
+ joeSeries = new SimpleXYSeries(Arrays.asList(joeHits),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Joe");
+
+ plot.addSeries(joeSeries, joeFormatter);
+
+ // SERIES #4:
+ jamesFormatter = new LineAndPointFormatter(
+ Color.rgb(220, 25, 20),
+ Color.rgb(220, 25, 20),
+ null, null);
+
+ jamesFormatter.getLinePaint().setStrokeWidth(PixelUtils.dpToPix(LINE_THICKNESS_DP));
+ jamesFormatter.getVertexPaint().setStrokeWidth(PixelUtils.dpToPix(POINT_SIZE_DP));
+
+ jamesSeries = new SimpleXYSeries(Arrays.asList(jamesHits),
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,"James");
+ plot.addSeries(jamesSeries, jamesFormatter);
+
+ plot.setRangeStep(XYStepMode.INCREMENT_BY_VAL, 100);
+ //plot.setTicksPerRangeLabel(1);
+ plot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 1);
+ }
+
+ /**
+ * Add some color coded regions to our axis labels.
+ */
+ private void axisLabelSetup() {
+ // DOMAIN
+ plot.getGraphWidget().addDomainAxisValueLabelRegion(
+ Double.NEGATIVE_INFINITY, 2, new AxisValueLabelFormatter(Color.GRAY));
+ plot.getGraphWidget().addDomainAxisValueLabelRegion(
+ 2, Double.POSITIVE_INFINITY, new AxisValueLabelFormatter(Color.WHITE));
+ // RANGE
+ plot.getGraphWidget().addRangeAxisValueLabelRegion(
+ Double.NEGATIVE_INFINITY, HOME_RUN_DIST, new AxisValueLabelFormatter(Color.RED));
+ plot.getGraphWidget().addRangeAxisValueLabelRegion(
+ HOME_RUN_DIST, Double.POSITIVE_INFINITY, new AxisValueLabelFormatter(Color.GREEN));
+ }
+
+ /**
+ * Add some markers to our plot.
+ */
+ private void markerSetup() {
+
+ YValueMarker fenwayLfMarker = new YValueMarker(
+ 380, // y-val to mark
+ "Fenway Park LF Wall", // marker label
+ new XPositionMetric( // object instance to set text positioning on the marker
+ PixelUtils.dpToPix(5), // 5dp offset
+ XLayoutStyle.ABSOLUTE_FROM_RIGHT), // offset origin
+ Color.BLUE, // line paint color
+ Color.BLUE); // text paint color
+
+ YValueMarker attRfMarker = new YValueMarker(
+ 309, // y-val to mark
+ "ATT Park RF Wall", // marker label
+ new XPositionMetric( // object instance to set text positioning on the marker
+ PixelUtils.dpToPix(5), // 5dp offset
+ XLayoutStyle.ABSOLUTE_FROM_RIGHT), // offset origin
+ Color.CYAN, // line paint color
+ Color.CYAN); // text paint color
+
+
+ fenwayLfMarker.getTextPaint().setTextSize(PixelUtils.dpToPix(14));
+ attRfMarker.getTextPaint().setTextSize(PixelUtils.dpToPix(14));
+
+ DashPathEffect dpe = new DashPathEffect(
+ new float[]{PixelUtils.dpToPix(2), PixelUtils.dpToPix(2)}, 0);
+
+ fenwayLfMarker.getLinePaint().setPathEffect(dpe);
+ attRfMarker.getLinePaint().setPathEffect(dpe);
+
+ plot.addMarker(fenwayLfMarker);
+ plot.addMarker(attRfMarker);
+ }
+
+ /**
+ * Add some fill regions to our series data
+ */
+ private void regionSetup() {
+
+
+ // and another region:
+ shortRegionFormatter = new XYRegionFormatter(Color.RED);
+ shortRegionFormatter.getPaint().setAlpha(75);
+ shortRegion = new RectRegion(2, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, HOME_RUN_DIST, "Short");
+ timFormatter.addRegion(shortRegion, shortRegionFormatter);
+ nickFormatter.addRegion(shortRegion, shortRegionFormatter);
+ joeFormatter.addRegion(shortRegion, shortRegionFormatter);
+ jamesFormatter.addRegion(shortRegion, shortRegionFormatter);
+
+ // the next three regions are horizontal regions with minY/maxY
+ // set to negative and positive infinity respectively.
+ warmupRegionFormatter = new XYRegionFormatter(Color.WHITE);
+ warmupRegionFormatter.getPaint().setAlpha(75);
+
+ warmupRegion = new RectRegion(0, 2, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, "Warmup");
+ timFormatter.addRegion(warmupRegion, warmupRegionFormatter);
+ nickFormatter.addRegion(warmupRegion, warmupRegionFormatter);
+ joeFormatter.addRegion(warmupRegion, warmupRegionFormatter);
+ jamesFormatter.addRegion(warmupRegion, warmupRegionFormatter);
+
+ homeRunRegionFormatter = new XYRegionFormatter(Color.GREEN);
+ homeRunRegionFormatter.getPaint().setAlpha(75);
+
+ homeRunRegion = new RectRegion(2, Double.POSITIVE_INFINITY, HOME_RUN_DIST, Double.POSITIVE_INFINITY, "H. Run");
+ timFormatter.addRegion(homeRunRegion, homeRunRegionFormatter);
+ nickFormatter.addRegion(homeRunRegion, homeRunRegionFormatter);
+ joeFormatter.addRegion(homeRunRegion, homeRunRegionFormatter);
+ jamesFormatter.addRegion(homeRunRegion, homeRunRegionFormatter);
+
+ nickFormatter.setFillDirection(FillDirection.RANGE_ORIGIN);
+ }
+} \ No newline at end of file
diff --git a/Examples/DemoApp/src/com/androidplot/demos/widget/DemoAppWidgetProvider.java b/Examples/DemoApp/src/com/androidplot/demos/widget/DemoAppWidgetProvider.java
new file mode 100644
index 0000000..f44a277
--- /dev/null
+++ b/Examples/DemoApp/src/com/androidplot/demos/widget/DemoAppWidgetProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 AndroidPlot.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.androidplot.demos.widget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.widget.RemoteViews;
+import com.androidplot.demos.R;
+import com.androidplot.xy.XYSeries;
+import com.androidplot.xy.LineAndPointFormatter;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYPlot;
+
+import java.util.Arrays;
+
+public class DemoAppWidgetProvider extends AppWidgetProvider {
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ for (int widgetId : appWidgetIds) {
+ XYPlot plot = new XYPlot(context, "Widget Example");
+ //plot.getLayoutParams().height = 100;
+ //plot.getLayoutParams().width = 100;
+ plot.measure(150,150);
+ plot.layout(0,0,150,150);
+ plot.setDrawingCacheEnabled(true);
+
+ // Create a couple arrays of y-values to plot:
+ Number[] series1Numbers = {1, 8, 5, 2, 7, 4};
+ Number[] series2Numbers = {4, 6, 3, 8, 2, 10};
+
+ // Turn the above arrays into XYSeries':
+ XYSeries series1 = new SimpleXYSeries(
+ Arrays.asList(series1Numbers), // SimpleXYSeries takes a List so turn our array into a List
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, // Y_VALS_ONLY means use the element index as the x value
+ "Series1"); // Set the display title of the series
+
+ // same as above
+ XYSeries series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series2");
+
+ // Create a formatter to use for drawing a series using LineAndPointRenderer:
+ LineAndPointFormatter series1Format = new LineAndPointFormatter(
+ Color.rgb(0, 200, 0), // line color
+ Color.rgb(0, 100, 0), // point color
+ null, null); // fill color (none)
+
+ // add a new series' to the xyplot:
+ plot.addSeries(series1, series1Format);
+
+ // same as above:
+ plot.addSeries(series2,
+ new LineAndPointFormatter(
+ Color.rgb(0, 0, 200), Color.rgb(0, 0, 100), null, null));
+
+
+ // reduce the number of range labels
+ plot.setTicksPerRangeLabel(3);
+
+ // by default, AndroidPlot displays developer guides to aid in laying out your plot.
+ // To get rid of them call disableAllMarkup():
+ //plot.disableAllMarkup();
+
+ Bitmap bmp = plot.getDrawingCache();
+
+ RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.demo_app_widget);
+ rv.setBitmap(R.id.imgView, "setImageBitmap", bmp);
+ appWidgetManager.updateAppWidget(widgetId, rv);
+ }
+ }
+}
diff --git a/Examples/pom.xml b/Examples/pom.xml
new file mode 100644
index 0000000..c706493
--- /dev/null
+++ b/Examples/pom.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>androidplot</artifactId>
+ <groupId>com.androidplot</groupId>
+ <version>0.6.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>androidplot-examples</artifactId>
+ <name>AndroidPlot-Examples</name>
+ <packaging>pom</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ </dependency>
+ <!--<dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <version>2.1_r1</version>
+ <scope>provided</scope>
+ </dependency>-->
+ <!-- TODO: figure out how to use this dependency without breaking the clean target;
+ used as-is, the clean target can fail because the dependency is erased by maven.
+ <dependency>
+ <groupId>com.androidplot</groupId>
+ <artifactId>androidplot-core</artifactId>
+ <version>${applicationVersion}</version>
+ </dependency>
+ -->
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <id>compile</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+
+ <configuration>
+ <target name="build-examples">
+ <!--
+ <property name="key.store" value="test"/>
+ <property name="key.alias" value="test"/>
+ -->
+ <!--<property name="sdk.dir" value="C:/android-sdk"/>-->
+ <property environment="env"/>
+ <property name="sdk.dir" value="${env.ANDROID_HOME}"/>
+ <!-- todo: include only the release jar, not everything inside
+ the artifacts dir.-->
+ <!--<property name="jar.libs.dir" value="../../AndroidPlot-Core/target/obfuscated/"/>-->
+ <property name="build.compiler" value="extJavac"/>
+ <property environment="env"/>
+ <property name="sdk.dir" value="${env.ANDROID_HOME}"/>
+ <!--<property name="apkeystore" value="${env.AP_KEYSTORE}"/>-->
+
+ <!-- DemoApp-->
+ <ant antfile="build.xml" target="debug" dir="DemoApp"/>
+
+ <!-- DynamicXYPlotExample-->
+ <ant antfile="build.xml" target="debug" dir="DynamicXYPlotExample"/>
+
+ <!-- HelperExample-->
+ <ant antfile="build.xml" target="debug" dir="HelperExample"/>
+
+ <!-- OrientationSensorExample-->
+ <ant antfile="build.xml" target="debug" dir="OrientationSensorExample"/>
+
+ <!-- SimpleXYPlotExample-->
+ <ant antfile="build.xml" target="debug" dir="SimpleXYPlotExample"/>
+
+ <!-- StepChartExample-->
+ <ant antfile="build.xml" target="debug" dir="StepChartExample"/>
+
+ <!-- TimedXYPlotExample
+ <ant antfile="build.xml" target="debug" dir="TimedXYPlotExample"/>
+ -->
+ </target>
+
+ </configuration>
+
+ </execution>
+ <execution>
+ <id>clean</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <tasks>
+ <property environment="env"/>
+ <property name="sdk.dir" value="${env.ANDROID_HOME}"/>
+
+ <!-- DemoApp-->
+ <ant antfile="build.xml" target="clean" dir="DemoApp"/>
+
+ <!-- DynamicXYPlotExample-->
+ <ant antfile="build.xml" target="clean" dir="DynamicXYPlotExample"/>
+
+ <!-- HelperExample-->
+ <ant antfile="build.xml" target="clean" dir="HelperExample"/>
+
+ <!-- OrientationSensorExample-->
+ <ant antfile="build.xml" target="clean" dir="OrientationSensorExample"/>
+
+ <!-- SimpleXYPlotExample-->
+ <ant antfile="build.xml" target="clean" dir="SimpleXYPlotExample"/>
+
+ <!-- StepChartExample-->
+ <ant antfile="build.xml" target="clean" dir="StepChartExample"/>
+
+ <!-- TimedXYPlotExample
+ <ant antfile="build.xml" target="clean" dir="TimedXYPlotExample"/>
+-->
+ </tasks>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+
+</project> \ No newline at end of file
diff --git a/androidplot.iml b/androidplot.iml
new file mode 100644
index 0000000..9b79bdb
--- /dev/null
+++ b/androidplot.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..fd4c3de
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2012 AndroidPlot.com
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>7</version>
+ </parent>
+
+ <groupId>com.androidplot</groupId>
+ <artifactId>androidplot</artifactId>
+ <version>0.6.0</version>
+ <name>AndroidPlot-Parent</name>
+ <description>A charting library for the Android platform</description>
+ <url>http://androidplot.com</url>
+ <inceptionYear>2010</inceptionYear>
+ <organization>
+ <name>androidplot.com</name>
+ <url>http://androidplot.com</url>
+ </organization>
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ <comments>A business-friendly OSS license</comments>
+ </license>
+ </licenses>
+ <developers>
+ <developer>
+ <id>Nick</id>
+ <name>Nick Fellows</name>
+ <email>nick@androidplot.com</email>
+ <organization>AndroidPlot</organization>
+ <organizationUrl>http://androidplot.com</organizationUrl>
+ <roles>
+ <role>architect</role>
+ <role>developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <id>Tim</id>
+ <name>Tim Hepner</name>
+ <email>tim@androidplot.com</email>
+ <organization>AndroidPlot</organization>
+ <organizationUrl>http://androidplot.com</organizationUrl>
+ <roles>
+ <role>developer</role>
+ </roles>
+ </developer>
+ </developers>
+ <packaging>pom</packaging>
+
+ <properties>
+ <!--<applicationVersion>0.5.1-SNAPSHOT</applicationVersion>-->
+ <examplesDir>Examples</examplesDir>
+ <!--<obfuscatedJarPath>${project.build.directory}/obfuscated</obfuscatedJarPath>
+ <obfuscatedJarName>${project.build.finalName}.jar</obfuscatedJarName>-->
+ </properties>
+
+ <modules>
+ <module>AndroidPlot-Core</module>
+ <!--<module>Examples</module>-->
+ <module>Examples/DemoApp</module>
+ </modules>
+
+ <!--<repositories>
+ <repository>
+ <id>java.net2</id>
+ <url>http://download.java.net/maven/2/</url>
+ </repository>
+ </repositories>-->
+
+ <scm>
+ <connection>scm:git:https://bitbucket.org/androidplot/androidplot.git</connection>
+ <developerConnection>scm:git:https://bitbucket.org/androidplot/androidplot.git</developerConnection>
+ <url>https://bitbucket.org/androidplot/androidplot.git</url>
+ <tag>0.6.0</tag>
+ </scm>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <tagNameFormat>v@{project.version}</tagNameFormat>
+ <!-- Added to fix an issue with hidden gpg signing prompts
+ described here: http://jira.codehaus.org/browse/MGPG-9-->
+ <mavenExecutorId>forked-path</mavenExecutorId>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.7</version>
+ <!--<configuration>
+ <skip>true</skip>
+ </configuration>-->
+ </plugin>
+ </plugins>
+ </build>
+
+ <!-- dependencies to be inherited -->
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <version>4.1.1.4</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+</project> \ No newline at end of file