From 3df75415056ecfefc26d7d223f1fc4af27ac3c9c Mon Sep 17 00:00:00 2001 From: Avior Date: Thu, 16 Feb 2023 10:07:41 +0100 Subject: [PATCH] feat: Make sample more customizable (#43) --- README.md | 2 +- build.gradle.kts | 3 + .../main/java/com/dzeio/charts/ChartView.kt | 12 +- .../main/java/com/dzeio/charts/axis/XAxis.kt | 15 +- .../main/java/com/dzeio/charts/axis/YAxis.kt | 2 +- .../com/dzeio/charts/components/Annotation.kt | 5 + .../com/dzeio/charts/series/BarSerie.kt.old | 160 ---------- sample/build.gradle.kts | 23 +- sample/src/main/AndroidManifest.xml | 4 +- .../com/dzeio/chartsapp/ui/ChartFragment.kt | 178 ++++++++++++ .../com/dzeio/chartsapp/ui/MainActivity.kt | 97 +++++++ .../com/dzeio/chartsapp/ui/MainFragment.kt | 93 ++++++ .../dzeio/chartsapp/utils/MaterialUtils.kt | 57 ++++ .../java/com/dzeio/chartsapp/utils/Utils.kt | 19 ++ .../com/dzeio/chartstest/ui/MainActivity.kt | 19 -- .../com/dzeio/chartstest/ui/MainFragment.kt | 273 ------------------ .../src/main/res/drawable/shape_divider.xml | 6 - sample/src/main/res/layout/activity_main.xml | 55 +++- sample/src/main/res/layout/fragment_chart.xml | 216 ++++++++++++++ sample/src/main/res/layout/fragment_main.xml | 46 +-- sample/src/main/res/navigation/nav_graph.xml | 27 +- .../res/values/ic_launcher_background.xml | 4 - sample/src/main/res/values/strings.xml | 3 - sample/src/main/res/values/themes.xml | 10 +- 24 files changed, 798 insertions(+), 531 deletions(-) delete mode 100644 library/src/main/java/com/dzeio/charts/series/BarSerie.kt.old create mode 100644 sample/src/main/java/com/dzeio/chartsapp/ui/ChartFragment.kt create mode 100644 sample/src/main/java/com/dzeio/chartsapp/ui/MainActivity.kt create mode 100644 sample/src/main/java/com/dzeio/chartsapp/ui/MainFragment.kt create mode 100644 sample/src/main/java/com/dzeio/chartsapp/utils/MaterialUtils.kt create mode 100644 sample/src/main/java/com/dzeio/chartsapp/utils/Utils.kt delete mode 100644 sample/src/main/java/com/dzeio/chartstest/ui/MainActivity.kt delete mode 100644 sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt delete mode 100644 sample/src/main/res/drawable/shape_divider.xml create mode 100644 sample/src/main/res/layout/fragment_chart.xml delete mode 100644 sample/src/main/res/values/ic_launcher_background.xml delete mode 100644 sample/src/main/res/values/strings.xml diff --git a/README.md b/README.md index 62cdde4..a3cfed8 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ chart.refresh()

-_note: Every charts used above used a helper function to have Material 3 colors [See MainFragment.kt the materialTheme function](./sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt)_ +_note: Every charts used above used a helper function to have Material 3 colors [See the MaterialUtils.kt class](sample/src/main/java/com/dzeio/chartsapp/utils/MaterialUtils.kt)_ ## Build diff --git a/build.gradle.kts b/build.gradle.kts index 5652e57..5628f89 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,9 @@ buildscript { } dependencies { classpath("com.android.tools.build:gradle:7.4.1") + + // Safe Navigation + classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3") } } diff --git a/library/src/main/java/com/dzeio/charts/ChartView.kt b/library/src/main/java/com/dzeio/charts/ChartView.kt index 910f657..99ccb35 100644 --- a/library/src/main/java/com/dzeio/charts/ChartView.kt +++ b/library/src/main/java/com/dzeio/charts/ChartView.kt @@ -22,7 +22,7 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet View(context, attrs), ChartViewInterface { private companion object { - const val TAG = "Charts/ChartView" + const val TAG = "ChartView" } override val animator: Animation = Animation() @@ -97,6 +97,10 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet refresh() } + setOnToggleScroll { + // note: true == no scroll + parent?.requestDisallowInterceptTouchEvent(!it && (yAxis.scrollEnabled || xAxis.scrollEnabled)) + } setOnChartClick { x, y -> if (getDataset().isEmpty()) { return@setOnChartClick @@ -161,15 +165,11 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet // invalidate the view invalidate() -// removeCallbacks(animator) -// post(animator) } - private var lastRun = runUpdates - override fun onDraw(canvas: Canvas) { // don't draw anything if everything is empty - if (!runUpdates && lastRun == runUpdates && series.isEmpty() || series.maxOf { it.entries.size } == 0) { + if (!runUpdates || series.isEmpty() || series.maxOf { it.entries.size } == 0) { super.onDraw(canvas) return } diff --git a/library/src/main/java/com/dzeio/charts/axis/XAxis.kt b/library/src/main/java/com/dzeio/charts/axis/XAxis.kt index 07406f8..7cf7da5 100644 --- a/library/src/main/java/com/dzeio/charts/axis/XAxis.kt +++ b/library/src/main/java/com/dzeio/charts/axis/XAxis.kt @@ -92,18 +92,14 @@ class XAxis( var maxHeight = 0f - val graphIncrement = space.width() / (labelCount - 1) - val valueIncrement = getDataWidth() / (labelCount - 1) + val valueIncrement = getDataWidth() / (labelCount - 1).coerceAtLeast(1) for (index in 0 until labelCount) { val text = onValueFormat(x + valueIncrement * index) textPaint.getTextBounds(text, 0, text.length, rect) + getPositionOnRect(valueIncrement, space) maxHeight = maxHeight.coerceAtLeast(rect.height().toFloat() + 1) - var xPos = space.left + graphIncrement * index - - if (xPos + rect.width() > space.right) { - xPos = space.right - rect.width() - } + val xPos = getPositionOnRect(x + valueIncrement * index, space).toFloat() canvas.drawText( text, @@ -134,8 +130,8 @@ class XAxis( .coerceIn(1.0, drawableSpace.width().toDouble()) // handle grouped series - if (view.type == ChartType.GROUPED) { - return result / view.series.size - spacing / 2 * (view.series.size - 1) + if (view.type == ChartType.GROUPED && view.series.size > 1) { + return ((result - (spacing / 2 * view.series.size)) / view.series.size).coerceAtLeast(1.0) } return result @@ -145,5 +141,4 @@ class XAxis( // TODO: handle the auto dataWidth better (still not sure it is good enough) return dataWidth ?: (getXMax() - getXMin() + 1) } - } diff --git a/library/src/main/java/com/dzeio/charts/axis/YAxis.kt b/library/src/main/java/com/dzeio/charts/axis/YAxis.kt index bba96a3..95f07bf 100644 --- a/library/src/main/java/com/dzeio/charts/axis/YAxis.kt +++ b/library/src/main/java/com/dzeio/charts/axis/YAxis.kt @@ -162,7 +162,7 @@ class YAxis( val max = getYMax() - min var maxWidth = 0f - val valueIncrement = max / (labelCount - 1) + val valueIncrement = max / (labelCount - 1).coerceAtLeast(1) for (index in 0 until labelCount) { val value = min + (valueIncrement * index) diff --git a/library/src/main/java/com/dzeio/charts/components/Annotation.kt b/library/src/main/java/com/dzeio/charts/components/Annotation.kt index 84b55e7..6a1262b 100644 --- a/library/src/main/java/com/dzeio/charts/components/Annotation.kt +++ b/library/src/main/java/com/dzeio/charts/components/Annotation.kt @@ -73,6 +73,11 @@ class Annotation( val xCenter = view.xAxis.getEntryWidth(space) / 2.0 + x + if (xCenter < space.left || xCenter > space.right) { + entry = null + return + } + val xText = annotationSubTitleFormat.invoke(entry!!) val yText = annotationTitleFormat.invoke(entry!!) diff --git a/library/src/main/java/com/dzeio/charts/series/BarSerie.kt.old b/library/src/main/java/com/dzeio/charts/series/BarSerie.kt.old deleted file mode 100644 index b6205e0..0000000 --- a/library/src/main/java/com/dzeio/charts/series/BarSerie.kt.old +++ /dev/null @@ -1,160 +0,0 @@ -package com.dzeio.charts.series - -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.Rect -import android.graphics.RectF -import android.util.Log -import kotlin.math.max - -class BarSerie : SerieAbstract() { - - companion object { - const val TAG = "DzeioCharts/BarSerie" - } - - var spacing: Float = 8f - - /** - * Values displayed on the grapd - */ - var displayedDatas = arrayListOf() - - /** - * Target values - */ - var targetDatas = arrayListOf() - - var targetPercentList = arrayListOf() - var percentList = arrayListOf() - - var previousRefresh = 0 - - private var fgPaint: Paint = Paint().apply { - isAntiAlias = true - } - - private val r = Rect() - - override fun onUpdate(): Boolean { - var needNewFrame = false - for (i in targetPercentList.indices) { - val value = view.animation.updateValue( - 1f, - targetPercentList[i], - percentList[i], - 0f, - 0.00f - ) - - if (value != percentList[i]) { - needNewFrame = true - percentList[i] = value - } - } - return needNewFrame - } - - override fun prepareData() { - val max: Float = if (view.yAxis.max != null) view.yAxis.max!! else { - getYMax(true) - } - - targetPercentList = arrayListOf() - -// Log.d(TAG, "offset: ${view.getXOffset()}, displayed: ${view.getDisplayedEntries()}") - for (item in getDisplayedEntries()) { -// // // Process bottom texts -// val text = view.xAxis.onValueFormat(item.x) -// bottomTexts.add(text) -// -// // get Text boundaries -// view.xAxis.labels.build().getTextBounds(text, 0, text.length, r) -// -// // get height of text -// if (bottomTextHeight < r.height()) { -// bottomTextHeight = r.height() -// } -// -// // get text descent -// val descent = abs(r.bottom) -// if (bottomTextDescent < descent) { -// bottomTextDescent = descent -// } - - // process values -// Log.d(TAG, item.y.toString()) - - // add to animations the values - targetPercentList.add(1 - item.y / max) - } - - // post list - val offset = view.getXOffset() - val movement = offset - previousRefresh - Log.d(TAG, "$offset - $previousRefresh = $movement") - if (movement != 0) { - previousRefresh = offset - } -// if (movement != 0) { -// Log.d(TAG, movement.toString()) -// } - if (movement >= 1) { - percentList = percentList.subList(1, percentList.size).toCollection(ArrayList()) - percentList.add(1f) - } else if (movement <= -1) { - percentList = percentList.subList(0, percentList.size - 1).toCollection(ArrayList()) - percentList.add(0, 1f) - } - - if (percentList.isEmpty() || percentList.size < targetPercentList.size) { - val temp = targetPercentList.size - percentList.size - for (i in 0 until temp) { - percentList.add(1f) - } - } else if (percentList.size > targetPercentList.size) { - val temp = percentList.size - targetPercentList.size - for (i in 0 until temp) { - percentList.removeAt(percentList.size - 1) - } - } - - fgPaint.color = view.yAxis.color - } - - override fun displayData(canvas: Canvas, rect: RectF) { - val barWidth = (rect.width() - view.padding * 2) / view.getDisplayedEntries() - spacing - - if (percentList.isNotEmpty()) { - // draw each rectangles - for (i in 1..percentList.size) { -// Log.d(TAG, percentList[i - 1].toString()) - val left = rect.left + spacing * i + barWidth * (i - 1).toFloat() + view.padding -// Log.d(TAG, "$spacing, $i, $barWidth = $left") - val right = rect.left + (spacing + barWidth) * i.toFloat() - val bottom = rect.top + rect.height() - view.padding - val top = (bottom - rect.top) * percentList[i - 1] + view.padding - - // create rounded rect - canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, fgPaint) - // remove the bottom corners DUH - canvas.drawRect(left, max(top, bottom - 8f), right, bottom, fgPaint) - val targetTop = (bottom - rect.top) * targetPercentList[i - 1] - - val text = view.yAxis.onValueFormat(getYMax(true) - getYMax(true) * targetPercentList[i - 1], true) - view.xAxis.labels.build().getTextBounds(text, 0, text.length, r) - val doDisplayIn = - r.width() + 10f < barWidth && bottom - targetTop > r.height() + 40f - if (view.debug || !doDisplayIn || (doDisplayIn && bottom - top > r.height() + 40f)) { - val y = if (doDisplayIn) top + r.height() + 20f else top - r.height() - canvas.drawText( - text, - left + (right - left) / 2, - y, - view.xAxis.labels.build() - ) - } - } - } - } -} diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 20bf19c..579121e 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -1,14 +1,23 @@ plugins { + // Android Application? id("com.android.application") + + // Support for kotlin in Android kotlin("android") + + // Safe Navigation + id("androidx.navigation.safeargs") + + // keep at bottom + kotlin("kapt") } android { - namespace = "com.dzeio.chartstest" + namespace = "com.dzeio.chartsapp" compileSdk = 33 defaultConfig { - applicationId = "com.dzeio.chartstest" + applicationId = "com.dzeio.chartsapp" minSdk = 21 targetSdk = 33 versionCode = 1 @@ -31,19 +40,21 @@ android { kotlinOptions { jvmTarget = "11" } - + buildFeatures { viewBinding = true + dataBinding = true } } dependencies { implementation(project(":library")) - + // Material Design - implementation("com.google.android.material:material:1.7.0") + implementation("com.google.android.material:material:1.8.0") // Navigation because I don't want to maintain basic transactions and shit implementation("androidx.navigation:navigation-fragment-ktx:2.5.3") -} \ No newline at end of file + implementation("androidx.navigation:navigation-ui-ktx:2.5.3") +} diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 1b3c192..d9982ea 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ - \ No newline at end of file + diff --git a/sample/src/main/java/com/dzeio/chartsapp/ui/ChartFragment.kt b/sample/src/main/java/com/dzeio/chartsapp/ui/ChartFragment.kt new file mode 100644 index 0000000..35164e7 --- /dev/null +++ b/sample/src/main/java/com/dzeio/chartsapp/ui/ChartFragment.kt @@ -0,0 +1,178 @@ +package com.dzeio.chartsapp.ui + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs +import com.dzeio.charts.ChartType +import com.dzeio.charts.ChartViewInterface +import com.dzeio.charts.Entry +import com.dzeio.charts.series.BarSerie +import com.dzeio.charts.series.LineSerie +import com.dzeio.charts.series.SerieInterface +import com.dzeio.chartsapp.databinding.FragmentChartBinding +import com.dzeio.chartsapp.utils.MaterialUtils +import com.dzeio.chartsapp.utils.Utils +import kotlin.math.roundToInt +import kotlin.random.Random + +class ChartFragment : Fragment() { + private var _binding: FragmentChartBinding? = null + + private val args: ChartFragmentArgs by navArgs() + + private var numberOfValues = 5 + + private lateinit var chart: ChartViewInterface + private val binding: FragmentChartBinding get() = _binding!! + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + _binding = FragmentChartBinding.inflate(inflater, container, false) + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + chart = binding.chart + addSerie() + + MaterialUtils.materielTheme(chart, requireView()) + + chart.refresh() + + binding.addValue.setOnClickListener { + chart.series.forEach { + it.entries.add( + Entry( + it.entries.size.toDouble(), + Random.nextInt(0, 100).toFloat() + ) + ) + } + numberOfValues++ + chart.refresh() + } + + binding.removeValue.setOnClickListener { + chart.series.forEach { + it.entries.removeLastOrNull() + } + numberOfValues-- + chart.refresh() + } + + binding.addSerie.setOnClickListener { + addSerie() + chart.refresh() + } + + binding.removeSerie.setOnClickListener { + chart.series.removeLastOrNull() + chart.refresh() + } + + binding.switchSubtype.setOnClickListener { + when (chart.type) { + ChartType.BASIC -> { + chart.type = ChartType.GROUPED + binding.switchSubtype.setText("Grouped Chart") + } + ChartType.GROUPED -> { + chart.type = ChartType.STACKED + binding.switchSubtype.setText("Stacked Chart") + } + else -> { + chart.type = ChartType.BASIC + binding.switchSubtype.setText("Basic Chart") + } + } + chart.refresh() + } + + binding.switchAnimations.setOnCheckedChangeListener { _, isChecked -> + chart.animator.enabled = isChecked + } + + binding.switchXAxis.setOnCheckedChangeListener { _, isChecked -> + chart.xAxis.enabled = isChecked + chart.refresh() + } + + binding.switchYAxis.setOnCheckedChangeListener { _, isChecked -> + chart.yAxis.enabled = isChecked + chart.refresh() + } + + binding.sliderXAxis.addOnChangeListener { _, value, _ -> + chart.xAxis.labelCount = value.roundToInt() + chart.refresh() + } + + binding.sliderYAxis.addOnChangeListener { _, value, _ -> + chart.yAxis.labelCount = value.roundToInt() + chart.refresh() + } + + binding.switchXAxisScrollable.setOnCheckedChangeListener { _, isChecked -> + chart.xAxis.scrollEnabled = isChecked + if (isChecked) { + chart.xAxis.dataWidth = binding.sliderXAxisScroll.value.toDouble() + binding.sliderXAxisScroll.visibility = View.VISIBLE + } else { + chart.xAxis.dataWidth = null + chart.xAxis.x = 0.0 + binding.sliderXAxisScroll.visibility = View.GONE + } + chart.refresh() + } + + binding.sliderXAxisScroll.visibility = View.GONE + binding.sliderXAxisScroll.addOnChangeListener { _, value, _ -> + if (chart.xAxis.dataWidth != null) { + chart.xAxis.dataWidth = value.toDouble() + chart.refresh() + } + } + } + + private var lastGenerated = 0 + private fun addSerie(): SerieInterface { + val toGet = if (args.chartType == null) { + if (lastGenerated == 0) { + "barchart" + } else { + "linechart" + } + } else { + args.chartType + } + if (lastGenerated++ >= 1) { + lastGenerated = 0 + } + val serie = if (toGet === "barchart") { + BarSerie(chart).apply { + barPaint.color = randomColor() + } + } else { + LineSerie(chart).apply { + linePaint.color = randomColor() + } + } + serie.entries = Utils.generateRandomDataset(numberOfValues) + return serie + } + + private fun randomColor(): Int { + return Color.argb( + 255, + Random.nextInt(), + Random.nextInt(), + Random.nextInt() + ) + } +} diff --git a/sample/src/main/java/com/dzeio/chartsapp/ui/MainActivity.kt b/sample/src/main/java/com/dzeio/chartsapp/ui/MainActivity.kt new file mode 100644 index 0000000..c6a7473 --- /dev/null +++ b/sample/src/main/java/com/dzeio/chartsapp/ui/MainActivity.kt @@ -0,0 +1,97 @@ +package com.dzeio.chartsapp.ui + +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import android.view.WindowManager +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.WindowCompat +import androidx.core.view.updatePadding +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import com.dzeio.chartsapp.R +import com.dzeio.chartsapp.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + + private lateinit var appBarConfiguration: AppBarConfiguration + + private lateinit var navController: NavController + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) + + // Comportement chelou API 28- + // Comportement normal 31+ + + // do not do the cool status/navigation bars for API 29 & 30 + if (Build.VERSION.SDK_INT != Build.VERSION_CODES.R && Build.VERSION.SDK_INT != Build.VERSION_CODES.Q) { + // allow to put the content behind the status bar & Navigation bar (one of them at least lul) + // ALSO: make the status/navigation bars semi-transparent + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + + // Make the color of the navigation bar semi-transparent +// window.navigationBarColor = Color.TRANSPARENT + // Make the color of the status bar transparent +// window.statusBarColor = Color.TRANSPARENT + // Apply the previous changes +// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + + // Update toolbar height with the statusbar size included + // ALSO: make both the status/navigation bars transparent (WHYYYYYYY) + val toolbarHeight = binding.toolbar.layoutParams.height + window.decorView.setOnApplyWindowInsetsListener { _, insets -> + val statusBarSize = insets.systemWindowInsetTop + // Add padding to the toolbar (YaY I know how something works) + binding.toolbar.updatePadding(top = statusBarSize) + binding.toolbar.layoutParams.height = toolbarHeight + statusBarSize + return@setOnApplyWindowInsetsListener insets + } + + // normally makes sure icons are at the correct color but idk if it works + when (this.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) { + Configuration.UI_MODE_NIGHT_YES -> { + WindowCompat.getInsetsController(window, window.decorView).apply { + // force to display the bars in light color + isAppearanceLightNavigationBars = true + isAppearanceLightStatusBars = false // WHY + } + } + Configuration.UI_MODE_NIGHT_NO, Configuration.UI_MODE_NIGHT_UNDEFINED -> { + WindowCompat.getInsetsController(window, window.decorView).apply { + // force to display the bars in dark color + isAppearanceLightNavigationBars = false + isAppearanceLightStatusBars = true // WHY + } + } + } + } + + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + + navController = navHostFragment.navController + + appBarConfiguration = AppBarConfiguration( + setOf( + R.id.main_fragment + ) + ) + + setupActionBarWithNavController(navController, appBarConfiguration) + } + + override fun onSupportNavigateUp(): Boolean = + navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() +} diff --git a/sample/src/main/java/com/dzeio/chartsapp/ui/MainFragment.kt b/sample/src/main/java/com/dzeio/chartsapp/ui/MainFragment.kt new file mode 100644 index 0000000..3590d22 --- /dev/null +++ b/sample/src/main/java/com/dzeio/chartsapp/ui/MainFragment.kt @@ -0,0 +1,93 @@ +package com.dzeio.chartsapp.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.dzeio.charts.series.BarSerie +import com.dzeio.charts.series.LineSerie +import com.dzeio.chartsapp.databinding.FragmentMainBinding +import com.dzeio.chartsapp.utils.MaterialUtils +import com.dzeio.chartsapp.utils.Utils + +class MainFragment : Fragment() { + private var _binding: FragmentMainBinding? = null + private val binding get() = _binding!! + + private val months = arrayListOf( + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + ) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentMainBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.gotoBarchart.setOnClickListener { + findNavController().navigate( + MainFragmentDirections.actionMainFragmentToChartFragment("barchart") + ) + } + + binding.gotoLinechart.setOnClickListener { + findNavController().navigate( + MainFragmentDirections.actionMainFragmentToChartFragment("linechart") + ) + } + + binding.gotoBarLineChart.setOnClickListener { + findNavController().navigate( + MainFragmentDirections.actionMainFragmentToChartFragment(null) + ) + } + + binding.barchart.apply { + BarSerie(this).apply { + entries = Utils.generateRandomDataset(5) + } + MaterialUtils.materielTheme(this, requireView()) + } + + binding.linechart.apply { + LineSerie(this).apply { + entries = Utils.generateRandomDataset(5) + } + MaterialUtils.materielTheme(this, requireView()) + } + + binding.bothchart.apply { + BarSerie(this).apply { + entries = Utils.generateRandomDataset(5) + } + LineSerie(this).apply { + entries = Utils.generateRandomDataset(5) + } + MaterialUtils.materielTheme(this, requireView()) + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/sample/src/main/java/com/dzeio/chartsapp/utils/MaterialUtils.kt b/sample/src/main/java/com/dzeio/chartsapp/utils/MaterialUtils.kt new file mode 100644 index 0000000..346b9c1 --- /dev/null +++ b/sample/src/main/java/com/dzeio/chartsapp/utils/MaterialUtils.kt @@ -0,0 +1,57 @@ +package com.dzeio.chartsapp.utils + +import android.view.View +import com.dzeio.charts.ChartViewInterface +import com.dzeio.charts.series.BarSerie +import com.dzeio.charts.series.LineSerie +import com.google.android.material.color.MaterialColors + +object MaterialUtils { + fun materielTheme(chart: ChartViewInterface, view: View) { + chart.yAxis.apply { + textLabel.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer) + linePaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer) + goalLinePaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorError) + } + + chart.xAxis.textPaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer) + chart.annotator.apply { + backgroundPaint.color = MaterialColors.getColor( + view, + com.google.android.material.R.attr.colorBackgroundFloating + ) + titlePaint.color = MaterialColors.getColor( + view, + com.google.android.material.R.attr.colorOnPrimaryContainer + ) + subTitlePaint.color = MaterialColors.getColor( + view, + com.google.android.material.R.attr.colorOnPrimaryContainer + ) + } + + for (serie in chart.series) { + if (serie is BarSerie) { + serie.apply { + barPaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorPrimary) + textPaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimary) + textExternalPaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorPrimary) + } + } else if (serie is LineSerie) { + serie.apply { + linePaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorPrimary) + textPaint.color = + MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimary) + } + } + } + } +} diff --git a/sample/src/main/java/com/dzeio/chartsapp/utils/Utils.kt b/sample/src/main/java/com/dzeio/chartsapp/utils/Utils.kt new file mode 100644 index 0000000..9d2bec4 --- /dev/null +++ b/sample/src/main/java/com/dzeio/chartsapp/utils/Utils.kt @@ -0,0 +1,19 @@ +package com.dzeio.chartsapp.utils + +import com.dzeio.charts.Entry +import kotlin.random.Random.Default.nextInt + +object Utils { + fun generateRandomDataset(size: Int = 100, min: Int = 0, max: Int = 100, xStep: Int = 1): ArrayList { + val dataset: ArrayList = ArrayList() + for (i in 0 until size) { + dataset.add( + Entry( + (i * xStep).toDouble(), + nextInt(min, max).toFloat() + ) + ) + } + return dataset + } +} diff --git a/sample/src/main/java/com/dzeio/chartstest/ui/MainActivity.kt b/sample/src/main/java/com/dzeio/chartstest/ui/MainActivity.kt deleted file mode 100644 index 583cc93..0000000 --- a/sample/src/main/java/com/dzeio/chartstest/ui/MainActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.dzeio.chartstest.ui - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.WindowCompat -import com.dzeio.chartstest.databinding.ActivityMainBinding - -class MainActivity : AppCompatActivity() { - - private lateinit var binding: ActivityMainBinding - - override fun onCreate(savedInstanceState: Bundle?) { - WindowCompat.setDecorFitsSystemWindows(window, false) - super.onCreate(savedInstanceState) - - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - } -} \ No newline at end of file diff --git a/sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt b/sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt deleted file mode 100644 index 0b02622..0000000 --- a/sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt +++ /dev/null @@ -1,273 +0,0 @@ -package com.dzeio.chartstest.ui - -import android.graphics.Color -import android.graphics.Paint -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import com.dzeio.charts.ChartType -import com.dzeio.charts.ChartView -import com.dzeio.charts.Entry -import com.dzeio.charts.axis.Line -import com.dzeio.charts.series.BarSerie -import com.dzeio.charts.series.LineSerie -import com.dzeio.chartstest.databinding.FragmentMainBinding -import com.google.android.material.color.MaterialColors -import kotlin.math.roundToInt -import kotlin.random.Random - -class MainFragment : Fragment() { - private var _binding: FragmentMainBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentMainBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.chartGrouped.apply { - // setup the Serie - val serie1 = BarSerie(this) - val serie2 = BarSerie(this) - - animator.duration = 750 - - // transform the chart into a grouped chart - type = ChartType.GROUPED - yAxis.setYMin(0f) - - // utils function to use Material3 auto colors - materielTheme(this, requireView()) - serie2.barPaint.color = Color.RED - - // give the serie it's entries - serie1.entries = generateRandomDataset(5) - serie2.entries = generateRandomDataset(5) - - // refresh the Chart - refresh() - } - - binding.chartStacked.apply { - // setup the Serie - val serie1 = BarSerie(this) - val serie2 = BarSerie(this) - - animator.duration = 750 - - // transform the chart into a grouped chart - type = ChartType.STACKED - yAxis.setYMin(0f) - - // utils function to use Material3 auto colors - materielTheme(this, requireView()) - serie2.barPaint.color = Color.RED - - // give the serie it's entries - serie1.entries = generateRandomDataset(10) - serie2.entries = generateRandomDataset(10) - - // refresh the Chart - refresh() - } - - binding.chartLine.apply { - // setup the Serie - val serie = LineSerie(this) - - // utils function to use Material3 auto colors - materielTheme(this, requireView()) - - // give the serie its entries - serie.entries = generateRandomDataset(10) - - // refresh the Chart - refresh() - } - - binding.chartBar.apply { - // setup the Serie - val serie = BarSerie(this) - yAxis.setYMin(0f) - - // utils function to use Material3 auto colors - materielTheme(this, requireView()) - - // give the serie its entries - serie.entries = generateRandomDataset(10) - - // refresh the Chart - refresh() - } - - binding.chartCustomization.apply { - // setup the Series - val serie1 = BarSerie(this) - val serie2 = LineSerie(this) - - // utils function to use Material3 auto colors - materielTheme(this, requireView()) - - // give the series their entries - serie2.entries = generateRandomDataset(20, -50, 50) - serie1.entries = generateRandomDataset(20, -50, 50).apply { - for (idx in 0 until size) { - val compared = serie2.entries[idx] - val toCompare = this[idx] - if (compared.y > toCompare.y) { - toCompare.color = Color.RED - } else { - toCompare.color = Color.GREEN - } - } - } - -// serie1.textExternalPaint = Color.WHITE - - // make the lineSerie red - serie2.linePaint.color = Color.WHITE - - // strokeWidth also control the points width - serie2.linePaint.strokeWidth = 10f - - yAxis.apply { - // Enable vertical scrolling - scrollEnabled = true - - // change the number of labels - labelCount = 11 - - // change how labels are displayed - onValueFormat = { "${it.roundToInt()}g" } - - // change labels colors - textLabel.color = Color.WHITE - - // change line color - linePaint.color = Color.WHITE - - // Add horizontal Lines - val paint: Paint = Paint(yAxis.linePaint).apply { - strokeWidth = 8f - } - addLine(10f, Line(true, paint)) - addLine(-10f, Line(true, paint)) - - // change the min/max high - setYMin(-20f) - setYMax(20f) - } - - xAxis.apply { - // Enable horizontal scrolling - scrollEnabled = true - - // set the width of the datas - dataWidth = 10.0 - - // change the number of labels displayed - labelCount = 5 - - // change the spacing between values (it can be overriden if size to to small) - spacing = 8.0 - - // set the offset in data (use with [dataWidth]) - x = 5.0 - } - - // refresh the Chart - refresh() - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - /** - * Generate a random dataset - */ - private fun generateRandomDataset(size: Int = 100, min: Int = 0, max: Int = 100): ArrayList { - val dataset: ArrayList = arrayListOf() - - for (i in 0 until size) { - dataset.add( - Entry( - i.toDouble(), - Random.nextInt(min, max).toFloat() - ) - ) - } - - return dataset - } - - /** - * Apply Material3 theme to a [ChartView] - */ - private fun materielTheme(chart: ChartView, view: View) { - chart.apply { - yAxis.apply { - textLabel.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorOnPrimaryContainer - ) - linePaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorOnPrimaryContainer - ) - goalLinePaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorError - ) - } - - xAxis.apply { - textPaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorOnPrimaryContainer - ) - } - - for (serie in series) { - if (serie is BarSerie) { - serie.apply { - barPaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorPrimary - ) - textPaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorOnPrimary - ) - textExternalPaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorPrimary - ) - } - } else if (serie is LineSerie) { - serie.apply { - linePaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorPrimary - ) - textPaint.color = MaterialColors.getColor( - view, - com.google.android.material.R.attr.colorOnPrimary - ) - } - } - } - } - } -} diff --git a/sample/src/main/res/drawable/shape_divider.xml b/sample/src/main/res/drawable/shape_divider.xml deleted file mode 100644 index a7e6373..0000000 --- a/sample/src/main/res/drawable/shape_divider.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 084b69a..df288f6 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -1,23 +1,52 @@ - - + + + > - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/layout/fragment_chart.xml b/sample/src/main/res/layout/fragment_chart.xml new file mode 100644 index 0000000..9bf58ff --- /dev/null +++ b/sample/src/main/res/layout/fragment_chart.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + +