项目第一次创建并提交
commit
6ec8f7aa22
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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,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,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…
Reference in New Issue