feat: Make sample more customizable (#43)

This commit is contained in:
Florian Bouillon 2023-02-16 10:07:41 +01:00 committed by GitHub
parent 1a812acb9e
commit 3df7541505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 798 additions and 531 deletions

View File

@ -58,7 +58,7 @@ chart.refresh()
<img width="40%" src=".github/usage-example.jpg" />
</p>
_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

View File

@ -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")
}
}

View File

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

View File

@ -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)
}
}

View File

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

View File

@ -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!!)

View File

@ -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<Float>()
/**
* Target values
*/
var targetDatas = arrayListOf<Float>()
var targetPercentList = arrayListOf<Float>()
var percentList = arrayListOf<Float>()
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()
)
}
}
}
}
}

View File

@ -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
@ -34,6 +43,7 @@ android {
buildFeatures {
viewBinding = true
dataBinding = true
}
}
@ -42,8 +52,9 @@ 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")
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
}

View File

@ -3,7 +3,7 @@
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="Dzeio Charts"
android:theme="@style/Theme.Charts">
<activity
android:name=".ui.MainActivity"

View File

@ -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()
)
}
}

View File

@ -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()
}

View File

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

View File

@ -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)
}
}
}
}
}

View File

@ -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<Entry> {
val dataset: ArrayList<Entry> = ArrayList()
for (i in 0 until size) {
dataset.add(
Entry(
(i * xStep).toDouble(),
nextInt(min, max).toFloat()
)
)
}
return dataset
}
}

View File

@ -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)
}
}

View File

@ -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<Entry> {
val dataset: ArrayList<Entry> = 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
)
}
}
}
}
}
}

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:height="16dp"
android:width="0dp" />
</shape>

View File

@ -1,23 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:titleCentered="true" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.dzeio.charts.ChartView
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginBottom="16dp" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/remove_value"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Remove Value"
android:layout_weight="1"
android:layout_marginEnd="8dp" />
<Button
android:id="@+id/add_value"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Add Value"
android:layout_weight="1"
android:layout_marginStart="8dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/remove_serie"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Remove Serie"
android:layout_weight="1"
android:layout_marginEnd="8dp" />
<Button
android:id="@+id/add_serie"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Add Serie"
android:layout_weight="1"
android:layout_marginStart="8dp" />
</LinearLayout>
<Button
android:id="@+id/switch_subtype"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Basic Chart"
android:layout_marginTop="16dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_animations"
android:layout_width="match_parent"
android:layout_marginTop="8dp"
android:text="Switch Animations"
android:checked="true"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:text="X Axis manipulation"
android:textAlignment="center"
android:layout_height="wrap_content" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_x_axis"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_marginTop="8dp"
android:text="Enable X Axis"
android:checked="true"
android:layout_height="wrap_content" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:text="X Axis Number of labels"
android:textAlignment="center"
android:layout_height="wrap_content" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_x_axis"
android:layout_width="match_parent"
android:layout_marginTop="8dp"
android:value="2"
android:valueFrom="0"
android:valueTo="20"
android:stepSize="1"
android:layout_weight="1"
android:layout_height="wrap_content" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_x_axis_scrollable"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_marginTop="8dp"
android:checked="false"
android:text="scrollable"
android:layout_height="wrap_content" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:text="X Axis number of items in view"
android:textAlignment="center"
android:layout_height="wrap_content" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_x_axis_scroll"
android:layout_width="match_parent"
android:layout_marginTop="8dp"
android:value="5"
android:valueFrom="0"
android:valueTo="10"
android:stepSize="1"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:text="Y Axis manipulation"
android:textAlignment="center"
android:layout_height="wrap_content" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:text="Y Axis Number of labels"
android:textAlignment="center"
android:layout_height="wrap_content" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_y_axis"
android:layout_width="match_parent"
android:layout_marginTop="8dp"
android:text="Switch Y Axis"
android:checked="true"
android:layout_weight="1"
android:layout_height="wrap_content" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_y_axis"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_marginTop="8dp"
android:text="Switch X Axis"
android:checked="true"
android:value="5"
android:valueFrom="0"
android:stepSize="1"
android:valueTo="20"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".ui.MainFragment">
<ScrollView
@ -13,35 +14,44 @@
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:showDividers="middle"
android:divider="@drawable/shape_divider"
android:padding="16dp">
android:layout_height="wrap_content">
<com.dzeio.charts.ChartView
android:id="@+id/chart_grouped"
android:id="@+id/barchart"
android:layout_width="match_parent"
android:layout_height="200dp" />
android:layout_height="100dp" />
<Button
android:layout_width="match_parent"
android:id="@+id/goto_barchart"
android:text="BarChart"
android:layout_marginBottom="16dp"
android:layout_height="wrap_content"/>
<com.dzeio.charts.ChartView
android:id="@+id/chart_stacked"
android:id="@+id/linechart"
android:layout_width="match_parent"
android:layout_height="200dp" />
android:layout_height="100dp" />
<Button
android:layout_width="match_parent"
android:id="@+id/goto_linechart"
android:text="LineChart"
android:layout_marginBottom="16dp"
android:layout_height="wrap_content"/>
<com.dzeio.charts.ChartView
android:id="@+id/chart_customization"
android:id="@+id/bothchart"
android:layout_width="match_parent"
android:layout_height="200dp" />
android:layout_height="100dp" />
<com.dzeio.charts.ChartView
android:id="@+id/chart_bar"
<Button
android:layout_width="match_parent"
android:layout_height="200dp" />
<com.dzeio.charts.ChartView
android:id="@+id/chart_line"
android:layout_width="match_parent"
android:layout_height="200dp" />
android:id="@+id/goto_bar_line_chart"
android:text="Both barChart &amp; lineChart"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>

View File

@ -3,10 +3,27 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/MainFragment">
app:startDestination="@id/main_fragment">
<fragment
android:id="@+id/MainFragment"
android:name="com.dzeio.chartstest.ui.MainFragment"
tools:layout="@layout/fragment_main"/>
android:id="@+id/main_fragment"
android:name="com.dzeio.chartsapp.ui.MainFragment"
android:label="Dzeio Charts Examples"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_MainFragment_to_ChartFragment"
app:destination="@id/chart_fragment" />
</fragment>
<fragment
android:id="@+id/chart_fragment"
android:name="com.dzeio.chartsapp.ui.ChartFragment"
android:label="Test the chart"
tools:layout="@layout/fragment_chart">
<argument
android:name="chart_type"
app:nullable="true"
app:argType="string" />
</fragment>
</navigation>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -1,3 +0,0 @@
<resources>
<string name="app_name" translatable="false">Dzeio Charts</string>
</resources>

View File

@ -1,4 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Charts" parent="Theme.Material3.DynamicColors.DayNight" />
<resources>
<style name="Theme.Charts" parent="Theme.Material3.DynamicColors.DayNight">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>