项目第一次创建并提交

develop
px 2 years ago
commit 6ec8f7aa22

15
.gitignore vendored

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/AYCrashGuard" />
<option value="$PROJECT_DIR$/MPChartLib" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/base" />
<option value="$PROJECT_DIR$/common" />
<option value="$PROJECT_DIR$/network" />
<option value="$PROJECT_DIR$/videocompressor" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven4" />
<option name="name" value="maven4" />
<option name="url" value="https://www.jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://maven.aliyun.com/repository/gradle-plugin/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven6" />
<option name="name" value="maven6" />
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://maven.aliyun.com/repository/google/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://maven.aliyun.com/repository/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven7" />
<option name="name" value="maven7" />
<option name="url" value="https://oss.sonatype.org/content/repositories/snapshots" />
</remote-repository>
<remote-repository>
<option name="id" value="maven5" />
<option name="name" value="maven5" />
<option name="url" value="https://developer.huawei.com/repo/" />
</remote-repository>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.6.21" />
</component>
</project>

@ -0,0 +1,8 @@
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1 @@
/build

@ -0,0 +1,37 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.auparty.android.crashguard'
compileSdkVersion 33
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
// api project(':base')
implementation 'com.github.tiann:FreeReflection:3.1.0'
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,24 @@
package com.auparty.android.crashguard
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.auparty.android.crashguard.test", appContext.packageName)
}
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

@ -0,0 +1,18 @@
package android.app.servertransaction
import android.os.IBinder
import android.util.Log
/**
* @author SimonHou 侯威
* @description:
* @date :2023/4/28 16:35
*/
class ClientTransaction {
fun getActivityToken():IBinder?{
Log.e("simon","------finish by hook")
return null
}
}

@ -0,0 +1,183 @@
package com.auparty.android.crashguard
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import com.auparty.android.crashguard.compat.*
import me.weishu.reflection.Reflection
/**
* @author SimonHou
* @description:
* @date :2023/4/28 16:36
*/
object CrashGuard {
//当异常发生在生命周期的时候 会调用此对象杀死发生异常的Activity
private var sActivityKiller: IActivityKiller? = null
private var mExceptionHandler: ExceptionHandler? = null
//判断是否已经装在崩溃防护组件
private var sInstalled: Boolean = false
fun install(ctx: Context, exceptionHandler: ExceptionHandler) {
if (sInstalled) {
return
}
try {
Reflection.unseal(ctx)
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
sInstalled = true
mExceptionHandler = exceptionHandler
//根据不同的的API版本装载不同的ActivityKiller
initActivityKiller()
Thread.setDefaultUncaughtExceptionHandler { p0, p1 ->
Log.e("simon", "111111111111111")
mExceptionHandler?.uncaughtException(p0, p1)
Log.e("simon", "2222222222")
}
Log.e("simon", "33333333")
Handler(ctx.mainLooper).post {
while (true) {
try {
Log.e("simon", "4444444")
Looper.loop()
Log.e("simon", "555555")
} catch (e: Throwable) {
Log.e("simon", "7777")
mExceptionHandler?.uncaughtException(Looper.getMainLooper().thread, e)
Log.e("simon", "8888")
}
}
}
}
private fun initActivityKiller() {
val sdkVersion = Build.VERSION.SDK_INT
sActivityKiller = if (sdkVersion >= 28) {
ActivityKillerV28()
} else if (sdkVersion >= 26) {
ActivityKillerV26()
} else if (sdkVersion == 25 || sdkVersion == 24) {
ActivityKillerV24_V25()
} else if (sdkVersion in 21..23) {
ActivityKillerV21_V23()
} else {
ActivityKillerV15_V20()
}
try {
//反射注入自己的callBack拦截处理系统的Handler消息
setHCallBack()
} catch (e: Throwable) {
e.printStackTrace()
}
}
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
private fun setHCallBack() {
val launchActivity = 100
val pauseActivity = 101
val pauseActivityFinishing = 102
val stopActivityHide = 104
val resumeActivity = 107
val destroyActivity = 109
val executeTransaction = 159
val activityThreadClass = Class.forName("android.app.ActivityThread")
val activityThread =
activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)
val mHField = activityThreadClass.getDeclaredField("mH")
mHField.isAccessible = true
val mhHandler = mHField.get(activityThread) as Handler
val callbackField = Handler::class.java.getDeclaredField("mCallback")
callbackField.isAccessible = true
callbackField.set(mhHandler, object : Handler.Callback {
override fun handleMessage(msg: Message): Boolean {
if (Build.VERSION.SDK_INT >= 28) { //android P 生命周期全部走这
if (msg.what == executeTransaction) {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishLaunchActivity(msg)
lifeCycleException(throwable, msg.what)
}
return true
}
return false
}
//以下生命周期都是针对android P以下的代码
when (msg.what) {
launchActivity -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishLaunchActivity(msg)
lifeCycleException(throwable, msg.what)
}
return true
}
resumeActivity -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishResumeActivity(msg)
lifeCycleException(throwable, msg.what)
}
return true
}
pauseActivityFinishing -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishResumeActivity(msg)
lifeCycleException(throwable, msg.what)
}
return true
}
pauseActivity -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishPauseActivity(msg)
lifeCycleException(throwable, msg.what)
}
return true
}
stopActivityHide -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
sActivityKiller?.finishStopActivity(msg)
lifeCycleException(throwable, msg.what)
}
return true
}
destroyActivity -> {
try {
mhHandler.handleMessage(msg)
} catch (throwable: Throwable) {
lifeCycleException(throwable, msg.what)
}
return true
}
}
return false
}
})
}
private fun lifeCycleException(throwable: Throwable, what: Int) {
Log.e("notifyException", "--->notifyException: mH出错 生命周期:${what}")
mExceptionHandler?.unCatchLifeCycleException(Looper.getMainLooper().thread, throwable)
}
}

@ -0,0 +1,33 @@
package com.auparty.android.crashguard
import android.util.Log
/**
* @author SimonHou
* @description:
* @date :2023/4/28 17:55
*/
abstract class ExceptionHandler {
fun uncaughtException(thread: Thread, throwable: Throwable){
try {
Log.e("simon","666666"+thread.name)
onUncaughtException(thread, throwable)
}catch (e:Throwable){
e.printStackTrace()
}
}
fun unCatchLifeCycleException(thread: Thread, throwable: Throwable){
try {
onLifeCycleException(thread,throwable)
}catch (e:Throwable){
e.printStackTrace()
}
}
protected abstract fun onUncaughtException(thread: Thread, throwable: Throwable)
protected abstract fun onLifeCycleException(thread: Thread,e:Throwable)
}

@ -0,0 +1,64 @@
package com.auparty.android.crashguard.compat
import android.app.Activity
import android.content.Intent
import android.os.IBinder
import android.os.Message
/**
* @author SimonHou
* @description:
* @date :2023/4/28 17:41
*/
class ActivityKillerV15_V20 : IActivityKiller {
override fun finishLaunchActivity(message: Message) {
try {
val activityClientRecord = message.obj as Object
val tokenField = activityClientRecord.`class`.getDeclaredField("token")
tokenField.isAccessible = true
val binder = tokenField.get(activityClientRecord) as IBinder
finish(binder)
}catch (e:Exception){
e.printStackTrace()
}
}
override fun finishResumeActivity(message: Message) {
try {
finish(message.obj as IBinder)
}catch (e:Exception){
e.printStackTrace()
}
}
override fun finishPauseActivity(message: Message) {
try {
finish(message.obj as IBinder)
}catch (e:Exception){
e.printStackTrace()
}
}
override fun finishStopActivity(message: Message) {
try {
finish(message.obj as IBinder)
}catch (e:Exception){
e.printStackTrace()
}
}
@Throws(Exception::class)
private fun finish(binder:IBinder) {
val activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative")
val getDefaultMethod = activityManagerNativeClass.getDeclaredMethod("getDefault")
val activityManager = getDefaultMethod.invoke(null)
val finishActivityMethod = activityManager.javaClass.getDeclaredMethod("finishActivity", IBinder::class.java, Int::class.javaPrimitiveType, Intent::class.java)
finishActivityMethod.invoke(activityManager,binder,Activity.RESULT_CANCELED,null)
}
}

