From ce0117d746cccac4081fae9ff1043f0186447c11 Mon Sep 17 00:00:00 2001 From: Mohan Noone Date: Thu, 30 Nov 2023 23:51:16 +0530 Subject: [PATCH 1/7] Advanced Cubic Bezier algorithm for prettier line charts --- .../co/yml/charts/ui/linechart/LineChart.kt | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt index b7230d10..438e63e0 100644 --- a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt +++ b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt @@ -450,10 +450,13 @@ fun DrawScope.drawShadowUnderLineAndIntersectionPoint( } } - +private fun getInBetweenSlope(m1: Float, m2: Float) : Float { + return (m1 + m2) / ( 1 - m1 * m2 + ((m1.pow(2)+1) * (m2.pow(2) + 1)).pow(0.5f) ) +} /** * * getCubicPoints method provides left and right average value for a given point to get a smooth curve. + * Using the Advanced Cubic Bezier method as given in https://medium.com/mobile-app-development-publication/making-graph-plotting-function-in-jetpack-compose-95c80ee6fc7f * @param pointsData : List of the points on the Line graph. */ fun getCubicPoints(pointsData: List): Pair, MutableList> { @@ -461,14 +464,30 @@ fun getCubicPoints(pointsData: List): Pair, MutableL val cubicPoints2 = mutableListOf() for (i in 1 until pointsData.size) { + val p1x = ( (pointsData[i-1].x * 2f) + pointsData[i].x)/3f + val nextP = if (i < pointsData.size-1) pointsData[i+1] else null + val prevP = if (i >1 ) pointsData[i-2] else null + val currSlope = (pointsData[i].y - pointsData[i-1].y) / (pointsData[i].x - pointsData[i-1].x) + val nextSlope = if (nextP!=null) (nextP.y - pointsData[i].y) / (nextP.x - pointsData[i].x) else null + val prevSlope = if (prevP!=null) (pointsData[i-1].y - prevP.y) / (pointsData[i-1].x - prevP.x) else null + + val p1slope = if (prevSlope !=null) getInBetweenSlope(prevSlope, currSlope) else currSlope + val p1c = pointsData[i-1].y - (p1slope * pointsData[i-1].x) + val p1y = (p1slope * p1x) + p1c + + val p2x = (pointsData[i-1].x + (pointsData[i].x * 2f))/3f + val p2slope = if (nextSlope !=null) getInBetweenSlope(currSlope, nextSlope) else currSlope + val p2c = pointsData[i].y - (p2slope * pointsData[i].x) + val p2y = (p2slope * p2x ) + p2c + cubicPoints1.add( Offset( - (pointsData[i].x + pointsData[i - 1].x) / 2, pointsData[i - 1].y + p1x, p1y ) ) cubicPoints2.add( Offset( - (pointsData[i].x + pointsData[i - 1].x) / 2, pointsData[i].y + p2x, p2y ) ) } From b78570030d563bc97f98e7cfa834930a958ba29d Mon Sep 17 00:00:00 2001 From: Mohan Noone Date: Fri, 1 Dec 2023 04:32:31 +0530 Subject: [PATCH 2/7] Advanced Cubic Bezier algorithm for prettier line charts: Added required import (kotlin.math.pow) --- YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt index 438e63e0..ee5249b8 100644 --- a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt +++ b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt @@ -59,6 +59,7 @@ import co.yml.charts.ui.linechart.model.LineType import co.yml.charts.ui.linechart.model.SelectionHighlightPoint import co.yml.charts.ui.linechart.model.SelectionHighlightPopUp import kotlinx.coroutines.launch +import kotlin.math.pow /** * From bcb26dedfd3f802b3fd871ceda9519a220f6a3bf Mon Sep 17 00:00:00 2001 From: Mohan Noone Date: Fri, 1 Dec 2023 13:40:02 +0530 Subject: [PATCH 3/7] Advanced Cubic Bezier algorithm for prettier line charts: Optimised slope calculation by using a holding FloatArray to avoid recalculation, and using null is also avoided by this --- .../co/yml/charts/ui/linechart/LineChart.kt | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt index ee5249b8..90afa302 100644 --- a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt +++ b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt @@ -452,7 +452,11 @@ fun DrawScope.drawShadowUnderLineAndIntersectionPoint( } private fun getInBetweenSlope(m1: Float, m2: Float) : Float { - return (m1 + m2) / ( 1 - m1 * m2 + ((m1.pow(2)+1) * (m2.pow(2) + 1)).pow(0.5f) ) + return if (m1 == m2) m1 else (m1 + m2) / ( 1 - m1 * m2 + ((m1.pow(2)+1) * (m2.pow(2) + 1)).pow(0.5f) ) +} + +private fun getSlope(p1: Offset, p2: Offset): Float { + return (p2.y-p1.y) / (p2.x-p1.x) } /** * @@ -464,31 +468,34 @@ fun getCubicPoints(pointsData: List): Pair, MutableL val cubicPoints1 = mutableListOf() val cubicPoints2 = mutableListOf() + val slopes = FloatArray(pointsData.size+1) + for (i in 1 until pointsData.size) { - val p1x = ( (pointsData[i-1].x * 2f) + pointsData[i].x)/3f - val nextP = if (i < pointsData.size-1) pointsData[i+1] else null - val prevP = if (i >1 ) pointsData[i-2] else null - val currSlope = (pointsData[i].y - pointsData[i-1].y) / (pointsData[i].x - pointsData[i-1].x) - val nextSlope = if (nextP!=null) (nextP.y - pointsData[i].y) / (nextP.x - pointsData[i].x) else null - val prevSlope = if (prevP!=null) (pointsData[i-1].y - prevP.y) / (pointsData[i-1].x - prevP.x) else null - - val p1slope = if (prevSlope !=null) getInBetweenSlope(prevSlope, currSlope) else currSlope - val p1c = pointsData[i-1].y - (p1slope * pointsData[i-1].x) - val p1y = (p1slope * p1x) + p1c - - val p2x = (pointsData[i-1].x + (pointsData[i].x * 2f))/3f - val p2slope = if (nextSlope !=null) getInBetweenSlope(currSlope, nextSlope) else currSlope - val p2c = pointsData[i].y - (p2slope * pointsData[i].x) - val p2y = (p2slope * p2x ) + p2c + val currSlope = if (i == 1) getSlope(pointsData[1], pointsData[0]) else slopes [i] + + val nextSlope = if (i < pointsData.size-1) getSlope (pointsData[i+1], pointsData[i]) else currSlope + slopes[i+1] = nextSlope + + val prevSlope = if (i >1 ) slopes[i-1] else currSlope + + val cp1x = ( (pointsData[i-1].x * 2f) + pointsData[i].x)/3f + val cp1slope = getInBetweenSlope(prevSlope, currSlope) + val cp1c = pointsData[i-1].y - (cp1slope * pointsData[i-1].x) + val cp1y = (cp1slope * cp1x) + cp1c + + val cp2x = (pointsData[i-1].x + (pointsData[i].x * 2f))/3f + val cp2slope = getInBetweenSlope(currSlope, nextSlope) + val cp2c = pointsData[i].y - (cp2slope * pointsData[i].x) + val cp2y = (cp2slope * cp2x ) + cp2c cubicPoints1.add( Offset( - p1x, p1y + cp1x, cp1y ) ) cubicPoints2.add( Offset( - p2x, p2y + cp2x, cp2y ) ) } From 9282a52acee7259f65bf765c6661d7439038d279 Mon Sep 17 00:00:00 2001 From: Mohan Noone Date: Fri, 1 Dec 2023 15:11:52 +0530 Subject: [PATCH 4/7] Advanced Cubic Bezier algorithm for prettier line charts: Further optimised slopes array by removing redundant entry of last value --- .../java/co/yml/charts/ui/linechart/LineChart.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt index 90afa302..922e699b 100644 --- a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt +++ b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt @@ -468,17 +468,23 @@ fun getCubicPoints(pointsData: List): Pair, MutableL val cubicPoints1 = mutableListOf() val cubicPoints2 = mutableListOf() - val slopes = FloatArray(pointsData.size+1) + val slopes = FloatArray(pointsData.size) for (i in 1 until pointsData.size) { val currSlope = if (i == 1) getSlope(pointsData[1], pointsData[0]) else slopes [i] - val nextSlope = if (i < pointsData.size-1) getSlope (pointsData[i+1], pointsData[i]) else currSlope - slopes[i+1] = nextSlope + val nextSlope: Float + if (i < pointsData.size-1) { + nextSlope = getSlope(pointsData[i + 1], pointsData[i]) + slopes[i+1] = nextSlope + } + else { + nextSlope = currSlope + } val prevSlope = if (i >1 ) slopes[i-1] else currSlope - val cp1x = ( (pointsData[i-1].x * 2f) + pointsData[i].x)/3f + val cp1x = ((pointsData[i-1].x * 2f) + pointsData[i].x)/3f val cp1slope = getInBetweenSlope(prevSlope, currSlope) val cp1c = pointsData[i-1].y - (cp1slope * pointsData[i-1].x) val cp1y = (cp1slope * cp1x) + cp1c From 786fc5ec4cad633388cc6c27cc59f2ef3552771a Mon Sep 17 00:00:00 2001 From: Mohan Noone Date: Sat, 2 Dec 2023 10:52:21 +0530 Subject: [PATCH 5/7] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 44a5e334..4737ce7d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,18 @@ # YCharts +This is a fork the YCharts library using Advanced Cubic Bezier algorithm for prettier line charts. +The algorithm is explained in detail at [https://medium.com/mobile-app-development-publication/making-graph-plotting-function-in-jetpack-compose-95c80ee6fc7f](https://medium.com/mobile-app-development-publication/making-graph-plotting-function-in-jetpack-compose-95c80ee6fc7f) +
+
+ +
+
+ Advanced Cubic Bezier +
+
+ +
+
+ Basic Cubic Bezier YCharts is a Jetpack-compose based charts library which enables developers to easily integrate various types of charts/graphs into their existing ui to visually represent statistical data. YCharts supports both cartesian(XY-charts) and polar charts(Radial charts), which include: From dc4132ca2b881bce38b150074158cc25b8a8a0c3 Mon Sep 17 00:00:00 2001 From: Mohan Noone Date: Sat, 2 Dec 2023 22:58:56 +0530 Subject: [PATCH 6/7] Advanced Cubic Bezier algorithm for prettier line charts: Optimised in-between slope calculation by replacing 'pow' with 'sqrt' and self multiplication --- .../src/main/java/co/yml/charts/ui/linechart/LineChart.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt index 922e699b..f704c1be 100644 --- a/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt +++ b/YChartsLib/src/main/java/co/yml/charts/ui/linechart/LineChart.kt @@ -59,7 +59,7 @@ import co.yml.charts.ui.linechart.model.LineType import co.yml.charts.ui.linechart.model.SelectionHighlightPoint import co.yml.charts.ui.linechart.model.SelectionHighlightPopUp import kotlinx.coroutines.launch -import kotlin.math.pow +import kotlin.math.sqrt /** * @@ -452,7 +452,7 @@ fun DrawScope.drawShadowUnderLineAndIntersectionPoint( } private fun getInBetweenSlope(m1: Float, m2: Float) : Float { - return if (m1 == m2) m1 else (m1 + m2) / ( 1 - m1 * m2 + ((m1.pow(2)+1) * (m2.pow(2) + 1)).pow(0.5f) ) + return if (m1 == m2) m1 else (m1 + m2) / ( 1 - m1 * m2 + sqrt(((m1*m1) + 1) * ((m2*m2) + 1)) ) } private fun getSlope(p1: Offset, p2: Offset): Float { From 303183ba56739a73465f9cdfc45752f64766ba6e Mon Sep 17 00:00:00 2001 From: Mohan Noone Date: Tue, 17 Dec 2024 10:57:13 +0530 Subject: [PATCH 7/7] Further dependency fixes --- YChartsLib/build.gradle.kts | 2 +- build.gradle.kts | 2 +- gradle/libs.versions.toml | 42 ++++++++++++++++++------------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/YChartsLib/build.gradle.kts b/YChartsLib/build.gradle.kts index b99e40b6..37f47887 100644 --- a/YChartsLib/build.gradle.kts +++ b/YChartsLib/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } android { - compileSdk = 33 + compileSdk = 35 namespace = "co.yml.charts.components" defaultConfig { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/build.gradle.kts b/build.gradle.kts index b1b684b9..efa4ed35 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ buildscript { dependencies { classpath(versionCatalogLibs.android.gradle.plugin) classpath(versionCatalogLibs.kotlin.gradle.plugin) - classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.8.20") + classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.9.23") } tasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 731b9783..e4ba9897 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,47 +2,47 @@ # Define the dependency versions #Project #kotlin -kotlinVersion = "1.8.20" +kotlinVersion = "1.9.21" kotlinxSerializationJson = "1.5.0" #gradle androidGradlePlugin = "8.0.2" #Modules #androidx -androidxComposeBom = "2023.05.01" -androidxActivityCompose = "1.7.0" -androidxAppCompat = "1.6.1" -androidxComposeCompiler = "1.4.6" -androidxCore = "1.9.0" -androidxLifecycle = "2.6.1" -androidxNavigation = "2.5.3" -androidxEspresso = "3.5.1" -androidxTestRules = "1.5.0" -androidxTestRunner = "1.5.2" -androidxTestMonitor = "1.6.1" -androidxTestCore = "1.5.0" -androidxTestExt = "1.1.5" +androidxComposeBom = "2024.12.01" +androidxActivityCompose = "1.9.3" +androidxAppCompat = "1.7.0" +androidxComposeCompiler = "1.5.7" +androidxCore = "1.15.0" +androidxLifecycle = "2.8.7" +androidxNavigation = "2.8.5" +androidxEspresso = "3.6.1" +androidxTestRules = "1.6.1" +androidxTestRunner = "1.6.2" +androidxTestMonitor = "1.7.2" +androidxTestCore = "1.6.1" +androidxTestExt = "1.2.1" androidxCrypto = "1.1.0-alpha04" androidxDatastore = "1.0.0" #compose -compose_ui = "1.4.3" -compose_ui_testing = "1.4.3" +compose_ui = "1.7.6" +compose_ui_testing = "1.7.6" compose_constraint_layout = "1.1.0-alpha05" #ktx -core_ktx = "1.9.0" +core_ktx = "1.15.0" #coroutine -coroutine = "1.7.1" +coroutine = "1.8.1" turbine = "1.0.0" #android test junit4 = "4.13.2" -android_junit = "1.1.5" +android_junit = "1.2.1" espresso = "3.5.0" jupiter_junit = "5.9.2" #Mock -anotation = "1.6.0" +anotation = "1.9.1" mockk_android = "1.13.4" -test_runner = "1.5.2" +test_runner = "1.6.2" #sonar sonar = "4.0.0.2929" #dokka