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
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