@ -0,0 +1,65 @@
package com.auparty.android.crashguard.compat
import android.app.Activity
import android.content.Intent
import android.os.IBinder
import android.os.Message
/**
* @author SimonHou
* @description:
* @date :2023/4/28 17:45
*/
class ActivityKillerV21_V23 : IActivityKiller {
override fun finishLaunchActivity(message: Message) {
try {
val activityClientRecord = message.obj
val tokenField = activityClientRecord.javaClass.getDeclaredField("token")
tokenField.isAccessible = true
val binder = tokenField[activityClientRecord] as IBinder
finish(binder)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun finishResumeActivity(message: Message) {
try {
finish((message.obj as IBinder))
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun finishPauseActivity(message: Message) {
try {
finish((message.obj as IBinder))
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun finishStopActivity(message: Message) {
try {
finish((message.obj as IBinder))
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
@Throws(Exception::class)
private fun finish(binder: IBinder) {
val activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative")
val getDefaultMethod = activityManagerNativeClass.getDeclaredMethod("getDefault")
val activityManager = getDefaultMethod.invoke(null)
val finishActivityMethod =
activityManager.javaClass.getDeclaredMethod("finishActivity",
IBinder::class.java, Int::class.javaPrimitiveType, Intent::class.java,
Boolean::class.javaPrimitiveType)
finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, false)
}
}

@ -0,0 +1,69 @@
package com.auparty.android.crashguard.compat
import android.app.Activity
import android.content.Intent
import android.os.IBinder
import android.os.Message
/**
* @author SimonHou
* @description:
* @date :2023/4/28 17:44
*/
class ActivityKillerV24_V25: IActivityKiller {
override fun finishLaunchActivity(message: Message) {
try {
val activityClientRecord = message.obj
val tokenField = activityClientRecord.javaClass.getDeclaredField("token")
tokenField.isAccessible = true
val binder = tokenField[activityClientRecord] as IBinder
finish(binder)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun finishResumeActivity(message: Message) {
finishSomeArgs(message)
}
override fun finishPauseActivity(message: Message) {
finishSomeArgs(message)
}
override fun finishStopActivity(message: Message) {
finishSomeArgs(message)
}
private fun finishSomeArgs(message: Message) {
try {
val someArgs = message.obj
val arg1Field = someArgs.javaClass.getDeclaredField("arg1")
arg1Field.isAccessible = true
val binder = arg1Field[someArgs] as IBinder
finish(binder)
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
}
@Throws(Exception::class)
private fun finish(binder: IBinder) {
/*
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null, Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
*/
val activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative")
val getDefaultMethod = activityManagerNativeClass.getDeclaredMethod("getDefault")
val activityManager = getDefaultMethod.invoke(null)
val finishActivityMethod = activityManager.javaClass.getDeclaredMethod("finishActivity", IBinder::class.java, Int::class.javaPrimitiveType, Intent::class.java, Int::class.javaPrimitiveType)
val DONT_FINISH_TASK_WITH_ACTIVITY = 0
finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, DONT_FINISH_TASK_WITH_ACTIVITY)
}
}

@ -0,0 +1,64 @@
package com.auparty.android.crashguard.compat
import android.app.Activity
import android.app.ActivityManager
import android.content.Intent
import android.os.IBinder
import android.os.Message
/**
* @author SimonHou
* @description:
* @date :2023/4/28 17:43
*/
class ActivityKillerV26 : IActivityKiller {
override fun finishLaunchActivity(message: Message) {
try {
val activityClientRecord = message.obj
val tokenField = activityClientRecord.javaClass.getDeclaredField("token")
tokenField.isAccessible = true
val binder = tokenField[activityClientRecord] as IBinder
finish(binder)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun finishResumeActivity(message: Message) {
finishSomeArgs(message)
}
override fun finishPauseActivity(message: Message) {
finishSomeArgs(message)
}
override fun finishStopActivity(message: Message) {
finishSomeArgs(message)
}
private fun finishSomeArgs(message: Message) {
try {
val someArgs = message.obj
val arg1Field = someArgs.javaClass.getDeclaredField("arg1")
arg1Field.isAccessible = true
val binder = arg1Field[someArgs] as IBinder
finish(binder)
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
}
@Throws(Exception::class)
private fun finish(binder: IBinder) {
val getServiceMethod = ActivityManager::class.java.getDeclaredMethod("getService")
val activityManager = getServiceMethod.invoke(null)
val finishActivityMethod = activityManager.javaClass.getDeclaredMethod("finishActivity", IBinder::class.java, Int::class.javaPrimitiveType, Intent::class.java, Int::class.javaPrimitiveType)
finishActivityMethod.isAccessible = true
val DONT_FINISH_TASK_WITH_ACTIVITY = 0
finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, DONT_FINISH_TASK_WITH_ACTIVITY)
}
}

@ -0,0 +1,82 @@
package com.auparty.android.crashguard.compat
import android.app.Activity
import android.app.ActivityManager
import android.app.servertransaction.ClientTransaction
import android.content.Intent
import android.os.IBinder
import android.os.Message
/**
* @author SimonHou
* @description:
* @date :2023/4/28 17:48
*/
class ActivityKillerV28 : IActivityKiller {
override fun finishLaunchActivity(message: Message) {
try {
tryFinish1(message)
return
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
try {
tryFinish2(message)
return
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
try {
tryFinish3(message)
return
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
}
override fun finishResumeActivity(message: Message) {
}
override fun finishPauseActivity(message: Message) {
}
override fun finishStopActivity(message: Message) {
}
@Throws(Throwable::class)
private fun tryFinish1(message: Message) {
val clientTransaction: ClientTransaction = message.obj as ClientTransaction
val binder = clientTransaction.getActivityToken() as IBinder
finish(binder)
}
@Throws(Throwable::class)
private fun tryFinish3(message: Message) {
val clientTransaction = message.obj
val mActivityTokenField = clientTransaction.javaClass.getDeclaredField("mActivityToken")
val binder = mActivityTokenField[clientTransaction] as IBinder
finish(binder)
}
@Throws(Throwable::class)
private fun tryFinish2(message: Message) {
val clientTransaction = message.obj
val getActivityTokenMethod = clientTransaction.javaClass.getDeclaredMethod("getActivityToken")
val binder = getActivityTokenMethod.invoke(clientTransaction) as IBinder
finish(binder)
}
@Throws(Exception::class)
private fun finish(binder: IBinder) {
val getServiceMethod = ActivityManager::class.java.getDeclaredMethod("getService")
val activityManager = getServiceMethod.invoke(null)
val finishActivityMethod = activityManager.javaClass.getDeclaredMethod("finishActivity", IBinder::class.java, Int::class.javaPrimitiveType, Intent::class.java, Int::class.javaPrimitiveType)
finishActivityMethod.isAccessible = true
val DONT_FINISH_TASK_WITH_ACTIVITY = 0
finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, DONT_FINISH_TASK_WITH_ACTIVITY)
}
}

@ -0,0 +1,18 @@
package com.auparty.android.crashguard.compat
import android.os.Message
/**
* @author SimonHou
* @description:
* @date :2023/4/28 17:41
*/
interface IActivityKiller {
fun finishLaunchActivity(message: Message)
fun finishResumeActivity(message: Message)
fun finishPauseActivity(message: Message)
fun finishStopActivity(message: Message)
}

@ -0,0 +1,17 @@
package com.auparty.android.crashguard
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

@ -0,0 +1 @@
/build

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
</GradleProjectSettings>
</option>
</component>
</project>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/MPChartLib.iml" filepath="$PROJECT_DIR$/.idea/modules/MPChartLib.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

@ -0,0 +1,48 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 3
versionName '3.1.0'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
testOptions {
unitTests.returnDefaultValues = true // this prevents "not mocked" error
}
}
dependencies {
implementation 'androidx.annotation:annotation:1.0.0'
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
options.charSet = 'UTF-8'
failOnError false
source = android.sourceSets.main.java.sourceFiles
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives sourcesJar
archives javadocJar
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2014 Philipp Jahoda <philjay.librarysup@gmail.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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<version>1.4.2-SNAPSHOT</version>
<groupId>com.github.mikephil</groupId>
<artifactId>MPAndroidChart</artifactId>
<name>MPAndroidChart</name>
<description>A simple Android chart view/graph view library, supporting line- bar- and piecharts as well as scaling, dragging and animations</description>
<url>https://github.com/PhilJay/MPAndroidChart</url>
<packaging>apklib</packaging>
<!--<packaging>aar</packaging>-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<version>3.9.0-rc.2</version>
<extensions>true</extensions>
<configuration>
<!--<sdk>-->
<!--<path>${env.ANDROID_HOME}</path>-->
<!--<platform>16</platform>-->
<!--</sdk>-->
<undeployBeforeDeploy>true</undeployBeforeDeploy>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<scope>provided</scope>
<version>4.1.1.4</version>
</dependency>
</dependencies>
<issueManagement>
<url>https://github.com/PhilJay/MPAndroidChart/issues</url>
<system>GitHub Issues</system>
</issueManagement>
<licenses>
<license>
<name>Apache License Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>https://github.com/PhilJay/MPAndroidChart</url>
<connection>scm:git:git://github.com/PhilJay/MPAndroidChart.git</connection>
<developerConnection>scm:git:git@github.com:PhilJay/MPAndroidChart.git</developerConnection>
</scm>
<developers>
<developer>
<name>Philipp Jahoda</name>
<email>philjay.librarysup@gmail.com</email>
<url>http://stackoverflow.com/users/1590502/philipp-jahoda</url>
<id>PhilJay</id>
</developer>
</developers>
</project>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.github.mikephil.charting">
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
</application> -->
</manifest>

@ -0,0 +1,207 @@
package com.github.mikephil.charting.animation;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import androidx.annotation.RequiresApi;
import com.github.mikephil.charting.animation.Easing.EasingFunction;
/**
* Object responsible for all animations in the Chart. Animations require API level 11.
*
* @author Philipp Jahoda
* @author Mick Ashton
*/
public class ChartAnimator {
/** object that is updated upon animation update */
private AnimatorUpdateListener mListener;
/** The phase of drawn values on the y-axis. 0 - 1 */
@SuppressWarnings("WeakerAccess")
protected float mPhaseY = 1f;
/** The phase of drawn values on the x-axis. 0 - 1 */
@SuppressWarnings("WeakerAccess")
protected float mPhaseX = 1f;
public ChartAnimator() { }
@RequiresApi(11)
public ChartAnimator(AnimatorUpdateListener listener) {
mListener = listener;
}
@RequiresApi(11)
private ObjectAnimator xAnimator(int duration, EasingFunction easing) {
ObjectAnimator animatorX = ObjectAnimator.ofFloat(this, "phaseX", 0f, 1f);
animatorX.setInterpolator(easing);
animatorX.setDuration(duration);
return animatorX;
}
@RequiresApi(11)
private ObjectAnimator yAnimator(int duration, EasingFunction easing) {
ObjectAnimator animatorY = ObjectAnimator.ofFloat(this, "phaseY", 0f, 1f);
animatorY.setInterpolator(easing);
animatorY.setDuration(duration);
return animatorY;
}
/**
* Animates values along the X axis, in a linear fashion.
*
* @param durationMillis animation duration
*/
@RequiresApi(11)
public void animateX(int durationMillis) {
animateX(durationMillis, Easing.Linear);
}
/**
* Animates values along the X axis.
*
* @param durationMillis animation duration
* @param easing EasingFunction
*/
@RequiresApi(11)
public void animateX(int durationMillis, EasingFunction easing) {
ObjectAnimator animatorX = xAnimator(durationMillis, easing);
animatorX.addUpdateListener(mListener);
animatorX.start();
}
/**
* Animates values along both the X and Y axes, in a linear fashion.
*
* @param durationMillisX animation duration along the X axis
* @param durationMillisY animation duration along the Y axis
*/
@RequiresApi(11)
public void animateXY(int durationMillisX, int durationMillisY) {
animateXY(durationMillisX, durationMillisY, Easing.Linear, Easing.Linear);
}
/**
* Animates values along both the X and Y axes.
*
* @param durationMillisX animation duration along the X axis
* @param durationMillisY animation duration along the Y axis
* @param easing EasingFunction for both axes
*/
@RequiresApi(11)
public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easing) {
ObjectAnimator xAnimator = xAnimator(durationMillisX, easing);
ObjectAnimator yAnimator = yAnimator(durationMillisY, easing);
if (durationMillisX > durationMillisY) {
xAnimator.addUpdateListener(mListener);
} else {
yAnimator.addUpdateListener(mListener);
}
xAnimator.start();
yAnimator.start();
}
/**
* Animates values along both the X and Y axes.
*
* @param durationMillisX animation duration along the X axis
* @param durationMillisY animation duration along the Y axis
* @param easingX EasingFunction for the X axis
* @param easingY EasingFunction for the Y axis
*/
@RequiresApi(11)
public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easingX,
EasingFunction easingY) {
ObjectAnimator xAnimator = xAnimator(durationMillisX, easingX);
ObjectAnimator yAnimator = yAnimator(durationMillisY, easingY);
if (durationMillisX > durationMillisY) {
xAnimator.addUpdateListener(mListener);
} else {
yAnimator.addUpdateListener(mListener);
}
xAnimator.start();
yAnimator.start();
}
/**
* Animates values along the Y axis, in a linear fashion.
*
* @param durationMillis animation duration
*/
@RequiresApi(11)
public void animateY(int durationMillis) {
animateY(durationMillis, Easing.Linear);
}
/**
* Animates values along the Y axis.
*
* @param durationMillis animation duration
* @param easing EasingFunction
*/
@RequiresApi(11)
public void animateY(int durationMillis, EasingFunction easing) {
ObjectAnimator animatorY = yAnimator(durationMillis, easing);
animatorY.addUpdateListener(mListener);
animatorY.start();
}
/**
* Gets the Y axis phase of the animation.
*
* @return float value of {@link #mPhaseY}
*/
public float getPhaseY() {
return mPhaseY;
}
/**
* Sets the Y axis phase of the animation.
*
* @param phase float value between 0 - 1
*/
public void setPhaseY(float phase) {
if (phase > 1f) {
phase = 1f;
} else if (phase < 0f) {
phase = 0f;
}
mPhaseY = phase;
}
/**
* Gets the X axis phase of the animation.
*
* @return float value of {@link #mPhaseX}
*/
public float getPhaseX() {
return mPhaseX;
}
/**
* Sets the X axis phase of the animation.
*
* @param phase float value between 0 - 1
*/
public void setPhaseX(float phase) {
if (phase > 1f) {
phase = 1f;
} else if (phase < 0f) {
phase = 0f;
}
mPhaseX = phase;
}
}

@ -0,0 +1,309 @@
package com.github.mikephil.charting.animation;
import android.animation.TimeInterpolator;
import androidx.annotation.RequiresApi;
/**
* Easing options.
*
* @author Daniel Cohen Gindi
* @author Mick Ashton
*/
@SuppressWarnings("WeakerAccess")
@RequiresApi(11)
public class Easing {
public interface EasingFunction extends TimeInterpolator {
@Override
float getInterpolation(float input);
}
private static final float DOUBLE_PI = 2f * (float) Math.PI;
@SuppressWarnings("unused")
public static final EasingFunction Linear = new EasingFunction() {
public float getInterpolation(float input) {
return input;
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInQuad = new EasingFunction() {
public float getInterpolation(float input) {
return input * input;
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutQuad = new EasingFunction() {
public float getInterpolation(float input) {
return -input * (input - 2f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutQuad = new EasingFunction() {
public float getInterpolation(float input) {
input *= 2f;
if (input < 1f) {
return 0.5f * input * input;
}
return -0.5f * ((--input) * (input - 2f) - 1f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInCubic = new EasingFunction() {
public float getInterpolation(float input) {
return (float) Math.pow(input, 3);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutCubic = new EasingFunction() {
public float getInterpolation(float input) {
input--;
return (float) Math.pow(input, 3) + 1f;
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutCubic = new EasingFunction() {
public float getInterpolation(float input) {
input *= 2f;
if (input < 1f) {
return 0.5f * (float) Math.pow(input, 3);
}
input -= 2f;
return 0.5f * ((float) Math.pow(input, 3) + 2f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInQuart = new EasingFunction() {
public float getInterpolation(float input) {
return (float) Math.pow(input, 4);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutQuart = new EasingFunction() {
public float getInterpolation(float input) {
input--;
return -((float) Math.pow(input, 4) - 1f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutQuart = new EasingFunction() {
public float getInterpolation(float input) {
input *= 2f;
if (input < 1f) {
return 0.5f * (float) Math.pow(input, 4);
}
input -= 2f;
return -0.5f * ((float) Math.pow(input, 4) - 2f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInSine = new EasingFunction() {
public float getInterpolation(float input) {
return -(float) Math.cos(input * (Math.PI / 2f)) + 1f;
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutSine = new EasingFunction() {
public float getInterpolation(float input) {
return (float) Math.sin(input * (Math.PI / 2f));
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutSine = new EasingFunction() {
public float getInterpolation(float input) {
return -0.5f * ((float) Math.cos(Math.PI * input) - 1f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInExpo = new EasingFunction() {
public float getInterpolation(float input) {
return (input == 0) ? 0f : (float) Math.pow(2f, 10f * (input - 1f));
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutExpo = new EasingFunction() {
public float getInterpolation(float input) {
return (input == 1f) ? 1f : (-(float) Math.pow(2f, -10f * (input + 1f)));
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutExpo = new EasingFunction() {
public float getInterpolation(float input) {
if (input == 0) {
return 0f;
} else if (input == 1f) {
return 1f;
}
input *= 2f;
if (input < 1f) {
return 0.5f * (float) Math.pow(2f, 10f * (input - 1f));
}
return 0.5f * (-(float) Math.pow(2f, -10f * --input) + 2f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInCirc = new EasingFunction() {
public float getInterpolation(float input) {
return -((float) Math.sqrt(1f - input * input) - 1f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutCirc = new EasingFunction() {
public float getInterpolation(float input) {
input--;
return (float) Math.sqrt(1f - input * input);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutCirc = new EasingFunction() {
public float getInterpolation(float input) {
input *= 2f;
if (input < 1f) {
return -0.5f * ((float) Math.sqrt(1f - input * input) - 1f);
}
return 0.5f * ((float) Math.sqrt(1f - (input -= 2f) * input) + 1f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInElastic = new EasingFunction() {
public float getInterpolation(float input) {
if (input == 0) {
return 0f;
} else if (input == 1) {
return 1f;
}
float p = 0.3f;
float s = p / DOUBLE_PI * (float) Math.asin(1f);
return -((float) Math.pow(2f, 10f * (input -= 1f))
*(float) Math.sin((input - s) * DOUBLE_PI / p));
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutElastic = new EasingFunction() {
public float getInterpolation(float input) {
if (input == 0) {
return 0f;
} else if (input == 1) {
return 1f;
}
float p = 0.3f;
float s = p / DOUBLE_PI * (float) Math.asin(1f);
return 1f
+ (float) Math.pow(2f, -10f * input)
* (float) Math.sin((input - s) * DOUBLE_PI / p);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutElastic = new EasingFunction() {
public float getInterpolation(float input) {
if (input == 0) {
return 0f;
}
input *= 2f;
if (input == 2) {
return 1f;
}
float p = 1f / 0.45f;
float s = 0.45f / DOUBLE_PI * (float) Math.asin(1f);
if (input < 1f) {
return -0.5f
* ((float) Math.pow(2f, 10f * (input -= 1f))
* (float) Math.sin((input * 1f - s) * DOUBLE_PI * p));
}
return 1f + 0.5f
* (float) Math.pow(2f, -10f * (input -= 1f))
* (float) Math.sin((input * 1f - s) * DOUBLE_PI * p);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInBack = new EasingFunction() {
public float getInterpolation(float input) {
final float s = 1.70158f;
return input * input * ((s + 1f) * input - s);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutBack = new EasingFunction() {
public float getInterpolation(float input) {
final float s = 1.70158f;
input--;
return (input * input * ((s + 1f) * input + s) + 1f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutBack = new EasingFunction() {
public float getInterpolation(float input) {
float s = 1.70158f;
input *= 2f;
if (input < 1f) {
return 0.5f * (input * input * (((s *= (1.525f)) + 1f) * input - s));
}
return 0.5f * ((input -= 2f) * input * (((s *= (1.525f)) + 1f) * input + s) + 2f);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInBounce = new EasingFunction() {
public float getInterpolation(float input) {
return 1f - EaseOutBounce.getInterpolation(1f - input);
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseOutBounce = new EasingFunction() {
public float getInterpolation(float input) {
float s = 7.5625f;
if (input < (1f / 2.75f)) {
return s * input * input;
} else if (input < (2f / 2.75f)) {
return s * (input -= (1.5f / 2.75f)) * input + 0.75f;
} else if (input < (2.5f / 2.75f)) {
return s * (input -= (2.25f / 2.75f)) * input + 0.9375f;
}
return s * (input -= (2.625f / 2.75f)) * input + 0.984375f;
}
};
@SuppressWarnings("unused")
public static final EasingFunction EaseInOutBounce = new EasingFunction() {
public float getInterpolation(float input) {
if (input < 0.5f) {
return EaseInBounce.getInterpolation(input * 2f) * 0.5f;
}
return EaseOutBounce.getInterpolation(input * 2f - 1f) * 0.5f + 0.5f;
}
};
}

@ -0,0 +1,91 @@
package com.github.mikephil.charting.buffer;
import java.util.List;
/**
* Buffer class to boost performance while drawing. Concept: Replace instead of
* recreate.
*
* @author Philipp Jahoda
* @param <T> The data the buffer accepts to be fed with.
*/
public abstract class AbstractBuffer<T> {
/** index in the buffer */
protected int index = 0;
/** float-buffer that holds the data points to draw, order: x,y,x,y,... */
public final float[] buffer;
/** animation phase x-axis */
protected float phaseX = 1f;
/** animation phase y-axis */
protected float phaseY = 1f;
/** indicates from which x-index the visible data begins */
protected int mFrom = 0;
/** indicates to which x-index the visible data ranges */
protected int mTo = 0;
/**
* Initialization with buffer-size.
*
* @param size
*/
public AbstractBuffer(int size) {
index = 0;
buffer = new float[size];
}
/** limits the drawing on the x-axis */
public void limitFrom(int from) {
if (from < 0)
from = 0;
mFrom = from;
}
/** limits the drawing on the x-axis */
public void limitTo(int to) {
if (to < 0)
to = 0;
mTo = to;
}
/**
* Resets the buffer index to 0 and makes the buffer reusable.
*/
public void reset() {
index = 0;
}
/**
* Returns the size (length) of the buffer array.
*
* @return
*/
public int size() {
return buffer.length;
}
/**
* Set the phases used for animations.
*
* @param phaseX
* @param phaseY
*/
public void setPhases(float phaseX, float phaseY) {
this.phaseX = phaseX;
this.phaseY = phaseY;
}
/**
* Builds up the buffer with the provided data and resets the buffer-index
* after feed-completion. This needs to run FAST.
*
* @param data
*/
public abstract void feed(T data);
}

@ -0,0 +1,130 @@
package com.github.mikephil.charting.buffer;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
public class BarBuffer extends AbstractBuffer<IBarDataSet> {
protected int mDataSetIndex = 0;
protected int mDataSetCount = 1;
protected boolean mContainsStacks = false;
protected boolean mInverted = false;
/** width of the bar on the x-axis, in values (not pixels) */
protected float mBarWidth = 1f;
public BarBuffer(int size, int dataSetCount, boolean containsStacks) {
super(size);
this.mDataSetCount = dataSetCount;
this.mContainsStacks = containsStacks;
}
public void setBarWidth(float barWidth) {
this.mBarWidth = barWidth;
}
public void setDataSet(int index) {
this.mDataSetIndex = index;
}
public void setInverted(boolean inverted) {
this.mInverted = inverted;
}
protected void addBar(float left, float top, float right, float bottom) {
buffer[index++] = left;
buffer[index++] = top;
buffer[index++] = right;
buffer[index++] = bottom;
}
@Override
public void feed(IBarDataSet data) {
float size = data.getEntryCount() * phaseX;
float barWidthHalf = mBarWidth / 2f;
for (int i = 0; i < size; i++) {
BarEntry e = data.getEntryForIndex(i);
if(e == null)
continue;
float x = e.getX();
float y = e.getY();
float[] vals = e.getYVals();
if (!mContainsStacks || vals == null) {
float left = x - barWidthHalf;
float right = x + barWidthHalf;
float bottom, top;
if (mInverted) {
bottom = y >= 0 ? y : 0;
top = y <= 0 ? y : 0;
} else {
top = y >= 0 ? y : 0;
bottom = y <= 0 ? y : 0;
}
// multiply the height of the rect with the phase
if (top > 0)
top *= phaseY;
else
bottom *= phaseY;
addBar(left, top, right, bottom);
} else {
float posY = 0f;
float negY = -e.getNegativeSum();
float yStart = 0f;
// fill the stack
for (int k = 0; k < vals.length; k++) {
float value = vals[k];
if (value == 0.0f && (posY == 0.0f || negY == 0.0f)) {
// Take care of the situation of a 0.0 value, which overlaps a non-zero bar
y = value;
yStart = y;
} else if (value >= 0.0f) {
y = posY;
yStart = posY + value;
posY = yStart;
} else {
y = negY;
yStart = negY + Math.abs(value);
negY += Math.abs(value);
}
float left = x - barWidthHalf;
float right = x + barWidthHalf;
float bottom, top;
if (mInverted) {
bottom = y >= yStart ? y : yStart;
top = y <= yStart ? y : yStart;
} else {
top = y >= yStart ? y : yStart;
bottom = y <= yStart ? y : yStart;
}
// multiply the height of the rect with the phase
top *= phaseY;
bottom *= phaseY;
addBar(left, top, right, bottom);
}
}
}
reset();
}
}

@ -0,0 +1,94 @@
package com.github.mikephil.charting.buffer;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
public class HorizontalBarBuffer extends BarBuffer {
public HorizontalBarBuffer(int size, int dataSetCount, boolean containsStacks) {
super(size, dataSetCount, containsStacks);
}
@Override
public void feed(IBarDataSet data) {
float size = data.getEntryCount() * phaseX;
float barWidthHalf = mBarWidth / 2f;
for (int i = 0; i < size; i++) {
BarEntry e = data.getEntryForIndex(i);
if(e == null)
continue;
float x = e.getX();
float y = e.getY();
float[] vals = e.getYVals();
if (!mContainsStacks || vals == null) {
float bottom = x - barWidthHalf;
float top = x + barWidthHalf;
float left, right;
if (mInverted) {
left = y >= 0 ? y : 0;
right = y <= 0 ? y : 0;
} else {
right = y >= 0 ? y : 0;
left = y <= 0 ? y : 0;
}
// multiply the height of the rect with the phase
if (right > 0)
right *= phaseY;
else
left *= phaseY;
addBar(left, top, right, bottom);
} else {
float posY = 0f;
float negY = -e.getNegativeSum();
float yStart = 0f;
// fill the stack
for (int k = 0; k < vals.length; k++) {
float value = vals[k];
if (value >= 0f) {
y = posY;
yStart = posY + value;
posY = yStart;
} else {
y = negY;
yStart = negY + Math.abs(value);
negY += Math.abs(value);
}
float bottom = x - barWidthHalf;
float top = x + barWidthHalf;
float left, right;
if (mInverted) {
left = y >= yStart ? y : yStart;
right = y <= yStart ? y : yStart;
} else {
right = y >= yStart ? y : yStart;
left = y <= yStart ? y : yStart;
}
// multiply the height of the rect with the phase
right *= phaseY;
left *= phaseY;
addBar(left, top, right, bottom);
}
}
}
reset();
}
}

@ -0,0 +1,258 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.highlight.BarHighlighter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import com.github.mikephil.charting.renderer.BarChartRenderer;
/**
* Chart that draws bars.
*
* @author Philipp Jahoda
*/
public class BarChart extends BarLineChartBase<BarData> implements BarDataProvider {
/**
* flag that indicates whether the highlight should be full-bar oriented, or single-value?
*/
protected boolean mHighlightFullBarEnabled = false;
/**
* if set to true, all values are drawn above their bars, instead of below their top
*/
private boolean mDrawValueAboveBar = true;
/**
* if set to true, a grey area is drawn behind each bar that indicates the maximum value
*/
private boolean mDrawBarShadow = false;
private boolean mFitBars = false;
public BarChart(Context context) {
super(context);
}
public BarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BarChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new BarChartRenderer(this, mAnimator, mViewPortHandler);
setHighlighter(new BarHighlighter(this));
getXAxis().setSpaceMin(0.5f);
getXAxis().setSpaceMax(0.5f);
}
@Override
protected void calcMinMax() {
if (mFitBars) {
mXAxis.calculate(mData.getXMin() - mData.getBarWidth() / 2f, mData.getXMax() + mData.getBarWidth() / 2f);
} else {
mXAxis.calculate(mData.getXMin(), mData.getXMax());
}
// calculate axis range (min / max) according to provided data
mAxisLeft.calculate(mData.getYMin(YAxis.AxisDependency.LEFT), mData.getYMax(YAxis.AxisDependency.LEFT));
mAxisRight.calculate(mData.getYMin(YAxis.AxisDependency.RIGHT), mData.getYMax(YAxis.AxisDependency
.RIGHT));
}
/**
* Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch
* point
* inside the BarChart.
*
* @param x
* @param y
* @return
*/
@Override
public Highlight getHighlightByTouchPoint(float x, float y) {
if (mData == null) {
Log.e(LOG_TAG, "Can't select by touch. No data set.");
return null;
} else {
Highlight h = getHighlighter().getHighlight(x, y);
if (h == null || !isHighlightFullBarEnabled()) return h;
// For isHighlightFullBarEnabled, remove stackIndex
return new Highlight(h.getX(), h.getY(),
h.getXPx(), h.getYPx(),
h.getDataSetIndex(), -1, h.getAxis());
}
}
/**
* Returns the bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be
* found in the charts data. Performance-intensive code should use void getBarBounds(BarEntry, RectF) instead.
*
* @param e
* @return
*/
public RectF getBarBounds(BarEntry e) {
RectF bounds = new RectF();
getBarBounds(e, bounds);
return bounds;
}
/**
* The passed outputRect will be assigned the values of the bounding box of the specified Entry in the specified DataSet.
* The rect will be assigned Float.MIN_VALUE in all locations if the Entry could not be found in the charts data.
*
* @param e
* @return
*/
public void getBarBounds(BarEntry e, RectF outputRect) {
RectF bounds = outputRect;
IBarDataSet set = mData.getDataSetForEntry(e);
if (set == null) {
bounds.set(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
return;
}
float y = e.getY();
float x = e.getX();
float barWidth = mData.getBarWidth();
float left = x - barWidth / 2f;
float right = x + barWidth / 2f;
float top = y >= 0 ? y : 0;
float bottom = y <= 0 ? y : 0;
bounds.set(left, top, right, bottom);
getTransformer(set.getAxisDependency()).rectValueToPixel(outputRect);
}
/**
* If set to true, all values are drawn above their bars, instead of below their top.
*
* @param enabled
*/
public void setDrawValueAboveBar(boolean enabled) {
mDrawValueAboveBar = enabled;
}
/**
* returns true if drawing values above bars is enabled, false if not
*
* @return
*/
public boolean isDrawValueAboveBarEnabled() {
return mDrawValueAboveBar;
}
/**
* If set to true, a grey area is drawn behind each bar that indicates the maximum value. Enabling his will reduce
* performance by about 50%.
*
* @param enabled
*/
public void setDrawBarShadow(boolean enabled) {
mDrawBarShadow = enabled;
}
/**
* returns true if drawing shadows (maxvalue) for each bar is enabled, false if not
*
* @return
*/
public boolean isDrawBarShadowEnabled() {
return mDrawBarShadow;
}
/**
* Set this to true to make the highlight operation full-bar oriented, false to make it highlight single values (relevant
* only for stacked). If enabled, highlighting operations will highlight the whole bar, even if only a single stack entry
* was tapped.
* Default: false
*
* @param enabled
*/
public void setHighlightFullBarEnabled(boolean enabled) {
mHighlightFullBarEnabled = enabled;
}
/**
* @return true the highlight operation is be full-bar oriented, false if single-value
*/
@Override
public boolean isHighlightFullBarEnabled() {
return mHighlightFullBarEnabled;
}
/**
* Highlights the value at the given x-value in the given DataSet. Provide
* -1 as the dataSetIndex to undo all highlighting.
*
* @param x
* @param dataSetIndex
* @param stackIndex the index inside the stack - only relevant for stacked entries
*/
public void highlightValue(float x, int dataSetIndex, int stackIndex) {
highlightValue(new Highlight(x, dataSetIndex, stackIndex), false);
}
@Override
public BarData getBarData() {
return mData;
}
/**
* Adds half of the bar width to each side of the x-axis range in order to allow the bars of the barchart to be
* fully displayed.
* Default: false
*
* @param enabled
*/
public void setFitBars(boolean enabled) {
mFitBars = enabled;
}
/**
* Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries.
* Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified
* by the parameters.
* Calls notifyDataSetChanged() afterwards.
*
* @param fromX the starting point on the x-axis where the grouping should begin
* @param groupSpace the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f
* @param barSpace the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f
*/
public void groupBars(float fromX, float groupSpace, float barSpace) {
if (getBarData() == null) {
throw new RuntimeException("You need to set data for the chart before grouping bars.");
} else {
getBarData().groupBars(fromX, groupSpace, barSpace);
notifyDataSetChanged();
}
}
}

@ -0,0 +1,43 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.data.BubbleData;
import com.github.mikephil.charting.interfaces.dataprovider.BubbleDataProvider;
import com.github.mikephil.charting.renderer.BubbleChartRenderer;
/**
* The BubbleChart. Draws bubbles. Bubble chart implementation: Copyright 2015
* Pierre-Marc Airoldi Licensed under Apache License 2.0. In the BubbleChart, it
* is the area of the bubble, not the radius or diameter of the bubble that
* conveys the data.
*
* @author Philipp Jahoda
*/
public class BubbleChart extends BarLineChartBase<BubbleData> implements BubbleDataProvider {
public BubbleChart(Context context) {
super(context);
}
public BubbleChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BubbleChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new BubbleChartRenderer(this, mAnimator, mViewPortHandler);
}
public BubbleData getBubbleData() {
return mData;
}
}

@ -0,0 +1,44 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.data.CandleData;
import com.github.mikephil.charting.interfaces.dataprovider.CandleDataProvider;
import com.github.mikephil.charting.renderer.CandleStickChartRenderer;
/**
* Financial chart type that draws candle-sticks (OHCL chart).
*
* @author Philipp Jahoda
*/
public class CandleStickChart extends BarLineChartBase<CandleData> implements CandleDataProvider {
public CandleStickChart(Context context) {
super(context);
}
public CandleStickChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CandleStickChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new CandleStickChartRenderer(this, mAnimator, mViewPortHandler);
getXAxis().setSpaceMin(0.5f);
getXAxis().setSpaceMax(0.5f);
}
@Override
public CandleData getCandleData() {
return mData;
}
}

@ -0,0 +1,272 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BubbleData;
import com.github.mikephil.charting.data.CandleData;
import com.github.mikephil.charting.data.CombinedData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.ScatterData;
import com.github.mikephil.charting.highlight.CombinedHighlighter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.dataprovider.CombinedDataProvider;
import com.github.mikephil.charting.interfaces.datasets.IDataSet;
import com.github.mikephil.charting.renderer.CombinedChartRenderer;
/**
* This chart class allows the combination of lines, bars, scatter and candle
* data all displayed in one chart area.
*
* @author Philipp Jahoda
*/
public class CombinedChart extends BarLineChartBase<CombinedData> implements CombinedDataProvider {
/**
* if set to true, all values are drawn above their bars, instead of below
* their top
*/
private boolean mDrawValueAboveBar = true;
/**
* flag that indicates whether the highlight should be full-bar oriented, or single-value?
*/
protected boolean mHighlightFullBarEnabled = false;
/**
* if set to true, a grey area is drawn behind each bar that indicates the
* maximum value
*/
private boolean mDrawBarShadow = false;
protected DrawOrder[] mDrawOrder;
/**
* enum that allows to specify the order in which the different data objects
* for the combined-chart are drawn
*/
public enum DrawOrder {
BAR, BUBBLE, LINE, CANDLE, SCATTER
}
public CombinedChart(Context context) {
super(context);
}
public CombinedChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CombinedChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
// Default values are not ready here yet
mDrawOrder = new DrawOrder[]{
DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.LINE, DrawOrder.CANDLE, DrawOrder.SCATTER
};
setHighlighter(new CombinedHighlighter(this, this));
// Old default behaviour
setHighlightFullBarEnabled(true);
mRenderer = new CombinedChartRenderer(this, mAnimator, mViewPortHandler);
}
@Override
public CombinedData getCombinedData() {
return mData;
}
@Override
public void setData(CombinedData data) {
super.setData(data);
setHighlighter(new CombinedHighlighter(this, this));
((CombinedChartRenderer)mRenderer).createRenderers();
mRenderer.initBuffers();
}
/**
* Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch
* point
* inside the CombinedChart.
*
* @param x
* @param y
* @return
*/
@Override
public Highlight getHighlightByTouchPoint(float x, float y) {
if (mData == null) {
Log.e(LOG_TAG, "Can't select by touch. No data set.");
return null;
} else {
Highlight h = getHighlighter().getHighlight(x, y);
if (h == null || !isHighlightFullBarEnabled()) return h;
// For isHighlightFullBarEnabled, remove stackIndex
return new Highlight(h.getX(), h.getY(),
h.getXPx(), h.getYPx(),
h.getDataSetIndex(), -1, h.getAxis());
}
}
@Override
public LineData getLineData() {
if (mData == null)
return null;
return mData.getLineData();
}
@Override
public BarData getBarData() {
if (mData == null)
return null;
return mData.getBarData();
}
@Override
public ScatterData getScatterData() {
if (mData == null)
return null;
return mData.getScatterData();
}
@Override
public CandleData getCandleData() {
if (mData == null)
return null;
return mData.getCandleData();
}
@Override
public BubbleData getBubbleData() {
if (mData == null)
return null;
return mData.getBubbleData();
}
@Override
public boolean isDrawBarShadowEnabled() {
return mDrawBarShadow;
}
@Override
public boolean isDrawValueAboveBarEnabled() {
return mDrawValueAboveBar;
}
/**
* If set to true, all values are drawn above their bars, instead of below
* their top.
*
* @param enabled
*/
public void setDrawValueAboveBar(boolean enabled) {
mDrawValueAboveBar = enabled;
}
/**
* If set to true, a grey area is drawn behind each bar that indicates the
* maximum value. Enabling his will reduce performance by about 50%.
*
* @param enabled
*/
public void setDrawBarShadow(boolean enabled) {
mDrawBarShadow = enabled;
}
/**
* Set this to true to make the highlight operation full-bar oriented,
* false to make it highlight single values (relevant only for stacked).
*
* @param enabled
*/
public void setHighlightFullBarEnabled(boolean enabled) {
mHighlightFullBarEnabled = enabled;
}
/**
* @return true the highlight operation is be full-bar oriented, false if single-value
*/
@Override
public boolean isHighlightFullBarEnabled() {
return mHighlightFullBarEnabled;
}
/**
* Returns the currently set draw order.
*
* @return
*/
public DrawOrder[] getDrawOrder() {
return mDrawOrder;
}
/**
* Sets the order in which the provided data objects should be drawn. The
* earlier you place them in the provided array, the further they will be in
* the background. e.g. if you provide new DrawOrer[] { DrawOrder.BAR,
* DrawOrder.LINE }, the bars will be drawn behind the lines.
*
* @param order
*/
public void setDrawOrder(DrawOrder[] order) {
if (order == null || order.length <= 0)
return;
mDrawOrder = order;
}
/**
* draws all MarkerViews on the highlighted positions
*/
protected void drawMarkers(Canvas canvas) {
// if there is no marker view or drawing marker is disabled
if (mMarker == null || !isDrawMarkersEnabled() || !valuesToHighlight())
return;
for (int i = 0; i < mIndicesToHighlight.length; i++) {
Highlight highlight = mIndicesToHighlight[i];
IDataSet set = mData.getDataSetByHighlight(highlight);
Entry e = mData.getEntryForHighlight(highlight);
if (e == null)
continue;
int entryIndex = set.getEntryIndex(e);
// make sure entry not null
if (entryIndex > set.getEntryCount() * mAnimator.getPhaseX())
continue;
float[] pos = getMarkerPosition(highlight);
// check bounds
if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
continue;
// callbacks to update the content
mMarker.refreshContent(e, highlight);
// draw the marker
mMarker.draw(canvas, pos[0], pos[1]);
}
}
}

@ -0,0 +1,346 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import com.github.mikephil.charting.components.XAxis.XAxisPosition;
import com.github.mikephil.charting.components.YAxis.AxisDependency;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.highlight.HorizontalBarHighlighter;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer;
import com.github.mikephil.charting.renderer.XAxisRendererHorizontalBarChart;
import com.github.mikephil.charting.renderer.YAxisRendererHorizontalBarChart;
import com.github.mikephil.charting.utils.HorizontalViewPortHandler;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.TransformerHorizontalBarChart;
import com.github.mikephil.charting.utils.Utils;
/**
* BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched, meaning the YAxis class
* represents the horizontal values and the XAxis class represents the vertical values.
*
* @author Philipp Jahoda
*/
public class HorizontalBarChart extends BarChart {
public HorizontalBarChart(Context context) {
super(context);
}
public HorizontalBarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HorizontalBarChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
mViewPortHandler = new HorizontalViewPortHandler();
super.init();
mLeftAxisTransformer = new TransformerHorizontalBarChart(mViewPortHandler);
mRightAxisTransformer = new TransformerHorizontalBarChart(mViewPortHandler);
mRenderer = new HorizontalBarChartRenderer(this, mAnimator, mViewPortHandler);
setHighlighter(new HorizontalBarHighlighter(this));
mAxisRendererLeft = new YAxisRendererHorizontalBarChart(mViewPortHandler, mAxisLeft, mLeftAxisTransformer);
mAxisRendererRight = new YAxisRendererHorizontalBarChart(mViewPortHandler, mAxisRight, mRightAxisTransformer);
mXAxisRenderer = new XAxisRendererHorizontalBarChart(mViewPortHandler, mXAxis, mLeftAxisTransformer, this);
}
private RectF mOffsetsBuffer = new RectF();
protected void calculateLegendOffsets(RectF offsets) {
offsets.left = 0.f;
offsets.right = 0.f;
offsets.top = 0.f;
offsets.bottom = 0.f;
if (mLegend == null || !mLegend.isEnabled() || mLegend.isDrawInsideEnabled())
return;
switch (mLegend.getOrientation()) {
case VERTICAL:
switch (mLegend.getHorizontalAlignment()) {
case LEFT:
offsets.left += Math.min(mLegend.mNeededWidth,
mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent())
+ mLegend.getXOffset();
break;
case RIGHT:
offsets.right += Math.min(mLegend.mNeededWidth,
mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent())
+ mLegend.getXOffset();
break;
case CENTER:
switch (mLegend.getVerticalAlignment()) {
case TOP:
offsets.top += Math.min(mLegend.mNeededHeight,
mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent())
+ mLegend.getYOffset();
break;
case BOTTOM:
offsets.bottom += Math.min(mLegend.mNeededHeight,
mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent())
+ mLegend.getYOffset();
break;
default:
break;
}
}
break;
case HORIZONTAL:
switch (mLegend.getVerticalAlignment()) {
case TOP:
offsets.top += Math.min(mLegend.mNeededHeight,
mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent())
+ mLegend.getYOffset();
if (mAxisLeft.isEnabled() && mAxisLeft.isDrawLabelsEnabled())
offsets.top += mAxisLeft.getRequiredHeightSpace(
mAxisRendererLeft.getPaintAxisLabels());
break;
case BOTTOM:
offsets.bottom += Math.min(mLegend.mNeededHeight,
mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent())
+ mLegend.getYOffset();
if (mAxisRight.isEnabled() && mAxisRight.isDrawLabelsEnabled())
offsets.bottom += mAxisRight.getRequiredHeightSpace(
mAxisRendererRight.getPaintAxisLabels());
break;
default:
break;
}
break;
}
}
@Override
public void calculateOffsets() {
float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f;
calculateLegendOffsets(mOffsetsBuffer);
offsetLeft += mOffsetsBuffer.left;
offsetTop += mOffsetsBuffer.top;
offsetRight += mOffsetsBuffer.right;
offsetBottom += mOffsetsBuffer.bottom;
// offsets for y-labels
if (mAxisLeft.needsOffset()) {
offsetTop += mAxisLeft.getRequiredHeightSpace(mAxisRendererLeft.getPaintAxisLabels());
}
if (mAxisRight.needsOffset()) {
offsetBottom += mAxisRight.getRequiredHeightSpace(mAxisRendererRight.getPaintAxisLabels());
}
float xlabelwidth = mXAxis.mLabelRotatedWidth;
if (mXAxis.isEnabled()) {
// offsets for x-labels
if (mXAxis.getPosition() == XAxisPosition.BOTTOM) {
offsetLeft += xlabelwidth;
} else if (mXAxis.getPosition() == XAxisPosition.TOP) {
offsetRight += xlabelwidth;
} else if (mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) {
offsetLeft += xlabelwidth;
offsetRight += xlabelwidth;
}
}
offsetTop += getExtraTopOffset();
offsetRight += getExtraRightOffset();
offsetBottom += getExtraBottomOffset();
offsetLeft += getExtraLeftOffset();
float minOffset = Utils.convertDpToPixel(mMinOffset);
mViewPortHandler.restrainViewPort(
Math.max(minOffset, offsetLeft),
Math.max(minOffset, offsetTop),
Math.max(minOffset, offsetRight),
Math.max(minOffset, offsetBottom));
if (mLogEnabled) {
Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop + ", offsetRight: " +
offsetRight + ", offsetBottom: "
+ offsetBottom);
Log.i(LOG_TAG, "Content: " + mViewPortHandler.getContentRect().toString());
}
prepareOffsetMatrix();
prepareValuePxMatrix();
}
@Override
protected void prepareValuePxMatrix() {
mRightAxisTransformer.prepareMatrixValuePx(mAxisRight.mAxisMinimum, mAxisRight.mAxisRange, mXAxis.mAxisRange,
mXAxis.mAxisMinimum);
mLeftAxisTransformer.prepareMatrixValuePx(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisRange, mXAxis.mAxisRange,
mXAxis.mAxisMinimum);
}
@Override
protected float[] getMarkerPosition(Highlight high) {
return new float[]{high.getDrawY(), high.getDrawX()};
}
@Override
public void getBarBounds(BarEntry e, RectF outputRect) {
RectF bounds = outputRect;
IBarDataSet set = mData.getDataSetForEntry(e);
if (set == null) {
outputRect.set(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
return;
}
float y = e.getY();
float x = e.getX();
float barWidth = mData.getBarWidth();
float top = x - barWidth / 2f;
float bottom = x + barWidth / 2f;
float left = y >= 0 ? y : 0;
float right = y <= 0 ? y : 0;
bounds.set(left, top, right, bottom);
getTransformer(set.getAxisDependency()).rectValueToPixel(bounds);
}
protected float[] mGetPositionBuffer = new float[2];
/**
* Returns a recyclable MPPointF instance.
*
* @param e
* @param axis
* @return
*/
@Override
public MPPointF getPosition(Entry e, AxisDependency axis) {
if (e == null)
return null;
float[] vals = mGetPositionBuffer;
vals[0] = e.getY();
vals[1] = e.getX();
getTransformer(axis).pointValuesToPixel(vals);
return MPPointF.getInstance(vals[0], vals[1]);
}
/**
* Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point
* inside the BarChart.
*
* @param x
* @param y
* @return
*/
@Override
public Highlight getHighlightByTouchPoint(float x, float y) {
if (mData == null) {
if (mLogEnabled)
Log.e(LOG_TAG, "Can't select by touch. No data set.");
return null;
} else
return getHighlighter().getHighlight(y, x); // switch x and y
}
@Override
public float getLowestVisibleX() {
getTransformer(AxisDependency.LEFT).getValuesByTouchPoint(mViewPortHandler.contentLeft(),
mViewPortHandler.contentBottom(), posForGetLowestVisibleX);
float result = (float) Math.max(mXAxis.mAxisMinimum, posForGetLowestVisibleX.y);
return result;
}
@Override
public float getHighestVisibleX() {
getTransformer(AxisDependency.LEFT).getValuesByTouchPoint(mViewPortHandler.contentLeft(),
mViewPortHandler.contentTop(), posForGetHighestVisibleX);
float result = (float) Math.min(mXAxis.mAxisMaximum, posForGetHighestVisibleX.y);
return result;
}
/**
* ###### VIEWPORT METHODS BELOW THIS ######
*/
@Override
public void setVisibleXRangeMaximum(float maxXRange) {
float xScale = mXAxis.mAxisRange / (maxXRange);
mViewPortHandler.setMinimumScaleY(xScale);
}
@Override
public void setVisibleXRangeMinimum(float minXRange) {
float xScale = mXAxis.mAxisRange / (minXRange);
mViewPortHandler.setMaximumScaleY(xScale);
}
@Override
public void setVisibleXRange(float minXRange, float maxXRange) {
float minScale = mXAxis.mAxisRange / minXRange;
float maxScale = mXAxis.mAxisRange / maxXRange;
mViewPortHandler.setMinMaxScaleY(minScale, maxScale);
}
@Override
public void setVisibleYRangeMaximum(float maxYRange, AxisDependency axis) {
float yScale = getAxisRange(axis) / maxYRange;
mViewPortHandler.setMinimumScaleX(yScale);
}
@Override
public void setVisibleYRangeMinimum(float minYRange, AxisDependency axis) {
float yScale = getAxisRange(axis) / minYRange;
mViewPortHandler.setMaximumScaleX(yScale);
}
@Override
public void setVisibleYRange(float minYRange, float maxYRange, AxisDependency axis) {
float minScale = getAxisRange(axis) / minYRange;
float maxScale = getAxisRange(axis) / maxYRange;
mViewPortHandler.setMinMaxScaleX(minScale, maxScale);
}
}

@ -0,0 +1,50 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
import com.github.mikephil.charting.renderer.LineChartRenderer;
/**
* Chart that draws lines, surfaces, circles, ...
*
* @author Philipp Jahoda
*/
public class LineChart extends BarLineChartBase<LineData> implements LineDataProvider {
public LineChart(Context context) {
super(context);
}
public LineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LineChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new LineChartRenderer(this, mAnimator, mViewPortHandler);
}
@Override
public LineData getLineData() {
return mData;
}
@Override
protected void onDetachedFromWindow() {
// releases the bitmap in the renderer to avoid oom error
if (mRenderer != null && mRenderer instanceof LineChartRenderer) {
((LineChartRenderer) mRenderer).releaseBitmap();
}
super.onDetachedFromWindow();
}
}

@ -0,0 +1,804 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.highlight.PieHighlighter;
import com.github.mikephil.charting.interfaces.datasets.IPieDataSet;
import com.github.mikephil.charting.renderer.PieChartRenderer;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Utils;
import java.util.List;
/**
* View that represents a pie chart. Draws cake like slices.
*
* @author Philipp Jahoda
*/
public class PieChart extends PieRadarChartBase<PieData> {
/**
* rect object that represents the bounds of the piechart, needed for
* drawing the circle
*/
private RectF mCircleBox = new RectF();
/**
* flag indicating if entry labels should be drawn or not
*/
private boolean mDrawEntryLabels = true;
/**
* array that holds the width of each pie-slice in degrees
*/
private float[] mDrawAngles = new float[1];
/**
* array that holds the absolute angle in degrees of each slice
*/
private float[] mAbsoluteAngles = new float[1];
/**
* if true, the white hole inside the chart will be drawn
*/
private boolean mDrawHole = true;
/**
* if true, the hole will see-through to the inner tips of the slices
*/
private boolean mDrawSlicesUnderHole = false;
/**
* if true, the values inside the piechart are drawn as percent values
*/
private boolean mUsePercentValues = false;
/**
* if true, the slices of the piechart are rounded
*/
private boolean mDrawRoundedSlices = false;
/**
* variable for the text that is drawn in the center of the pie-chart
*/
private CharSequence mCenterText = "";
private MPPointF mCenterTextOffset = MPPointF.getInstance(0, 0);
/**
* indicates the size of the hole in the center of the piechart, default:
* radius / 2
*/
private float mHoleRadiusPercent = 50f;
/**
* the radius of the transparent circle next to the chart-hole in the center
*/
protected float mTransparentCircleRadiusPercent = 55f;
/**
* if enabled, centertext is drawn
*/
private boolean mDrawCenterText = true;
private float mCenterTextRadiusPercent = 100.f;
protected float mMaxAngle = 360f;
/**
* Minimum angle to draw slices, this only works if there is enough room for all slices to have
* the minimum angle, default 0f.
*/
private float mMinAngleForSlices = 0f;
public PieChart(Context context) {
super(context);
}
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PieChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new PieChartRenderer(this, mAnimator, mViewPortHandler);
mXAxis = null;
mHighlighter = new PieHighlighter(this);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mData == null)
return;
mRenderer.drawData(canvas);
if (valuesToHighlight())
mRenderer.drawHighlighted(canvas, mIndicesToHighlight);
mRenderer.drawExtras(canvas);
mRenderer.drawValues(canvas);
mLegendRenderer.renderLegend(canvas);
drawDescription(canvas);
drawMarkers(canvas);
}
@Override
public void calculateOffsets() {
super.calculateOffsets();
// prevent nullpointer when no data set
if (mData == null)
return;
float diameter = getDiameter();
float radius = diameter / 2f;
MPPointF c = getCenterOffsets();
float shift = mData.getDataSet().getSelectionShift();
// create the circle box that will contain the pie-chart (the bounds of
// the pie-chart)
mCircleBox.set(c.x - radius + shift,
c.y - radius + shift,
c.x + radius - shift,
c.y + radius - shift);
MPPointF.recycleInstance(c);
}
@Override
protected void calcMinMax() {
calcAngles();
}
@Override
protected float[] getMarkerPosition(Highlight highlight) {
MPPointF center = getCenterCircleBox();
float r = getRadius();
float off = r / 10f * 3.6f;
if (isDrawHoleEnabled()) {
off = (r - (r / 100f * getHoleRadius())) / 2f;
}
r -= off; // offset to keep things inside the chart
float rotationAngle = getRotationAngle();
int entryIndex = (int) highlight.getX();
// offset needed to center the drawn text in the slice
float offset = mDrawAngles[entryIndex] / 2;
// calculate the text position
float x = (float) (r
* Math.cos(Math.toRadians((rotationAngle + mAbsoluteAngles[entryIndex] - offset)
* mAnimator.getPhaseY())) + center.x);
float y = (float) (r
* Math.sin(Math.toRadians((rotationAngle + mAbsoluteAngles[entryIndex] - offset)
* mAnimator.getPhaseY())) + center.y);
MPPointF.recycleInstance(center);
return new float[]{x, y};
}
/**
* calculates the needed angles for the chart slices
*/
private void calcAngles() {
int entryCount = mData.getEntryCount();
if (mDrawAngles.length != entryCount) {
mDrawAngles = new float[entryCount];
} else {
for (int i = 0; i < entryCount; i++) {
mDrawAngles[i] = 0;
}
}
if (mAbsoluteAngles.length != entryCount) {
mAbsoluteAngles = new float[entryCount];
} else {
for (int i = 0; i < entryCount; i++) {
mAbsoluteAngles[i] = 0;
}
}
float yValueSum = mData.getYValueSum();
List<IPieDataSet> dataSets = mData.getDataSets();
boolean hasMinAngle = mMinAngleForSlices != 0f && entryCount * mMinAngleForSlices <= mMaxAngle;
float[] minAngles = new float[entryCount];
int cnt = 0;
float offset = 0f;
float diff = 0f;
for (int i = 0; i < mData.getDataSetCount(); i++) {
IPieDataSet set = dataSets.get(i);
for (int j = 0; j < set.getEntryCount(); j++) {
float drawAngle = calcAngle(Math.abs(set.getEntryForIndex(j).getY()), yValueSum);
if (hasMinAngle) {
float temp = drawAngle - mMinAngleForSlices;
if (temp <= 0) {
minAngles[cnt] = mMinAngleForSlices;
offset += -temp;
} else {
minAngles[cnt] = drawAngle;
diff += temp;
}
}
mDrawAngles[cnt] = drawAngle;
if (cnt == 0) {
mAbsoluteAngles[cnt] = mDrawAngles[cnt];
} else {
mAbsoluteAngles[cnt] = mAbsoluteAngles[cnt - 1] + mDrawAngles[cnt];
}
cnt++;
}
}
if (hasMinAngle) {
// Correct bigger slices by relatively reducing their angles based on the total angle needed to subtract
// This requires that `entryCount * mMinAngleForSlices <= mMaxAngle` be true to properly work!
for (int i = 0; i < entryCount; i++) {
minAngles[i] -= (minAngles[i] - mMinAngleForSlices) / diff * offset;
if (i == 0) {
mAbsoluteAngles[0] = minAngles[0];
} else {
mAbsoluteAngles[i] = mAbsoluteAngles[i - 1] + minAngles[i];
}
}
mDrawAngles = minAngles;
}
}
/**
* Checks if the given index is set to be highlighted.
*
* @param index
* @return
*/
public boolean needsHighlight(int index) {
// no highlight
if (!valuesToHighlight())
return false;
for (int i = 0; i < mIndicesToHighlight.length; i++)
// check if the xvalue for the given dataset needs highlight
if ((int) mIndicesToHighlight[i].getX() == index)
return true;
return false;
}
/**
* calculates the needed angle for a given value
*
* @param value
* @return
*/
private float calcAngle(float value) {
return calcAngle(value, mData.getYValueSum());
}
/**
* calculates the needed angle for a given value
*
* @param value
* @param yValueSum
* @return
*/
private float calcAngle(float value, float yValueSum) {
return value / yValueSum * mMaxAngle;
}
/**
* This will throw an exception, PieChart has no XAxis object.
*
* @return
*/
@Deprecated
@Override
public XAxis getXAxis() {
throw new RuntimeException("PieChart has no XAxis");
}
@Override
public int getIndexForAngle(float angle) {
// take the current angle of the chart into consideration
float a = Utils.getNormalizedAngle(angle - getRotationAngle());
for (int i = 0; i < mAbsoluteAngles.length; i++) {
if (mAbsoluteAngles[i] > a)
return i;
}
return -1; // return -1 if no index found
}
/**
* Returns the index of the DataSet this x-index belongs to.
*
* @param xIndex
* @return
*/
public int getDataSetIndexForIndex(int xIndex) {
List<IPieDataSet> dataSets = mData.getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
if (dataSets.get(i).getEntryForXValue(xIndex, Float.NaN) != null)
return i;
}
return -1;
}
/**
* returns an integer array of all the different angles the chart slices
* have the angles in the returned array determine how much space (of 360°)
* each slice takes
*
* @return
*/
public float[] getDrawAngles() {
return mDrawAngles;
}
/**
* returns the absolute angles of the different chart slices (where the
* slices end)
*
* @return
*/
public float[] getAbsoluteAngles() {
return mAbsoluteAngles;
}
/**
* Sets the color for the hole that is drawn in the center of the PieChart
* (if enabled).
*
* @param color
*/
public void setHoleColor(int color) {
((PieChartRenderer) mRenderer).getPaintHole().setColor(color);
}
/**
* Enable or disable the visibility of the inner tips of the slices behind the hole
*/
public void setDrawSlicesUnderHole(boolean enable) {
mDrawSlicesUnderHole = enable;
}
/**
* Returns true if the inner tips of the slices are visible behind the hole,
* false if not.
*
* @return true if slices are visible behind the hole.
*/
public boolean isDrawSlicesUnderHoleEnabled() {
return mDrawSlicesUnderHole;
}
/**
* set this to true to draw the pie center empty
*
* @param enabled
*/
public void setDrawHoleEnabled(boolean enabled) {
this.mDrawHole = enabled;
}
/**
* returns true if the hole in the center of the pie-chart is set to be
* visible, false if not
*
* @return
*/
public boolean isDrawHoleEnabled() {
return mDrawHole;
}
/**
* Sets the text String that is displayed in the center of the PieChart.
*
* @param text
*/
public void setCenterText(CharSequence text) {
if (text == null)
mCenterText = "";
else
mCenterText = text;
}
/**
* returns the text that is drawn in the center of the pie-chart
*
* @return
*/
public CharSequence getCenterText() {
return mCenterText;
}
/**
* set this to true to draw the text that is displayed in the center of the
* pie chart
*
* @param enabled
*/
public void setDrawCenterText(boolean enabled) {
this.mDrawCenterText = enabled;
}
/**
* returns true if drawing the center text is enabled
*
* @return
*/
public boolean isDrawCenterTextEnabled() {
return mDrawCenterText;
}
@Override
protected float getRequiredLegendOffset() {
return mLegendRenderer.getLabelPaint().getTextSize() * 2.f;
}
@Override
protected float getRequiredBaseOffset() {
return 0;
}
@Override
public float getRadius() {
if (mCircleBox == null)
return 0;
else
return Math.min(mCircleBox.width() / 2f, mCircleBox.height() / 2f);
}
/**
* returns the circlebox, the boundingbox of the pie-chart slices
*
* @return
*/
public RectF getCircleBox() {
return mCircleBox;
}
/**
* returns the center of the circlebox
*
* @return
*/
public MPPointF getCenterCircleBox() {
return MPPointF.getInstance(mCircleBox.centerX(), mCircleBox.centerY());
}
/**
* sets the typeface for the center-text paint
*
* @param t
*/
public void setCenterTextTypeface(Typeface t) {
((PieChartRenderer) mRenderer).getPaintCenterText().setTypeface(t);
}
/**
* Sets the size of the center text of the PieChart in dp.
*
* @param sizeDp
*/
public void setCenterTextSize(float sizeDp) {
((PieChartRenderer) mRenderer).getPaintCenterText().setTextSize(
Utils.convertDpToPixel(sizeDp));
}
/**
* Sets the size of the center text of the PieChart in pixels.
*
* @param sizePixels
*/
public void setCenterTextSizePixels(float sizePixels) {
((PieChartRenderer) mRenderer).getPaintCenterText().setTextSize(sizePixels);
}
/**
* Sets the offset the center text should have from it's original position in dp. Default x = 0, y = 0
*
* @param x
* @param y
*/
public void setCenterTextOffset(float x, float y) {
mCenterTextOffset.x = Utils.convertDpToPixel(x);
mCenterTextOffset.y = Utils.convertDpToPixel(y);
}
/**
* Returns the offset on the x- and y-axis the center text has in dp.
*
* @return
*/
public MPPointF getCenterTextOffset() {
return MPPointF.getInstance(mCenterTextOffset.x, mCenterTextOffset.y);
}
/**
* Sets the color of the center text of the PieChart.
*
* @param color
*/
public void setCenterTextColor(int color) {
((PieChartRenderer) mRenderer).getPaintCenterText().setColor(color);
}
/**
* sets the radius of the hole in the center of the piechart in percent of
* the maximum radius (max = the radius of the whole chart), default 50%
*
* @param percent
*/
public void setHoleRadius(final float percent) {
mHoleRadiusPercent = percent;
}
/**
* Returns the size of the hole radius in percent of the total radius.
*
* @return
*/
public float getHoleRadius() {
return mHoleRadiusPercent;
}
/**
* Sets the color the transparent-circle should have.
*
* @param color
*/
public void setTransparentCircleColor(int color) {
Paint p = ((PieChartRenderer) mRenderer).getPaintTransparentCircle();
int alpha = p.getAlpha();
p.setColor(color);
p.setAlpha(alpha);
}
/**
* sets the radius of the transparent circle that is drawn next to the hole
* in the piechart in percent of the maximum radius (max = the radius of the
* whole chart), default 55% -> means 5% larger than the center-hole by
* default
*
* @param percent
*/
public void setTransparentCircleRadius(final float percent) {
mTransparentCircleRadiusPercent = percent;
}
public float getTransparentCircleRadius() {
return mTransparentCircleRadiusPercent;
}
/**
* Sets the amount of transparency the transparent circle should have 0 = fully transparent,
* 255 = fully opaque.
* Default value is 100.
*
* @param alpha 0-255
*/
public void setTransparentCircleAlpha(int alpha) {
((PieChartRenderer) mRenderer).getPaintTransparentCircle().setAlpha(alpha);
}
/**
* Set this to true to draw the entry labels into the pie slices (Provided by the getLabel() method of the PieEntry class).
* Deprecated -> use setDrawEntryLabels(...) instead.
*
* @param enabled
*/
@Deprecated
public void setDrawSliceText(boolean enabled) {
mDrawEntryLabels = enabled;
}
/**
* Set this to true to draw the entry labels into the pie slices (Provided by the getLabel() method of the PieEntry class).
*
* @param enabled
*/
public void setDrawEntryLabels(boolean enabled) {
mDrawEntryLabels = enabled;
}
/**
* Returns true if drawing the entry labels is enabled, false if not.
*
* @return
*/
public boolean isDrawEntryLabelsEnabled() {
return mDrawEntryLabels;
}
/**
* Sets the color the entry labels are drawn with.
*
* @param color
*/
public void setEntryLabelColor(int color) {
((PieChartRenderer) mRenderer).getPaintEntryLabels().setColor(color);
}
/**
* Sets a custom Typeface for the drawing of the entry labels.
*
* @param tf
*/
public void setEntryLabelTypeface(Typeface tf) {
((PieChartRenderer) mRenderer).getPaintEntryLabels().setTypeface(tf);
}
/**
* Sets the size of the entry labels in dp. Default: 13dp
*
* @param size
*/
public void setEntryLabelTextSize(float size) {
((PieChartRenderer) mRenderer).getPaintEntryLabels().setTextSize(Utils.convertDpToPixel(size));
}
/**
* Sets whether to draw slices in a curved fashion, only works if drawing the hole is enabled
* and if the slices are not drawn under the hole.
*
* @param enabled draw curved ends of slices
*/
public void setDrawRoundedSlices(boolean enabled) {
mDrawRoundedSlices = enabled;
}
/**
* Returns true if the chart is set to draw each end of a pie-slice
* "rounded".
*
* @return
*/
public boolean isDrawRoundedSlicesEnabled() {
return mDrawRoundedSlices;
}
/**
* If this is enabled, values inside the PieChart are drawn in percent and
* not with their original value. Values provided for the IValueFormatter to
* format are then provided in percent.
*
* @param enabled
*/
public void setUsePercentValues(boolean enabled) {
mUsePercentValues = enabled;
}
/**
* Returns true if using percentage values is enabled for the chart.
*
* @return
*/
public boolean isUsePercentValuesEnabled() {
return mUsePercentValues;
}
/**
* the rectangular radius of the bounding box for the center text, as a percentage of the pie
* hole
* default 1.f (100%)
*/
public void setCenterTextRadiusPercent(float percent) {
mCenterTextRadiusPercent = percent;
}
/**
* the rectangular radius of the bounding box for the center text, as a percentage of the pie
* hole
* default 1.f (100%)
*/
public float getCenterTextRadiusPercent() {
return mCenterTextRadiusPercent;
}
public float getMaxAngle() {
return mMaxAngle;
}
/**
* Sets the max angle that is used for calculating the pie-circle. 360f means
* it's a full PieChart, 180f results in a half-pie-chart. Default: 360f
*
* @param maxangle min 90, max 360
*/
public void setMaxAngle(float maxangle) {
if (maxangle > 360)
maxangle = 360f;
if (maxangle < 90)
maxangle = 90f;
this.mMaxAngle = maxangle;
}
/**
* The minimum angle slices on the chart are rendered with, default is 0f.
*
* @return minimum angle for slices
*/
public float getMinAngleForSlices() {
return mMinAngleForSlices;
}
/**
* Set the angle to set minimum size for slices, you must call {@link #notifyDataSetChanged()}
* and {@link #invalidate()} when changing this, only works if there is enough room for all
* slices to have the minimum angle.
*
* @param minAngle minimum 0, maximum is half of {@link #setMaxAngle(float)}
*/
public void setMinAngleForSlices(float minAngle) {
if (minAngle > (mMaxAngle / 2f))
minAngle = mMaxAngle / 2f;
else if (minAngle < 0)
minAngle = 0f;
this.mMinAngleForSlices = minAngle;
}
@Override
protected void onDetachedFromWindow() {
// releases the bitmap in the renderer to avoid oom error
if (mRenderer != null && mRenderer instanceof PieChartRenderer) {
((PieChartRenderer) mRenderer).releaseBitmap();
}
super.onDetachedFromWindow();
}
}

@ -0,0 +1,499 @@
package com.github.mikephil.charting.charts;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.animation.Easing.EasingFunction;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.interfaces.datasets.IDataSet;
import com.github.mikephil.charting.listener.PieRadarChartTouchListener;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Utils;
/**
* Baseclass of PieChart and RadarChart.
*
* @author Philipp Jahoda
*/
public abstract class PieRadarChartBase<T extends ChartData<? extends IDataSet<? extends Entry>>>
extends Chart<T> {
/**
* holds the normalized version of the current rotation angle of the chart
*/
private float mRotationAngle = 270f;
/**
* holds the raw version of the current rotation angle of the chart
*/
private float mRawRotationAngle = 270f;
/**
* flag that indicates if rotation is enabled or not
*/
protected boolean mRotateEnabled = true;
/**
* Sets the minimum offset (padding) around the chart, defaults to 0.f
*/
protected float mMinOffset = 0.f;
public PieRadarChartBase(Context context) {
super(context);
}
public PieRadarChartBase(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PieRadarChartBase(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mChartTouchListener = new PieRadarChartTouchListener(this);
}
@Override
protected void calcMinMax() {
//mXAxis.mAxisRange = mData.getXVals().size() - 1;
}
@Override
public int getMaxVisibleCount() {
return mData.getEntryCount();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// use the pie- and radarchart listener own listener
if (mTouchEnabled && mChartTouchListener != null)
return mChartTouchListener.onTouch(this, event);
else
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
if (mChartTouchListener instanceof PieRadarChartTouchListener)
((PieRadarChartTouchListener) mChartTouchListener).computeScroll();
}
@Override
public void notifyDataSetChanged() {
if (mData == null)
return;
calcMinMax();
if (mLegend != null)
mLegendRenderer.computeLegend(mData);
calculateOffsets();
}
@Override
public void calculateOffsets() {
float legendLeft = 0f, legendRight = 0f, legendBottom = 0f, legendTop = 0f;
if (mLegend != null && mLegend.isEnabled() && !mLegend.isDrawInsideEnabled()) {
float fullLegendWidth = Math.min(mLegend.mNeededWidth,
mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent());
switch (mLegend.getOrientation()) {
case VERTICAL: {
float xLegendOffset = 0.f;
if (mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.LEFT
|| mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.RIGHT) {
if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.CENTER) {
// this is the space between the legend and the chart
final float spacing = Utils.convertDpToPixel(13f);
xLegendOffset = fullLegendWidth + spacing;
} else {
// this is the space between the legend and the chart
float spacing = Utils.convertDpToPixel(8f);
float legendWidth = fullLegendWidth + spacing;
float legendHeight = mLegend.mNeededHeight + mLegend.mTextHeightMax;
MPPointF center = getCenter();
float bottomX = mLegend.getHorizontalAlignment() ==
Legend.LegendHorizontalAlignment.RIGHT
? getWidth() - legendWidth + 15.f
: legendWidth - 15.f;
float bottomY = legendHeight + 15.f;
float distLegend = distanceToCenter(bottomX, bottomY);
MPPointF reference = getPosition(center, getRadius(),
getAngleForPoint(bottomX, bottomY));
float distReference = distanceToCenter(reference.x, reference.y);
float minOffset = Utils.convertDpToPixel(5f);
if (bottomY >= center.y && getHeight() - legendWidth > getWidth()) {
xLegendOffset = legendWidth;
} else if (distLegend < distReference) {
float diff = distReference - distLegend;
xLegendOffset = minOffset + diff;
}
MPPointF.recycleInstance(center);
MPPointF.recycleInstance(reference);
}
}
switch (mLegend.getHorizontalAlignment()) {
case LEFT:
legendLeft = xLegendOffset;
break;
case RIGHT:
legendRight = xLegendOffset;
break;
case CENTER:
switch (mLegend.getVerticalAlignment()) {
case TOP:
legendTop = Math.min(mLegend.mNeededHeight,
mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent());
break;
case BOTTOM:
legendBottom = Math.min(mLegend.mNeededHeight,
mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent());
break;
}
break;
}
}
break;
case HORIZONTAL:
float yLegendOffset = 0.f;
if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.TOP ||
mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.BOTTOM) {
// It's possible that we do not need this offset anymore as it
// is available through the extraOffsets, but changing it can mean
// changing default visibility for existing apps.
float yOffset = getRequiredLegendOffset();
yLegendOffset = Math.min(mLegend.mNeededHeight + yOffset,
mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent());
switch (mLegend.getVerticalAlignment()) {
case TOP:
legendTop = yLegendOffset;
break;
case BOTTOM:
legendBottom = yLegendOffset;
break;
}
}
break;
}
legendLeft += getRequiredBaseOffset();
legendRight += getRequiredBaseOffset();
legendTop += getRequiredBaseOffset();
legendBottom += getRequiredBaseOffset();
}
float minOffset = Utils.convertDpToPixel(mMinOffset);
if (this instanceof RadarChart) {
XAxis x = this.getXAxis();
if (x.isEnabled() && x.isDrawLabelsEnabled()) {
minOffset = Math.max(minOffset, x.mLabelRotatedWidth);
}
}
legendTop += getExtraTopOffset();
legendRight += getExtraRightOffset();
legendBottom += getExtraBottomOffset();
legendLeft += getExtraLeftOffset();
float offsetLeft = Math.max(minOffset, legendLeft);
float offsetTop = Math.max(minOffset, legendTop);
float offsetRight = Math.max(minOffset, legendRight);
float offsetBottom = Math.max(minOffset, Math.max(getRequiredBaseOffset(), legendBottom));
mViewPortHandler.restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom);
if (mLogEnabled)
Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop
+ ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom);
}
/**
* returns the angle relative to the chart center for the given point on the
* chart in degrees. The angle is always between 0 and 360°, 0° is NORTH,
* 90° is EAST, ...
*
* @param x
* @param y
* @return
*/
public float getAngleForPoint(float x, float y) {
MPPointF c = getCenterOffsets();
double tx = x - c.x, ty = y - c.y;
double length = Math.sqrt(tx * tx + ty * ty);
double r = Math.acos(ty / length);
float angle = (float) Math.toDegrees(r);
if (x > c.x)
angle = 360f - angle;
// add 90° because chart starts EAST
angle = angle + 90f;
// neutralize overflow
if (angle > 360f)
angle = angle - 360f;
MPPointF.recycleInstance(c);
return angle;
}
/**
* Returns a recyclable MPPointF instance.
* Calculates the position around a center point, depending on the distance
* from the center, and the angle of the position around the center.
*
* @param center
* @param dist
* @param angle in degrees, converted to radians internally
* @return
*/
public MPPointF getPosition(MPPointF center, float dist, float angle) {
MPPointF p = MPPointF.getInstance(0, 0);
getPosition(center, dist, angle, p);
return p;
}
public void getPosition(MPPointF center, float dist, float angle, MPPointF outputPoint) {
outputPoint.x = (float) (center.x + dist * Math.cos(Math.toRadians(angle)));
outputPoint.y = (float) (center.y + dist * Math.sin(Math.toRadians(angle)));
}
/**
* Returns the distance of a certain point on the chart to the center of the
* chart.
*
* @param x
* @param y
* @return
*/
public float distanceToCenter(float x, float y) {
MPPointF c = getCenterOffsets();
float dist = 0f;
float xDist = 0f;
float yDist = 0f;
if (x > c.x) {
xDist = x - c.x;
} else {
xDist = c.x - x;
}
if (y > c.y) {
yDist = y - c.y;
} else {
yDist = c.y - y;
}
// pythagoras
dist = (float) Math.sqrt(Math.pow(xDist, 2.0) + Math.pow(yDist, 2.0));
MPPointF.recycleInstance(c);
return dist;
}
/**
* Returns the xIndex for the given angle around the center of the chart.
* Returns -1 if not found / outofbounds.
*
* @param angle
* @return
*/
public abstract int getIndexForAngle(float angle);
/**
* Set an offset for the rotation of the RadarChart in degrees. Default 270f
* --> top (NORTH)
*
* @param angle
*/
public void setRotationAngle(float angle) {
mRawRotationAngle = angle;
mRotationAngle = Utils.getNormalizedAngle(mRawRotationAngle);
}
/**
* gets the raw version of the current rotation angle of the pie chart the
* returned value could be any value, negative or positive, outside of the
* 360 degrees. this is used when working with rotation direction, mainly by
* gestures and animations.
*
* @return
*/
public float getRawRotationAngle() {
return mRawRotationAngle;
}
/**
* gets a normalized version of the current rotation angle of the pie chart,
* which will always be between 0.0 < 360.0
*
* @return
*/
public float getRotationAngle() {
return mRotationAngle;
}
/**
* Set this to true to enable the rotation / spinning of the chart by touch.
* Set it to false to disable it. Default: true
*
* @param enabled
*/
public void setRotationEnabled(boolean enabled) {
mRotateEnabled = enabled;
}
/**
* Returns true if rotation of the chart by touch is enabled, false if not.
*
* @return
*/
public boolean isRotationEnabled() {
return mRotateEnabled;
}
/**
* Gets the minimum offset (padding) around the chart, defaults to 0.f
*/
public float getMinOffset() {
return mMinOffset;
}
/**
* Sets the minimum offset (padding) around the chart, defaults to 0.f
*/
public void setMinOffset(float minOffset) {
mMinOffset = minOffset;
}
/**
* returns the diameter of the pie- or radar-chart
*
* @return
*/
public float getDiameter() {
RectF content = mViewPortHandler.getContentRect();
content.left += getExtraLeftOffset();
content.top += getExtraTopOffset();
content.right -= getExtraRightOffset();
content.bottom -= getExtraBottomOffset();
return Math.min(content.width(), content.height());
}
/**
* Returns the radius of the chart in pixels.
*
* @return
*/
public abstract float getRadius();
/**
* Returns the required offset for the chart legend.
*
* @return
*/
protected abstract float getRequiredLegendOffset();
/**
* Returns the base offset needed for the chart without calculating the
* legend size.
*
* @return
*/
protected abstract float getRequiredBaseOffset();
@Override
public float getYChartMax() {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getYChartMin() {
// TODO Auto-generated method stub
return 0;
}
/**
* ################ ################ ################ ################
*/
/** CODE BELOW THIS RELATED TO ANIMATION */
/**
* Applys a spin animation to the Chart.
*
* @param durationmillis
* @param fromangle
* @param toangle
*/
@SuppressLint("NewApi")
public void spin(int durationmillis, float fromangle, float toangle, EasingFunction easing) {
setRotationAngle(fromangle);
ObjectAnimator spinAnimator = ObjectAnimator.ofFloat(this, "rotationAngle", fromangle,
toangle);
spinAnimator.setDuration(durationmillis);
spinAnimator.setInterpolator(easing);
spinAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
postInvalidate();
}
});
spinAnimator.start();
}
}

@ -0,0 +1,362 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.RectF;
import android.util.AttributeSet;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.components.YAxis.AxisDependency;
import com.github.mikephil.charting.data.RadarData;
import com.github.mikephil.charting.highlight.RadarHighlighter;
import com.github.mikephil.charting.renderer.RadarChartRenderer;
import com.github.mikephil.charting.renderer.XAxisRendererRadarChart;
import com.github.mikephil.charting.renderer.YAxisRendererRadarChart;
import com.github.mikephil.charting.utils.Utils;
/**
* Implementation of the RadarChart, a "spidernet"-like chart. It works best
* when displaying 5-10 entries per DataSet.
*
* @author Philipp Jahoda
*/
public class RadarChart extends PieRadarChartBase<RadarData> {
/**
* width of the main web lines
*/
private float mWebLineWidth = 2.5f;
/**
* width of the inner web lines
*/
private float mInnerWebLineWidth = 1.5f;
/**
* color for the main web lines
*/
private int mWebColor = Color.rgb(122, 122, 122);
/**
* color for the inner web
*/
private int mWebColorInner = Color.rgb(122, 122, 122);
/**
* transparency the grid is drawn with (0-255)
*/
private int mWebAlpha = 150;
/**
* flag indicating if the web lines should be drawn or not
*/
private boolean mDrawWeb = true;
/**
* modulus that determines how many labels and web-lines are skipped before the next is drawn
*/
private int mSkipWebLineCount = 0;
/**
* the object reprsenting the y-axis labels
*/
private YAxis mYAxis;
protected YAxisRendererRadarChart mYAxisRenderer;
protected XAxisRendererRadarChart mXAxisRenderer;
public RadarChart(Context context) {
super(context);
}
public RadarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RadarChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mYAxis = new YAxis(AxisDependency.LEFT);
mYAxis.setLabelXOffset(10f);
mWebLineWidth = Utils.convertDpToPixel(1.5f);
mInnerWebLineWidth = Utils.convertDpToPixel(0.75f);
mRenderer = new RadarChartRenderer(this, mAnimator, mViewPortHandler);
mYAxisRenderer = new YAxisRendererRadarChart(mViewPortHandler, mYAxis, this);
mXAxisRenderer = new XAxisRendererRadarChart(mViewPortHandler, mXAxis, this);
mHighlighter = new RadarHighlighter(this);
}
@Override
protected void calcMinMax() {
super.calcMinMax();
mYAxis.calculate(mData.getYMin(AxisDependency.LEFT), mData.getYMax(AxisDependency.LEFT));
mXAxis.calculate(0, mData.getMaxEntryCountSet().getEntryCount());
}
@Override
public void notifyDataSetChanged() {
if (mData == null)
return;
calcMinMax();
mYAxisRenderer.computeAxis(mYAxis.mAxisMinimum, mYAxis.mAxisMaximum, mYAxis.isInverted());
mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false);
if (mLegend != null && !mLegend.isLegendCustom())
mLegendRenderer.computeLegend(mData);
calculateOffsets();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mData == null)
return;
// if (mYAxis.isEnabled())
// mYAxisRenderer.computeAxis(mYAxis.mAxisMinimum, mYAxis.mAxisMaximum, mYAxis.isInverted());
if (mXAxis.isEnabled())
mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false);
mXAxisRenderer.renderAxisLabels(canvas);
if (mDrawWeb)
mRenderer.drawExtras(canvas);
if (mYAxis.isEnabled() && mYAxis.isDrawLimitLinesBehindDataEnabled())
mYAxisRenderer.renderLimitLines(canvas);
mRenderer.drawData(canvas);
if (valuesToHighlight())
mRenderer.drawHighlighted(canvas, mIndicesToHighlight);
if (mYAxis.isEnabled() && !mYAxis.isDrawLimitLinesBehindDataEnabled())
mYAxisRenderer.renderLimitLines(canvas);
mYAxisRenderer.renderAxisLabels(canvas);
mRenderer.drawValues(canvas);
mLegendRenderer.renderLegend(canvas);
drawDescription(canvas);
drawMarkers(canvas);
}
/**
* Returns the factor that is needed to transform values into pixels.
*
* @return
*/
public float getFactor() {
RectF content = mViewPortHandler.getContentRect();
return Math.min(content.width() / 2f, content.height() / 2f) / mYAxis.mAxisRange;
}
/**
* Returns the angle that each slice in the radar chart occupies.
*
* @return
*/
public float getSliceAngle() {
return 360f / (float) mData.getMaxEntryCountSet().getEntryCount();
}
@Override
public int getIndexForAngle(float angle) {
// take the current angle of the chart into consideration
float a = Utils.getNormalizedAngle(angle - getRotationAngle());
float sliceangle = getSliceAngle();
int max = mData.getMaxEntryCountSet().getEntryCount();
int index = 0;
for (int i = 0; i < max; i++) {
float referenceAngle = sliceangle * (i + 1) - sliceangle / 2f;
if (referenceAngle > a) {
index = i;
break;
}
}
return index;
}
/**
* Returns the object that represents all y-labels of the RadarChart.
*
* @return
*/
public YAxis getYAxis() {
return mYAxis;
}
/**
* Sets the width of the web lines that come from the center.
*
* @param width
*/
public void setWebLineWidth(float width) {
mWebLineWidth = Utils.convertDpToPixel(width);
}
public float getWebLineWidth() {
return mWebLineWidth;
}
/**
* Sets the width of the web lines that are in between the lines coming from
* the center.
*
* @param width
*/
public void setWebLineWidthInner(float width) {
mInnerWebLineWidth = Utils.convertDpToPixel(width);
}
public float getWebLineWidthInner() {
return mInnerWebLineWidth;
}
/**
* Sets the transparency (alpha) value for all web lines, default: 150, 255
* = 100% opaque, 0 = 100% transparent
*
* @param alpha
*/
public void setWebAlpha(int alpha) {
mWebAlpha = alpha;
}
/**
* Returns the alpha value for all web lines.
*
* @return
*/
public int getWebAlpha() {
return mWebAlpha;
}
/**
* Sets the color for the web lines that come from the center. Don't forget
* to use getResources().getColor(...) when loading a color from the
* resources. Default: Color.rgb(122, 122, 122)
*
* @param color
*/
public void setWebColor(int color) {
mWebColor = color;
}
public int getWebColor() {
return mWebColor;
}
/**
* Sets the color for the web lines in between the lines that come from the
* center. Don't forget to use getResources().getColor(...) when loading a
* color from the resources. Default: Color.rgb(122, 122, 122)
*
* @param color
*/
public void setWebColorInner(int color) {
mWebColorInner = color;
}
public int getWebColorInner() {
return mWebColorInner;
}
/**
* If set to true, drawing the web is enabled, if set to false, drawing the
* whole web is disabled. Default: true
*
* @param enabled
*/
public void setDrawWeb(boolean enabled) {
mDrawWeb = enabled;
}
/**
* Sets the number of web-lines that should be skipped on chart web before the
* next one is drawn. This targets the lines that come from the center of the RadarChart.
*
* @param count if count = 1 -> 1 line is skipped in between
*/
public void setSkipWebLineCount(int count) {
mSkipWebLineCount = Math.max(0, count);
}
/**
* Returns the modulus that is used for skipping web-lines.
*
* @return
*/
public int getSkipWebLineCount() {
return mSkipWebLineCount;
}
@Override
protected float getRequiredLegendOffset() {
return mLegendRenderer.getLabelPaint().getTextSize() * 4.f;
}
@Override
protected float getRequiredBaseOffset() {
return mXAxis.isEnabled() && mXAxis.isDrawLabelsEnabled() ?
mXAxis.mLabelRotatedWidth :
Utils.convertDpToPixel(10f);
}
@Override
public float getRadius() {
RectF content = mViewPortHandler.getContentRect();
return Math.min(content.width() / 2f, content.height() / 2f);
}
/**
* Returns the maximum value this chart can display on it's y-axis.
*/
public float getYChartMax() {
return mYAxis.mAxisMaximum;
}
/**
* Returns the minimum value this chart can display on it's y-axis.
*/
public float getYChartMin() {
return mYAxis.mAxisMinimum;
}
/**
* Returns the range of y-values this chart can display.
*
* @return
*/
public float getYRange() {
return mYAxis.mAxisRange;
}
}

@ -0,0 +1,77 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.data.ScatterData;
import com.github.mikephil.charting.interfaces.dataprovider.ScatterDataProvider;
import com.github.mikephil.charting.renderer.ScatterChartRenderer;
/**
* The ScatterChart. Draws dots, triangles, squares and custom shapes into the
* Chart-View. CIRCLE and SCQUARE offer the best performance, TRIANGLE has the
* worst performance.
*
* @author Philipp Jahoda
*/
public class ScatterChart extends BarLineChartBase<ScatterData> implements ScatterDataProvider {
public ScatterChart(Context context) {
super(context);
}
public ScatterChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScatterChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new ScatterChartRenderer(this, mAnimator, mViewPortHandler);
getXAxis().setSpaceMin(0.5f);
getXAxis().setSpaceMax(0.5f);
}
@Override
public ScatterData getScatterData() {
return mData;
}
/**
* Predefined ScatterShapes that allow the specification of a shape a ScatterDataSet should be drawn with.
* If a ScatterShape is specified for a ScatterDataSet, the required renderer is set.
*/
public enum ScatterShape {
SQUARE("SQUARE"),
CIRCLE("CIRCLE"),
TRIANGLE("TRIANGLE"),
CROSS("CROSS"),
X("X"),
CHEVRON_UP("CHEVRON_UP"),
CHEVRON_DOWN("CHEVRON_DOWN");
private final String shapeIdentifier;
ScatterShape(final String shapeIdentifier) {
this.shapeIdentifier = shapeIdentifier;
}
@Override
public String toString() {
return shapeIdentifier;
}
public static ScatterShape[] getAllDefaultShapes() {
return new ScatterShape[]{SQUARE, CIRCLE, TRIANGLE, CROSS, X, CHEVRON_UP, CHEVRON_DOWN};
}
}
}

@ -0,0 +1,886 @@
package com.github.mikephil.charting.components;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.util.Log;
import com.github.mikephil.charting.formatter.DefaultAxisValueFormatter;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.utils.Utils;
import java.util.ArrayList;
import java.util.List;
/**
* Base-class of all axes (previously called labels).
*
* @author Philipp Jahoda
*/
public abstract class AxisBase extends ComponentBase {
/**
* custom formatter that is used instead of the auto-formatter if set
*/
protected IAxisValueFormatter mAxisValueFormatter;
private int mGridColor = Color.GRAY;
private float mGridLineWidth = 1f;
private int mAxisLineColor = Color.GRAY;
private float mAxisLineWidth = 1f;
/**
* the actual array of entries
*/
public float[] mEntries = new float[]{};
/**
* axis label entries only used for centered labels
*/
public float[] mCenteredEntries = new float[]{};
/**
* the number of entries the legend contains
*/
public int mEntryCount;
/**
* the number of decimal digits to use
*/
public int mDecimals;
/**
* the number of label entries the axis should have, default 6
*/
private int mLabelCount = 6;
/**
* the minimum interval between axis values
*/
protected float mGranularity = 1.0f;
/**
* When true, axis labels are controlled by the `granularity` property.
* When false, axis values could possibly be repeated.
* This could happen if two adjacent axis values are rounded to same value.
* If using granularity this could be avoided by having fewer axis values visible.
*/
protected boolean mGranularityEnabled = false;
/**
* if true, the set number of y-labels will be forced
*/
protected boolean mForceLabels = false;
/**
* flag indicating if the grid lines for this axis should be drawn
*/
protected boolean mDrawGridLines = true;
/**
* flag that indicates if the line alongside the axis is drawn or not
*/
protected boolean mDrawAxisLine = true;
/**
* flag that indicates of the labels of this axis should be drawn or not
*/
protected boolean mDrawLabels = true;
protected boolean mCenterAxisLabels = false;
/**
* the path effect of the axis line that makes dashed lines possible
*/
private DashPathEffect mAxisLineDashPathEffect = null;
/**
* the path effect of the grid lines that makes dashed lines possible
*/
private DashPathEffect mGridDashPathEffect = null;
/**
* array of limit lines that can be set for the axis
*/
protected List<LimitLine> mLimitLines;
/**
* flag indicating the limit lines layer depth
*/
protected boolean mDrawLimitLineBehindData = false;
/**
* flag indicating the grid lines layer depth
*/
protected boolean mDrawGridLinesBehindData = true;
/**
* Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum`
*/
protected float mSpaceMin = 0.f;
/**
* Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum`
*/
protected float mSpaceMax = 0.f;
/**
* flag indicating that the axis-min value has been customized
*/
protected boolean mCustomAxisMin = false;
/**
* flag indicating that the axis-max value has been customized
*/
protected boolean mCustomAxisMax = false;
/**
* don't touch this direclty, use setter
*/
public float mAxisMaximum = 0f;
/**
* don't touch this directly, use setter
*/
public float mAxisMinimum = 0f;
/**
* the total range of values this axis covers
*/
public float mAxisRange = 0f;
private int mAxisMinLabels = 2;
private int mAxisMaxLabels = 25;
/**
* The minumum number of labels on the axis
*/
public int getAxisMinLabels() {
return mAxisMinLabels;
}
/**
* The minumum number of labels on the axis
*/
public void setAxisMinLabels(int labels) {
if (labels > 0)
mAxisMinLabels = labels;
}
/**
* The maximum number of labels on the axis
*/
public int getAxisMaxLabels() {
return mAxisMaxLabels;
}
/**
* The maximum number of labels on the axis
*/
public void setAxisMaxLabels(int labels) {
if (labels > 0)
mAxisMaxLabels = labels;
}
/**
* default constructor
*/
public AxisBase() {
this.mTextSize = Utils.convertDpToPixel(10f);
this.mXOffset = Utils.convertDpToPixel(5f);
this.mYOffset = Utils.convertDpToPixel(5f);
this.mLimitLines = new ArrayList<LimitLine>();
}
/**
* Set this to true to enable drawing the grid lines for this axis.
*
* @param enabled
*/
public void setDrawGridLines(boolean enabled) {
mDrawGridLines = enabled;
}
/**
* Returns true if drawing grid lines is enabled for this axis.
*
* @return
*/
public boolean isDrawGridLinesEnabled() {
return mDrawGridLines;
}
/**
* Set this to true if the line alongside the axis should be drawn or not.
*
* @param enabled
*/
public void setDrawAxisLine(boolean enabled) {
mDrawAxisLine = enabled;
}
/**
* Returns true if the line alongside the axis should be drawn.
*
* @return
*/
public boolean isDrawAxisLineEnabled() {
return mDrawAxisLine;
}
/**
* Centers the axis labels instead of drawing them at their original position.
* This is useful especially for grouped BarChart.
*
* @param enabled
*/
public void setCenterAxisLabels(boolean enabled) {
mCenterAxisLabels = enabled;
}
public boolean isCenterAxisLabelsEnabled() {
return mCenterAxisLabels && mEntryCount > 0;
}
/**
* Sets the color of the grid lines for this axis (the horizontal lines
* coming from each label).
*
* @param color
*/
public void setGridColor(int color) {
mGridColor = color;
}
/**
* Returns the color of the grid lines for this axis (the horizontal lines
* coming from each label).
*
* @return
*/
public int getGridColor() {
return mGridColor;
}
/**
* Sets the width of the border surrounding the chart in dp.
*
* @param width
*/
public void setAxisLineWidth(float width) {
mAxisLineWidth = Utils.convertDpToPixel(width);
}
/**
* Returns the width of the axis line (line alongside the axis).
*
* @return
*/
public float getAxisLineWidth() {
return mAxisLineWidth;
}
/**
* Sets the width of the grid lines that are drawn away from each axis
* label.
*
* @param width
*/
public void setGridLineWidth(float width) {
mGridLineWidth = Utils.convertDpToPixel(width);
}
/**
* Returns the width of the grid lines that are drawn away from each axis
* label.
*
* @return
*/
public float getGridLineWidth() {
return mGridLineWidth;
}
/**
* Sets the color of the border surrounding the chart.
*
* @param color
*/
public void setAxisLineColor(int color) {
mAxisLineColor = color;
}
/**
* Returns the color of the axis line (line alongside the axis).
*
* @return
*/
public int getAxisLineColor() {
return mAxisLineColor;
}
/**
* Set this to true to enable drawing the labels of this axis (this will not
* affect drawing the grid lines or axis lines).
*
* @param enabled
*/
public void setDrawLabels(boolean enabled) {
mDrawLabels = enabled;
}
/**
* Returns true if drawing the labels is enabled for this axis.
*
* @return
*/
public boolean isDrawLabelsEnabled() {
return mDrawLabels;
}
/**
* Sets the number of label entries for the y-axis max = 25, min = 2, default: 6, be aware
* that this number is not fixed.
*
* @param count the number of y-axis labels that should be displayed
*/
public void setLabelCount(int count) {
if (count > getAxisMaxLabels())
count = getAxisMaxLabels();
if (count < getAxisMinLabels())
count = getAxisMinLabels();
mLabelCount = count;
mForceLabels = false;
}
/**
* sets the number of label entries for the y-axis max = 25, min = 2, default: 6, be aware
* that this number is not
* fixed (if force == false) and can only be approximated.
*
* @param count the number of y-axis labels that should be displayed
* @param force if enabled, the set label count will be forced, meaning that the exact
* specified count of labels will
* be drawn and evenly distributed alongside the axis - this might cause labels
* to have uneven values
*/
public void setLabelCount(int count, boolean force) {
setLabelCount(count);
mForceLabels = force;
}
public final float MaxTopValue = Float.MAX_VALUE;
public final float MinTopValue = Float.MIN_VALUE;
public void setLabelCountAndMaxMinValue(int count, boolean force, float min, float max) {
setLabelCountAndMaxMinValue(count, force, min, max, MinTopValue, MaxTopValue);
}
/**
*
* @param count Y
* @param force
* @param min
* @param max
* @param minTop
* @param maxTop
*/
public void setLabelCountAndMaxMinValue(int count, boolean force, float min, float max, float minTop, float maxTop) {
System.out.println("Y轴设置count:" + count + ";force:" + force + ";min" + min + ";max:" + max + ";minTop:" + minTop + ";maxTop:" + maxTop);
int range;
boolean isInteger = isInteger(min) && isInteger(max);
if (isInteger){//只有最小值和最大值均为整型
range = (int) (max - min);
}else {
String[] minArray = String.valueOf(min).split("\\.");
String[] maxArray = String.valueOf(max).split("\\.");
int rangeInt = Integer.valueOf(maxArray[0]) - Integer.valueOf(minArray[0]);
int rangeDecimal = Integer.valueOf(maxArray[1]) - Integer.valueOf(minArray[1]);
StringBuilder sb = new StringBuilder();
sb.append(rangeInt > 0 ? rangeInt : "");
sb.append(rangeDecimal > 0 ? rangeDecimal : 0);
range = Integer.valueOf(sb.toString());
}
if (range < count) {//如果区间数小于label数则增加区间点
max += (float) Math.ceil((double) (count - range) / 2) / (isInteger? 1 : 10); //如果是整数不变小数除10
min -= (float) Math.floor((double) (count - range) / 2) / (isInteger? 1 : 10);//如果是整数不变小数除10
System.out.println("Y轴设置Range:" + range + ";min:" + min + ";max:" + max);
}
if (isInteger){//最小值和最大值均为整型的话y轴最大值加1最小值减1防止线画出外面
max += 1;
min -= 1;
}else {//最小值和最大值任意有小数的的话y轴最大值加0.2最小值减0.2,防止线画出外面
max += 0.2;
min -= 0.2;
}
System.out.println("Y轴设置:isInteger:" + isInteger + ";min:" + min + ";max:" + max);
if (max > maxTop){
min = min - (max - maxTop);
max = maxTop;
}
System.out.println("min:" + min + ";max:" + max);
if (min < minTop){
min = minTop;
}
System.out.println("min:" + min + ";max:" + max);
setAxisMaximum(max);
setAxisMinimum(min);
setLabelCount(count);
mForceLabels = force;
}
private boolean isInteger(Float f) {
String[] array = f.toString().split("\\.");
return (Integer.parseInt(array[1]) == 0);
}
/**
* Returns true if focing the y-label count is enabled. Default: false
*
* @return
*/
public boolean isForceLabelsEnabled() {
return mForceLabels;
}
/**
* Returns the number of label entries the y-axis should have
*
* @return
*/
public int getLabelCount() {
return mLabelCount;
}
/**
* @return true if granularity is enabled
*/
public boolean isGranularityEnabled() {
return mGranularityEnabled;
}
/**
* Enabled/disable granularity control on axis value intervals. If enabled, the axis
* interval is not allowed to go below a certain granularity. Default: false
*
* @param enabled
*/
public void setGranularityEnabled(boolean enabled) {
mGranularityEnabled = enabled;
}
/**
* @return the minimum interval between axis values
*/
public float getGranularity() {
return mGranularity;
}
/**
* Set a minimum interval for the axis when zooming in. The axis is not allowed to go below
* that limit. This can be used to avoid label duplicating when zooming in.
*
* @param granularity
*/
public void setGranularity(float granularity) {
mGranularity = granularity;
// set this to true if it was disabled, as it makes no sense to call this method with granularity disabled
mGranularityEnabled = true;
}
/**
* Adds a new LimitLine to this axis.
*
* @param l
*/
public void addLimitLine(LimitLine l) {
mLimitLines.add(l);
if (mLimitLines.size() > 6) {
Log.e("MPAndroiChart",
"Warning! You have more than 6 LimitLines on your axis, do you really want " +
"that?");
}
}
/**
* Removes the specified LimitLine from the axis.
*
* @param l
*/
public void removeLimitLine(LimitLine l) {
mLimitLines.remove(l);
}
/**
* Removes all LimitLines from the axis.
*/
public void removeAllLimitLines() {
mLimitLines.clear();
}
/**
* Returns the LimitLines of this axis.
*
* @return
*/
public List<LimitLine> getLimitLines() {
return mLimitLines;
}
/**
* If this is set to true, the LimitLines are drawn behind the actual data,
* otherwise on top. Default: false
*
* @param enabled
*/
public void setDrawLimitLinesBehindData(boolean enabled) {
mDrawLimitLineBehindData = enabled;
}
public boolean isDrawLimitLinesBehindDataEnabled() {
return mDrawLimitLineBehindData;
}
/**
* If this is set to false, the grid lines are draw on top of the actual data,
* otherwise behind. Default: true
*
* @param enabled
*/
public void setDrawGridLinesBehindData(boolean enabled) { mDrawGridLinesBehindData = enabled; }
public boolean isDrawGridLinesBehindDataEnabled() {
return mDrawGridLinesBehindData;
}
/**
* Returns the longest formatted label (in terms of characters), this axis
* contains.
*
* @return
*/
public String getLongestLabel() {
String longest = "";
for (int i = 0; i < mEntries.length; i++) {
String text = getFormattedLabel(i);
if (text != null && longest.length() < text.length())
longest = text;
}
return longest;
}
public String getFormattedLabel(int index) {
if (index < 0 || index >= mEntries.length)
return "";
else
return getValueFormatter().getFormattedValue(mEntries[index], this);
}
/**
* Sets the formatter to be used for formatting the axis labels. If no formatter is set, the
* chart will
* automatically determine a reasonable formatting (concerning decimals) for all the values
* that are drawn inside
* the chart. Use chart.getDefaultValueFormatter() to use the formatter calculated by the chart.
*
* @param f
*/
public void setValueFormatter(IAxisValueFormatter f) {
if (f == null)
mAxisValueFormatter = new DefaultAxisValueFormatter(mDecimals);
else
mAxisValueFormatter = f;
}
/**
* Returns the formatter used for formatting the axis labels.
*
* @return
*/
public IAxisValueFormatter getValueFormatter() {
if (mAxisValueFormatter == null ||
(mAxisValueFormatter instanceof DefaultAxisValueFormatter &&
((DefaultAxisValueFormatter)mAxisValueFormatter).getDecimalDigits() != mDecimals))
mAxisValueFormatter = new DefaultAxisValueFormatter(mDecimals);
return mAxisValueFormatter;
}
/**
* Enables the grid line to be drawn in dashed mode, e.g. like this
* "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF.
* Keep in mind that hardware acceleration boosts performance.
*
* @param lineLength the length of the line pieces
* @param spaceLength the length of space in between the pieces
* @param phase offset, in degrees (normally, use 0)
*/
public void enableGridDashedLine(float lineLength, float spaceLength, float phase) {
mGridDashPathEffect = new DashPathEffect(new float[]{
lineLength, spaceLength
}, phase);
}
/**
* Enables the grid line to be drawn in dashed mode, e.g. like this
* "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF.
* Keep in mind that hardware acceleration boosts performance.
*
* @param effect the DashPathEffect
*/
public void setGridDashedLine(DashPathEffect effect) {
mGridDashPathEffect = effect;
}
/**
* Disables the grid line to be drawn in dashed mode.
*/
public void disableGridDashedLine() {
mGridDashPathEffect = null;
}
/**
* Returns true if the grid dashed-line effect is enabled, false if not.
*
* @return
*/
public boolean isGridDashedLineEnabled() {
return mGridDashPathEffect == null ? false : true;
}
/**
* returns the DashPathEffect that is set for grid line
*
* @return
*/
public DashPathEffect getGridDashPathEffect() {
return mGridDashPathEffect;
}
/**
* Enables the axis line to be drawn in dashed mode, e.g. like this
* "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF.
* Keep in mind that hardware acceleration boosts performance.
*
* @param lineLength the length of the line pieces
* @param spaceLength the length of space in between the pieces
* @param phase offset, in degrees (normally, use 0)
*/
public void enableAxisLineDashedLine(float lineLength, float spaceLength, float phase) {
mAxisLineDashPathEffect = new DashPathEffect(new float[]{
lineLength, spaceLength
}, phase);
}
/**
* Enables the axis line to be drawn in dashed mode, e.g. like this
* "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF.
* Keep in mind that hardware acceleration boosts performance.
*
* @param effect the DashPathEffect
*/
public void setAxisLineDashedLine(DashPathEffect effect) {
mAxisLineDashPathEffect = effect;
}
/**
* Disables the axis line to be drawn in dashed mode.
*/
public void disableAxisLineDashedLine() {
mAxisLineDashPathEffect = null;
}
/**
* Returns true if the axis dashed-line effect is enabled, false if not.
*
* @return
*/
public boolean isAxisLineDashedLineEnabled() {
return mAxisLineDashPathEffect == null ? false : true;
}
/**
* returns the DashPathEffect that is set for axis line
*
* @return
*/
public DashPathEffect getAxisLineDashPathEffect() {
return mAxisLineDashPathEffect;
}
/**
* ###### BELOW CODE RELATED TO CUSTOM AXIS VALUES ######
*/
public float getAxisMaximum() {
return mAxisMaximum;
}
public float getAxisMinimum() {
return mAxisMinimum;
}
/**
* By calling this method, any custom maximum value that has been previously set is reseted,
* and the calculation is
* done automatically.
*/
public void resetAxisMaximum() {
mCustomAxisMax = false;
}
/**
* Returns true if the axis max value has been customized (and is not calculated automatically)
*
* @return
*/
public boolean isAxisMaxCustom() {
return mCustomAxisMax;
}
/**
* By calling this method, any custom minimum value that has been previously set is reseted,
* and the calculation is
* done automatically.
*/
public void resetAxisMinimum() {
mCustomAxisMin = false;
}
/**
* Returns true if the axis min value has been customized (and is not calculated automatically)
*
* @return
*/
public boolean isAxisMinCustom() {
return mCustomAxisMin;
}
/**
* Set a custom minimum value for this axis. If set, this value will not be calculated
* automatically depending on
* the provided data. Use resetAxisMinValue() to undo this. Do not forget to call
* setStartAtZero(false) if you use
* this method. Otherwise, the axis-minimum value will still be forced to 0.
*
* @param min
*/
public void setAxisMinimum(float min) {
mCustomAxisMin = true;
mAxisMinimum = min;
this.mAxisRange = Math.abs(mAxisMaximum - min);
}
/**
* Use setAxisMinimum(...) instead.
*
* @param min
*/
@Deprecated
public void setAxisMinValue(float min) {
setAxisMinimum(min);
}
/**
* Set a custom maximum value for this axis. If set, this value will not be calculated
* automatically depending on
* the provided data. Use resetAxisMaxValue() to undo this.
*
* @param max
*/
public void setAxisMaximum(float max) {
mCustomAxisMax = true;
mAxisMaximum = max;
this.mAxisRange = Math.abs(max - mAxisMinimum);
}
/**
* Use setAxisMaximum(...) instead.
*
* @param max
*/
@Deprecated
public void setAxisMaxValue(float max) {
setAxisMaximum(max);
}
/**
* Calculates the minimum / maximum and range values of the axis with the given
* minimum and maximum values from the chart data.
*
* @param dataMin the min value according to chart data
* @param dataMax the max value according to chart data
*/
public void calculate(float dataMin, float dataMax) {
// if custom, use value as is, else use data value
float min = mCustomAxisMin ? mAxisMinimum : (dataMin - mSpaceMin);
float max = mCustomAxisMax ? mAxisMaximum : (dataMax + mSpaceMax);
// temporary range (before calculations)
float range = Math.abs(max - min);
// in case all values are equal
if (range == 0f) {
max = max + 1f;
min = min - 1f;
}
this.mAxisMinimum = min;
this.mAxisMaximum = max;
// actual range
this.mAxisRange = Math.abs(max - min);
}
/**
* Gets extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum`
*/
public float getSpaceMin()
{
return mSpaceMin;
}
/**
* Sets extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum`
*/
public void setSpaceMin(float mSpaceMin)
{
this.mSpaceMin = mSpaceMin;
}
/**
* Gets extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum`
*/
public float getSpaceMax()
{
return mSpaceMax;
}
/**
* Sets extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum`
*/
public void setSpaceMax(float mSpaceMax)
{
this.mSpaceMax = mSpaceMax;
}
}

@ -0,0 +1,173 @@
package com.github.mikephil.charting.components;
import android.graphics.Color;
import android.graphics.Typeface;
import com.github.mikephil.charting.utils.Utils;
/**
* This class encapsulates everything both Axis, Legend and LimitLines have in common.
*
* @author Philipp Jahoda
*/
public abstract class ComponentBase {
/**
* flag that indicates if this axis / legend is enabled or not
*/
protected boolean mEnabled = true;
/**
* the offset in pixels this component has on the x-axis
*/
protected float mXOffset = 5f;
/**
* the offset in pixels this component has on the Y-axis
*/
protected float mYOffset = 5f;
/**
* the typeface used for the labels
*/
protected Typeface mTypeface = null;
/**
* the text size of the labels
*/
protected float mTextSize = Utils.convertDpToPixel(10f);
/**
* the text color to use for the labels
*/
protected int mTextColor = Color.BLACK;
public ComponentBase() {
}
/**
* Returns the used offset on the x-axis for drawing the axis or legend
* labels. This offset is applied before and after the label.
*
* @return
*/
public float getXOffset() {
return mXOffset;
}
/**
* Sets the used x-axis offset for the labels on this axis.
*
* @param xOffset
*/
public void setXOffset(float xOffset) {
mXOffset = Utils.convertDpToPixel(xOffset);
}
/**
* Returns the used offset on the x-axis for drawing the axis labels. This
* offset is applied before and after the label.
*
* @return
*/
public float getYOffset() {
return mYOffset;
}
/**
* Sets the used y-axis offset for the labels on this axis. For the legend,
* higher offset means the legend as a whole will be placed further away
* from the top.
*
* @param yOffset
*/
public void setYOffset(float yOffset) {
mYOffset = Utils.convertDpToPixel(yOffset);
}
/**
* returns the Typeface used for the labels, returns null if none is set
*
* @return
*/
public Typeface getTypeface() {
return mTypeface;
}
/**
* sets a specific Typeface for the labels
*
* @param tf
*/
public void setTypeface(Typeface tf) {
mTypeface = tf;
}
/**
* sets the size of the label text in density pixels min = 6f, max = 24f, default
* 10f
*
* @param size the text size, in DP
*/
public void setTextSize(float size) {
if (size > 24f)
size = 24f;
if (size < 6f)
size = 6f;
mTextSize = Utils.convertDpToPixel(size);
}
/**
* returns the text size that is currently set for the labels, in pixels
*
* @return
*/
public float getTextSize() {
return mTextSize;
}
/**
* Sets the text color to use for the labels. Make sure to use
* getResources().getColor(...) when using a color from the resources.
*
* @param color
*/
public void setTextColor(int color) {
mTextColor = color;
}
/**
* Returns the text color that is set for the labels.
*
* @return
*/
public int getTextColor() {
return mTextColor;
}
/**
* Set this to true if this component should be enabled (should be drawn),
* false if not. If disabled, nothing of this component will be drawn.
* Default: true
*
* @param enabled
*/
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
/**
* Returns true if this comonent is enabled (should be drawn), false if not.
*
* @return
*/
public boolean isEnabled() {
return mEnabled;
}
}

@ -0,0 +1,95 @@
package com.github.mikephil.charting.components;
import android.graphics.Paint;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Utils;
/**
* Created by Philipp Jahoda on 17/09/16.
*/
public class Description extends ComponentBase {
/**
* the text used in the description
*/
private String text = "Description Label";
/**
* the custom position of the description text
*/
private MPPointF mPosition;
/**
* the alignment of the description text
*/
private Paint.Align mTextAlign = Paint.Align.RIGHT;
public Description() {
super();
// default size
mTextSize = Utils.convertDpToPixel(8f);
}
/**
* Sets the text to be shown as the description.
* Never set this to null as this will cause nullpointer exception when drawing with Android Canvas.
*
* @param text
*/
public void setText(String text) {
this.text = text;
}
/**
* Returns the description text.
*
* @return
*/
public String getText() {
return text;
}
/**
* Sets a custom position for the description text in pixels on the screen.
*
* @param x - xcoordinate
* @param y - ycoordinate
*/
public void setPosition(float x, float y) {
if (mPosition == null) {
mPosition = MPPointF.getInstance(x, y);
} else {
mPosition.x = x;
mPosition.y = y;
}
}
/**
* Returns the customized position of the description, or null if none set.
*
* @return
*/
public MPPointF getPosition() {
return mPosition;
}
/**
* Sets the text alignment of the description text. Default RIGHT.
*
* @param align
*/
public void setTextAlign(Paint.Align align) {
this.mTextAlign = align;
}
/**
* Returns the text alignment of the description.
*
* @return
*/
public Paint.Align getTextAlign() {
return mTextAlign;
}
}

@ -0,0 +1,47 @@
package com.github.mikephil.charting.components;
import android.graphics.Canvas;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.utils.MPPointF;
public interface IMarker {
/**
* @return The desired (general) offset you wish the IMarker to have on the x- and y-axis.
* By returning x: -(width / 2) you will center the IMarker horizontally.
* By returning y: -(height / 2) you will center the IMarker vertically.
*/
MPPointF getOffset();
/**
* @return The offset for drawing at the specific `point`. This allows conditional adjusting of the Marker position.
* If you have no adjustments to make, return getOffset().
*
* @param posX This is the X position at which the marker wants to be drawn.
* You can adjust the offset conditionally based on this argument.
* @param posY This is the X position at which the marker wants to be drawn.
* You can adjust the offset conditionally based on this argument.
*/
MPPointF getOffsetForDrawingAtPoint(float posX, float posY);
/**
* This method enables a specified custom IMarker to update it's content every time the IMarker is redrawn.
*
* @param e The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or
* CandleEntry, simply cast it at runtime.
* @param highlight The highlight object contains information about the highlighted value such as it's dataset-index, the
* selected range or stack-index (only stacked bar entries).
*/
void refreshContent(Entry e, Highlight highlight);
/**
* Draws the IMarker on the given position on the screen with the given Canvas object.
*
* @param canvas
* @param posX
* @param posY
*/
void draw(Canvas canvas, float posX, float posY);
}

@ -0,0 +1,825 @@
package com.github.mikephil.charting.components;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import com.github.mikephil.charting.utils.ColorTemplate;
import com.github.mikephil.charting.utils.FSize;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;
import java.util.ArrayList;
import java.util.List;
/**
* Class representing the legend of the chart. The legend will contain one entry
* per color and DataSet. Multiple colors in one DataSet are grouped together.
* The legend object is NOT available before setting data to the chart.
*
* @author Philipp Jahoda
*/
public class Legend extends ComponentBase {
public enum LegendForm {
/**
* Avoid drawing a form
*/
NONE,
/**
* Do not draw the a form, but leave space for it
*/
EMPTY,
/**
* Use default (default dataset's form to the legend's form)
*/
DEFAULT,
/**
* Draw a square
*/
SQUARE,
/**
* Draw a circle
*/
CIRCLE,
/**
* Draw a horizontal line
*/
LINE
}
public enum LegendHorizontalAlignment {
LEFT, CENTER, RIGHT
}
public enum LegendVerticalAlignment {
TOP, CENTER, BOTTOM
}
public enum LegendOrientation {
HORIZONTAL, VERTICAL
}
public enum LegendDirection {
LEFT_TO_RIGHT, RIGHT_TO_LEFT
}
/**
* The legend entries array
*/
private LegendEntry[] mEntries = new LegendEntry[]{};
/**
* Entries that will be appended to the end of the auto calculated entries after calculating the legend.
* (if the legend has already been calculated, you will need to call notifyDataSetChanged() to let the changes take effect)
*/
private LegendEntry[] mExtraEntries;
/**
* Are the legend labels/colors a custom value or auto calculated? If false,
* then it's auto, if true, then custom. default false (automatic legend)
*/
private boolean mIsLegendCustom = false;
private LegendHorizontalAlignment mHorizontalAlignment = LegendHorizontalAlignment.LEFT;
private LegendVerticalAlignment mVerticalAlignment = LegendVerticalAlignment.BOTTOM;
private LegendOrientation mOrientation = LegendOrientation.HORIZONTAL;
private boolean mDrawInside = false;
/**
* the text direction for the legend
*/
private LegendDirection mDirection = LegendDirection.LEFT_TO_RIGHT;
/**
* the shape/form the legend colors are drawn in
*/
private LegendForm mShape = LegendForm.SQUARE;
/**
* the size of the legend forms/shapes
*/
private float mFormSize = 8f;
/**
* the size of the legend forms/shapes
*/
private float mFormLineWidth = 3f;
/**
* Line dash path effect used for shapes that consist of lines.
*/
private DashPathEffect mFormLineDashEffect = null;
/**
* the space between the legend entries on a horizontal axis, default 6f
*/
private float mXEntrySpace = 6f;
/**
* the space between the legend entries on a vertical axis, default 5f
*/
private float mYEntrySpace = 0f;
/**
* the space between the legend entries on a vertical axis, default 2f
* private float mYEntrySpace = 2f; /** the space between the form and the
* actual label/text
*/
private float mFormToTextSpace = 5f;
/**
* the space that should be left between stacked forms
*/
private float mStackSpace = 3f;
/**
* the maximum relative size out of the whole chart view in percent
*/
private float mMaxSizePercent = 0.95f;
/**
* default constructor
*/
public Legend() {
this.mTextSize = Utils.convertDpToPixel(10f);
this.mXOffset = Utils.convertDpToPixel(5f);
this.mYOffset = Utils.convertDpToPixel(3f); // 2
}
/**
* Constructor. Provide entries for the legend.
*
* @param entries
*/
public Legend(LegendEntry[] entries) {
this();
if (entries == null) {
throw new IllegalArgumentException("entries array is NULL");
}
this.mEntries = entries;
}
/**
* This method sets the automatically computed colors for the legend. Use setCustom(...) to set custom colors.
*
* @param entries
*/
public void setEntries(List<LegendEntry> entries) {
mEntries = entries.toArray(new LegendEntry[entries.size()]);
}
public LegendEntry[] getEntries() {
return mEntries;
}
/**
* returns the maximum length in pixels across all legend labels + formsize
* + formtotextspace
*
* @param p the paint object used for rendering the text
* @return
*/
public float getMaximumEntryWidth(Paint p) {
float max = 0f;
float maxFormSize = 0f;
float formToTextSpace = Utils.convertDpToPixel(mFormToTextSpace);
for (LegendEntry entry : mEntries) {
final float formSize = Utils.convertDpToPixel(
Float.isNaN(entry.formSize)
? mFormSize : entry.formSize);
if (formSize > maxFormSize)
maxFormSize = formSize;
String label = entry.label;
if (label == null) continue;
float length = (float) Utils.calcTextWidth(p, label);
if (length > max)
max = length;
}
return max + maxFormSize + formToTextSpace;
}
/**
* returns the maximum height in pixels across all legend labels
*
* @param p the paint object used for rendering the text
* @return
*/
public float getMaximumEntryHeight(Paint p) {
float max = 0f;
for (LegendEntry entry : mEntries) {
String label = entry.label;
if (label == null) continue;
float length = (float) Utils.calcTextHeight(p, label);
if (length > max)
max = length;
}
return max;
}
public LegendEntry[] getExtraEntries() {
return mExtraEntries;
}
public void setExtra(List<LegendEntry> entries) {
mExtraEntries = entries.toArray(new LegendEntry[entries.size()]);
}
public void setExtra(LegendEntry[] entries) {
if (entries == null)
entries = new LegendEntry[]{};
mExtraEntries = entries;
}
/**
* Entries that will be appended to the end of the auto calculated
* entries after calculating the legend.
* (if the legend has already been calculated, you will need to call notifyDataSetChanged()
* to let the changes take effect)
*/
public void setExtra(int[] colors, String[] labels) {
List<LegendEntry> entries = new ArrayList<>();
for (int i = 0; i < Math.min(colors.length, labels.length); i++) {
final LegendEntry entry = new LegendEntry();
entry.formColor = colors[i];
entry.label = labels[i];
if (entry.formColor == ColorTemplate.COLOR_SKIP ||
entry.formColor == 0)
entry.form = LegendForm.NONE;
else if (entry.formColor == ColorTemplate.COLOR_NONE)
entry.form = LegendForm.EMPTY;
entries.add(entry);
}
mExtraEntries = entries.toArray(new LegendEntry[entries.size()]);
}
/**
* Sets a custom legend's entries array.
* * A null label will start a group.
* This will disable the feature that automatically calculates the legend
* entries from the datasets.
* Call resetCustom() to re-enable automatic calculation (and then
* notifyDataSetChanged() is needed to auto-calculate the legend again)
*/
public void setCustom(LegendEntry[] entries) {
mEntries = entries;
mIsLegendCustom = true;
}
/**
* Sets a custom legend's entries array.
* * A null label will start a group.
* This will disable the feature that automatically calculates the legend
* entries from the datasets.
* Call resetCustom() to re-enable automatic calculation (and then
* notifyDataSetChanged() is needed to auto-calculate the legend again)
*/
public void setCustom(List<LegendEntry> entries) {
mEntries = entries.toArray(new LegendEntry[entries.size()]);
mIsLegendCustom = true;
}
/**
* Calling this will disable the custom legend entries (set by
* setCustom(...)). Instead, the entries will again be calculated
* automatically (after notifyDataSetChanged() is called).
*/
public void resetCustom() {
mIsLegendCustom = false;
}
/**
* @return true if a custom legend entries has been set default
* false (automatic legend)
*/
public boolean isLegendCustom() {
return mIsLegendCustom;
}
/**
* returns the horizontal alignment of the legend
*
* @return
*/
public LegendHorizontalAlignment getHorizontalAlignment() {
return mHorizontalAlignment;
}
/**
* sets the horizontal alignment of the legend
*
* @param value
*/
public void setHorizontalAlignment(LegendHorizontalAlignment value) {
mHorizontalAlignment = value;
}
/**
* returns the vertical alignment of the legend
*
* @return
*/
public LegendVerticalAlignment getVerticalAlignment() {
return mVerticalAlignment;
}
/**
* sets the vertical alignment of the legend
*
* @param value
*/
public void setVerticalAlignment(LegendVerticalAlignment value) {
mVerticalAlignment = value;
}
/**
* returns the orientation of the legend
*
* @return
*/
public LegendOrientation getOrientation() {
return mOrientation;
}
/**
* sets the orientation of the legend
*
* @param value
*/
public void setOrientation(LegendOrientation value) {
mOrientation = value;
}
/**
* returns whether the legend will draw inside the chart or outside
*
* @return
*/
public boolean isDrawInsideEnabled() {
return mDrawInside;
}
/**
* sets whether the legend will draw inside the chart or outside
*
* @param value
*/
public void setDrawInside(boolean value) {
mDrawInside = value;
}
/**
* returns the text direction of the legend
*
* @return
*/
public LegendDirection getDirection() {
return mDirection;
}
/**
* sets the text direction of the legend
*
* @param pos
*/
public void setDirection(LegendDirection pos) {
mDirection = pos;
}
/**
* returns the current form/shape that is set for the legend
*
* @return
*/
public LegendForm getForm() {
return mShape;
}
/**
* sets the form/shape of the legend forms
*
* @param shape
*/
public void setForm(LegendForm shape) {
mShape = shape;
}
/**
* sets the size in dp of the legend forms, default 8f
*
* @param size
*/
public void setFormSize(float size) {
mFormSize = size;
}
/**
* returns the size in dp of the legend forms
*
* @return
*/
public float getFormSize() {
return mFormSize;
}
/**
* sets the line width in dp for forms that consist of lines, default 3f
*
* @param size
*/
public void setFormLineWidth(float size) {
mFormLineWidth = size;
}
/**
* returns the line width in dp for drawing forms that consist of lines
*
* @return
*/
public float getFormLineWidth() {
return mFormLineWidth;
}
/**
* Sets the line dash path effect used for shapes that consist of lines.
*
* @param dashPathEffect
*/
public void setFormLineDashEffect(DashPathEffect dashPathEffect) {
mFormLineDashEffect = dashPathEffect;
}
/**
* @return The line dash path effect used for shapes that consist of lines.
*/
public DashPathEffect getFormLineDashEffect() {
return mFormLineDashEffect;
}
/**
* returns the space between the legend entries on a horizontal axis in
* pixels
*
* @return
*/
public float getXEntrySpace() {
return mXEntrySpace;
}
/**
* sets the space between the legend entries on a horizontal axis in pixels,
* converts to dp internally
*
* @param space
*/
public void setXEntrySpace(float space) {
mXEntrySpace = space;
}
/**
* returns the space between the legend entries on a vertical axis in pixels
*
* @return
*/
public float getYEntrySpace() {
return mYEntrySpace;
}
/**
* sets the space between the legend entries on a vertical axis in pixels,
* converts to dp internally
*
* @param space
*/
public void setYEntrySpace(float space) {
mYEntrySpace = space;
}
/**
* returns the space between the form and the actual label/text
*
* @return
*/
public float getFormToTextSpace() {
return mFormToTextSpace;
}
/**
* sets the space between the form and the actual label/text, converts to dp
* internally
*
* @param space
*/
public void setFormToTextSpace(float space) {
this.mFormToTextSpace = space;
}
/**
* returns the space that is left out between stacked forms (with no label)
*
* @return
*/
public float getStackSpace() {
return mStackSpace;
}
/**
* sets the space that is left out between stacked forms (with no label)
*
* @param space
*/
public void setStackSpace(float space) {
mStackSpace = space;
}
/**
* the total width of the legend (needed width space)
*/
public float mNeededWidth = 0f;
/**
* the total height of the legend (needed height space)
*/
public float mNeededHeight = 0f;
public float mTextHeightMax = 0f;
public float mTextWidthMax = 0f;
/**
* flag that indicates if word wrapping is enabled
*/
private boolean mWordWrapEnabled = false;
/**
* Should the legend word wrap? / this is currently supported only for:
* BelowChartLeft, BelowChartRight, BelowChartCenter. / note that word
* wrapping a legend takes a toll on performance. / you may want to set
* maxSizePercent when word wrapping, to set the point where the text wraps.
* / default: false
*
* @param enabled
*/
public void setWordWrapEnabled(boolean enabled) {
mWordWrapEnabled = enabled;
}
/**
* If this is set, then word wrapping the legend is enabled. This means the
* legend will not be cut off if too long.
*
* @return
*/
public boolean isWordWrapEnabled() {
return mWordWrapEnabled;
}
/**
* The maximum relative size out of the whole chart view. / If the legend is
* to the right/left of the chart, then this affects the width of the
* legend. / If the legend is to the top/bottom of the chart, then this
* affects the height of the legend. / If the legend is the center of the
* piechart, then this defines the size of the rectangular bounds out of the
* size of the "hole". / default: 0.95f (95%)
*
* @return
*/
public float getMaxSizePercent() {
return mMaxSizePercent;
}
/**
* The maximum relative size out of the whole chart view. / If
* the legend is to the right/left of the chart, then this affects the width
* of the legend. / If the legend is to the top/bottom of the chart, then
* this affects the height of the legend. / default: 0.95f (95%)
*
* @param maxSize
*/
public void setMaxSizePercent(float maxSize) {
mMaxSizePercent = maxSize;
}
private List<FSize> mCalculatedLabelSizes = new ArrayList<>(16);
private List<Boolean> mCalculatedLabelBreakPoints = new ArrayList<>(16);
private List<FSize> mCalculatedLineSizes = new ArrayList<>(16);
public List<FSize> getCalculatedLabelSizes() {
return mCalculatedLabelSizes;
}
public List<Boolean> getCalculatedLabelBreakPoints() {
return mCalculatedLabelBreakPoints;
}
public List<FSize> getCalculatedLineSizes() {
return mCalculatedLineSizes;
}
/**
* Calculates the dimensions of the Legend. This includes the maximum width
* and height of a single entry, as well as the total width and height of
* the Legend.
*
* @param labelpaint
*/
public void calculateDimensions(Paint labelpaint, ViewPortHandler viewPortHandler) {
float defaultFormSize = Utils.convertDpToPixel(mFormSize);
float stackSpace = Utils.convertDpToPixel(mStackSpace);
float formToTextSpace = Utils.convertDpToPixel(mFormToTextSpace);
float xEntrySpace = Utils.convertDpToPixel(mXEntrySpace);
float yEntrySpace = Utils.convertDpToPixel(mYEntrySpace);
boolean wordWrapEnabled = mWordWrapEnabled;
LegendEntry[] entries = mEntries;
int entryCount = entries.length;
mTextWidthMax = getMaximumEntryWidth(labelpaint);
mTextHeightMax = getMaximumEntryHeight(labelpaint);
switch (mOrientation) {
case VERTICAL: {
float maxWidth = 0f, maxHeight = 0f, width = 0f;
float labelLineHeight = Utils.getLineHeight(labelpaint);
boolean wasStacked = false;
for (int i = 0; i < entryCount; i++) {
LegendEntry e = entries[i];
boolean drawingForm = e.form != LegendForm.NONE;
float formSize = Float.isNaN(e.formSize)
? defaultFormSize
: Utils.convertDpToPixel(e.formSize);
String label = e.label;
if (!wasStacked)
width = 0.f;
if (drawingForm) {
if (wasStacked)
width += stackSpace;
width += formSize;
}
// grouped forms have null labels
if (label != null) {
// make a step to the left
if (drawingForm && !wasStacked)
width += formToTextSpace;
else if (wasStacked) {
maxWidth = Math.max(maxWidth, width);
maxHeight += labelLineHeight + yEntrySpace;
width = 0.f;
wasStacked = false;
}
width += Utils.calcTextWidth(labelpaint, label);
maxHeight += labelLineHeight + yEntrySpace;
} else {
wasStacked = true;
width += formSize;
if (i < entryCount - 1)
width += stackSpace;
}
maxWidth = Math.max(maxWidth, width);
}
mNeededWidth = maxWidth;
mNeededHeight = maxHeight;
break;
}
case HORIZONTAL: {
float labelLineHeight = Utils.getLineHeight(labelpaint);
float labelLineSpacing = Utils.getLineSpacing(labelpaint) + yEntrySpace;
float contentWidth = viewPortHandler.contentWidth() * mMaxSizePercent;
// Start calculating layout
float maxLineWidth = 0.f;
float currentLineWidth = 0.f;
float requiredWidth = 0.f;
int stackedStartIndex = -1;
mCalculatedLabelBreakPoints.clear();
mCalculatedLabelSizes.clear();
mCalculatedLineSizes.clear();
for (int i = 0; i < entryCount; i++) {
LegendEntry e = entries[i];
boolean drawingForm = e.form != LegendForm.NONE;
float formSize = Float.isNaN(e.formSize)
? defaultFormSize
: Utils.convertDpToPixel(e.formSize);
String label = e.label;
mCalculatedLabelBreakPoints.add(false);
if (stackedStartIndex == -1) {
// we are not stacking, so required width is for this label
// only
requiredWidth = 0.f;
} else {
// add the spacing appropriate for stacked labels/forms
requiredWidth += stackSpace;
}
// grouped forms have null labels
if (label != null) {
mCalculatedLabelSizes.add(Utils.calcTextSize(labelpaint, label));
requiredWidth += drawingForm ? formToTextSpace + formSize : 0.f;
requiredWidth += mCalculatedLabelSizes.get(i).width;
} else {
mCalculatedLabelSizes.add(FSize.getInstance(0.f, 0.f));
requiredWidth += drawingForm ? formSize : 0.f;
if (stackedStartIndex == -1) {
// mark this index as we might want to break here later
stackedStartIndex = i;
}
}
if (label != null || i == entryCount - 1) {
float requiredSpacing = currentLineWidth == 0.f ? 0.f : xEntrySpace;
if (!wordWrapEnabled // No word wrapping, it must fit.
// The line is empty, it must fit
|| currentLineWidth == 0.f
// It simply fits
|| (contentWidth - currentLineWidth >=
requiredSpacing + requiredWidth)) {
// Expand current line
currentLineWidth += requiredSpacing + requiredWidth;
} else { // It doesn't fit, we need to wrap a line
// Add current line size to array
mCalculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight));
maxLineWidth = Math.max(maxLineWidth, currentLineWidth);
// Start a new line
mCalculatedLabelBreakPoints.set(
stackedStartIndex > -1 ? stackedStartIndex
: i, true);
currentLineWidth = requiredWidth;
}
if (i == entryCount - 1) {
// Add last line size to array
mCalculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight));
maxLineWidth = Math.max(maxLineWidth, currentLineWidth);
}
}
stackedStartIndex = label != null ? -1 : stackedStartIndex;
}
mNeededWidth = maxLineWidth;
mNeededHeight = labelLineHeight
* (float) (mCalculatedLineSizes.size())
+ labelLineSpacing *
(float) (mCalculatedLineSizes.size() == 0
? 0
: (mCalculatedLineSizes.size() - 1));
break;
}
}
mNeededHeight += mYOffset;
mNeededWidth += mXOffset;
}
}

@ -0,0 +1,78 @@
package com.github.mikephil.charting.components;
import android.graphics.DashPathEffect;
import com.github.mikephil.charting.utils.ColorTemplate;
public class LegendEntry {
public LegendEntry() {
}
/**
*
* @param label The legend entry text. A `null` label will start a group.
* @param form The form to draw for this entry.
* @param formSize Set to NaN to use the legend's default.
* @param formLineWidth Set to NaN to use the legend's default.
* @param formLineDashEffect Set to nil to use the legend's default.
* @param formColor The color for drawing the form.
*/
public LegendEntry(String label,
Legend.LegendForm form,
float formSize,
float formLineWidth,
DashPathEffect formLineDashEffect,
int formColor)
{
this.label = label;
this.form = form;
this.formSize = formSize;
this.formLineWidth = formLineWidth;
this.formLineDashEffect = formLineDashEffect;
this.formColor = formColor;
}
/**
* The legend entry text.
* A `null` label will start a group.
*/
public String label;
/**
* The form to draw for this entry.
*
* `NONE` will avoid drawing a form, and any related space.
* `EMPTY` will avoid drawing a form, but keep its space.
* `DEFAULT` will use the Legend's default.
*/
public Legend.LegendForm form = Legend.LegendForm.DEFAULT;
/**
* Form size will be considered except for when .None is used
*
* Set as NaN to use the legend's default
*/
public float formSize = Float.NaN;
/**
* Line width used for shapes that consist of lines.
*
* Set as NaN to use the legend's default
*/
public float formLineWidth = Float.NaN;
/**
* Line dash path effect used for shapes that consist of lines.
*
* Set to null to use the legend's default
*/
public DashPathEffect formLineDashEffect = null;
/**
* The color for drawing the form
*/
public int formColor = ColorTemplate.COLOR_NONE;
}

@ -0,0 +1,215 @@
package com.github.mikephil.charting.components;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Typeface;
import com.github.mikephil.charting.utils.Utils;
/**
* The limit line is an additional feature for all Line-, Bar- and
* ScatterCharts. It allows the displaying of an additional line in the chart
* that marks a certain maximum / limit on the specified axis (x- or y-axis).
*
* @author Philipp Jahoda
*/
public class LimitLine extends ComponentBase {
/** limit / maximum (the y-value or xIndex) */
private float mLimit = 0f;
/** the width of the limit line */
private float mLineWidth = 2f;
/** the color of the limit line */
private int mLineColor = Color.rgb(237, 91, 91);
/** the style of the label text */
private Paint.Style mTextStyle = Paint.Style.FILL_AND_STROKE;
/** label string that is drawn next to the limit line */
private String mLabel = "";
/** the path effect of this LimitLine that makes dashed lines possible */
private DashPathEffect mDashPathEffect = null;
/** indicates the position of the LimitLine label */
private LimitLabelPosition mLabelPosition = LimitLabelPosition.RIGHT_TOP;
/** enum that indicates the position of the LimitLine label */
public enum LimitLabelPosition {
LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM
}
/**
* Constructor with limit.
*
* @param limit - the position (the value) on the y-axis (y-value) or x-axis
* (xIndex) where this line should appear
*/
public LimitLine(float limit) {
mLimit = limit;
}
/**
* Constructor with limit and label.
*
* @param limit - the position (the value) on the y-axis (y-value) or x-axis
* (xIndex) where this line should appear
* @param label - provide "" if no label is required
*/
public LimitLine(float limit, String label) {
mLimit = limit;
mLabel = label;
}
/**
* Returns the limit that is set for this line.
*
* @return
*/
public float getLimit() {
return mLimit;
}
/**
* set the line width of the chart (min = 0.2f, max = 12f); default 2f NOTE:
* thinner line == better performance, thicker line == worse performance
*
* @param width
*/
public void setLineWidth(float width) {
if (width < 0.2f)
width = 0.2f;
if (width > 12.0f)
width = 12.0f;
mLineWidth = Utils.convertDpToPixel(width);
}
/**
* returns the width of limit line
*
* @return
*/
public float getLineWidth() {
return mLineWidth;
}
/**
* Sets the linecolor for this LimitLine. Make sure to use
* getResources().getColor(...)
*
* @param color
*/
public void setLineColor(int color) {
mLineColor = color;
}
/**
* Returns the color that is used for this LimitLine
*
* @return
*/
public int getLineColor() {
return mLineColor;
}
/**
* Enables the line to be drawn in dashed mode, e.g. like this "- - - - - -"
*
* @param lineLength the length of the line pieces
* @param spaceLength the length of space inbetween the pieces
* @param phase offset, in degrees (normally, use 0)
*/
public void enableDashedLine(float lineLength, float spaceLength, float phase) {
mDashPathEffect = new DashPathEffect(new float[] {
lineLength, spaceLength
}, phase);
}
/**
* Disables the line to be drawn in dashed mode.
*/
public void disableDashedLine() {
mDashPathEffect = null;
}
/**
* Returns true if the dashed-line effect is enabled, false if not. Default:
* disabled
*
* @return
*/
public boolean isDashedLineEnabled() {
return mDashPathEffect == null ? false : true;
}
/**
* returns the DashPathEffect that is set for this LimitLine
*
* @return
*/
public DashPathEffect getDashPathEffect() {
return mDashPathEffect;
}
/**
* Sets the color of the value-text that is drawn next to the LimitLine.
* Default: Paint.Style.FILL_AND_STROKE
*
* @param style
*/
public void setTextStyle(Paint.Style style) {
this.mTextStyle = style;
}
/**
* Returns the color of the value-text that is drawn next to the LimitLine.
*
* @return
*/
public Paint.Style getTextStyle() {
return mTextStyle;
}
/**
* Sets the position of the LimitLine value label (either on the right or on
* the left edge of the chart). Not supported for RadarChart.
*
* @param pos
*/
public void setLabelPosition(LimitLabelPosition pos) {
mLabelPosition = pos;
}
/**
* Returns the position of the LimitLine label (value).
*
* @return
*/
public LimitLabelPosition getLabelPosition() {
return mLabelPosition;
}
/**
* Sets the label that is drawn next to the limit line. Provide "" if no
* label is required.
*
* @param label
*/
public void setLabel(String label) {
mLabel = label;
}
/**
* Returns the label that is drawn next to the limit line.
*
* @return
*/
public String getLabel() {
return mLabel;
}
}

@ -0,0 +1,167 @@
package com.github.mikephil.charting.components;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.utils.FSize;
import com.github.mikephil.charting.utils.MPPointF;
import java.lang.ref.WeakReference;
/**
* View that can be displayed when selecting values in the chart. Extend this class to provide custom layouts for your
* markers.
*
* @author Philipp Jahoda
*/
public class MarkerImage implements IMarker {
private Context mContext;
private Drawable mDrawable;
private MPPointF mOffset = new MPPointF();
private MPPointF mOffset2 = new MPPointF();
private WeakReference<Chart> mWeakChart;
private FSize mSize = new FSize();
private Rect mDrawableBoundsCache = new Rect();
/**
* Constructor. Sets up the MarkerView with a custom layout resource.
*
* @param context
* @param drawableResourceId the drawable resource to render
*/
public MarkerImage(Context context, int drawableResourceId) {
mContext = context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
mDrawable = mContext.getResources().getDrawable(drawableResourceId, null);
}
else
{
mDrawable = mContext.getResources().getDrawable(drawableResourceId);
}
}
public void setOffset(MPPointF offset) {
mOffset = offset;
if (mOffset == null) {
mOffset = new MPPointF();
}
}
public void setOffset(float offsetX, float offsetY) {
mOffset.x = offsetX;
mOffset.y = offsetY;
}
@Override
public MPPointF getOffset() {
return mOffset;
}
public void setSize(FSize size) {
mSize = size;
if (mSize == null) {
mSize = new FSize();
}
}
public FSize getSize() {
return mSize;
}
public void setChartView(Chart chart) {
mWeakChart = new WeakReference<>(chart);
}
public Chart getChartView() {
return mWeakChart == null ? null : mWeakChart.get();
}
@Override
public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) {
MPPointF offset = getOffset();
mOffset2.x = offset.x;
mOffset2.y = offset.y;
Chart chart = getChartView();
float width = mSize.width;
float height = mSize.height;
if (width == 0.f && mDrawable != null) {
width = mDrawable.getIntrinsicWidth();
}
if (height == 0.f && mDrawable != null) {
height = mDrawable.getIntrinsicHeight();
}
if (posX + mOffset2.x < 0) {
mOffset2.x = - posX;
} else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) {
mOffset2.x = chart.getWidth() - posX - width;
}
if (posY + mOffset2.y < 0) {
mOffset2.y = - posY;
} else if (chart != null && posY + height + mOffset2.y > chart.getHeight()) {
mOffset2.y = chart.getHeight() - posY - height;
}
return mOffset2;
}
@Override
public void refreshContent(Entry e, Highlight highlight) {
}
@Override
public void draw(Canvas canvas, float posX, float posY) {
if (mDrawable == null) return;
MPPointF offset = getOffsetForDrawingAtPoint(posX, posY);
float width = mSize.width;
float height = mSize.height;
if (width == 0.f) {
width = mDrawable.getIntrinsicWidth();
}
if (height == 0.f) {
height = mDrawable.getIntrinsicHeight();
}
mDrawable.copyBounds(mDrawableBoundsCache);
mDrawable.setBounds(
mDrawableBoundsCache.left,
mDrawableBoundsCache.top,
mDrawableBoundsCache.left + (int)width,
mDrawableBoundsCache.top + (int)height);
int saveId = canvas.save();
// translate to the correct position and draw
canvas.translate(posX + offset.x, posY + offset.y);
mDrawable.draw(canvas);
canvas.restoreToCount(saveId);
mDrawable.setBounds(mDrawableBoundsCache);
}
}

@ -0,0 +1,129 @@
package com.github.mikephil.charting.components;
import android.content.Context;
import android.graphics.Canvas;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.utils.FSize;
import com.github.mikephil.charting.utils.MPPointF;
import java.lang.ref.WeakReference;
/**
* View that can be displayed when selecting values in the chart. Extend this class to provide custom layouts for your
* markers.
*
* @author Philipp Jahoda
*/
public class MarkerView extends RelativeLayout implements IMarker {
private MPPointF mOffset = new MPPointF();
private MPPointF mOffset2 = new MPPointF();
private WeakReference<Chart> mWeakChart;
/**
* Constructor. Sets up the MarkerView with a custom layout resource.
*
* @param context
* @param layoutResource the layout resource to use for the MarkerView
*/
public MarkerView(Context context, int layoutResource) {
super(context);
setupLayoutResource(layoutResource);
}
/**
* Sets the layout resource for a custom MarkerView.
*
* @param layoutResource
*/
private void setupLayoutResource(int layoutResource) {
View inflated = LayoutInflater.from(getContext()).inflate(layoutResource, this);
inflated.setLayoutParams(new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
inflated.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
// measure(getWidth(), getHeight());
inflated.layout(0, 0, inflated.getMeasuredWidth(), inflated.getMeasuredHeight());
}
public void setOffset(MPPointF offset) {
mOffset = offset;
if (mOffset == null) {
mOffset = new MPPointF();
}
}
public void setOffset(float offsetX, float offsetY) {
mOffset.x = offsetX;
mOffset.y = offsetY;
}
@Override
public MPPointF getOffset() {
return mOffset;
}
public void setChartView(Chart chart) {
mWeakChart = new WeakReference<>(chart);
}
public Chart getChartView() {
return mWeakChart == null ? null : mWeakChart.get();
}
@Override
public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) {
MPPointF offset = getOffset();
mOffset2.x = offset.x;
mOffset2.y = offset.y;
Chart chart = getChartView();
float width = getWidth();
float height = getHeight();
if (posX + mOffset2.x < 0) {
mOffset2.x = - posX;
} else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) {
mOffset2.x = chart.getWidth() - posX - width;
}
if (posY + mOffset2.y < 0) {
mOffset2.y = - posY;
} else if (chart != null && posY + height + mOffset2.y > chart.getHeight()) {
mOffset2.y = chart.getHeight() - posY - height;
}
return mOffset2;
}
@Override
public void refreshContent(Entry e, Highlight highlight) {
measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
@Override
public void draw(Canvas canvas, float posX, float posY) {
MPPointF offset = getOffsetForDrawingAtPoint(posX, posY);
int saveId = canvas.save();
// translate to the correct position and draw
canvas.translate(posX + offset.x, posY + offset.y);
draw(canvas);
canvas.restoreToCount(saveId);
}
}

@ -0,0 +1,118 @@
package com.github.mikephil.charting.components;
import com.github.mikephil.charting.utils.Utils;
/**
* Class representing the x-axis labels settings. Only use the setter methods to
* modify it. Do not access public variables directly. Be aware that not all
* features the XLabels class provides are suitable for the RadarChart.
*
* @author Philipp Jahoda
*/
public class XAxis extends AxisBase {
/**
* width of the x-axis labels in pixels - this is automatically
* calculated by the computeSize() methods in the renderers
*/
public int mLabelWidth = 1;
/**
* height of the x-axis labels in pixels - this is automatically
* calculated by the computeSize() methods in the renderers
*/
public int mLabelHeight = 1;
/**
* width of the (rotated) x-axis labels in pixels - this is automatically
* calculated by the computeSize() methods in the renderers
*/
public int mLabelRotatedWidth = 1;
/**
* height of the (rotated) x-axis labels in pixels - this is automatically
* calculated by the computeSize() methods in the renderers
*/
public int mLabelRotatedHeight = 1;
/**
* This is the angle for drawing the X axis labels (in degrees)
*/
protected float mLabelRotationAngle = 0f;
/**
* if set to true, the chart will avoid that the first and last label entry
* in the chart "clip" off the edge of the chart
*/
private boolean mAvoidFirstLastClipping = false;
/**
* the position of the x-labels relative to the chart
*/
private XAxisPosition mPosition = XAxisPosition.TOP;
/**
* enum for the position of the x-labels relative to the chart
*/
public enum XAxisPosition {
TOP, BOTTOM, BOTH_SIDED, TOP_INSIDE, BOTTOM_INSIDE
}
public XAxis() {
super();
mYOffset = Utils.convertDpToPixel(4.f); // -3
}
/**
* returns the position of the x-labels
*/
public XAxisPosition getPosition() {
return mPosition;
}
/**
* sets the position of the x-labels
*
* @param pos
*/
public void setPosition(XAxisPosition pos) {
mPosition = pos;
}
/**
* returns the angle for drawing the X axis labels (in degrees)
*/
public float getLabelRotationAngle() {
return mLabelRotationAngle;
}
/**
* sets the angle for drawing the X axis labels (in degrees)
*
* @param angle the angle in degrees
*/
public void setLabelRotationAngle(float angle) {
mLabelRotationAngle = angle;
}
/**
* if set to true, the chart will avoid that the first and last label entry
* in the chart "clip" off the edge of the chart or the screen
*
* @param enabled
*/
public void setAvoidFirstLastClipping(boolean enabled) {
mAvoidFirstLastClipping = enabled;
}
/**
* returns true if avoid-first-lastclipping is enabled, false if not
*
* @return
*/
public boolean isAvoidFirstLastClippingEnabled() {
return mAvoidFirstLastClipping;
}
}

@ -0,0 +1,467 @@
package com.github.mikephil.charting.components;
import android.graphics.Color;
import android.graphics.Paint;
import com.github.mikephil.charting.utils.Utils;
/**
* Class representing the y-axis labels settings and its entries. Only use the setter methods to
* modify it. Do not
* access public variables directly. Be aware that not all features the YLabels class provides
* are suitable for the
* RadarChart. Customizations that affect the value range of the axis need to be applied before
* setting data for the
* chart.
*
* @author Philipp Jahoda
*/
public class YAxis extends AxisBase {
/**
* indicates if the bottom y-label entry is drawn or not
*/
private boolean mDrawBottomYLabelEntry = true;
/**
* indicates if the top y-label entry is drawn or not
*/
private boolean mDrawTopYLabelEntry = true;
/**
* flag that indicates if the axis is inverted or not
*/
protected boolean mInverted = false;
/**
* flag that indicates if the zero-line should be drawn regardless of other grid lines
*/
protected boolean mDrawZeroLine = false;
/**
* flag indicating that auto scale min restriction should be used
*/
private boolean mUseAutoScaleRestrictionMin = false;
/**
* flag indicating that auto scale max restriction should be used
*/
private boolean mUseAutoScaleRestrictionMax = false;
/**
* Color of the zero line
*/
protected int mZeroLineColor = Color.GRAY;
/**
* Width of the zero line in pixels
*/
protected float mZeroLineWidth = 1f;
/**
* axis space from the largest value to the top in percent of the total axis range
*/
protected float mSpacePercentTop = 10f;
/**
* axis space from the smallest value to the bottom in percent of the total axis range
*/
protected float mSpacePercentBottom = 10f;
/**
* the position of the y-labels relative to the chart
*/
private YAxisLabelPosition mPosition = YAxisLabelPosition.OUTSIDE_CHART;
/**
* the horizontal offset of the y-label
*/
private float mXLabelOffset = 0.0f;
/**
* enum for the position of the y-labels relative to the chart
*/
public enum YAxisLabelPosition {
OUTSIDE_CHART, INSIDE_CHART
}
/**
* the side this axis object represents
*/
private AxisDependency mAxisDependency;
/**
* the minimum width that the axis should take (in dp).
* <p/>
* default: 0.0
*/
protected float mMinWidth = 0.f;
/**
* the maximum width that the axis can take (in dp).
* use Inifinity for disabling the maximum
* default: Float.POSITIVE_INFINITY (no maximum specified)
*/
protected float mMaxWidth = Float.POSITIVE_INFINITY;
/**
* Enum that specifies the axis a DataSet should be plotted against, either LEFT or RIGHT.
*
* @author Philipp Jahoda
*/
public enum AxisDependency {
LEFT, RIGHT
}
public YAxis() {
super();
// default left
this.mAxisDependency = AxisDependency.LEFT;
this.mYOffset = 0f;
}
public YAxis(AxisDependency position) {
super();
this.mAxisDependency = position;
this.mYOffset = 0f;
}
public AxisDependency getAxisDependency() {
return mAxisDependency;
}
/**
* @return the minimum width that the axis should take (in dp).
*/
public float getMinWidth() {
return mMinWidth;
}
/**
* Sets the minimum width that the axis should take (in dp).
*
* @param minWidth
*/
public void setMinWidth(float minWidth) {
mMinWidth = minWidth;
}
/**
* @return the maximum width that the axis can take (in dp).
*/
public float getMaxWidth() {
return mMaxWidth;
}
/**
* Sets the maximum width that the axis can take (in dp).
*
* @param maxWidth
*/
public void setMaxWidth(float maxWidth) {
mMaxWidth = maxWidth;
}
/**
* returns the position of the y-labels
*/
public YAxisLabelPosition getLabelPosition() {
return mPosition;
}
/**
* sets the position of the y-labels
*
* @param pos
*/
public void setPosition(YAxisLabelPosition pos) {
mPosition = pos;
}
/**
* returns the horizontal offset of the y-label
*/
public float getLabelXOffset() {
return mXLabelOffset;
}
/**
* sets the horizontal offset of the y-label
*
* @param xOffset
*/
public void setLabelXOffset(float xOffset) {
mXLabelOffset = xOffset;
}
/**
* returns true if drawing the top y-axis label entry is enabled
*
* @return
*/
public boolean isDrawTopYLabelEntryEnabled() {
return mDrawTopYLabelEntry;
}
/**
* returns true if drawing the bottom y-axis label entry is enabled
*
* @return
*/
public boolean isDrawBottomYLabelEntryEnabled() {
return mDrawBottomYLabelEntry;
}
/**
* set this to true to enable drawing the top y-label entry. Disabling this can be helpful
* when the top y-label and
* left x-label interfere with each other. default: true
*
* @param enabled
*/
public void setDrawTopYLabelEntry(boolean enabled) {
mDrawTopYLabelEntry = enabled;
}
/**
* If this is set to true, the y-axis is inverted which means that low values are on top of
* the chart, high values
* on bottom.
*
* @param enabled
*/
public void setInverted(boolean enabled) {
mInverted = enabled;
}
/**
* If this returns true, the y-axis is inverted.
*
* @return
*/
public boolean isInverted() {
return mInverted;
}
/**
* This method is deprecated.
* Use setAxisMinimum(...) / setAxisMaximum(...) instead.
*
* @param startAtZero
*/
@Deprecated
public void setStartAtZero(boolean startAtZero) {
if (startAtZero)
setAxisMinimum(0f);
else
resetAxisMinimum();
}
/**
* Sets the top axis space in percent of the full range. Default 10f
*
* @param percent
*/
public void setSpaceTop(float percent) {
mSpacePercentTop = percent;
}
/**
* Returns the top axis space in percent of the full range. Default 10f
*
* @return
*/
public float getSpaceTop() {
return mSpacePercentTop;
}
/**
* Sets the bottom axis space in percent of the full range. Default 10f
*
* @param percent
*/
public void setSpaceBottom(float percent) {
mSpacePercentBottom = percent;
}
/**
* Returns the bottom axis space in percent of the full range. Default 10f
*
* @return
*/
public float getSpaceBottom() {
return mSpacePercentBottom;
}
public boolean isDrawZeroLineEnabled() {
return mDrawZeroLine;
}
/**
* Set this to true to draw the zero-line regardless of weather other
* grid-lines are enabled or not. Default: false
*
* @param mDrawZeroLine
*/
public void setDrawZeroLine(boolean mDrawZeroLine) {
this.mDrawZeroLine = mDrawZeroLine;
}
public int getZeroLineColor() {
return mZeroLineColor;
}
/**
* Sets the color of the zero line
*
* @param color
*/
public void setZeroLineColor(int color) {
mZeroLineColor = color;
}
public float getZeroLineWidth() {
return mZeroLineWidth;
}
/**
* Sets the width of the zero line in dp
*
* @param width
*/
public void setZeroLineWidth(float width) {
this.mZeroLineWidth = Utils.convertDpToPixel(width);
}
/**
* This is for normal (not horizontal) charts horizontal spacing.
*
* @param p
* @return
*/
public float getRequiredWidthSpace(Paint p) {
p.setTextSize(mTextSize);
String label = getLongestLabel();
float width = (float) Utils.calcTextWidth(p, label) + getXOffset() * 2f;
float minWidth = getMinWidth();
float maxWidth = getMaxWidth();
if (minWidth > 0.f)
minWidth = Utils.convertDpToPixel(minWidth);
if (maxWidth > 0.f && maxWidth != Float.POSITIVE_INFINITY)
maxWidth = Utils.convertDpToPixel(maxWidth);
width = Math.max(minWidth, Math.min(width, maxWidth > 0.0 ? maxWidth : width));
return width;
}
/**
* This is for HorizontalBarChart vertical spacing.
*
* @param p
* @return
*/
public float getRequiredHeightSpace(Paint p) {
p.setTextSize(mTextSize);
String label = getLongestLabel();
return (float) Utils.calcTextHeight(p, label) + getYOffset() * 2f;
}
/**
* Returns true if this axis needs horizontal offset, false if no offset is needed.
*
* @return
*/
public boolean needsOffset() {
if (isEnabled() && isDrawLabelsEnabled() && getLabelPosition() == YAxisLabelPosition
.OUTSIDE_CHART)
return true;
else
return false;
}
/**
* Returns true if autoscale restriction for axis min value is enabled
*/
@Deprecated
public boolean isUseAutoScaleMinRestriction( ) {
return mUseAutoScaleRestrictionMin;
}
/**
* Sets autoscale restriction for axis min value as enabled/disabled
*/
@Deprecated
public void setUseAutoScaleMinRestriction( boolean isEnabled ) {
mUseAutoScaleRestrictionMin = isEnabled;
}
/**
* Returns true if autoscale restriction for axis max value is enabled
*/
@Deprecated
public boolean isUseAutoScaleMaxRestriction() {
return mUseAutoScaleRestrictionMax;
}
/**
* Sets autoscale restriction for axis max value as enabled/disabled
*/
@Deprecated
public void setUseAutoScaleMaxRestriction( boolean isEnabled ) {
mUseAutoScaleRestrictionMax = isEnabled;
}
@Override
public void calculate(float dataMin, float dataMax) {
float min = dataMin;
float max = dataMax;
// Make sure max is greater than min
// Discussion: https://github.com/danielgindi/Charts/pull/3650#discussion_r221409991
if (min > max)
{
if (mCustomAxisMax && mCustomAxisMin)
{
float t = min;
min = max;
max = t;
}
else if (mCustomAxisMax)
{
min = max < 0f ? max * 1.5f : max * 0.5f;
}
else if (mCustomAxisMin)
{
max = min < 0f ? min * 0.5f : min * 1.5f;
}
}
float range = Math.abs(max - min);
// in case all values are equal
if (range == 0f) {
max = max + 1f;
min = min - 1f;
}
// recalculate
range = Math.abs(max - min);
// calc extra spacing
this.mAxisMinimum = mCustomAxisMin ? this.mAxisMinimum : min - (range / 100f) * getSpaceBottom();
this.mAxisMaximum = mCustomAxisMax ? this.mAxisMaximum : max + (range / 100f) * getSpaceTop();
this.mAxisRange = Math.abs(this.mAxisMinimum - this.mAxisMaximum);
}
}

@ -0,0 +1,119 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import java.util.List;
/**
* Data object that represents all data for the BarChart.
*
* @author Philipp Jahoda
*/
public class BarData extends BarLineScatterCandleBubbleData<IBarDataSet> {
/**
* the width of the bars on the x-axis, in values (not pixels)
*/
private float mBarWidth = 0.85f;
public BarData() {
super();
}
public BarData(IBarDataSet... dataSets) {
super(dataSets);
}
public BarData(List<IBarDataSet> dataSets) {
super(dataSets);
}
/**
* Sets the width each bar should have on the x-axis (in values, not pixels).
* Default 0.85f
*
* @param mBarWidth
*/
public void setBarWidth(float mBarWidth) {
this.mBarWidth = mBarWidth;
}
public float getBarWidth() {
return mBarWidth;
}
/**
* Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries.
* Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified
* by the parameters.
* Do not forget to call notifyDataSetChanged() on your BarChart object after calling this method.
*
* @param fromX the starting point on the x-axis where the grouping should begin
* @param groupSpace the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f
* @param barSpace the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f
*/
public void groupBars(float fromX, float groupSpace, float barSpace) {
int setCount = mDataSets.size();
if (setCount <= 1) {
throw new RuntimeException("BarData needs to hold at least 2 BarDataSets to allow grouping.");
}
IBarDataSet max = getMaxEntryCountSet();
int maxEntryCount = max.getEntryCount();
float groupSpaceWidthHalf = groupSpace / 2f;
float barSpaceHalf = barSpace / 2f;
float barWidthHalf = mBarWidth / 2f;
float interval = getGroupWidth(groupSpace, barSpace);
for (int i = 0; i < maxEntryCount; i++) {
float start = fromX;
fromX += groupSpaceWidthHalf;
for (IBarDataSet set : mDataSets) {
fromX += barSpaceHalf;
fromX += barWidthHalf;
if (i < set.getEntryCount()) {
BarEntry entry = set.getEntryForIndex(i);
if (entry != null) {
entry.setX(fromX);
}
}
fromX += barWidthHalf;
fromX += barSpaceHalf;
}
fromX += groupSpaceWidthHalf;
float end = fromX;
float innerInterval = end - start;
float diff = interval - innerInterval;
// correct rounding errors
if (diff > 0 || diff < 0) {
fromX += diff;
}
}
notifyDataChanged();
}
/**
* In case of grouped bars, this method returns the space an individual group of bar needs on the x-axis.
*
* @param groupSpace
* @param barSpace
* @return
*/
public float getGroupWidth(float groupSpace, float barSpace) {
return mDataSets.size() * (mBarWidth + barSpace) + groupSpace;
}
}

@ -0,0 +1,299 @@
package com.github.mikephil.charting.data;
import android.graphics.Color;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import com.github.mikephil.charting.utils.Fill;
import java.util.ArrayList;
import java.util.List;
public class BarDataSet extends BarLineScatterCandleBubbleDataSet<BarEntry> implements IBarDataSet {
/**
* the maximum number of bars that are stacked upon each other, this value
* is calculated from the Entries that are added to the DataSet
*/
private int mStackSize = 1;
/**
* the color used for drawing the bar shadows
*/
private int mBarShadowColor = Color.rgb(215, 215, 215);
private float mBarBorderWidth = 0.0f;
private int mBarBorderColor = Color.BLACK;
/**
* the alpha value used to draw the highlight indicator bar
*/
private int mHighLightAlpha = 120;
/**
* the overall entry count, including counting each stack-value individually
*/
private int mEntryCountStacks = 0;
/**
* array of labels used to describe the different values of the stacked bars
*/
private String[] mStackLabels = new String[]{};
protected List<Fill> mFills = null;
public BarDataSet(List<BarEntry> yVals, String label) {
super(yVals, label);
mHighLightColor = Color.rgb(0, 0, 0);
calcStackSize(yVals);
calcEntryCountIncludingStacks(yVals);
}
@Override
public DataSet<BarEntry> copy() {
List<BarEntry> entries = new ArrayList<BarEntry>();
for (int i = 0; i < mEntries.size(); i++) {
entries.add(mEntries.get(i).copy());
}
BarDataSet copied = new BarDataSet(entries, getLabel());
copy(copied);
return copied;
}
protected void copy(BarDataSet barDataSet) {
super.copy(barDataSet);
barDataSet.mStackSize = mStackSize;
barDataSet.mBarShadowColor = mBarShadowColor;
barDataSet.mBarBorderWidth = mBarBorderWidth;
barDataSet.mStackLabels = mStackLabels;
barDataSet.mHighLightAlpha = mHighLightAlpha;
}
@Override
public List<Fill> getFills() {
return mFills;
}
@Override
public Fill getFill(int index) {
return mFills.get(index % mFills.size());
}
/**
* This method is deprecated.
* Use getFills() instead.
*/
@Deprecated
public List<Fill> getGradients() {
return mFills;
}
/**
* This method is deprecated.
* Use getFill(...) instead.
*
* @param index
*/
@Deprecated
public Fill getGradient(int index) {
return getFill(index);
}
/**
* Sets the start and end color for gradient color, ONLY color that should be used for this DataSet.
*
* @param startColor
* @param endColor
*/
public void setGradientColor(int startColor, int endColor) {
mFills.clear();
mFills.add(new Fill(startColor, endColor));
}
/**
* This method is deprecated.
* Use setFills(...) instead.
*
* @param gradientColors
*/
@Deprecated
public void setGradientColors(List<Fill> gradientColors) {
this.mFills = gradientColors;
}
/**
* Sets the fills for the bars in this dataset.
*
* @param fills
*/
public void setFills(List<Fill> fills) {
this.mFills = fills;
}
/**
* Calculates the total number of entries this DataSet represents, including
* stacks. All values belonging to a stack are calculated separately.
*/
private void calcEntryCountIncludingStacks(List<BarEntry> yVals) {
mEntryCountStacks = 0;
for (int i = 0; i < yVals.size(); i++) {
float[] vals = yVals.get(i).getYVals();
if (vals == null)
mEntryCountStacks++;
else
mEntryCountStacks += vals.length;
}
}
/**
* calculates the maximum stacksize that occurs in the Entries array of this
* DataSet
*/
private void calcStackSize(List<BarEntry> yVals) {
for (int i = 0; i < yVals.size(); i++) {
float[] vals = yVals.get(i).getYVals();
if (vals != null && vals.length > mStackSize)
mStackSize = vals.length;
}
}
@Override
protected void calcMinMax(BarEntry e) {
if (e != null && !Float.isNaN(e.getY())) {
if (e.getYVals() == null) {
if (e.getY() < mYMin)
mYMin = e.getY();
if (e.getY() > mYMax)
mYMax = e.getY();
} else {
if (-e.getNegativeSum() < mYMin)
mYMin = -e.getNegativeSum();
if (e.getPositiveSum() > mYMax)
mYMax = e.getPositiveSum();
}
calcMinMaxX(e);
}
}
@Override
public int getStackSize() {
return mStackSize;
}
@Override
public boolean isStacked() {
return mStackSize > 1 ? true : false;
}
/**
* returns the overall entry count, including counting each stack-value
* individually
*
* @return
*/
public int getEntryCountStacks() {
return mEntryCountStacks;
}
/**
* Sets the color used for drawing the bar-shadows. The bar shadows is a
* surface behind the bar that indicates the maximum value. Don't for get to
* use getResources().getColor(...) to set this. Or Color.rgb(...).
*
* @param color
*/
public void setBarShadowColor(int color) {
mBarShadowColor = color;
}
@Override
public int getBarShadowColor() {
return mBarShadowColor;
}
/**
* Sets the width used for drawing borders around the bars.
* If borderWidth == 0, no border will be drawn.
*
* @return
*/
public void setBarBorderWidth(float width) {
mBarBorderWidth = width;
}
/**
* Returns the width used for drawing borders around the bars.
* If borderWidth == 0, no border will be drawn.
*
* @return
*/
@Override
public float getBarBorderWidth() {
return mBarBorderWidth;
}
/**
* Sets the color drawing borders around the bars.
*
* @return
*/
public void setBarBorderColor(int color) {
mBarBorderColor = color;
}
/**
* Returns the color drawing borders around the bars.
*
* @return
*/
@Override
public int getBarBorderColor() {
return mBarBorderColor;
}
/**
* Set the alpha value (transparency) that is used for drawing the highlight
* indicator bar. min = 0 (fully transparent), max = 255 (fully opaque)
*
* @param alpha
*/
public void setHighLightAlpha(int alpha) {
mHighLightAlpha = alpha;
}
@Override
public int getHighLightAlpha() {
return mHighLightAlpha;
}
/**
* Sets labels for different values of bar-stacks, in case there are one.
*
* @param labels
*/
public void setStackLabels(String[] labels) {
mStackLabels = labels;
}
@Override
public String[] getStackLabels() {
return mStackLabels;
}
}

@ -0,0 +1,310 @@
package com.github.mikephil.charting.data;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import com.github.mikephil.charting.highlight.Range;
/**
* Entry class for the BarChart. (especially stacked bars)
*
* @author Philipp Jahoda
*/
@SuppressLint("ParcelCreator")
public class BarEntry extends Entry {
/**
* the values the stacked barchart holds
*/
private float[] mYVals;
/**
* the ranges for the individual stack values - automatically calculated
*/
private Range[] mRanges;
/**
* the sum of all negative values this entry (if stacked) contains
*/
private float mNegativeSum;
/**
* the sum of all positive values this entry (if stacked) contains
*/
private float mPositiveSum;
/**
* Constructor for normal bars (not stacked).
*
* @param x
* @param y
*/
public BarEntry(float x, float y) {
super(x, y);
}
/**
* Constructor for normal bars (not stacked).
*
* @param x
* @param y
* @param data - Spot for additional data this Entry represents.
*/
public BarEntry(float x, float y, Object data) {
super(x, y, data);
}
/**
* Constructor for normal bars (not stacked).
*
* @param x
* @param y
* @param icon - icon image
*/
public BarEntry(float x, float y, Drawable icon) {
super(x, y, icon);
}
/**
* Constructor for normal bars (not stacked).
*
* @param x
* @param y
* @param icon - icon image
* @param data - Spot for additional data this Entry represents.
*/
public BarEntry(float x, float y, Drawable icon, Object data) {
super(x, y, icon, data);
}
/**
* Constructor for stacked bar entries. One data object for whole stack
*
* @param x
* @param vals - the stack values, use at least 2
*/
public BarEntry(float x, float[] vals) {
super(x, calcSum(vals));
this.mYVals = vals;
calcPosNegSum();
calcRanges();
}
/**
* Constructor for stacked bar entries. One data object for whole stack
*
* @param x
* @param vals - the stack values, use at least 2
* @param data - Spot for additional data this Entry represents.
*/
public BarEntry(float x, float[] vals, Object data) {
super(x, calcSum(vals), data);
this.mYVals = vals;
calcPosNegSum();
calcRanges();
}
/**
* Constructor for stacked bar entries. One data object for whole stack
*
* @param x
* @param vals - the stack values, use at least 2
* @param icon - icon image
*/
public BarEntry(float x, float[] vals, Drawable icon) {
super(x, calcSum(vals), icon);
this.mYVals = vals;
calcPosNegSum();
calcRanges();
}
/**
* Constructor for stacked bar entries. One data object for whole stack
*
* @param x
* @param vals - the stack values, use at least 2
* @param icon - icon image
* @param data - Spot for additional data this Entry represents.
*/
public BarEntry(float x, float[] vals, Drawable icon, Object data) {
super(x, calcSum(vals), icon, data);
this.mYVals = vals;
calcPosNegSum();
calcRanges();
}
/**
* Returns an exact copy of the BarEntry.
*/
public BarEntry copy() {
BarEntry copied = new BarEntry(getX(), getY(), getData());
copied.setVals(mYVals);
return copied;
}
/**
* Returns the stacked values this BarEntry represents, or null, if only a single value is represented (then, use
* getY()).
*
* @return
*/
public float[] getYVals() {
return mYVals;
}
/**
* Set the array of values this BarEntry should represent.
*
* @param vals
*/
public void setVals(float[] vals) {
setY(calcSum(vals));
mYVals = vals;
calcPosNegSum();
calcRanges();
}
/**
* Returns the value of this BarEntry. If the entry is stacked, it returns the positive sum of all values.
*
* @return
*/
@Override
public float getY() {
return super.getY();
}
/**
* Returns the ranges of the individual stack-entries. Will return null if this entry is not stacked.
*
* @return
*/
public Range[] getRanges() {
return mRanges;
}
/**
* Returns true if this BarEntry is stacked (has a values array), false if not.
*
* @return
*/
public boolean isStacked() {
return mYVals != null;
}
/**
* Use `getSumBelow(stackIndex)` instead.
*/
@Deprecated
public float getBelowSum(int stackIndex) {
return getSumBelow(stackIndex);
}
public float getSumBelow(int stackIndex) {
if (mYVals == null)
return 0;
float remainder = 0f;
int index = mYVals.length - 1;
while (index > stackIndex && index >= 0) {
remainder += mYVals[index];
index--;
}
return remainder;
}
/**
* Reuturns the sum of all positive values this entry (if stacked) contains.
*
* @return
*/
public float getPositiveSum() {
return mPositiveSum;
}
/**
* Returns the sum of all negative values this entry (if stacked) contains. (this is a positive number)
*
* @return
*/
public float getNegativeSum() {
return mNegativeSum;
}
private void calcPosNegSum() {
if (mYVals == null) {
mNegativeSum = 0;
mPositiveSum = 0;
return;
}
float sumNeg = 0f;
float sumPos = 0f;
for (float f : mYVals) {
if (f <= 0f)
sumNeg += Math.abs(f);
else
sumPos += f;
}
mNegativeSum = sumNeg;
mPositiveSum = sumPos;
}
/**
* Calculates the sum across all values of the given stack.
*
* @param vals
* @return
*/
private static float calcSum(float[] vals) {
if (vals == null)
return 0f;
float sum = 0f;
for (float f : vals)
sum += f;
return sum;
}
protected void calcRanges() {
float[] values = getYVals();
if (values == null || values.length == 0)
return;
mRanges = new Range[values.length];
float negRemain = -getNegativeSum();
float posRemain = 0f;
for (int i = 0; i < mRanges.length; i++) {
float value = values[i];
if (value < 0) {
mRanges[i] = new Range(negRemain, negRemain - value);
negRemain -= value;
} else {
mRanges[i] = new Range(posRemain, posRemain + value);
posRemain += value;
}
}
}
}

@ -0,0 +1,27 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet;
import java.util.List;
/**
* Baseclass for all Line, Bar, Scatter, Candle and Bubble data.
*
* @author Philipp Jahoda
*/
public abstract class BarLineScatterCandleBubbleData<T extends IBarLineScatterCandleBubbleDataSet<? extends Entry>>
extends ChartData<T> {
public BarLineScatterCandleBubbleData() {
super();
}
public BarLineScatterCandleBubbleData(T... sets) {
super(sets);
}
public BarLineScatterCandleBubbleData(List<T> sets) {
super(sets);
}
}

@ -0,0 +1,48 @@
package com.github.mikephil.charting.data;
import android.graphics.Color;
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet;
import java.util.List;
/**
* Baseclass of all DataSets for Bar-, Line-, Scatter- and CandleStickChart.
*
* @author Philipp Jahoda
*/
public abstract class BarLineScatterCandleBubbleDataSet<T extends Entry>
extends DataSet<T>
implements IBarLineScatterCandleBubbleDataSet<T> {
/**
* default highlight color
*/
protected int mHighLightColor = Color.rgb(255, 187, 115);
public BarLineScatterCandleBubbleDataSet(List<T> yVals, String label) {
super(yVals, label);
}
/**
* Sets the color that is used for drawing the highlight indicators. Dont
* forget to resolve the color using getResources().getColor(...) or
* Color.rgb(...).
*
* @param color
*/
public void setHighLightColor(int color) {
mHighLightColor = color;
}
@Override
public int getHighLightColor() {
return mHighLightColor;
}
protected void copy(BarLineScatterCandleBubbleDataSet barLineScatterCandleBubbleDataSet) {
super.copy(barLineScatterCandleBubbleDataSet);
barLineScatterCandleBubbleDataSet.mHighLightColor = mHighLightColor;
}
}

@ -0,0 +1,506 @@
package com.github.mikephil.charting.data;
import android.content.Context;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Typeface;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.formatter.IValueFormatter;
import com.github.mikephil.charting.interfaces.datasets.IDataSet;
import com.github.mikephil.charting.utils.ColorTemplate;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Utils;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Philipp Jahoda on 21/10/15.
* This is the base dataset of all DataSets. It's purpose is to implement critical methods
* provided by the IDataSet interface.
*/
public abstract class BaseDataSet<T extends Entry> implements IDataSet<T> {
/**
* List representing all colors that are used for this DataSet
*/
protected List<Integer> mColors = null;
/**
* List representing all colors that are used for drawing the actual values for this DataSet
*/
protected List<Integer> mValueColors = null;
/**
* label that describes the DataSet or the data the DataSet represents
*/
private String mLabel = "DataSet";
/**
* this specifies which axis this DataSet should be plotted against
*/
protected YAxis.AxisDependency mAxisDependency = YAxis.AxisDependency.LEFT;
/**
* if true, value highlightning is enabled
*/
protected boolean mHighlightEnabled = true;
/**
* custom formatter that is used instead of the auto-formatter if set
*/
protected transient IValueFormatter mValueFormatter;
/**
* the typeface used for the value text
*/
protected Typeface mValueTypeface;
private Legend.LegendForm mForm = Legend.LegendForm.DEFAULT;
private float mFormSize = Float.NaN;
private float mFormLineWidth = Float.NaN;
private DashPathEffect mFormLineDashEffect = null;
/**
* if true, y-values are drawn on the chart
*/
protected boolean mDrawValues = true;
/**
* if true, y-icons are drawn on the chart
*/
protected boolean mDrawIcons = true;
/**
* the offset for drawing icons (in dp)
*/
protected MPPointF mIconsOffset = new MPPointF();
/**
* the size of the value-text labels
*/
protected float mValueTextSize = 17f;
/**
* flag that indicates if the DataSet is visible or not
*/
protected boolean mVisible = true;
/**
* Default constructor.
*/
public BaseDataSet() {
mColors = new ArrayList<Integer>();
mValueColors = new ArrayList<Integer>();
// default color
mColors.add(Color.rgb(140, 234, 255));
mValueColors.add(Color.BLACK);
}
/**
* Constructor with label.
*
* @param label
*/
public BaseDataSet(String label) {
this();
this.mLabel = label;
}
/**
* Use this method to tell the data set that the underlying data has changed.
*/
public void notifyDataSetChanged() {
calcMinMax();
}
/**
* ###### ###### COLOR GETTING RELATED METHODS ##### ######
*/
@Override
public List<Integer> getColors() {
return mColors;
}
public List<Integer> getValueColors() {
return mValueColors;
}
@Override
public int getColor() {
return mColors.get(0);
}
@Override
public int getColor(int index) {
return mColors.get(index % mColors.size());
}
/**
* ###### ###### COLOR SETTING RELATED METHODS ##### ######
*/
/**
* Sets the colors that should be used fore this DataSet. Colors are reused
* as soon as the number of Entries the DataSet represents is higher than
* the size of the colors array. If you are using colors from the resources,
* make sure that the colors are already prepared (by calling
* getResources().getColor(...)) before adding them to the DataSet.
*
* @param colors
*/
public void setColors(List<Integer> colors) {
this.mColors = colors;
}
/**
* Sets the colors that should be used fore this DataSet. Colors are reused
* as soon as the number of Entries the DataSet represents is higher than
* the size of the colors array. If you are using colors from the resources,
* make sure that the colors are already prepared (by calling
* getResources().getColor(...)) before adding them to the DataSet.
*
* @param colors
*/
public void setColors(int... colors) {
this.mColors = ColorTemplate.createColors(colors);
}
/**
* Sets the colors that should be used fore this DataSet. Colors are reused
* as soon as the number of Entries the DataSet represents is higher than
* the size of the colors array. You can use
* "new int[] { R.color.red, R.color.green, ... }" to provide colors for
* this method. Internally, the colors are resolved using
* getResources().getColor(...)
*
* @param colors
*/
public void setColors(int[] colors, Context c) {
if (mColors == null) {
mColors = new ArrayList<>();
}
mColors.clear();
for (int color : colors) {
mColors.add(c.getResources().getColor(color));
}
}
/**
* Adds a new color to the colors array of the DataSet.
*
* @param color
*/
public void addColor(int color) {
if (mColors == null)
mColors = new ArrayList<Integer>();
mColors.add(color);
}
/**
* Sets the one and ONLY color that should be used for this DataSet.
* Internally, this recreates the colors array and adds the specified color.
*
* @param color
*/
public void setColor(int color) {
resetColors();
mColors.add(color);
}
/**
* Sets a color with a specific alpha value.
*
* @param color
* @param alpha from 0-255
*/
public void setColor(int color, int alpha) {
setColor(Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)));
}
/**
* Sets colors with a specific alpha value.
*
* @param colors
* @param alpha
*/
public void setColors(int[] colors, int alpha) {
resetColors();
for (int color : colors) {
addColor(Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)));
}
}
/**
* Resets all colors of this DataSet and recreates the colors array.
*/
public void resetColors() {
if (mColors == null) {
mColors = new ArrayList<Integer>();
}
mColors.clear();
}
/**
* ###### ###### OTHER STYLING RELATED METHODS ##### ######
*/
@Override
public void setLabel(String label) {
mLabel = label;
}
@Override
public String getLabel() {
return mLabel;
}
@Override
public void setHighlightEnabled(boolean enabled) {
mHighlightEnabled = enabled;
}
@Override
public boolean isHighlightEnabled() {
return mHighlightEnabled;
}
@Override
public void setValueFormatter(IValueFormatter f) {
if (f == null)
return;
else
mValueFormatter = f;
}
@Override
public IValueFormatter getValueFormatter() {
if (needsFormatter())
return Utils.getDefaultValueFormatter();
return mValueFormatter;
}
@Override
public boolean needsFormatter() {
return mValueFormatter == null;
}
@Override
public void setValueTextColor(int color) {
mValueColors.clear();
mValueColors.add(color);
}
@Override
public void setValueTextColors(List<Integer> colors) {
mValueColors = colors;
}
@Override
public void setValueTypeface(Typeface tf) {
mValueTypeface = tf;
}
@Override
public void setValueTextSize(float size) {
mValueTextSize = Utils.convertDpToPixel(size);
}
@Override
public int getValueTextColor() {
return mValueColors.get(0);
}
@Override
public int getValueTextColor(int index) {
return mValueColors.get(index % mValueColors.size());
}
@Override
public Typeface getValueTypeface() {
return mValueTypeface;
}
@Override
public float getValueTextSize() {
return mValueTextSize;
}
public void setForm(Legend.LegendForm form) {
mForm = form;
}
@Override
public Legend.LegendForm getForm() {
return mForm;
}
public void setFormSize(float formSize) {
mFormSize = formSize;
}
@Override
public float getFormSize() {
return mFormSize;
}
public void setFormLineWidth(float formLineWidth) {
mFormLineWidth = formLineWidth;
}
@Override
public float getFormLineWidth() {
return mFormLineWidth;
}
public void setFormLineDashEffect(DashPathEffect dashPathEffect) {
mFormLineDashEffect = dashPathEffect;
}
@Override
public DashPathEffect getFormLineDashEffect() {
return mFormLineDashEffect;
}
@Override
public void setDrawValues(boolean enabled) {
this.mDrawValues = enabled;
}
@Override
public boolean isDrawValuesEnabled() {
return mDrawValues;
}
@Override
public void setDrawIcons(boolean enabled) {
mDrawIcons = enabled;
}
@Override
public boolean isDrawIconsEnabled() {
return mDrawIcons;
}
@Override
public void setIconsOffset(MPPointF offsetDp) {
mIconsOffset.x = offsetDp.x;
mIconsOffset.y = offsetDp.y;
}
@Override
public MPPointF getIconsOffset() {
return mIconsOffset;
}
@Override
public void setVisible(boolean visible) {
mVisible = visible;
}
@Override
public boolean isVisible() {
return mVisible;
}
@Override
public YAxis.AxisDependency getAxisDependency() {
return mAxisDependency;
}
@Override
public void setAxisDependency(YAxis.AxisDependency dependency) {
mAxisDependency = dependency;
}
/**
* ###### ###### DATA RELATED METHODS ###### ######
*/
@Override
public int getIndexInEntries(int xIndex) {
for (int i = 0; i < getEntryCount(); i++) {
if (xIndex == getEntryForIndex(i).getX())
return i;
}
return -1;
}
@Override
public boolean removeFirst() {
if (getEntryCount() > 0) {
T entry = getEntryForIndex(0);
return removeEntry(entry);
} else
return false;
}
@Override
public boolean removeLast() {
if (getEntryCount() > 0) {
T e = getEntryForIndex(getEntryCount() - 1);
return removeEntry(e);
} else
return false;
}
@Override
public boolean removeEntryByXValue(float xValue) {
T e = getEntryForXValue(xValue, Float.NaN);
return removeEntry(e);
}
@Override
public boolean removeEntry(int index) {
T e = getEntryForIndex(index);
return removeEntry(e);
}
@Override
public boolean contains(T e) {
for (int i = 0; i < getEntryCount(); i++) {
if (getEntryForIndex(i).equals(e))
return true;
}
return false;
}
protected void copy(BaseDataSet baseDataSet) {
baseDataSet.mAxisDependency = mAxisDependency;
baseDataSet.mColors = mColors;
baseDataSet.mDrawIcons = mDrawIcons;
baseDataSet.mDrawValues = mDrawValues;
baseDataSet.mForm = mForm;
baseDataSet.mFormLineDashEffect = mFormLineDashEffect;
baseDataSet.mFormLineWidth = mFormLineWidth;
baseDataSet.mFormSize = mFormSize;
baseDataSet.mHighlightEnabled = mHighlightEnabled;
baseDataSet.mIconsOffset = mIconsOffset;
baseDataSet.mValueColors = mValueColors;
baseDataSet.mValueFormatter = mValueFormatter;
baseDataSet.mValueColors = mValueColors;
baseDataSet.mValueTextSize = mValueTextSize;
baseDataSet.mVisible = mVisible;
}
}

@ -0,0 +1,97 @@
package com.github.mikephil.charting.data;
import android.graphics.drawable.Drawable;
/**
* Created by Philipp Jahoda on 02/06/16.
*/
public abstract class BaseEntry {
/** the y value */
private float y = 0f;
/** optional spot for additional data this Entry represents */
private Object mData = null;
/** optional icon image */
private Drawable mIcon = null;
public BaseEntry() {
}
public BaseEntry(float y) {
this.y = y;
}
public BaseEntry(float y, Object data) {
this(y);
this.mData = data;
}
public BaseEntry(float y, Drawable icon) {
this(y);
this.mIcon = icon;
}
public BaseEntry(float y, Drawable icon, Object data) {
this(y);
this.mIcon = icon;
this.mData = data;
}
/**
* Returns the y value of this Entry.
*
* @return
*/
public float getY() {
return y;
}
/**
* Sets the icon drawable
*
* @param icon
*/
public void setIcon(Drawable icon) {
this.mIcon = icon;
}
/**
* Returns the icon of this Entry.
*
* @return
*/
public Drawable getIcon() {
return mIcon;
}
/**
* Sets the y-value for the Entry.
*
* @param y
*/
public void setY(float y) {
this.y = y;
}
/**
* Returns the data, additional information that this Entry represents, or
* null, if no data has been specified.
*
* @return
*/
public Object getData() {
return mData;
}
/**
* Sets additional data this Entry should represent.
*
* @param data
*/
public void setData(Object data) {
this.mData = data;
}
}

@ -0,0 +1,34 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet;
import java.util.List;
public class BubbleData extends BarLineScatterCandleBubbleData<IBubbleDataSet> {
public BubbleData() {
super();
}
public BubbleData(IBubbleDataSet... dataSets) {
super(dataSets);
}
public BubbleData(List<IBubbleDataSet> dataSets) {
super(dataSets);
}
/**
* Sets the width of the circle that surrounds the bubble when highlighted
* for all DataSet objects this data object contains, in dp.
*
* @param width
*/
public void setHighlightCircleWidth(float width) {
for (IBubbleDataSet set : mDataSets) {
set.setHighlightCircleWidth(width);
}
}
}

@ -0,0 +1,71 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet;
import com.github.mikephil.charting.utils.Utils;
import java.util.ArrayList;
import java.util.List;
public class BubbleDataSet extends BarLineScatterCandleBubbleDataSet<BubbleEntry> implements IBubbleDataSet {
protected float mMaxSize;
protected boolean mNormalizeSize = true;
private float mHighlightCircleWidth = 2.5f;
public BubbleDataSet(List<BubbleEntry> yVals, String label) {
super(yVals, label);
}
@Override
public void setHighlightCircleWidth(float width) {
mHighlightCircleWidth = Utils.convertDpToPixel(width);
}
@Override
public float getHighlightCircleWidth() {
return mHighlightCircleWidth;
}
@Override
protected void calcMinMax(BubbleEntry e) {
super.calcMinMax(e);
final float size = e.getSize();
if (size > mMaxSize) {
mMaxSize = size;
}
}
@Override
public DataSet<BubbleEntry> copy() {
List<BubbleEntry> entries = new ArrayList<BubbleEntry>();
for (int i = 0; i < mEntries.size(); i++) {
entries.add(mEntries.get(i).copy());
}
BubbleDataSet copied = new BubbleDataSet(entries, getLabel());
copy(copied);
return copied;
}
protected void copy(BubbleDataSet bubbleDataSet) {
bubbleDataSet.mHighlightCircleWidth = mHighlightCircleWidth;
bubbleDataSet.mNormalizeSize = mNormalizeSize;
}
@Override
public float getMaxSize() {
return mMaxSize;
}
@Override
public boolean isNormalizeSizeEnabled() {
return mNormalizeSize;
}
public void setNormalizeSizeEnabled(boolean normalizeSize) {
mNormalizeSize = normalizeSize;
}
}

@ -0,0 +1,91 @@
package com.github.mikephil.charting.data;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
/**
* Subclass of Entry that holds a value for one entry in a BubbleChart. Bubble
* chart implementation: Copyright 2015 Pierre-Marc Airoldi Licensed under
* Apache License 2.0
*
* @author Philipp Jahoda
*/
@SuppressLint("ParcelCreator")
public class BubbleEntry extends Entry {
/** size value */
private float mSize = 0f;
/**
* Constructor.
*
* @param x The value on the x-axis.
* @param y The value on the y-axis.
* @param size The size of the bubble.
*/
public BubbleEntry(float x, float y, float size) {
super(x, y);
this.mSize = size;
}
/**
* Constructor.
*
* @param x The value on the x-axis.
* @param y The value on the y-axis.
* @param size The size of the bubble.
* @param data Spot for additional data this Entry represents.
*/
public BubbleEntry(float x, float y, float size, Object data) {
super(x, y, data);
this.mSize = size;
}
/**
* Constructor.
*
* @param x The value on the x-axis.
* @param y The value on the y-axis.
* @param size The size of the bubble.
* @param icon Icon image
*/
public BubbleEntry(float x, float y, float size, Drawable icon) {
super(x, y, icon);
this.mSize = size;
}
/**
* Constructor.
*
* @param x The value on the x-axis.
* @param y The value on the y-axis.
* @param size The size of the bubble.
* @param icon Icon image
* @param data Spot for additional data this Entry represents.
*/
public BubbleEntry(float x, float y, float size, Drawable icon, Object data) {
super(x, y, icon, data);
this.mSize = size;
}
public BubbleEntry copy() {
BubbleEntry c = new BubbleEntry(getX(), getY(), mSize, getData());
return c;
}
/**
* Returns the size of this entry (the size of the bubble).
*
* @return
*/
public float getSize() {
return mSize;
}
public void setSize(float size) {
this.mSize = size;
}
}

@ -0,0 +1,21 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet;
import java.util.ArrayList;
import java.util.List;
public class CandleData extends BarLineScatterCandleBubbleData<ICandleDataSet> {
public CandleData() {
super();
}
public CandleData(List<ICandleDataSet> dataSets) {
super(dataSets);
}
public CandleData(ICandleDataSet... dataSets) {
super(dataSets);
}
}

@ -0,0 +1,295 @@
package com.github.mikephil.charting.data;
import android.graphics.Paint;
import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet;
import com.github.mikephil.charting.utils.ColorTemplate;
import com.github.mikephil.charting.utils.Utils;
import java.util.ArrayList;
import java.util.List;
/**
* DataSet for the CandleStickChart.
*
* @author Philipp Jahoda
*/
public class CandleDataSet extends LineScatterCandleRadarDataSet<CandleEntry> implements ICandleDataSet {
/**
* the width of the shadow of the candle
*/
private float mShadowWidth = 3f;
/**
* should the candle bars show?
* when false, only "ticks" will show
* <p/>
* - default: true
*/
private boolean mShowCandleBar = true;
/**
* the space between the candle entries, default 0.1f (10%)
*/
private float mBarSpace = 0.1f;
/**
* use candle color for the shadow
*/
private boolean mShadowColorSameAsCandle = false;
/**
* paint style when open < close
* increasing candlesticks are traditionally hollow
*/
protected Paint.Style mIncreasingPaintStyle = Paint.Style.STROKE;
/**
* paint style when open > close
* descreasing candlesticks are traditionally filled
*/
protected Paint.Style mDecreasingPaintStyle = Paint.Style.FILL;
/**
* color for open == close
*/
protected int mNeutralColor = ColorTemplate.COLOR_SKIP;
/**
* color for open < close
*/
protected int mIncreasingColor = ColorTemplate.COLOR_SKIP;
/**
* color for open > close
*/
protected int mDecreasingColor = ColorTemplate.COLOR_SKIP;
/**
* shadow line color, set -1 for backward compatibility and uses default
* color
*/
protected int mShadowColor = ColorTemplate.COLOR_SKIP;
public CandleDataSet(List<CandleEntry> yVals, String label) {
super(yVals, label);
}
@Override
public DataSet<CandleEntry> copy() {
List<CandleEntry> entries = new ArrayList<CandleEntry>();
for (int i = 0; i < mEntries.size(); i++) {
entries.add(mEntries.get(i).copy());
}
CandleDataSet copied = new CandleDataSet(entries, getLabel());
copy(copied);
return copied;
}
protected void copy(CandleDataSet candleDataSet) {
super.copy(candleDataSet);
candleDataSet.mShadowWidth = mShadowWidth;
candleDataSet.mShowCandleBar = mShowCandleBar;
candleDataSet.mBarSpace = mBarSpace;
candleDataSet.mShadowColorSameAsCandle = mShadowColorSameAsCandle;
candleDataSet.mHighLightColor = mHighLightColor;
candleDataSet.mIncreasingPaintStyle = mIncreasingPaintStyle;
candleDataSet.mDecreasingPaintStyle = mDecreasingPaintStyle;
candleDataSet.mNeutralColor = mNeutralColor;
candleDataSet.mIncreasingColor = mIncreasingColor;
candleDataSet.mDecreasingColor = mDecreasingColor;
candleDataSet.mShadowColor = mShadowColor;
}
@Override
protected void calcMinMax(CandleEntry e) {
if (e.getLow() < mYMin)
mYMin = e.getLow();
if (e.getHigh() > mYMax)
mYMax = e.getHigh();
calcMinMaxX(e);
}
@Override
protected void calcMinMaxY(CandleEntry e) {
if (e.getHigh() < mYMin)
mYMin = e.getHigh();
if (e.getHigh() > mYMax)
mYMax = e.getHigh();
if (e.getLow() < mYMin)
mYMin = e.getLow();
if (e.getLow() > mYMax)
mYMax = e.getLow();
}
/**
* Sets the space that is left out on the left and right side of each
* candle, default 0.1f (10%), max 0.45f, min 0f
*
* @param space
*/
public void setBarSpace(float space) {
if (space < 0f)
space = 0f;
if (space > 0.45f)
space = 0.45f;
mBarSpace = space;
}
@Override
public float getBarSpace() {
return mBarSpace;
}
/**
* Sets the width of the candle-shadow-line in pixels. Default 3f.
*
* @param width
*/
public void setShadowWidth(float width) {
mShadowWidth = Utils.convertDpToPixel(width);
}
@Override
public float getShadowWidth() {
return mShadowWidth;
}
/**
* Sets whether the candle bars should show?
*
* @param showCandleBar
*/
public void setShowCandleBar(boolean showCandleBar) {
mShowCandleBar = showCandleBar;
}
@Override
public boolean getShowCandleBar() {
return mShowCandleBar;
}
// TODO
/**
* It is necessary to implement ColorsList class that will encapsulate
* colors list functionality, because It's wrong to copy paste setColor,
* addColor, ... resetColors for each time when we want to add a coloring
* options for one of objects
*
* @author Mesrop
*/
/** BELOW THIS COLOR HANDLING */
/**
* Sets the one and ONLY color that should be used for this DataSet when
* open == close.
*
* @param color
*/
public void setNeutralColor(int color) {
mNeutralColor = color;
}
@Override
public int getNeutralColor() {
return mNeutralColor;
}
/**
* Sets the one and ONLY color that should be used for this DataSet when
* open <= close.
*
* @param color
*/
public void setIncreasingColor(int color) {
mIncreasingColor = color;
}
@Override
public int getIncreasingColor() {
return mIncreasingColor;
}
/**
* Sets the one and ONLY color that should be used for this DataSet when
* open > close.
*
* @param color
*/
public void setDecreasingColor(int color) {
mDecreasingColor = color;
}
@Override
public int getDecreasingColor() {
return mDecreasingColor;
}
@Override
public Paint.Style getIncreasingPaintStyle() {
return mIncreasingPaintStyle;
}
/**
* Sets paint style when open < close
*
* @param paintStyle
*/
public void setIncreasingPaintStyle(Paint.Style paintStyle) {
this.mIncreasingPaintStyle = paintStyle;
}
@Override
public Paint.Style getDecreasingPaintStyle() {
return mDecreasingPaintStyle;
}
/**
* Sets paint style when open > close
*
* @param decreasingPaintStyle
*/
public void setDecreasingPaintStyle(Paint.Style decreasingPaintStyle) {
this.mDecreasingPaintStyle = decreasingPaintStyle;
}
@Override
public int getShadowColor() {
return mShadowColor;
}
/**
* Sets shadow color for all entries
*
* @param shadowColor
*/
public void setShadowColor(int shadowColor) {
this.mShadowColor = shadowColor;
}
@Override
public boolean getShadowColorSameAsCandle() {
return mShadowColorSameAsCandle;
}
/**
* Sets shadow color to be the same color as the candle color
*
* @param shadowColorSameAsCandle
*/
public void setShadowColorSameAsCandle(boolean shadowColorSameAsCandle) {
this.mShadowColorSameAsCandle = shadowColorSameAsCandle;
}
}

@ -0,0 +1,193 @@
package com.github.mikephil.charting.data;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
/**
* Subclass of Entry that holds all values for one entry in a CandleStickChart.
*
* @author Philipp Jahoda
*/
@SuppressLint("ParcelCreator")
public class CandleEntry extends Entry {
/** shadow-high value */
private float mShadowHigh = 0f;
/** shadow-low value */
private float mShadowLow = 0f;
/** close value */
private float mClose = 0f;
/** open value */
private float mOpen = 0f;
/**
* Constructor.
*
* @param x The value on the x-axis
* @param shadowH The (shadow) high value
* @param shadowL The (shadow) low value
* @param open The open value
* @param close The close value
*/
public CandleEntry(float x, float shadowH, float shadowL, float open, float close) {
super(x, (shadowH + shadowL) / 2f);
this.mShadowHigh = shadowH;
this.mShadowLow = shadowL;
this.mOpen = open;
this.mClose = close;
}
/**
* Constructor.
*
* @param x The value on the x-axis
* @param shadowH The (shadow) high value
* @param shadowL The (shadow) low value
* @param open
* @param close
* @param data Spot for additional data this Entry represents
*/
public CandleEntry(float x, float shadowH, float shadowL, float open, float close,
Object data) {
super(x, (shadowH + shadowL) / 2f, data);
this.mShadowHigh = shadowH;
this.mShadowLow = shadowL;
this.mOpen = open;
this.mClose = close;
}
/**
* Constructor.
*
* @param x The value on the x-axis
* @param shadowH The (shadow) high value
* @param shadowL The (shadow) low value
* @param open
* @param close
* @param icon Icon image
*/
public CandleEntry(float x, float shadowH, float shadowL, float open, float close,
Drawable icon) {
super(x, (shadowH + shadowL) / 2f, icon);
this.mShadowHigh = shadowH;
this.mShadowLow = shadowL;
this.mOpen = open;
this.mClose = close;
}
/**
* Constructor.
*
* @param x The value on the x-axis
* @param shadowH The (shadow) high value
* @param shadowL The (shadow) low value
* @param open
* @param close
* @param icon Icon image
* @param data Spot for additional data this Entry represents
*/
public CandleEntry(float x, float shadowH, float shadowL, float open, float close,
Drawable icon, Object data) {
super(x, (shadowH + shadowL) / 2f, icon, data);
this.mShadowHigh = shadowH;
this.mShadowLow = shadowL;
this.mOpen = open;
this.mClose = close;
}
/**
* Returns the overall range (difference) between shadow-high and
* shadow-low.
*
* @return
*/
public float getShadowRange() {
return Math.abs(mShadowHigh - mShadowLow);
}
/**
* Returns the body size (difference between open and close).
*
* @return
*/
public float getBodyRange() {
return Math.abs(mOpen - mClose);
}
/**
* Returns the center value of the candle. (Middle value between high and
* low)
*/
@Override
public float getY() {
return super.getY();
}
public CandleEntry copy() {
CandleEntry c = new CandleEntry(getX(), mShadowHigh, mShadowLow, mOpen,
mClose, getData());
return c;
}
/**
* Returns the upper shadows highest value.
*
* @return
*/
public float getHigh() {
return mShadowHigh;
}
public void setHigh(float mShadowHigh) {
this.mShadowHigh = mShadowHigh;
}
/**
* Returns the lower shadows lowest value.
*
* @return
*/
public float getLow() {
return mShadowLow;
}
public void setLow(float mShadowLow) {
this.mShadowLow = mShadowLow;
}
/**
* Returns the bodys close value.
*
* @return
*/
public float getClose() {
return mClose;
}
public void setClose(float mClose) {
this.mClose = mClose;
}
/**
* Returns the bodys open value.
*
* @return
*/
public float getOpen() {
return mOpen;
}
public void setOpen(float mOpen) {
this.mOpen = mOpen;
}
}

@ -0,0 +1,821 @@
package com.github.mikephil.charting.data;
import android.graphics.Typeface;
import android.util.Log;
import com.github.mikephil.charting.components.YAxis.AxisDependency;
import com.github.mikephil.charting.formatter.IValueFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.IDataSet;
import java.util.ArrayList;
import java.util.List;
/**
* Class that holds all relevant data that represents the chart. That involves
* at least one (or more) DataSets, and an array of x-values.
*
* @author Philipp Jahoda
*/
public abstract class ChartData<T extends IDataSet<? extends Entry>> {
/**
* maximum y-value in the value array across all axes
*/
protected float mYMax = -Float.MAX_VALUE;
/**
* the minimum y-value in the value array across all axes
*/
protected float mYMin = Float.MAX_VALUE;
/**
* maximum x-value in the value array
*/
protected float mXMax = -Float.MAX_VALUE;
/**
* minimum x-value in the value array
*/
protected float mXMin = Float.MAX_VALUE;
protected float mLeftAxisMax = -Float.MAX_VALUE;
protected float mLeftAxisMin = Float.MAX_VALUE;
protected float mRightAxisMax = -Float.MAX_VALUE;
protected float mRightAxisMin = Float.MAX_VALUE;
/**
* array that holds all DataSets the ChartData object represents
*/
protected List<T> mDataSets;
/**
* Default constructor.
*/
public ChartData() {
mDataSets = new ArrayList<T>();
}
/**
* Constructor taking single or multiple DataSet objects.
*
* @param dataSets
*/
public ChartData(T... dataSets) {
mDataSets = arrayToList(dataSets);
notifyDataChanged();
}
/**
* Created because Arrays.asList(...) does not support modification.
*
* @param array
* @return
*/
private List<T> arrayToList(T[] array) {
List<T> list = new ArrayList<>();
for (T set : array) {
list.add(set);
}
return list;
}
/**
* constructor for chart data
*
* @param sets the dataset array
*/
public ChartData(List<T> sets) {
this.mDataSets = sets;
notifyDataChanged();
}
/**
* Call this method to let the ChartData know that the underlying data has
* changed. Calling this performs all necessary recalculations needed when
* the contained data has changed.
*/
public void notifyDataChanged() {
calcMinMax();
}
/**
* Calc minimum and maximum y-values over all DataSets.
* Tell DataSets to recalculate their min and max y-values, this is only needed for autoScaleMinMax.
*
* @param fromX the x-value to start the calculation from
* @param toX the x-value to which the calculation should be performed
*/
public void calcMinMaxY(float fromX, float toX) {
for (T set : mDataSets) {
set.calcMinMaxY(fromX, toX);
}
// apply the new data
calcMinMax();
}
/**
* Calc minimum and maximum values (both x and y) over all DataSets.
*/
protected void calcMinMax() {
if (mDataSets == null)
return;
mYMax = -Float.MAX_VALUE;
mYMin = Float.MAX_VALUE;
mXMax = -Float.MAX_VALUE;
mXMin = Float.MAX_VALUE;
for (T set : mDataSets) {
calcMinMax(set);
}
mLeftAxisMax = -Float.MAX_VALUE;
mLeftAxisMin = Float.MAX_VALUE;
mRightAxisMax = -Float.MAX_VALUE;
mRightAxisMin = Float.MAX_VALUE;
// left axis
T firstLeft = getFirstLeft(mDataSets);
if (firstLeft != null) {
mLeftAxisMax = firstLeft.getYMax();
mLeftAxisMin = firstLeft.getYMin();
for (T dataSet : mDataSets) {
if (dataSet.getAxisDependency() == AxisDependency.LEFT) {
if (dataSet.getYMin() < mLeftAxisMin)
mLeftAxisMin = dataSet.getYMin();
if (dataSet.getYMax() > mLeftAxisMax)
mLeftAxisMax = dataSet.getYMax();
}
}
}
// right axis
T firstRight = getFirstRight(mDataSets);
if (firstRight != null) {
mRightAxisMax = firstRight.getYMax();
mRightAxisMin = firstRight.getYMin();
for (T dataSet : mDataSets) {
if (dataSet.getAxisDependency() == AxisDependency.RIGHT) {
if (dataSet.getYMin() < mRightAxisMin)
mRightAxisMin = dataSet.getYMin();
if (dataSet.getYMax() > mRightAxisMax)
mRightAxisMax = dataSet.getYMax();
}
}
}
}
/** ONLY GETTERS AND SETTERS BELOW THIS */
/**
* returns the number of LineDataSets this object contains
*
* @return
*/
public int getDataSetCount() {
if (mDataSets == null)
return 0;
return mDataSets.size();
}
/**
* Returns the smallest y-value the data object contains.
*
* @return
*/
public float getYMin() {
return mYMin;
}
/**
* Returns the minimum y-value for the specified axis.
*
* @param axis
* @return
*/
public float getYMin(AxisDependency axis) {
if (axis == AxisDependency.LEFT) {
if (mLeftAxisMin == Float.MAX_VALUE) {
return mRightAxisMin;
} else
return mLeftAxisMin;
} else {
if (mRightAxisMin == Float.MAX_VALUE) {
return mLeftAxisMin;
} else
return mRightAxisMin;
}
}
/**
* Returns the greatest y-value the data object contains.
*
* @return
*/
public float getYMax() {
return mYMax;
}
/**
* Returns the maximum y-value for the specified axis.
*
* @param axis
* @return
*/
public float getYMax(AxisDependency axis) {
if (axis == AxisDependency.LEFT) {
if (mLeftAxisMax == -Float.MAX_VALUE) {
return mRightAxisMax;
} else
return mLeftAxisMax;
} else {
if (mRightAxisMax == -Float.MAX_VALUE) {
return mLeftAxisMax;
} else
return mRightAxisMax;
}
}
/**
* Returns the minimum x-value this data object contains.
*
* @return
*/
public float getXMin() {
return mXMin;
}
/**
* Returns the maximum x-value this data object contains.
*
* @return
*/
public float getXMax() {
return mXMax;
}
/**
* Returns all DataSet objects this ChartData object holds.
*
* @return
*/
public List<T> getDataSets() {
return mDataSets;
}
/**
* Retrieve the index of a DataSet with a specific label from the ChartData.
* Search can be case sensitive or not. IMPORTANT: This method does
* calculations at runtime, do not over-use in performance critical
* situations.
*
* @param dataSets the DataSet array to search
* @param label
* @param ignorecase if true, the search is not case-sensitive
* @return
*/
protected int getDataSetIndexByLabel(List<T> dataSets, String label,
boolean ignorecase) {
if (ignorecase) {
for (int i = 0; i < dataSets.size(); i++)
if (label.equalsIgnoreCase(dataSets.get(i).getLabel()))
return i;
} else {
for (int i = 0; i < dataSets.size(); i++)
if (label.equals(dataSets.get(i).getLabel()))
return i;
}
return -1;
}
/**
* Returns the labels of all DataSets as a string array.
*
* @return
*/
public String[] getDataSetLabels() {
String[] types = new String[mDataSets.size()];
for (int i = 0; i < mDataSets.size(); i++) {
types[i] = mDataSets.get(i).getLabel();
}
return types;
}
/**
* Get the Entry for a corresponding highlight object
*
* @param highlight
* @return the entry that is highlighted
*/
public Entry getEntryForHighlight(Highlight highlight) {
if (highlight.getDataSetIndex() >= mDataSets.size())
return null;
else {
return mDataSets.get(highlight.getDataSetIndex()).getEntryForXValue(highlight.getX(), highlight.getY());
}
}
/**
* Returns the DataSet object with the given label. Search can be case
* sensitive or not. IMPORTANT: This method does calculations at runtime.
* Use with care in performance critical situations.
*
* @param label
* @param ignorecase
* @return
*/
public T getDataSetByLabel(String label, boolean ignorecase) {
int index = getDataSetIndexByLabel(mDataSets, label, ignorecase);
if (index < 0 || index >= mDataSets.size())
return null;
else
return mDataSets.get(index);
}
public T getDataSetByIndex(int index) {
if (mDataSets == null || index < 0 || index >= mDataSets.size())
return null;
return mDataSets.get(index);
}
/**
* Adds a DataSet dynamically.
*
* @param d
*/
public void addDataSet(T d) {
if (d == null)
return;
calcMinMax(d);
mDataSets.add(d);
}
/**
* Removes the given DataSet from this data object. Also recalculates all
* minimum and maximum values. Returns true if a DataSet was removed, false
* if no DataSet could be removed.
*
* @param d
*/
public boolean removeDataSet(T d) {
if (d == null)
return false;
boolean removed = mDataSets.remove(d);
// if a DataSet was removed
if (removed) {
notifyDataChanged();
}
return removed;
}
/**
* Removes the DataSet at the given index in the DataSet array from the data
* object. Also recalculates all minimum and maximum values. Returns true if
* a DataSet was removed, false if no DataSet could be removed.
*
* @param index
*/
public boolean removeDataSet(int index) {
if (index >= mDataSets.size() || index < 0)
return false;
T set = mDataSets.get(index);
return removeDataSet(set);
}
/**
* Adds an Entry to the DataSet at the specified index.
* Entries are added to the end of the list.
*
* @param e
* @param dataSetIndex
*/
public void addEntry(Entry e, int dataSetIndex) {
if (mDataSets.size() > dataSetIndex && dataSetIndex >= 0) {
IDataSet set = mDataSets.get(dataSetIndex);
// add the entry to the dataset
if (!set.addEntry(e))
return;
calcMinMax(e, set.getAxisDependency());
} else {
Log.e("addEntry", "Cannot add Entry because dataSetIndex too high or too low.");
}
}
/**
* Adjusts the current minimum and maximum values based on the provided Entry object.
*
* @param e
* @param axis
*/
protected void calcMinMax(Entry e, AxisDependency axis) {
if (mYMax < e.getY())
mYMax = e.getY();
if (mYMin > e.getY())
mYMin = e.getY();
if (mXMax < e.getX())
mXMax = e.getX();
if (mXMin > e.getX())
mXMin = e.getX();
if (axis == AxisDependency.LEFT) {
if (mLeftAxisMax < e.getY())
mLeftAxisMax = e.getY();
if (mLeftAxisMin > e.getY())
mLeftAxisMin = e.getY();
} else {
if (mRightAxisMax < e.getY())
mRightAxisMax = e.getY();
if (mRightAxisMin > e.getY())
mRightAxisMin = e.getY();
}
}
/**
* Adjusts the minimum and maximum values based on the given DataSet.
*
* @param d
*/
protected void calcMinMax(T d) {
if (mYMax < d.getYMax())
mYMax = d.getYMax();
if (mYMin > d.getYMin())
mYMin = d.getYMin();
if (mXMax < d.getXMax())
mXMax = d.getXMax();
if (mXMin > d.getXMin())
mXMin = d.getXMin();
if (d.getAxisDependency() == AxisDependency.LEFT) {
if (mLeftAxisMax < d.getYMax())
mLeftAxisMax = d.getYMax();
if (mLeftAxisMin > d.getYMin())
mLeftAxisMin = d.getYMin();
} else {
if (mRightAxisMax < d.getYMax())
mRightAxisMax = d.getYMax();
if (mRightAxisMin > d.getYMin())
mRightAxisMin = d.getYMin();
}
}
/**
* Removes the given Entry object from the DataSet at the specified index.
*
* @param e
* @param dataSetIndex
*/
public boolean removeEntry(Entry e, int dataSetIndex) {
// entry null, outofbounds
if (e == null || dataSetIndex >= mDataSets.size())
return false;
IDataSet set = mDataSets.get(dataSetIndex);
if (set != null) {
// remove the entry from the dataset
boolean removed = set.removeEntry(e);
if (removed) {
notifyDataChanged();
}
return removed;
} else
return false;
}
/**
* Removes the Entry object closest to the given DataSet at the
* specified index. Returns true if an Entry was removed, false if no Entry
* was found that meets the specified requirements.
*
* @param xValue
* @param dataSetIndex
* @return
*/
public boolean removeEntry(float xValue, int dataSetIndex) {
if (dataSetIndex >= mDataSets.size())
return false;
IDataSet dataSet = mDataSets.get(dataSetIndex);
Entry e = dataSet.getEntryForXValue(xValue, Float.NaN);
if (e == null)
return false;
return removeEntry(e, dataSetIndex);
}
/**
* Returns the DataSet that contains the provided Entry, or null, if no
* DataSet contains this Entry.
*
* @param e
* @return
*/
public T getDataSetForEntry(Entry e) {
if (e == null)
return null;
for (int i = 0; i < mDataSets.size(); i++) {
T set = mDataSets.get(i);
for (int j = 0; j < set.getEntryCount(); j++) {
if (e.equalTo(set.getEntryForXValue(e.getX(), e.getY())))
return set;
}
}
return null;
}
/**
* Returns all colors used across all DataSet objects this object
* represents.
*
* @return
*/
public int[] getColors() {
if (mDataSets == null)
return null;
int clrcnt = 0;
for (int i = 0; i < mDataSets.size(); i++) {
clrcnt += mDataSets.get(i).getColors().size();
}
int[] colors = new int[clrcnt];
int cnt = 0;
for (int i = 0; i < mDataSets.size(); i++) {
List<Integer> clrs = mDataSets.get(i).getColors();
for (Integer clr : clrs) {
colors[cnt] = clr;
cnt++;
}
}
return colors;
}
/**
* Returns the index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist.
*
* @param dataSet
* @return
*/
public int getIndexOfDataSet(T dataSet) {
return mDataSets.indexOf(dataSet);
}
/**
* Returns the first DataSet from the datasets-array that has it's dependency on the left axis.
* Returns null if no DataSet with left dependency could be found.
*
* @return
*/
protected T getFirstLeft(List<T> sets) {
for (T dataSet : sets) {
if (dataSet.getAxisDependency() == AxisDependency.LEFT)
return dataSet;
}
return null;
}
/**
* Returns the first DataSet from the datasets-array that has it's dependency on the right axis.
* Returns null if no DataSet with right dependency could be found.
*
* @return
*/
public T getFirstRight(List<T> sets) {
for (T dataSet : sets) {
if (dataSet.getAxisDependency() == AxisDependency.RIGHT)
return dataSet;
}
return null;
}
/**
* Sets a custom IValueFormatter for all DataSets this data object contains.
*
* @param f
*/
public void setValueFormatter(IValueFormatter f) {
if (f == null)
return;
else {
for (IDataSet set : mDataSets) {
set.setValueFormatter(f);
}
}
}
/**
* Sets the color of the value-text (color in which the value-labels are
* drawn) for all DataSets this data object contains.
*
* @param color
*/
public void setValueTextColor(int color) {
for (IDataSet set : mDataSets) {
set.setValueTextColor(color);
}
}
/**
* Sets the same list of value-colors for all DataSets this
* data object contains.
*
* @param colors
*/
public void setValueTextColors(List<Integer> colors) {
for (IDataSet set : mDataSets) {
set.setValueTextColors(colors);
}
}
/**
* Sets the Typeface for all value-labels for all DataSets this data object
* contains.
*
* @param tf
*/
public void setValueTypeface(Typeface tf) {
for (IDataSet set : mDataSets) {
set.setValueTypeface(tf);
}
}
/**
* Sets the size (in dp) of the value-text for all DataSets this data object
* contains.
*
* @param size
*/
public void setValueTextSize(float size) {
for (IDataSet set : mDataSets) {
set.setValueTextSize(size);
}
}
/**
* Enables / disables drawing values (value-text) for all DataSets this data
* object contains.
*
* @param enabled
*/
public void setDrawValues(boolean enabled) {
for (IDataSet set : mDataSets) {
set.setDrawValues(enabled);
}
}
/**
* Enables / disables highlighting values for all DataSets this data object
* contains. If set to true, this means that values can
* be highlighted programmatically or by touch gesture.
*/
public void setHighlightEnabled(boolean enabled) {
for (IDataSet set : mDataSets) {
set.setHighlightEnabled(enabled);
}
}
/**
* Returns true if highlighting of all underlying values is enabled, false
* if not.
*
* @return
*/
public boolean isHighlightEnabled() {
for (IDataSet set : mDataSets) {
if (!set.isHighlightEnabled())
return false;
}
return true;
}
/**
* Clears this data object from all DataSets and removes all Entries. Don't
* forget to invalidate the chart after this.
*/
public void clearValues() {
if (mDataSets != null) {
mDataSets.clear();
}
notifyDataChanged();
}
/**
* Checks if this data object contains the specified DataSet. Returns true
* if so, false if not.
*
* @param dataSet
* @return
*/
public boolean contains(T dataSet) {
for (T set : mDataSets) {
if (set.equals(dataSet))
return true;
}
return false;
}
/**
* Returns the total entry count across all DataSet objects this data object contains.
*
* @return
*/
public int getEntryCount() {
int count = 0;
for (T set : mDataSets) {
count += set.getEntryCount();
}
return count;
}
/**
* Returns the DataSet object with the maximum number of entries or null if there are no DataSets.
*
* @return
*/
public T getMaxEntryCountSet() {
if (mDataSets == null || mDataSets.isEmpty())
return null;
T max = mDataSets.get(0);
for (T set : mDataSets) {
if (set.getEntryCount() > max.getEntryCount())
max = set;
}
return max;
}
}

@ -0,0 +1,272 @@
package com.github.mikephil.charting.data;
import android.util.Log;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet;
import java.util.ArrayList;
import java.util.List;
/**
* Data object that allows the combination of Line-, Bar-, Scatter-, Bubble- and
* CandleData. Used in the CombinedChart class.
*
* @author Philipp Jahoda
*/
public class CombinedData extends BarLineScatterCandleBubbleData<IBarLineScatterCandleBubbleDataSet<? extends Entry>> {
private LineData mLineData;
private BarData mBarData;
private ScatterData mScatterData;
private CandleData mCandleData;
private BubbleData mBubbleData;
public CombinedData() {
super();
}
public void setData(LineData data) {
mLineData = data;
notifyDataChanged();
}
public void setData(BarData data) {
mBarData = data;
notifyDataChanged();
}
public void setData(ScatterData data) {
mScatterData = data;
notifyDataChanged();
}
public void setData(CandleData data) {
mCandleData = data;
notifyDataChanged();
}
public void setData(BubbleData data) {
mBubbleData = data;
notifyDataChanged();
}
@Override
public void calcMinMax() {
if(mDataSets == null){
mDataSets = new ArrayList<>();
}
mDataSets.clear();
mYMax = -Float.MAX_VALUE;
mYMin = Float.MAX_VALUE;
mXMax = -Float.MAX_VALUE;
mXMin = Float.MAX_VALUE;
mLeftAxisMax = -Float.MAX_VALUE;
mLeftAxisMin = Float.MAX_VALUE;
mRightAxisMax = -Float.MAX_VALUE;
mRightAxisMin = Float.MAX_VALUE;
List<BarLineScatterCandleBubbleData> allData = getAllData();
for (ChartData data : allData) {
data.calcMinMax();
List<IBarLineScatterCandleBubbleDataSet<? extends Entry>> sets = data.getDataSets();
mDataSets.addAll(sets);
if (data.getYMax() > mYMax)
mYMax = data.getYMax();
if (data.getYMin() < mYMin)
mYMin = data.getYMin();
if (data.getXMax() > mXMax)
mXMax = data.getXMax();
if (data.getXMin() < mXMin)
mXMin = data.getXMin();
for (IBarLineScatterCandleBubbleDataSet<? extends Entry> dataset : sets) {
if (dataset.getAxisDependency() == YAxis.AxisDependency.LEFT) {
if (dataset.getYMax() > mLeftAxisMax) {
mLeftAxisMax = dataset.getYMax();
}
if (dataset.getYMin() < mLeftAxisMin) {
mLeftAxisMin = dataset.getYMin();
}
}
else {
if (dataset.getYMax() > mRightAxisMax) {
mRightAxisMax = dataset.getYMax();
}
if (dataset.getYMin() < mRightAxisMin) {
mRightAxisMin = dataset.getYMin();
}
}
}
}
}
public BubbleData getBubbleData() {
return mBubbleData;
}
public LineData getLineData() {
return mLineData;
}
public BarData getBarData() {
return mBarData;
}
public ScatterData getScatterData() {
return mScatterData;
}
public CandleData getCandleData() {
return mCandleData;
}
/**
* Returns all data objects in row: line-bar-scatter-candle-bubble if not null.
*
* @return
*/
public List<BarLineScatterCandleBubbleData> getAllData() {
List<BarLineScatterCandleBubbleData> data = new ArrayList<BarLineScatterCandleBubbleData>();
if (mLineData != null)
data.add(mLineData);
if (mBarData != null)
data.add(mBarData);
if (mScatterData != null)
data.add(mScatterData);
if (mCandleData != null)
data.add(mCandleData);
if (mBubbleData != null)
data.add(mBubbleData);
return data;
}
public BarLineScatterCandleBubbleData getDataByIndex(int index) {
return getAllData().get(index);
}
@Override
public void notifyDataChanged() {
if (mLineData != null)
mLineData.notifyDataChanged();
if (mBarData != null)
mBarData.notifyDataChanged();
if (mCandleData != null)
mCandleData.notifyDataChanged();
if (mScatterData != null)
mScatterData.notifyDataChanged();
if (mBubbleData != null)
mBubbleData.notifyDataChanged();
calcMinMax(); // recalculate everything
}
/**
* Get the Entry for a corresponding highlight object
*
* @param highlight
* @return the entry that is highlighted
*/
@Override
public Entry getEntryForHighlight(Highlight highlight) {
if (highlight.getDataIndex() >= getAllData().size())
return null;
ChartData data = getDataByIndex(highlight.getDataIndex());
if (highlight.getDataSetIndex() >= data.getDataSetCount())
return null;
// The value of the highlighted entry could be NaN -
// if we are not interested in highlighting a specific value.
List<Entry> entries = data.getDataSetByIndex(highlight.getDataSetIndex())
.getEntriesForXValue(highlight.getX());
for (Entry entry : entries)
if (entry.getY() == highlight.getY() ||
Float.isNaN(highlight.getY()))
return entry;
return null;
}
/**
* Get dataset for highlight
*
* @param highlight current highlight
* @return dataset related to highlight
*/
public IBarLineScatterCandleBubbleDataSet<? extends Entry> getDataSetByHighlight(Highlight highlight) {
if (highlight.getDataIndex() >= getAllData().size())
return null;
BarLineScatterCandleBubbleData data = getDataByIndex(highlight.getDataIndex());
if (highlight.getDataSetIndex() >= data.getDataSetCount())
return null;
return (IBarLineScatterCandleBubbleDataSet<? extends Entry>)
data.getDataSets().get(highlight.getDataSetIndex());
}
public int getDataIndex(ChartData data) {
return getAllData().indexOf(data);
}
@Override
public boolean removeDataSet(IBarLineScatterCandleBubbleDataSet<? extends Entry> d) {
List<BarLineScatterCandleBubbleData> datas = getAllData();
boolean success = false;
for (ChartData data : datas) {
success = data.removeDataSet(d);
if (success) {
break;
}
}
return success;
}
@Deprecated
@Override
public boolean removeDataSet(int index) {
Log.e("MPAndroidChart", "removeDataSet(int index) not supported for CombinedData");
return false;
}
@Deprecated
@Override
public boolean removeEntry(Entry e, int dataSetIndex) {
Log.e("MPAndroidChart", "removeEntry(...) not supported for CombinedData");
return false;
}
@Deprecated
@Override
public boolean removeEntry(float xValue, int dataSetIndex) {
Log.e("MPAndroidChart", "removeEntry(...) not supported for CombinedData");
return false;
}
}

@ -0,0 +1,456 @@
package com.github.mikephil.charting.data;
import java.util.ArrayList;
import java.util.List;
/**
* The DataSet class represents one group or type of entries (Entry) in the
* Chart that belong together. It is designed to logically separate different
* groups of values inside the Chart (e.g. the values for a specific line in the
* LineChart, or the values of a specific group of bars in the BarChart).
*
* @author Philipp Jahoda
*/
public abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
/**
* the entries that this DataSet represents / holds together
*/
protected List<T> mEntries;
/**
* maximum y-value in the value array
*/
protected float mYMax = -Float.MAX_VALUE;
/**
* minimum y-value in the value array
*/
protected float mYMin = Float.MAX_VALUE;
/**
* maximum x-value in the value array
*/
protected float mXMax = -Float.MAX_VALUE;
/**
* minimum x-value in the value array
*/
protected float mXMin = Float.MAX_VALUE;
/**
* Creates a new DataSet object with the given values (entries) it represents. Also, a
* label that describes the DataSet can be specified. The label can also be
* used to retrieve the DataSet from a ChartData object.
*
* @param entries
* @param label
*/
public DataSet(List<T> entries, String label) {
super(label);
this.mEntries = entries;
if (mEntries == null)
mEntries = new ArrayList<T>();
calcMinMax();
}
@Override
public void calcMinMax() {
mYMax = -Float.MAX_VALUE;
mYMin = Float.MAX_VALUE;
mXMax = -Float.MAX_VALUE;
mXMin = Float.MAX_VALUE;
if (mEntries == null || mEntries.isEmpty())
return;
for (T e : mEntries) {
calcMinMax(e);
}
}
@Override
public void calcMinMaxY(float fromX, float toX) {
mYMax = -Float.MAX_VALUE;
mYMin = Float.MAX_VALUE;
if (mEntries == null || mEntries.isEmpty())
return;
int indexFrom = getEntryIndex(fromX, Float.NaN, Rounding.DOWN);
int indexTo = getEntryIndex(toX, Float.NaN, Rounding.UP);
if (indexTo < indexFrom) return;
for (int i = indexFrom; i <= indexTo; i++) {
// only recalculate y
calcMinMaxY(mEntries.get(i));
}
}
/**
* Updates the min and max x and y value of this DataSet based on the given Entry.
*
* @param e
*/
protected void calcMinMax(T e) {
if (e == null)
return;
calcMinMaxX(e);
calcMinMaxY(e);
}
protected void calcMinMaxX(T e) {
if (e.getX() < mXMin)
mXMin = e.getX();
if (e.getX() > mXMax)
mXMax = e.getX();
}
protected void calcMinMaxY(T e) {
if (e.getY() < mYMin)
mYMin = e.getY();
if (e.getY() > mYMax)
mYMax = e.getY();
}
@Override
public int getEntryCount() {
return mEntries.size();
}
/**
* This method is deprecated.
* Use getEntries() instead.
*
* @return
*/
@Deprecated
public List<T> getValues() {
return mEntries;
}
/**
* Returns the array of entries that this DataSet represents.
*
* @return
*/
public List<T> getEntries() {
return mEntries;
}
/**
* This method is deprecated.
* Use setEntries(...) instead.
*
* @param values
*/
@Deprecated
public void setValues(List<T> values) {
setEntries(values);
}
/**
* Sets the array of entries that this DataSet represents, and calls notifyDataSetChanged()
*
* @return
*/
public void setEntries(List<T> entries) {
mEntries = entries;
notifyDataSetChanged();
}
/**
* Provides an exact copy of the DataSet this method is used on.
*
* @return
*/
public abstract DataSet<T> copy();
/**
*
* @param dataSet
*/
protected void copy(DataSet dataSet) {
super.copy(dataSet);
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(toSimpleString());
for (int i = 0; i < mEntries.size(); i++) {
buffer.append(mEntries.get(i).toString() + " ");
}
return buffer.toString();
}
/**
* Returns a simple string representation of the DataSet with the type and
* the number of Entries.
*
* @return
*/
public String toSimpleString() {
StringBuffer buffer = new StringBuffer();
buffer.append("DataSet, label: " + (getLabel() == null ? "" : getLabel()) + ", entries: " + mEntries.size() +
"\n");
return buffer.toString();
}
@Override
public float getYMin() {
return mYMin;
}
@Override
public float getYMax() {
return mYMax;
}
@Override
public float getXMin() {
return mXMin;
}
@Override
public float getXMax() {
return mXMax;
}
@Override
public void addEntryOrdered(T e) {
if (e == null)
return;
if (mEntries == null) {
mEntries = new ArrayList<T>();
}
calcMinMax(e);
if (mEntries.size() > 0 && mEntries.get(mEntries.size() - 1).getX() > e.getX()) {
int closestIndex = getEntryIndex(e.getX(), e.getY(), Rounding.UP);
mEntries.add(closestIndex, e);
} else {
mEntries.add(e);
}
}
@Override
public void clear() {
mEntries.clear();
notifyDataSetChanged();
}
@Override
public boolean addEntry(T e) {
if (e == null)
return false;
List<T> values = getEntries();
if (values == null) {
values = new ArrayList<>();
}
calcMinMax(e);
// add the entry
return values.add(e);
}
@Override
public boolean removeEntry(T e) {
if (e == null)
return false;
if (mEntries == null)
return false;
// remove the entry
boolean removed = mEntries.remove(e);
if (removed) {
calcMinMax();
}
return removed;
}
@Override
public int getEntryIndex(Entry e) {
return mEntries.indexOf(e);
}
@Override
public T getEntryForXValue(float xValue, float closestToY, Rounding rounding) {
int index = getEntryIndex(xValue, closestToY, rounding);
if (index > -1)
return mEntries.get(index);
return null;
}
@Override
public T getEntryForXValue(float xValue, float closestToY) {
return getEntryForXValue(xValue, closestToY, Rounding.CLOSEST);
}
@Override
public T getEntryForIndex(int index) {
return mEntries.get(index);
}
@Override
public int getEntryIndex(float xValue, float closestToY, Rounding rounding) {
if (mEntries == null || mEntries.isEmpty())
return -1;
int low = 0;
int high = mEntries.size() - 1;
int closest = high;
while (low < high) {
int m = (low + high) / 2;
final float d1 = mEntries.get(m).getX() - xValue,
d2 = mEntries.get(m + 1).getX() - xValue,
ad1 = Math.abs(d1), ad2 = Math.abs(d2);
if (ad2 < ad1) {
// [m + 1] is closer to xValue
// Search in an higher place
low = m + 1;
} else if (ad1 < ad2) {
// [m] is closer to xValue
// Search in a lower place
high = m;
} else {
// We have multiple sequential x-value with same distance
if (d1 >= 0.0) {
// Search in a lower place
high = m;
} else if (d1 < 0.0) {
// Search in an higher place
low = m + 1;
}
}
closest = high;
}
if (closest != -1) {
float closestXValue = mEntries.get(closest).getX();
if (rounding == Rounding.UP) {
// If rounding up, and found x-value is lower than specified x, and we can go upper...
if (closestXValue < xValue && closest < mEntries.size() - 1) {
++closest;
}
} else if (rounding == Rounding.DOWN) {
// If rounding down, and found x-value is upper than specified x, and we can go lower...
if (closestXValue > xValue && closest > 0) {
--closest;
}
}
// Search by closest to y-value
if (!Float.isNaN(closestToY)) {
while (closest > 0 && mEntries.get(closest - 1).getX() == closestXValue)
closest -= 1;
float closestYValue = mEntries.get(closest).getY();
int closestYIndex = closest;
while (true) {
closest += 1;
if (closest >= mEntries.size())
break;
final Entry value = mEntries.get(closest);
if (value.getX() != closestXValue)
break;
if (Math.abs(value.getY() - closestToY) <= Math.abs(closestYValue - closestToY)) {
closestYValue = closestToY;
closestYIndex = closest;
}
}
closest = closestYIndex;
}
}
return closest;
}
@Override
public List<T> getEntriesForXValue(float xValue) {
List<T> entries = new ArrayList<T>();
int low = 0;
int high = mEntries.size() - 1;
while (low <= high) {
int m = (high + low) / 2;
T entry = mEntries.get(m);
// if we have a match
if (xValue == entry.getX()) {
while (m > 0 && mEntries.get(m - 1).getX() == xValue)
m--;
high = mEntries.size();
// loop over all "equal" entries
for (; m < high; m++) {
entry = mEntries.get(m);
if (entry.getX() == xValue) {
entries.add(entry);
} else {
break;
}
}
break;
} else {
if (xValue > entry.getX())
low = m + 1;
else
high = m - 1;
}
}
return entries;
}
/**
* Determines how to round DataSet index values for
* {@link DataSet#getEntryIndex(float, float, Rounding)} DataSet.getEntryIndex()}
* when an exact x-index is not found.
*/
public enum Rounding {
UP,
DOWN,
CLOSEST,
}
}

@ -0,0 +1,173 @@
package com.github.mikephil.charting.data;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
import com.github.mikephil.charting.utils.Utils;
/**
* Class representing one entry in the chart. Might contain multiple values.
* Might only contain a single value depending on the used constructor.
*
* @author Philipp Jahoda
*/
public class Entry extends BaseEntry implements Parcelable {
/** the x value */
private float x = 0f;
public Entry() {
}
/**
* A Entry represents one single entry in the chart.
*
* @param x the x value
* @param y the y value (the actual value of the entry)
*/
public Entry(float x, float y) {
super(y);
this.x = x;
}
/**
* A Entry represents one single entry in the chart.
*
* @param x the x value
* @param y the y value (the actual value of the entry)
* @param data Spot for additional data this Entry represents.
*/
public Entry(float x, float y, Object data) {
super(y, data);
this.x = x;
}
/**
* A Entry represents one single entry in the chart.
*
* @param x the x value
* @param y the y value (the actual value of the entry)
* @param icon icon image
*/
public Entry(float x, float y, Drawable icon) {
super(y, icon);
this.x = x;
}
/**
* A Entry represents one single entry in the chart.
*
* @param x the x value
* @param y the y value (the actual value of the entry)
* @param icon icon image
* @param data Spot for additional data this Entry represents.
*/
public Entry(float x, float y, Drawable icon, Object data) {
super(y, icon, data);
this.x = x;
}
/**
* Returns the x-value of this Entry object.
*
* @return
*/
public float getX() {
return x;
}
/**
* Sets the x-value of this Entry object.
*
* @param x
*/
public void setX(float x) {
this.x = x;
}
/**
* returns an exact copy of the entry
*
* @return
*/
public Entry copy() {
Entry e = new Entry(x, getY(), getData());
return e;
}
/**
* Compares value, xIndex and data of the entries. Returns true if entries
* are equal in those points, false if not. Does not check by hash-code like
* it's done by the "equals" method.
*
* @param e
* @return
*/
public boolean equalTo(Entry e) {
if (e == null)
return false;
if (e.getData() != this.getData())
return false;
if (Math.abs(e.x - this.x) > Utils.FLOAT_EPSILON)
return false;
if (Math.abs(e.getY() - this.getY()) > Utils.FLOAT_EPSILON)
return false;
return true;
}
/**
* returns a string representation of the entry containing x-index and value
*/
@Override
public String toString() {
return "Entry, x: " + x + " y: " + getY();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(this.x);
dest.writeFloat(this.getY());
if (getData() != null) {
if (getData() instanceof Parcelable) {
dest.writeInt(1);
dest.writeParcelable((Parcelable) this.getData(), flags);
} else {
throw new ParcelFormatException("Cannot parcel an Entry with non-parcelable data");
}
} else {
dest.writeInt(0);
}
}
protected Entry(Parcel in) {
this.x = in.readFloat();
this.setY(in.readFloat());
if (in.readInt() == 1) {
this.setData(in.readParcelable(Object.class.getClassLoader()));
}
}
public static final Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator<Entry>() {
public Entry createFromParcel(Parcel source) {
return new Entry(source);
}
public Entry[] newArray(int size) {
return new Entry[size];
}
};
}

@ -0,0 +1,27 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import java.util.ArrayList;
import java.util.List;
/**
* Data object that encapsulates all data associated with a LineChart.
*
* @author Philipp Jahoda
*/
public class LineData extends BarLineScatterCandleBubbleData<ILineDataSet> {
public LineData() {
super();
}
public LineData(ILineDataSet... dataSets) {
super(dataSets);
}
public LineData(List<ILineDataSet> dataSets) {
super(dataSets);
}
}

@ -0,0 +1,417 @@
package com.github.mikephil.charting.data;
import android.content.Context;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.util.Log;
import com.github.mikephil.charting.formatter.DefaultFillFormatter;
import com.github.mikephil.charting.formatter.IFillFormatter;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.utils.ColorTemplate;
import com.github.mikephil.charting.utils.Utils;
import java.util.ArrayList;
import java.util.List;
public class LineDataSet extends LineRadarDataSet<Entry> implements ILineDataSet {
/**
* Drawing mode for this line dataset
**/
private LineDataSet.Mode mMode = Mode.LINEAR;
/**
* List representing all colors that are used for the circles
*/
private List<Integer> mCircleColors = null;
/**
* the color of the inner circles
*/
private int mCircleHoleColor = Color.WHITE;
/**
* the radius of the circle-shaped value indicators
*/
private float mCircleRadius = 8f;
/**
* the hole radius of the circle-shaped value indicators
*/
private float mCircleHoleRadius = 4f;
/**
* sets the intensity of the cubic lines
*/
private float mCubicIntensity = 0.2f;
/**
* the path effect of this DataSet that makes dashed lines possible
*/
private DashPathEffect mDashPathEffect = null;
/**
* formatter for customizing the position of the fill-line
*/
private IFillFormatter mFillFormatter = new DefaultFillFormatter();
/**
* if true, drawing circles is enabled
*/
private boolean mDrawCircles = true;
private boolean mDrawCircleHole = true;
public LineDataSet(List<Entry> yVals, String label) {
super(yVals, label);
// mCircleRadius = Utils.convertDpToPixel(4f);
// mLineWidth = Utils.convertDpToPixel(1f);
if (mCircleColors == null) {
mCircleColors = new ArrayList<Integer>();
}
mCircleColors.clear();
// default colors
// mColors.add(Color.rgb(192, 255, 140));
// mColors.add(Color.rgb(255, 247, 140));
mCircleColors.add(Color.rgb(140, 234, 255));
}
@Override
public DataSet<Entry> copy() {
List<Entry> entries = new ArrayList<Entry>();
for (int i = 0; i < mEntries.size(); i++) {
entries.add(mEntries.get(i).copy());
}
LineDataSet copied = new LineDataSet(entries, getLabel());
copy(copied);
return copied;
}
protected void copy(LineDataSet lineDataSet) {
super.copy(lineDataSet);
lineDataSet.mCircleColors = mCircleColors;
lineDataSet.mCircleHoleColor = mCircleHoleColor;
lineDataSet.mCircleHoleRadius = mCircleHoleRadius;
lineDataSet.mCircleRadius = mCircleRadius;
lineDataSet.mCubicIntensity = mCubicIntensity;
lineDataSet.mDashPathEffect = mDashPathEffect;
lineDataSet.mDrawCircleHole = mDrawCircleHole;
lineDataSet.mDrawCircles = mDrawCircleHole;
lineDataSet.mFillFormatter = mFillFormatter;
lineDataSet.mMode = mMode;
}
/**
* Returns the drawing mode for this line dataset
*
* @return
*/
@Override
public LineDataSet.Mode getMode() {
return mMode;
}
/**
* Returns the drawing mode for this LineDataSet
*
* @return
*/
public void setMode(LineDataSet.Mode mode) {
mMode = mode;
}
/**
* Sets the intensity for cubic lines (if enabled). Max = 1f = very cubic,
* Min = 0.05f = low cubic effect, Default: 0.2f
*
* @param intensity
*/
public void setCubicIntensity(float intensity) {
if (intensity > 1f)
intensity = 1f;
if (intensity < 0.05f)
intensity = 0.05f;
mCubicIntensity = intensity;
}
@Override
public float getCubicIntensity() {
return mCubicIntensity;
}
/**
* Sets the radius of the drawn circles.
* Default radius = 4f, Min = 1f
*
* @param radius
*/
public void setCircleRadius(float radius) {
if (radius >= 1f) {
mCircleRadius = Utils.convertDpToPixel(radius);
} else {
Log.e("LineDataSet", "Circle radius cannot be < 1");
}
}
@Override
public float getCircleRadius() {
return mCircleRadius;
}
/**
* Sets the hole radius of the drawn circles.
* Default radius = 2f, Min = 0.5f
*
* @param holeRadius
*/
public void setCircleHoleRadius(float holeRadius) {
if (holeRadius >= 0.5f) {
mCircleHoleRadius = Utils.convertDpToPixel(holeRadius);
} else {
Log.e("LineDataSet", "Circle radius cannot be < 0.5");
}
}
@Override
public float getCircleHoleRadius() {
return mCircleHoleRadius;
}
/**
* sets the size (radius) of the circle shpaed value indicators,
* default size = 4f
* <p/>
* This method is deprecated because of unclarity. Use setCircleRadius instead.
*
* @param size
*/
@Deprecated
public void setCircleSize(float size) {
setCircleRadius(size);
}
/**
* This function is deprecated because of unclarity. Use getCircleRadius instead.
*/
@Deprecated
public float getCircleSize() {
return getCircleRadius();
}
/**
* Enables the line to be drawn in dashed mode, e.g. like this
* "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF.
* Keep in mind that hardware acceleration boosts performance.
*
* @param lineLength the length of the line pieces
* @param spaceLength the length of space in between the pieces
* @param phase offset, in degrees (normally, use 0)
*/
public void enableDashedLine(float lineLength, float spaceLength, float phase) {
mDashPathEffect = new DashPathEffect(new float[]{
lineLength, spaceLength
}, phase);
}
/**
* Disables the line to be drawn in dashed mode.
*/
public void disableDashedLine() {
mDashPathEffect = null;
}
@Override
public boolean isDashedLineEnabled() {
return mDashPathEffect == null ? false : true;
}
@Override
public DashPathEffect getDashPathEffect() {
return mDashPathEffect;
}
/**
* set this to true to enable the drawing of circle indicators for this
* DataSet, default true
*
* @param enabled
*/
public void setDrawCircles(boolean enabled) {
this.mDrawCircles = enabled;
}
@Override
public boolean isDrawCirclesEnabled() {
return mDrawCircles;
}
@Deprecated
@Override
public boolean isDrawCubicEnabled() {
return mMode == Mode.CUBIC_BEZIER;
}
@Deprecated
@Override
public boolean isDrawSteppedEnabled() {
return mMode == Mode.STEPPED;
}
/** ALL CODE BELOW RELATED TO CIRCLE-COLORS */
/**
* returns all colors specified for the circles
*
* @return
*/
public List<Integer> getCircleColors() {
return mCircleColors;
}
@Override
public int getCircleColor(int index) {
return mCircleColors.get(index);
}
@Override
public int getCircleColorCount() {
return mCircleColors.size();
}
/**
* Sets the colors that should be used for the circles of this DataSet.
* Colors are reused as soon as the number of Entries the DataSet represents
* is higher than the size of the colors array. Make sure that the colors
* are already prepared (by calling getResources().getColor(...)) before
* adding them to the DataSet.
*
* @param colors
*/
public void setCircleColors(List<Integer> colors) {
mCircleColors = colors;
}
/**
* Sets the colors that should be used for the circles of this DataSet.
* Colors are reused as soon as the number of Entries the DataSet represents
* is higher than the size of the colors array. Make sure that the colors
* are already prepared (by calling getResources().getColor(...)) before
* adding them to the DataSet.
*
* @param colors
*/
public void setCircleColors(int... colors) {
this.mCircleColors = ColorTemplate.createColors(colors);
}
/**
* ets the colors that should be used for the circles of this DataSet.
* Colors are reused as soon as the number of Entries the DataSet represents
* is higher than the size of the colors array. You can use
* "new String[] { R.color.red, R.color.green, ... }" to provide colors for
* this method. Internally, the colors are resolved using
* getResources().getColor(...)
*
* @param colors
*/
public void setCircleColors(int[] colors, Context c) {
List<Integer> clrs = mCircleColors;
if (clrs == null) {
clrs = new ArrayList<>();
}
clrs.clear();
for (int color : colors) {
clrs.add(c.getResources().getColor(color));
}
mCircleColors = clrs;
}
/**
* Sets the one and ONLY color that should be used for this DataSet.
* Internally, this recreates the colors array and adds the specified color.
*
* @param color
*/
public void setCircleColor(int color) {
resetCircleColors();
mCircleColors.add(color);
}
/**
* resets the circle-colors array and creates a new one
*/
public void resetCircleColors() {
if (mCircleColors == null) {
mCircleColors = new ArrayList<Integer>();
}
mCircleColors.clear();
}
/**
* Sets the color of the inner circle of the line-circles.
*
* @param color
*/
public void setCircleHoleColor(int color) {
mCircleHoleColor = color;
}
@Override
public int getCircleHoleColor() {
return mCircleHoleColor;
}
/**
* Set this to true to allow drawing a hole in each data circle.
*
* @param enabled
*/
public void setDrawCircleHole(boolean enabled) {
mDrawCircleHole = enabled;
}
@Override
public boolean isDrawCircleHoleEnabled() {
return mDrawCircleHole;
}
/**
* Sets a custom IFillFormatter to the chart that handles the position of the
* filled-line for each DataSet. Set this to null to use the default logic.
*
* @param formatter
*/
public void setFillFormatter(IFillFormatter formatter) {
if (formatter == null)
mFillFormatter = new DefaultFillFormatter();
else
mFillFormatter = formatter;
}
@Override
public IFillFormatter getFillFormatter() {
return mFillFormatter;
}
public enum Mode {
LINEAR,
STEPPED,
CUBIC_BEZIER,
HORIZONTAL_BEZIER
}
}

@ -0,0 +1,135 @@
package com.github.mikephil.charting.data;
import android.annotation.TargetApi;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import com.github.mikephil.charting.interfaces.datasets.ILineRadarDataSet;
import com.github.mikephil.charting.utils.Utils;
import java.util.List;
/**
* Base dataset for line and radar DataSets.
*
* @author Philipp Jahoda
*/
public abstract class LineRadarDataSet<T extends Entry> extends LineScatterCandleRadarDataSet<T> implements ILineRadarDataSet<T> {
// TODO: Move to using `Fill` class
/**
* the color that is used for filling the line surface
*/
private int mFillColor = Color.rgb(140, 234, 255);
/**
* the drawable to be used for filling the line surface
*/
protected Drawable mFillDrawable;
/**
* transparency used for filling line surface
*/
private int mFillAlpha = 85;
/**
* the width of the drawn data lines
*/
private float mLineWidth = 2.5f;
/**
* if true, the data will also be drawn filled
*/
private boolean mDrawFilled = false;
public LineRadarDataSet(List<T> yVals, String label) {
super(yVals, label);
}
@Override
public int getFillColor() {
return mFillColor;
}
/**
* Sets the color that is used for filling the area below the line.
* Resets an eventually set "fillDrawable".
*
* @param color
*/
public void setFillColor(int color) {
mFillColor = color;
mFillDrawable = null;
}
@Override
public Drawable getFillDrawable() {
return mFillDrawable;
}
/**
* Sets the drawable to be used to fill the area below the line.
*
* @param drawable
*/
@TargetApi(18)
public void setFillDrawable(Drawable drawable) {
this.mFillDrawable = drawable;
}
@Override
public int getFillAlpha() {
return mFillAlpha;
}
/**
* sets the alpha value (transparency) that is used for filling the line
* surface (0-255), default: 85
*
* @param alpha
*/
public void setFillAlpha(int alpha) {
mFillAlpha = alpha;
}
/**
* set the line width of the chart (min = 0.2f, max = 10f); default 1f NOTE:
* thinner line == better performance, thicker line == worse performance
*
* @param width
*/
public void setLineWidth(float width) {
if (width < 0.0f)
width = 0.0f;
if (width > 10.0f)
width = 10.0f;
mLineWidth = Utils.convertDpToPixel(width);
}
@Override
public float getLineWidth() {
return mLineWidth;
}
@Override
public void setDrawFilled(boolean filled) {
mDrawFilled = filled;
}
@Override
public boolean isDrawFilledEnabled() {
return mDrawFilled;
}
protected void copy(LineRadarDataSet lineRadarDataSet) {
super.copy(lineRadarDataSet);
lineRadarDataSet.mDrawFilled = mDrawFilled;
lineRadarDataSet.mFillAlpha = mFillAlpha;
lineRadarDataSet.mFillColor = mFillColor;
lineRadarDataSet.mFillDrawable = mFillDrawable;
lineRadarDataSet.mLineWidth = mLineWidth;
}
}

@ -0,0 +1,120 @@
package com.github.mikephil.charting.data;
import android.graphics.DashPathEffect;
import com.github.mikephil.charting.interfaces.datasets.ILineScatterCandleRadarDataSet;
import com.github.mikephil.charting.utils.Utils;
import java.util.List;
/**
* Created by Philipp Jahoda on 11/07/15.
*/
public abstract class LineScatterCandleRadarDataSet<T extends Entry> extends BarLineScatterCandleBubbleDataSet<T> implements ILineScatterCandleRadarDataSet<T> {
protected boolean mDrawVerticalHighlightIndicator = true;
protected boolean mDrawHorizontalHighlightIndicator = true;
/** the width of the highlight indicator lines */
protected float mHighlightLineWidth = 0.5f;
/** the path effect for dashed highlight-lines */
protected DashPathEffect mHighlightDashPathEffect = null;
public LineScatterCandleRadarDataSet(List<T> yVals, String label) {
super(yVals, label);
mHighlightLineWidth = Utils.convertDpToPixel(0.5f);
}
/**
* Enables / disables the horizontal highlight-indicator. If disabled, the indicator is not drawn.
* @param enabled
*/
public void setDrawHorizontalHighlightIndicator(boolean enabled) {
this.mDrawHorizontalHighlightIndicator = enabled;
}
/**
* Enables / disables the vertical highlight-indicator. If disabled, the indicator is not drawn.
* @param enabled
*/
public void setDrawVerticalHighlightIndicator(boolean enabled) {
this.mDrawVerticalHighlightIndicator = enabled;
}
/**
* Enables / disables both vertical and horizontal highlight-indicators.
* @param enabled
*/
public void setDrawHighlightIndicators(boolean enabled) {
setDrawVerticalHighlightIndicator(enabled);
setDrawHorizontalHighlightIndicator(enabled);
}
@Override
public boolean isVerticalHighlightIndicatorEnabled() {
return mDrawVerticalHighlightIndicator;
}
@Override
public boolean isHorizontalHighlightIndicatorEnabled() {
return mDrawHorizontalHighlightIndicator;
}
/**
* Sets the width of the highlight line in dp.
* @param width
*/
public void setHighlightLineWidth(float width) {
mHighlightLineWidth = Utils.convertDpToPixel(width);
}
@Override
public float getHighlightLineWidth() {
return mHighlightLineWidth;
}
/**
* Enables the highlight-line to be drawn in dashed mode, e.g. like this "- - - - - -"
*
* @param lineLength the length of the line pieces
* @param spaceLength the length of space inbetween the line-pieces
* @param phase offset, in degrees (normally, use 0)
*/
public void enableDashedHighlightLine(float lineLength, float spaceLength, float phase) {
mHighlightDashPathEffect = new DashPathEffect(new float[] {
lineLength, spaceLength
}, phase);
}
/**
* Disables the highlight-line to be drawn in dashed mode.
*/
public void disableDashedHighlightLine() {
mHighlightDashPathEffect = null;
}
/**
* Returns true if the dashed-line effect is enabled for highlight lines, false if not.
* Default: disabled
*
* @return
*/
public boolean isDashedHighlightLineEnabled() {
return mHighlightDashPathEffect == null ? false : true;
}
@Override
public DashPathEffect getDashPathEffectHighlight() {
return mHighlightDashPathEffect;
}
protected void copy(LineScatterCandleRadarDataSet lineScatterCandleRadarDataSet) {
super.copy(lineScatterCandleRadarDataSet);
lineScatterCandleRadarDataSet.mDrawHorizontalHighlightIndicator = mDrawHorizontalHighlightIndicator;
lineScatterCandleRadarDataSet.mDrawVerticalHighlightIndicator = mDrawVerticalHighlightIndicator;
lineScatterCandleRadarDataSet.mHighlightLineWidth = mHighlightLineWidth;
lineScatterCandleRadarDataSet.mHighlightDashPathEffect = mHighlightDashPathEffect;
}
}

@ -0,0 +1,100 @@
package com.github.mikephil.charting.data;
import android.util.Log;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.IPieDataSet;
import java.util.ArrayList;
import java.util.List;
/**
* A PieData object can only represent one DataSet. Unlike all other charts, the
* legend labels of the PieChart are created from the x-values array, and not
* from the DataSet labels. Each PieData object can only represent one
* PieDataSet (multiple PieDataSets inside a single PieChart are not possible).
*
* @author Philipp Jahoda
*/
public class PieData extends ChartData<IPieDataSet> {
public PieData() {
super();
}
public PieData(IPieDataSet dataSet) {
super(dataSet);
}
/**
* Sets the PieDataSet this data object should represent.
*
* @param dataSet
*/
public void setDataSet(IPieDataSet dataSet) {
mDataSets.clear();
mDataSets.add(dataSet);
notifyDataChanged();
}
/**
* Returns the DataSet this PieData object represents. A PieData object can
* only contain one DataSet.
*
* @return
*/
public IPieDataSet getDataSet() {
return mDataSets.get(0);
}
@Override
public List<IPieDataSet> getDataSets() {
List<IPieDataSet> dataSets = super.getDataSets();
if (dataSets.size() < 1) {
Log.e("MPAndroidChart",
"Found multiple data sets while pie chart only allows one");
}
return dataSets;
}
/**
* The PieData object can only have one DataSet. Use getDataSet() method instead.
*
* @param index
* @return
*/
@Override
public IPieDataSet getDataSetByIndex(int index) {
return index == 0 ? getDataSet() : null;
}
@Override
public IPieDataSet getDataSetByLabel(String label, boolean ignorecase) {
return ignorecase ? label.equalsIgnoreCase(mDataSets.get(0).getLabel()) ? mDataSets.get(0)
: null : label.equals(mDataSets.get(0).getLabel()) ? mDataSets.get(0) : null;
}
@Override
public Entry getEntryForHighlight(Highlight highlight) {
return getDataSet().getEntryForIndex((int) highlight.getX());
}
/**
* Returns the sum of all values in this PieData object.
*
* @return
*/
public float getYValueSum() {
float sum = 0;
for (int i = 0; i < getDataSet().getEntryCount(); i++)
sum += getDataSet().getEntryForIndex(i).getY();
return sum;
}
}

@ -0,0 +1,261 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.IPieDataSet;
import com.github.mikephil.charting.utils.Utils;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class PieDataSet extends DataSet<PieEntry> implements IPieDataSet {
/**
* the space in pixels between the chart-slices, default 0f
*/
private float mSliceSpace = 0f;
private boolean mAutomaticallyDisableSliceSpacing;
/**
* indicates the selection distance of a pie slice
*/
private float mShift = 18f;
private ValuePosition mXValuePosition = ValuePosition.INSIDE_SLICE;
private ValuePosition mYValuePosition = ValuePosition.INSIDE_SLICE;
private int mValueLineColor = 0xff000000;
private boolean mUseValueColorForLine = false;
private float mValueLineWidth = 1.0f;
private float mValueLinePart1OffsetPercentage = 75.f;
private float mValueLinePart1Length = 0.3f;
private float mValueLinePart2Length = 0.4f;
private boolean mValueLineVariableLength = true;
private Integer mHighlightColor = null;
public PieDataSet(List<PieEntry> yVals, String label) {
super(yVals, label);
// mShift = Utils.convertDpToPixel(12f);
}
@Override
public DataSet<PieEntry> copy() {
List<PieEntry> entries = new ArrayList<>();
for (int i = 0; i < mEntries.size(); i++) {
entries.add(mEntries.get(i).copy());
}
PieDataSet copied = new PieDataSet(entries, getLabel());
copy(copied);
return copied;
}
protected void copy(PieDataSet pieDataSet) {
super.copy(pieDataSet);
}
@Override
protected void calcMinMax(PieEntry e) {
if (e == null)
return;
calcMinMaxY(e);
}
/**
* Sets the space that is left out between the piechart-slices in dp.
* Default: 0 --> no space, maximum 20f
*
* @param spaceDp
*/
public void setSliceSpace(float spaceDp) {
if (spaceDp > 20)
spaceDp = 20f;
if (spaceDp < 0)
spaceDp = 0f;
mSliceSpace = Utils.convertDpToPixel(spaceDp);
}
@Override
public float getSliceSpace() {
return mSliceSpace;
}
/**
* When enabled, slice spacing will be 0.0 when the smallest value is going to be
* smaller than the slice spacing itself.
*
* @param autoDisable
*/
public void setAutomaticallyDisableSliceSpacing(boolean autoDisable) {
mAutomaticallyDisableSliceSpacing = autoDisable;
}
/**
* When enabled, slice spacing will be 0.0 when the smallest value is going to be
* smaller than the slice spacing itself.
*
* @return
*/
@Override
public boolean isAutomaticallyDisableSliceSpacingEnabled() {
return mAutomaticallyDisableSliceSpacing;
}
/**
* sets the distance the highlighted piechart-slice of this DataSet is
* "shifted" away from the center of the chart, default 12f
*
* @param shift
*/
public void setSelectionShift(float shift) {
mShift = Utils.convertDpToPixel(shift);
}
@Override
public float getSelectionShift() {
return mShift;
}
@Override
public ValuePosition getXValuePosition() {
return mXValuePosition;
}
public void setXValuePosition(ValuePosition xValuePosition) {
this.mXValuePosition = xValuePosition;
}
@Override
public ValuePosition getYValuePosition() {
return mYValuePosition;
}
public void setYValuePosition(ValuePosition yValuePosition) {
this.mYValuePosition = yValuePosition;
}
/**
* This method is deprecated.
* Use isUseValueColorForLineEnabled() instead.
*/
@Deprecated
public boolean isUsingSliceColorAsValueLineColor() {
return isUseValueColorForLineEnabled();
}
/**
* This method is deprecated.
* Use setUseValueColorForLine(...) instead.
*
* @param enabled
*/
@Deprecated
public void setUsingSliceColorAsValueLineColor(boolean enabled) {
setUseValueColorForLine(enabled);
}
/**
* When valuePosition is OutsideSlice, indicates line color
*/
@Override
public int getValueLineColor() {
return mValueLineColor;
}
public void setValueLineColor(int valueLineColor) {
this.mValueLineColor = valueLineColor;
}
@Override
public boolean isUseValueColorForLineEnabled()
{
return mUseValueColorForLine;
}
public void setUseValueColorForLine(boolean enabled)
{
mUseValueColorForLine = enabled;
}
/**
* When valuePosition is OutsideSlice, indicates line width
*/
@Override
public float getValueLineWidth() {
return mValueLineWidth;
}
public void setValueLineWidth(float valueLineWidth) {
this.mValueLineWidth = valueLineWidth;
}
/**
* When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size
*/
@Override
public float getValueLinePart1OffsetPercentage() {
return mValueLinePart1OffsetPercentage;
}
public void setValueLinePart1OffsetPercentage(float valueLinePart1OffsetPercentage) {
this.mValueLinePart1OffsetPercentage = valueLinePart1OffsetPercentage;
}
/**
* When valuePosition is OutsideSlice, indicates length of first half of the line
*/
@Override
public float getValueLinePart1Length() {
return mValueLinePart1Length;
}
public void setValueLinePart1Length(float valueLinePart1Length) {
this.mValueLinePart1Length = valueLinePart1Length;
}
/**
* When valuePosition is OutsideSlice, indicates length of second half of the line
*/
@Override
public float getValueLinePart2Length() {
return mValueLinePart2Length;
}
public void setValueLinePart2Length(float valueLinePart2Length) {
this.mValueLinePart2Length = valueLinePart2Length;
}
/**
* When valuePosition is OutsideSlice, this allows variable line length
*/
@Override
public boolean isValueLineVariableLength() {
return mValueLineVariableLength;
}
public void setValueLineVariableLength(boolean valueLineVariableLength) {
this.mValueLineVariableLength = valueLineVariableLength;
}
/** Gets the color for the highlighted sector */
@Override
@Nullable
public Integer getHighlightColor()
{
return mHighlightColor;
}
/** Sets the color for the highlighted sector (null for using entry color) */
public void setHighlightColor(@Nullable Integer color)
{
this.mHighlightColor = color;
}
public enum ValuePosition {
INSIDE_SLICE,
OUTSIDE_SLICE
}
}

@ -0,0 +1,86 @@
package com.github.mikephil.charting.data;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.util.Log;
/**
* @author Philipp Jahoda
*/
@SuppressLint("ParcelCreator")
public class PieEntry extends Entry {
private String label;
public PieEntry(float value) {
super(0f, value);
}
public PieEntry(float value, Object data) {
super(0f, value, data);
}
public PieEntry(float value, Drawable icon) {
super(0f, value, icon);
}
public PieEntry(float value, Drawable icon, Object data) {
super(0f, value, icon, data);
}
public PieEntry(float value, String label) {
super(0f, value);
this.label = label;
}
public PieEntry(float value, String label, Object data) {
super(0f, value, data);
this.label = label;
}
public PieEntry(float value, String label, Drawable icon) {
super(0f, value, icon);
this.label = label;
}
public PieEntry(float value, String label, Drawable icon, Object data) {
super(0f, value, icon, data);
this.label = label;
}
/**
* This is the same as getY(). Returns the value of the PieEntry.
*
* @return
*/
public float getValue() {
return getY();
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Deprecated
@Override
public void setX(float x) {
super.setX(x);
Log.i("DEPRECATED", "Pie entries do not have x values");
}
@Deprecated
@Override
public float getX() {
Log.i("DEPRECATED", "Pie entries do not have x values");
return super.getX();
}
public PieEntry copy() {
PieEntry e = new PieEntry(getY(), label, getData());
return e;
}
}

@ -0,0 +1,58 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Data container for the RadarChart.
*
* @author Philipp Jahoda
*/
public class RadarData extends ChartData<IRadarDataSet> {
private List<String> mLabels;
public RadarData() {
super();
}
public RadarData(List<IRadarDataSet> dataSets) {
super(dataSets);
}
public RadarData(IRadarDataSet... dataSets) {
super(dataSets);
}
/**
* Sets the labels that should be drawn around the RadarChart at the end of each web line.
*
* @param labels
*/
public void setLabels(List<String> labels) {
this.mLabels = labels;
}
/**
* Sets the labels that should be drawn around the RadarChart at the end of each web line.
*
* @param labels
*/
public void setLabels(String... labels) {
this.mLabels = Arrays.asList(labels);
}
public List<String> getLabels() {
return mLabels;
}
@Override
public Entry getEntryForHighlight(Highlight highlight) {
return getDataSetByIndex(highlight.getDataSetIndex()).getEntryForIndex((int) highlight.getX());
}
}

@ -0,0 +1,122 @@
package com.github.mikephil.charting.data;
import android.graphics.Color;
import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet;
import com.github.mikephil.charting.utils.ColorTemplate;
import java.util.ArrayList;
import java.util.List;
public class RadarDataSet extends LineRadarDataSet<RadarEntry> implements IRadarDataSet {
/// flag indicating whether highlight circle should be drawn or not
protected boolean mDrawHighlightCircleEnabled = false;
protected int mHighlightCircleFillColor = Color.WHITE;
/// The stroke color for highlight circle.
/// If Utils.COLOR_NONE, the color of the dataset is taken.
protected int mHighlightCircleStrokeColor = ColorTemplate.COLOR_NONE;
protected int mHighlightCircleStrokeAlpha = (int) (0.3 * 255);
protected float mHighlightCircleInnerRadius = 3.0f;
protected float mHighlightCircleOuterRadius = 4.0f;
protected float mHighlightCircleStrokeWidth = 2.0f;
public RadarDataSet(List<RadarEntry> yVals, String label) {
super(yVals, label);
}
/// Returns true if highlight circle should be drawn, false if not
@Override
public boolean isDrawHighlightCircleEnabled() {
return mDrawHighlightCircleEnabled;
}
/// Sets whether highlight circle should be drawn or not
@Override
public void setDrawHighlightCircleEnabled(boolean enabled) {
mDrawHighlightCircleEnabled = enabled;
}
@Override
public int getHighlightCircleFillColor() {
return mHighlightCircleFillColor;
}
public void setHighlightCircleFillColor(int color) {
mHighlightCircleFillColor = color;
}
/// Returns the stroke color for highlight circle.
/// If Utils.COLOR_NONE, the color of the dataset is taken.
@Override
public int getHighlightCircleStrokeColor() {
return mHighlightCircleStrokeColor;
}
/// Sets the stroke color for highlight circle.
/// Set to Utils.COLOR_NONE in order to use the color of the dataset;
public void setHighlightCircleStrokeColor(int color) {
mHighlightCircleStrokeColor = color;
}
@Override
public int getHighlightCircleStrokeAlpha() {
return mHighlightCircleStrokeAlpha;
}
public void setHighlightCircleStrokeAlpha(int alpha) {
mHighlightCircleStrokeAlpha = alpha;
}
@Override
public float getHighlightCircleInnerRadius() {
return mHighlightCircleInnerRadius;
}
public void setHighlightCircleInnerRadius(float radius) {
mHighlightCircleInnerRadius = radius;
}
@Override
public float getHighlightCircleOuterRadius() {
return mHighlightCircleOuterRadius;
}
public void setHighlightCircleOuterRadius(float radius) {
mHighlightCircleOuterRadius = radius;
}
@Override
public float getHighlightCircleStrokeWidth() {
return mHighlightCircleStrokeWidth;
}
public void setHighlightCircleStrokeWidth(float strokeWidth) {
mHighlightCircleStrokeWidth = strokeWidth;
}
@Override
public DataSet<RadarEntry> copy() {
List<RadarEntry> entries = new ArrayList<RadarEntry>();
for (int i = 0; i < mEntries.size(); i++) {
entries.add(mEntries.get(i).copy());
}
RadarDataSet copied = new RadarDataSet(entries, getLabel());
copy(copied);
return copied;
}
protected void copy(RadarDataSet radarDataSet) {
super.copy(radarDataSet);
radarDataSet.mDrawHighlightCircleEnabled = mDrawHighlightCircleEnabled;
radarDataSet.mHighlightCircleFillColor = mHighlightCircleFillColor;
radarDataSet.mHighlightCircleInnerRadius = mHighlightCircleInnerRadius;
radarDataSet.mHighlightCircleStrokeAlpha = mHighlightCircleStrokeAlpha;
radarDataSet.mHighlightCircleStrokeColor = mHighlightCircleStrokeColor;
radarDataSet.mHighlightCircleStrokeWidth = mHighlightCircleStrokeWidth;
}
}

@ -0,0 +1,44 @@
package com.github.mikephil.charting.data;
import android.annotation.SuppressLint;
/**
* Created by philipp on 13/06/16.
*/
@SuppressLint("ParcelCreator")
public class RadarEntry extends Entry {
public RadarEntry(float value) {
super(0f, value);
}
public RadarEntry(float value, Object data) {
super(0f, value, data);
}
/**
* This is the same as getY(). Returns the value of the RadarEntry.
*
* @return
*/
public float getValue() {
return getY();
}
public RadarEntry copy() {
RadarEntry e = new RadarEntry(getY(), getData());
return e;
}
@Deprecated
@Override
public void setX(float x) {
super.setX(x);
}
@Deprecated
@Override
public float getX() {
return super.getX();
}
}

@ -0,0 +1,40 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet;
import java.util.List;
public class ScatterData extends BarLineScatterCandleBubbleData<IScatterDataSet> {
public ScatterData() {
super();
}
public ScatterData(List<IScatterDataSet> dataSets) {
super(dataSets);
}
public ScatterData(IScatterDataSet... dataSets) {
super(dataSets);
}
/**
* Returns the maximum shape-size across all DataSets.
*
* @return
*/
public float getGreatestShapeSize() {
float max = 0f;
for (IScatterDataSet set : mDataSets) {
float size = set.getScatterShapeSize();
if (size > max)
max = size;
}
return max;
}
}

@ -0,0 +1,157 @@
package com.github.mikephil.charting.data;
import com.github.mikephil.charting.charts.ScatterChart;
import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet;
import com.github.mikephil.charting.renderer.scatter.ChevronDownShapeRenderer;
import com.github.mikephil.charting.renderer.scatter.ChevronUpShapeRenderer;
import com.github.mikephil.charting.renderer.scatter.CircleShapeRenderer;
import com.github.mikephil.charting.renderer.scatter.CrossShapeRenderer;
import com.github.mikephil.charting.renderer.scatter.IShapeRenderer;
import com.github.mikephil.charting.renderer.scatter.SquareShapeRenderer;
import com.github.mikephil.charting.renderer.scatter.TriangleShapeRenderer;
import com.github.mikephil.charting.renderer.scatter.XShapeRenderer;
import com.github.mikephil.charting.utils.ColorTemplate;
import java.util.ArrayList;
import java.util.List;
public class ScatterDataSet extends LineScatterCandleRadarDataSet<Entry> implements IScatterDataSet {
/**
* the size the scattershape will have, in density pixels
*/
private float mShapeSize = 15f;
/**
* Renderer responsible for rendering this DataSet, default: square
*/
protected IShapeRenderer mShapeRenderer = new SquareShapeRenderer();
/**
* The radius of the hole in the shape (applies to Square, Circle and Triangle)
* - default: 0.0
*/
private float mScatterShapeHoleRadius = 0f;
/**
* Color for the hole in the shape.
* Setting to `ColorTemplate.COLOR_NONE` will behave as transparent.
* - default: ColorTemplate.COLOR_NONE
*/
private int mScatterShapeHoleColor = ColorTemplate.COLOR_NONE;
public ScatterDataSet(List<Entry> yVals, String label) {
super(yVals, label);
}
@Override
public DataSet<Entry> copy() {
List<Entry> entries = new ArrayList<Entry>();
for (int i = 0; i < mEntries.size(); i++) {
entries.add(mEntries.get(i).copy());
}
ScatterDataSet copied = new ScatterDataSet(entries, getLabel());
copy(copied);
return copied;
}
protected void copy(ScatterDataSet scatterDataSet) {
super.copy(scatterDataSet);
scatterDataSet.mShapeSize = mShapeSize;
scatterDataSet.mShapeRenderer = mShapeRenderer;
scatterDataSet.mScatterShapeHoleRadius = mScatterShapeHoleRadius;
scatterDataSet.mScatterShapeHoleColor = mScatterShapeHoleColor;
}
/**
* Sets the size in density pixels the drawn scattershape will have. This
* only applies for non custom shapes.
*
* @param size
*/
public void setScatterShapeSize(float size) {
mShapeSize = size;
}
@Override
public float getScatterShapeSize() {
return mShapeSize;
}
/**
* Sets the ScatterShape this DataSet should be drawn with. This will search for an available IShapeRenderer and set this
* renderer for the DataSet.
*
* @param shape
*/
public void setScatterShape(ScatterChart.ScatterShape shape) {
mShapeRenderer = getRendererForShape(shape);
}
/**
* Sets a new IShapeRenderer responsible for drawing this DataSet.
* This can also be used to set a custom IShapeRenderer aside from the default ones.
*
* @param shapeRenderer
*/
public void setShapeRenderer(IShapeRenderer shapeRenderer) {
mShapeRenderer = shapeRenderer;
}
@Override
public IShapeRenderer getShapeRenderer() {
return mShapeRenderer;
}
/**
* Sets the radius of the hole in the shape (applies to Square, Circle and Triangle)
* Set this to <= 0 to remove holes.
*
* @param holeRadius
*/
public void setScatterShapeHoleRadius(float holeRadius) {
mScatterShapeHoleRadius = holeRadius;
}
@Override
public float getScatterShapeHoleRadius() {
return mScatterShapeHoleRadius;
}
/**
* Sets the color for the hole in the shape.
*
* @param holeColor
*/
public void setScatterShapeHoleColor(int holeColor) {
mScatterShapeHoleColor = holeColor;
}
@Override
public int getScatterShapeHoleColor() {
return mScatterShapeHoleColor;
}
public static IShapeRenderer getRendererForShape(ScatterChart.ScatterShape shape) {
switch (shape) {
case SQUARE:
return new SquareShapeRenderer();
case CIRCLE:
return new CircleShapeRenderer();
case TRIANGLE:
return new TriangleShapeRenderer();
case CROSS:
return new CrossShapeRenderer();
case X:
return new XShapeRenderer();
case CHEVRON_UP:
return new ChevronUpShapeRenderer();
case CHEVRON_DOWN:
return new ChevronDownShapeRenderer();
}
return null;
}
}

@ -0,0 +1,102 @@
package com.github.mikephil.charting.data.filter;
import android.annotation.TargetApi;
import android.os.Build;
import java.util.Arrays;
/**
* Implemented according to Wiki-Pseudocode {@link}
* http://en.wikipedia.org/wiki/Ramer<65>Douglas<61>Peucker_algorithm
*
* @author Philipp Baldauf & Phliipp Jahoda
*/
public class Approximator {
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public float[] reduceWithDouglasPeucker(float[] points, float tolerance) {
int greatestIndex = 0;
float greatestDistance = 0f;
Line line = new Line(points[0], points[1], points[points.length - 2], points[points.length - 1]);
for (int i = 2; i < points.length - 2; i += 2) {
float distance = line.distance(points[i], points[i + 1]);
if (distance > greatestDistance) {
greatestDistance = distance;
greatestIndex = i;
}
}
if (greatestDistance > tolerance) {
float[] reduced1 = reduceWithDouglasPeucker(Arrays.copyOfRange(points, 0, greatestIndex + 2), tolerance);
float[] reduced2 = reduceWithDouglasPeucker(Arrays.copyOfRange(points, greatestIndex, points.length),
tolerance);
float[] result1 = reduced1;
float[] result2 = Arrays.copyOfRange(reduced2, 2, reduced2.length);
return concat(result1, result2);
} else {
return line.getPoints();
}
}
/**
* Combine arrays.
*
* @param arrays
* @return
*/
float[] concat(float[]... arrays) {
int length = 0;
for (float[] array : arrays) {
length += array.length;
}
float[] result = new float[length];
int pos = 0;
for (float[] array : arrays) {
for (float element : array) {
result[pos] = element;
pos++;
}
}
return result;
}
private class Line {
private float[] points;
private float sxey;
private float exsy;
private float dx;
private float dy;
private float length;
public Line(float x1, float y1, float x2, float y2) {
dx = x1 - x2;
dy = y1 - y2;
sxey = x1 * y2;
exsy = x2 * y1;
length = (float) Math.sqrt(dx * dx + dy * dy);
points = new float[]{x1, y1, x2, y2};
}
public float distance(float x, float y) {
return Math.abs(dy * x - dx * y + sxey - exsy) / length;
}
public float[] getPoints() {
return points;
}
}
}

@ -0,0 +1,146 @@
package com.github.mikephil.charting.data.filter;
import java.util.ArrayList;
/**
* Implemented according to modified Douglas Peucker {@link}
* http://psimpl.sourceforge.net/douglas-peucker.html
*/
public class ApproximatorN
{
public float[] reduceWithDouglasPeucker(float[] points, float resultCount) {
int pointCount = points.length / 2;
// if a shape has 2 or less points it cannot be reduced
if (resultCount <= 2 || resultCount >= pointCount)
return points;
boolean[] keep = new boolean[pointCount];
// first and last always stay
keep[0] = true;
keep[pointCount - 1] = true;
int currentStoredPoints = 2;
ArrayList<Line> queue = new ArrayList<>();
Line line = new Line(0, pointCount - 1, points);
queue.add(line);
do {
line = queue.remove(queue.size() - 1);
// store the key
keep[line.index] = true;
// check point count tolerance
currentStoredPoints += 1;
if (currentStoredPoints == resultCount)
break;
// split the polyline at the key and recurse
Line left = new Line(line.start, line.index, points);
if (left.index > 0) {
int insertionIndex = insertionIndex(left, queue);
queue.add(insertionIndex, left);
}
Line right = new Line(line.index, line.end, points);
if (right.index > 0) {
int insertionIndex = insertionIndex(right, queue);
queue.add(insertionIndex, right);
}
} while (queue.isEmpty());
float[] reducedEntries = new float[currentStoredPoints * 2];
for (int i = 0, i2 = 0, r2 = 0; i < currentStoredPoints; i++, r2 += 2) {
if (keep[i]) {
reducedEntries[i2++] = points[r2];
reducedEntries[i2++] = points[r2 + 1];
}
}
return reducedEntries;
}
private static float distanceToLine(
float ptX, float ptY, float[]
fromLinePoint1, float[] fromLinePoint2) {
float dx = fromLinePoint2[0] - fromLinePoint1[0];
float dy = fromLinePoint2[1] - fromLinePoint1[1];
float dividend = Math.abs(
dy * ptX -
dx * ptY -
fromLinePoint1[0] * fromLinePoint2[1] +
fromLinePoint2[0] * fromLinePoint1[1]);
double divisor = Math.sqrt(dx * dx + dy * dy);
return (float)(dividend / divisor);
}
private static class Line {
int start;
int end;
float distance = 0;
int index = 0;
Line(int start, int end, float[] points) {
this.start = start;
this.end = end;
float[] startPoint = new float[]{points[start * 2], points[start * 2 + 1]};
float[] endPoint = new float[]{points[end * 2], points[end * 2 + 1]};
if (end <= start + 1) return;
for (int i = start + 1, i2 = i * 2; i < end; i++, i2 += 2) {
float distance = distanceToLine(
points[i2], points[i2 + 1],
startPoint, endPoint);
if (distance > this.distance) {
this.index = i;
this.distance = distance;
}
}
}
boolean equals(final Line rhs) {
return (start == rhs.start) && (end == rhs.end) && index == rhs.index;
}
boolean lessThan(final Line rhs) {
return distance < rhs.distance;
}
}
private static int insertionIndex(Line line, ArrayList<Line> queue) {
int min = 0;
int max = queue.size();
while (!queue.isEmpty()) {
int midIndex = min + (max - min) / 2;
Line midLine = queue.get(midIndex);
if (midLine.equals(line)) {
return midIndex;
}
else if (line.lessThan(midLine)) {
// perform search in left half
max = midIndex;
}
else {
// perform search in right half
min = midIndex + 1;
}
}
return min;
}
}

@ -0,0 +1,14 @@
package com.github.mikephil.charting.exception;
public class DrawingDataSetNotCreatedException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public DrawingDataSetNotCreatedException() {
super("Have to create a new drawing set first. Call ChartData's createNewDrawingDataSet() method");
}
}

@ -0,0 +1,24 @@
package com.github.mikephil.charting.formatter;
import com.github.mikephil.charting.data.DataSet;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.interfaces.datasets.IDataSet;
/**
* Interface that can be used to return a customized color instead of setting
* colors via the setColor(...) method of the DataSet.
*
* @author Philipp Jahoda
*/
public interface ColorFormatter {
/**
* Returns the color to be used for the given Entry at the given index (in the entries array)
*
* @param index index in the entries array
* @param e the entry to color
* @param set the DataSet the entry belongs to
* @return
*/
int getColor(int index, Entry e, IDataSet set);
}

@ -0,0 +1,56 @@
package com.github.mikephil.charting.formatter;
import com.github.mikephil.charting.components.AxisBase;
import java.text.DecimalFormat;
/**
* Created by philipp on 02/06/16.
*/
public class DefaultAxisValueFormatter implements IAxisValueFormatter
{
/**
* decimalformat for formatting
*/
protected DecimalFormat mFormat;
/**
* the number of decimal digits this formatter uses
*/
protected int digits = 0;
/**
* Constructor that specifies to how many digits the value should be
* formatted.
*
* @param digits
*/
public DefaultAxisValueFormatter(int digits) {
this.digits = digits;
StringBuffer b = new StringBuffer();
for (int i = 0; i < digits; i++) {
if (i == 0)
b.append(".");
b.append("0");
}
mFormat = new DecimalFormat("###,###,###,##0" + b.toString());
}
@Override
public String getFormattedValue(float value, AxisBase axis) {
// avoid memory allocations here (for performance)
return mFormat.format(value);
}
/**
* Returns the number of decimal digits this formatter uses or -1, if unspecified.
*
* @return
*/
public int getDecimalDigits() {
return digits;
}
}

@ -0,0 +1,45 @@
package com.github.mikephil.charting.formatter;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
/**
* Default formatter that calculates the position of the filled line.
*
* @author Philipp Jahoda
*/
public class DefaultFillFormatter implements IFillFormatter
{
@Override
public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
float fillMin = 0f;
float chartMaxY = dataProvider.getYChartMax();
float chartMinY = dataProvider.getYChartMin();
LineData data = dataProvider.getLineData();
if (dataSet.getYMax() > 0 && dataSet.getYMin() < 0) {
fillMin = 0f;
} else {
float max, min;
if (data.getYMax() > 0)
max = 0f;
else
max = chartMaxY;
if (data.getYMin() < 0)
min = 0f;
else
min = chartMinY;
fillMin = dataSet.getYMin() >= 0 ? min : max;
}
return fillMin;
}
}

@ -0,0 +1,71 @@
package com.github.mikephil.charting.formatter;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.utils.ViewPortHandler;
import java.text.DecimalFormat;
/**
* Default formatter used for formatting values inside the chart. Uses a DecimalFormat with
* pre-calculated number of digits (depending on max and min value).
*
* @author Philipp Jahoda
*/
public class DefaultValueFormatter implements IValueFormatter
{
/**
* DecimalFormat for formatting
*/
protected DecimalFormat mFormat;
protected int mDecimalDigits;
/**
* Constructor that specifies to how many digits the value should be
* formatted.
*
* @param digits
*/
public DefaultValueFormatter(int digits) {
setup(digits);
}
/**
* Sets up the formatter with a given number of decimal digits.
*
* @param digits
*/
public void setup(int digits) {
this.mDecimalDigits = digits;
StringBuffer b = new StringBuffer();
for (int i = 0; i < digits; i++) {
if (i == 0)
b.append(".");
b.append("0");
}
mFormat = new DecimalFormat("###,###,###,##0" + b.toString());
}
@Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
// put more logic here ...
// avoid memory allocations here (for performance reasons)
return mFormat.format(value);
}
/**
* Returns the number of decimal digits this formatter uses.
*
* @return
*/
public int getDecimalDigits() {
return mDecimalDigits;
}
}

@ -0,0 +1,23 @@
package com.github.mikephil.charting.formatter;
import com.github.mikephil.charting.components.AxisBase;
/**
* Created by Philipp Jahoda on 20/09/15.
* Custom formatter interface that allows formatting of
* axis labels before they are being drawn.
*/
public interface IAxisValueFormatter
{
/**
* Called when a value from an axis is to be formatted
* before being drawn. For performance reasons, avoid excessive calculations
* and memory allocations inside this method.
*
* @param value the value to be formatted
* @param axis the axis the value belongs to
* @return
*/
String getFormattedValue(float value, AxisBase axis);
}

@ -0,0 +1,24 @@
package com.github.mikephil.charting.formatter;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
/**
* Interface for providing a custom logic to where the filling line of a LineDataSet
* should end. This of course only works if setFillEnabled(...) is set to true.
*
* @author Philipp Jahoda
*/
public interface IFillFormatter
{
/**
* Returns the vertical (y-axis) position where the filled-line of the
* LineDataSet should end.
*
* @param dataSet the ILineDataSet that is currently drawn
* @param dataProvider
* @return
*/
float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save