feat: more charts | code cleanup

This commit is contained in:
rebelonion 2024-03-04 00:02:41 -06:00
parent 2673b7d9bc
commit 7a1ed4f83e
9 changed files with 782 additions and 790 deletions

View file

@ -211,7 +211,7 @@ class Query {
val statistics: NNUserStatisticTypes, val statistics: NNUserStatisticTypes,
@SerialName("siteUrl") @SerialName("siteUrl")
val siteUrl: String, val siteUrl: String,
) ): java.io.Serializable
@Serializable @Serializable
data class NNUserStatisticTypes( data class NNUserStatisticTypes(

View file

@ -0,0 +1,321 @@
package ani.dantotsu.profile
import android.content.Context
import android.graphics.Color
import android.util.TypedValue
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartModel
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartStackingType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartZoomType
import com.github.aachartmodel.aainfographics.aachartcreator.AADataElement
import com.github.aachartmodel.aainfographics.aachartcreator.AAOptions
import com.github.aachartmodel.aainfographics.aachartcreator.AASeriesElement
import com.github.aachartmodel.aainfographics.aachartcreator.aa_toAAOptions
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AADataLabels
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAItemStyle
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAScrollablePlotArea
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAStyle
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAYAxis
import com.github.aachartmodel.aainfographics.aatools.AAColor
class ChartBuilder {
companion object {
enum class ChartType {
OneDimensional, TwoDimensional
}
enum class StatType {
COUNT, TIME, MEAN_SCORE
}
enum class MediaType {
ANIME, MANGA
}
fun buildChart(
context: Context,
chartType: ChartType,
aaChartType: AAChartType,
statType: StatType,
mediaType: MediaType,
names: List<Any>,
statData: List<Number>,
xAxisName: String = "X Axis",
xAxisTickInterval: Int? = null,
polar: Boolean = false,
categories: List<String>? = null,
scrollPos: Float? = null,
): AAOptions {
val typedValue = TypedValue()
context.theme.resolveAttribute(
com.google.android.material.R.attr.colorPrimary,
typedValue,
true
)
val primaryColor = typedValue.data
val palette = generateColorPalette(primaryColor, names.size)
val aaChartModel = when (chartType) {
ChartType.OneDimensional -> {
val chart = AAChartModel()
.chartType(aaChartType)
.subtitle(getTypeName(statType, mediaType))
.zoomType(AAChartZoomType.None)
.dataLabelsEnabled(true)
.series(
get1DElements(
names,
statData,
palette,
primaryColor
)
)
xAxisTickInterval?.let { chart.xAxisTickInterval(it) }
categories?.let { chart.categories(it.toTypedArray()) }
chart
}
ChartType.TwoDimensional -> {
val hexColorsArray: Array<Any> =
palette.map { String.format("#%06X", 0xFFFFFF and it) }.toTypedArray()
val chart = AAChartModel()
.chartType(aaChartType)
.subtitle(getTypeName(statType, mediaType))
.zoomType(AAChartZoomType.None)
.dataLabelsEnabled(false)
.yAxisTitle(getTypeName(statType, mediaType))
.stacking(AAChartStackingType.Normal)
.series(get2DElements(names, statData, primaryColor))
.colorsTheme(hexColorsArray)
xAxisTickInterval?.let { chart.xAxisTickInterval(it) }
categories?.let { chart.categories(it.toTypedArray()) }
chart
}
}
val aaOptions = aaChartModel.aa_toAAOptions()
aaOptions.chart?.polar = polar
aaOptions.tooltip?.apply {
headerFormat
formatter(
getToolTipFunction(
chartType,
xAxisName,
getTypeName(statType, mediaType)
)
)
}
aaOptions.legend?.apply {
enabled(true)
.labelFormat = "{name}: {y}"
}
aaOptions.plotOptions?.series?.connectNulls(true)
aaOptions.chart?.panning = true
scrollPos?.let {
aaOptions.chart?.scrollablePlotArea(AAScrollablePlotArea().scrollPositionX(scrollPos))
aaOptions.chart?.scrollablePlotArea?.minWidth((context.resources.displayMetrics.widthPixels.toFloat() / context.resources.displayMetrics.density) * (names.size.toFloat() / 18.0f))
}
val min = statData.minOfOrNull { it.toDouble() } ?: 0.0
val max = statData.maxOfOrNull { it.toDouble() } ?: 0.0
val aaYaxis = AAYAxis().min(min).max(max)
val tickInterval = when (max) {
in 0.0..10.0 -> 1.0
in 10.0..30.0 -> 5.0
in 30.0..100.0 -> 10.0
in 100.0..1000.0 -> 100.0
in 1000.0..10000.0 -> 1000.0
else -> 10000.0
}
aaYaxis.tickInterval(tickInterval)
aaOptions.yAxis(aaYaxis)
setColors(aaOptions, context, primaryColor)
return aaOptions
}
private fun get2DElements(
names: List<Any>,
statData: List<Any>,
primaryColor: Int
): Array<Any> {
val statValues = mutableListOf<Array<Any>>()
for (i in statData.indices) {
statValues.add(arrayOf(names[i], statData[i], statData[i]))
}
return arrayOf(
AASeriesElement().name("Score")
.data(statValues.toTypedArray())
.dataLabels(
AADataLabels()
.enabled(false)
)
.colorByPoint(true)
.fillColor(AAColor.rgbaColor(
Color.red(primaryColor),
Color.green(primaryColor),
Color.blue(primaryColor),
0.9f
))
)
}
private fun get1DElements(
names: List<Any>,
statData: List<Number>,
colors: List<Int>,
primaryColor: Int
): Array<Any> {
val statDataElements = mutableListOf<AADataElement>()
for (i in statData.indices) {
val element = AADataElement()
.y(statData[i])
.color(
AAColor.rgbaColor(
Color.red(colors[i]),
Color.green(colors[i]),
Color.blue(colors[i]),
0.9f
)
)
if (names[i] is Number) {
element.x(names[i] as Number)
element.dataLabels(
AADataLabels()
.enabled(false)
.format("{point.y}")
.backgroundColor(AAColor.rgbaColor(255, 255, 255, 0.0f))
)
} else {
element.x(i)
element.name(names[i] as String)
}
statDataElements.add(element)
}
return arrayOf(
AASeriesElement().name("Score").color(primaryColor)
.data(statDataElements.toTypedArray())
)
}
private fun getTypeName(statType: StatType, mediaType: MediaType): String {
return when (statType) {
StatType.COUNT -> "Count"
StatType.TIME -> if (mediaType == MediaType.ANIME) "Hours Watched" else "Chapters Read"
StatType.MEAN_SCORE -> "Mean Score"
}
}
private fun generateColorPalette(
baseColor: Int,
size: Int,
hueDelta: Float = 8f,
saturationDelta: Float = 2.02f,
valueDelta: Float = 2.02f
): List<Int> {
val palette = mutableListOf<Int>()
val hsv = FloatArray(3)
Color.colorToHSV(baseColor, hsv)
for (i in 0 until size) {
val newHue =
(hsv[0] + hueDelta * i) % 360 // Ensure hue stays within the 0-360 range
val newSaturation = (hsv[1] + saturationDelta * i).coerceIn(0f, 1f)
val newValue = (hsv[2] + valueDelta * i).coerceIn(0f, 1f)
val newHsv = floatArrayOf(newHue, newSaturation, newValue)
palette.add(Color.HSVToColor(newHsv))
}
return palette
}
private fun setColors(aaOptions: AAOptions, context: Context, primaryColor: Int) {
val backgroundColor = TypedValue()
context.theme.resolveAttribute(
com.google.android.material.R.attr.colorSurfaceVariant,
backgroundColor,
true
)
val backgroundStyle = AAStyle().color(
AAColor.rgbaColor(
Color.red(backgroundColor.data),
Color.green(backgroundColor.data),
Color.blue(backgroundColor.data),
1f
)
)
val colorOnBackground = TypedValue()
context.theme.resolveAttribute(
com.google.android.material.R.attr.colorOnSurface,
colorOnBackground,
true
)
val onBackgroundStyle = AAStyle().color(
AAColor.rgbaColor(
Color.red(colorOnBackground.data),
Color.green(colorOnBackground.data),
Color.blue(colorOnBackground.data),
0.9f
)
)
aaOptions.chart?.backgroundColor(backgroundStyle.color)
aaOptions.tooltip?.backgroundColor(
AAColor.rgbaColor(
Color.red(primaryColor),
Color.green(primaryColor),
Color.blue(primaryColor),
0.9f
)
)
aaOptions.title?.style(onBackgroundStyle)
aaOptions.subtitle?.style(onBackgroundStyle)
aaOptions.tooltip?.style(backgroundStyle)
aaOptions.credits?.style(onBackgroundStyle)
aaOptions.xAxis?.labels?.style(onBackgroundStyle)
aaOptions.yAxis?.labels?.style(onBackgroundStyle)
aaOptions.plotOptions?.series?.dataLabels?.style(onBackgroundStyle)
aaOptions.plotOptions?.series?.dataLabels?.backgroundColor(backgroundStyle.color)
aaOptions.legend?.itemStyle(AAItemStyle().color(onBackgroundStyle.color))
aaOptions.touchEventEnabled(true)
}
private fun getToolTipFunction(
chartType: ChartType,
type: String,
typeName: String
): String {
return when (chartType) {
ChartType.OneDimensional -> {
"""
function () {
return this.point.name
+ ': <br/> '
+ '<b> '
+ this.y
+ ', '
+ (this.percentage).toFixed(2)
+ '%'
}
""".trimIndent()
}
ChartType.TwoDimensional -> {
"""
function () {
return '$type: ' +
this.x +
'<br/> ' +
' $typeName ' +
this.y
}
""".trimIndent()
}
}
}
}
}

View file

@ -0,0 +1,26 @@
package ani.dantotsu.profile
import android.view.View
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemChartBinding
import com.github.aachartmodel.aainfographics.aachartcreator.AAOptions
import com.xwray.groupie.viewbinding.BindableItem
class ChartItem(
private val title: String,
private val aaOptions: AAOptions): BindableItem<ItemChartBinding>() {
private lateinit var binding: ItemChartBinding
override fun bind(viewBinding: ItemChartBinding, position: Int) {
binding = viewBinding
binding.typeText.text = title
binding.chartView.aa_drawChartWithChartOptions(aaOptions)
}
override fun getLayout(): Int {
return R.layout.item_chart
}
override fun initializeViewBinding(view: View): ItemChartBinding {
return ItemChartBinding.bind(view)
}
}

View file

@ -13,6 +13,7 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.Query
@ -39,6 +40,7 @@ class ProfileActivity : AppCompatActivity(){
private lateinit var binding: ActivityProfileBinding private lateinit var binding: ActivityProfileBinding
private var selected: Int = 0 private var selected: Int = 0
private lateinit var navBar: AnimatedBottomBar private lateinit var navBar: AnimatedBottomBar
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -65,7 +67,7 @@ class ProfileActivity : AppCompatActivity(){
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.profileViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarHeight } binding.profileViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarHeight }
binding.profileViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, user, this@ProfileActivity) binding.profileViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, user)
navBar.visibility = View.VISIBLE navBar.visibility = View.VISIBLE
navBar.selectTabAt(selected) navBar.selectTabAt(selected)
navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
@ -129,20 +131,18 @@ class ProfileActivity : AppCompatActivity(){
} }
super.onResume() super.onResume()
} }
private class ViewPagerAdapter( private class ViewPagerAdapter(
fragmentManager: FragmentManager, fragmentManager: FragmentManager,
lifecycle: Lifecycle, lifecycle: Lifecycle,
private val user: Query.UserProfile, private val user: Query.UserProfile
private val activity: ProfileActivity
) : ) :
FragmentStateAdapter(fragmentManager, lifecycle) { FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int = 2 override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = when (position) { override fun createFragment(position: Int): Fragment = when (position) {
0 -> ProfileFragment(user, activity) 0 -> ProfileFragment.newInstance(user)
1 -> StatsFragment(user, activity) 1 -> StatsFragment.newInstance(user)
else -> ProfileFragment(user, activity) else -> ProfileFragment.newInstance(user)
} }
} }
} }

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.bottomBar
import ani.dantotsu.buildMarkwon import ani.dantotsu.buildMarkwon
import ani.dantotsu.connections.anilist.ProfileViewModel import ani.dantotsu.connections.anilist.ProfileViewModel
import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.Query
@ -27,8 +26,10 @@ import ani.dantotsu.setSlideUp
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ProfileFragment(private val user: Query.UserProfile, private val activity: ProfileActivity): Fragment() { class ProfileFragment(): Fragment() {
lateinit var binding: FragmentProfileBinding lateinit var binding: FragmentProfileBinding
private lateinit var activity: ProfileActivity
private lateinit var user: Query.UserProfile
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -41,6 +42,8 @@ class ProfileFragment(private val user: Query.UserProfile, private val activity:
val model: ProfileViewModel by activityViewModels() val model: ProfileViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity = requireActivity() as ProfileActivity
user = arguments?.getSerializable("user") as Query.UserProfile
val markwon = buildMarkwon(activity, false) val markwon = buildMarkwon(activity, false)
markwon.setMarkdown(binding.profileUserBio, user.about?:"") markwon.setMarkdown(binding.profileUserBio, user.about?:"")
binding.userInfoContainer.visibility = if (user.about != null) View.VISIBLE else View.GONE binding.userInfoContainer.visibility = if (user.about != null) View.VISIBLE else View.GONE
@ -96,6 +99,14 @@ class ProfileFragment(private val user: Query.UserProfile, private val activity:
binding.profileFavManga binding.profileFavManga
) )
} }
override fun onResume() {
super.onResume()
if (this::binding.isInitialized) {
binding.root.requestLayout()
}
}
private fun initRecyclerView( private fun initRecyclerView(
mode: LiveData<ArrayList<Media>>, mode: LiveData<ArrayList<Media>>,
container: View, container: View,
@ -135,4 +146,15 @@ class ProfileFragment(private val user: Query.UserProfile, private val activity:
} }
} }
companion object {
fun newInstance(query: Query.UserProfile): ProfileFragment {
val args = Bundle().apply {
putSerializable("user", query)
}
return ProfileFragment().apply {
arguments = args
}
}
}
} }

View file

@ -1,48 +1,37 @@
package ani.dantotsu.profile package ani.dantotsu.profile
import android.content.Context
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.databinding.FragmentStatisticsBinding import ani.dantotsu.databinding.FragmentStatisticsBinding
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartModel import ani.dantotsu.profile.ChartBuilder.Companion.ChartType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartStackingType import ani.dantotsu.profile.ChartBuilder.Companion.StatType
import ani.dantotsu.profile.ChartBuilder.Companion.MediaType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartZoomType
import com.github.aachartmodel.aainfographics.aachartcreator.AADataElement
import com.github.aachartmodel.aainfographics.aachartcreator.AAOptions
import com.github.aachartmodel.aainfographics.aachartcreator.AASeriesElement
import com.github.aachartmodel.aainfographics.aachartcreator.aa_toAAOptions
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAArea
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAChart
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AADataLabels
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAItemStyle
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAMarker
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAPlotOptions
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAStyle
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAYAxis import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAYAxis
import com.github.aachartmodel.aainfographics.aatools.AAColor import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.Locale import java.util.Locale
class StatsFragment(private val user: Query.UserProfile, private val activity: ProfileActivity) : class StatsFragment() :
Fragment() { Fragment() {
private lateinit var binding: FragmentStatisticsBinding private lateinit var binding: FragmentStatisticsBinding
private var adapter: GroupieAdapter = GroupieAdapter()
private var stats: Query.StatisticsResponse? = null private var stats: Query.StatisticsResponse? = null
private var type: MediaType = MediaType.ANIME private var type: MediaType = MediaType.ANIME
private var statType: StatType = StatType.COUNT private var statType: StatType = StatType.COUNT
private var primaryColor: Int = 0 private lateinit var user: Query.UserProfile
private lateinit var activity: ProfileActivity
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -55,14 +44,14 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity = requireActivity() as ProfileActivity
user = arguments?.getSerializable("user") as Query.UserProfile
val typedValue = TypedValue() binding.statisticList.adapter = adapter
activity.theme.resolveAttribute( binding.statisticList.setHasFixedSize(true)
com.google.android.material.R.attr.colorPrimary, binding.statisticList.isNestedScrollingEnabled = false
typedValue, binding.statisticList.layoutManager = LinearLayoutManager(requireContext())
true binding.statisticProgressBar.visibility = View.VISIBLE
)
primaryColor = typedValue.data
binding.sourceType.setAdapter( binding.sourceType.setAdapter(
ArrayAdapter( ArrayAdapter(
@ -94,161 +83,39 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
} }
loadStats(type == MediaType.ANIME) loadStats(type == MediaType.ANIME)
binding.statisticProgressBar.visibility = View.GONE binding.statisticProgressBar.visibility = View.GONE
binding.chartsContainer.visibility = View.VISIBLE
} }
} }
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (this::binding.isInitialized) {
binding.root.requestLayout()
}
loadStats(type == MediaType.ANIME) loadStats(type == MediaType.ANIME)
} }
private fun loadStats(anime: Boolean) { private fun loadStats(anime: Boolean) {
val formatChartModel = getFormatChartModel(anime) binding.statisticProgressBar.visibility = View.VISIBLE
if (formatChartModel != null) { binding.statisticList.visibility = View.GONE
binding.formatChartView.visibility = View.VISIBLE adapter.clear()
val aaOptions = buildOptions(formatChartModel) loadFormatChart(anime)
binding.formatChartView.aa_drawChartWithChartOptions(aaOptions) loadScoreChart(anime)
} else { loadStatusChart(anime)
binding.formatChartView.visibility = View.GONE loadReleaseYearChart(anime)
loadStartYearChart(anime)
loadLengthChart(anime)
loadGenreChart(anime)
loadTagChart(anime)
loadCountryChart(anime)
loadVoiceActorsChart(anime)
loadStudioChart(anime)
loadStaffChart(anime)
binding.statisticProgressBar.visibility = View.GONE
binding.statisticList.visibility = View.VISIBLE
} }
val statusChartModel = getStatusChartModel(anime) private fun loadFormatChart(anime: Boolean) {
if (statusChartModel != null) {
binding.statusChartView.visibility = View.VISIBLE
val aaOptions = buildOptions(statusChartModel)
binding.statusChartView.aa_drawChartWithChartOptions(aaOptions)
} else {
binding.statusChartView.visibility = View.GONE
}
val scoreChartModel = getScoreChartModel(anime)
if (scoreChartModel != null) {
binding.scoreChartView.visibility = View.VISIBLE
val aaOptions = buildOptions(scoreChartModel, false, """
function () {
return 'score: ' +
this.x +
'<br/> ' +
' ${getTypeName()} ' +
this.y
}
""".trimIndent()
)
binding.scoreChartView.aa_drawChartWithChartOptions(aaOptions)
} else {
binding.scoreChartView.visibility = View.GONE
}
val lengthChartModel = getLengthChartModel(anime)
if (lengthChartModel != null) {
binding.lengthChartView.visibility = View.VISIBLE
val aaOptions = buildOptions(lengthChartModel)
binding.lengthChartView.aa_drawChartWithChartOptions(aaOptions)
} else {
binding.lengthChartView.visibility = View.GONE
}
val releaseYearChartModel = getReleaseYearChartModel(anime)
if (releaseYearChartModel != null) {
binding.releaseYearChartView.visibility = View.VISIBLE
val aaOptions = buildOptions(releaseYearChartModel, false, """
function () {
return 'Year: ' +
this.x +
'<br/> ' +
' ${getTypeName()} ' +
this.y
}
""".trimIndent()
)
binding.releaseYearChartView.aa_drawChartWithChartOptions(aaOptions)
} else {
binding.releaseYearChartView.visibility = View.GONE
}
val startYearChartModel = getStartYearChartModel(anime)
if (startYearChartModel != null) {
binding.startYearChartView.visibility = View.VISIBLE
val aaOptions = buildOptions(startYearChartModel, false, """
function () {
return 'Year: ' +
this.x +
'<br/> ' +
' ${getTypeName()} ' +
this.y
}
""".trimIndent()
)
binding.startYearChartView.aa_drawChartWithChartOptions(aaOptions)
} else {
binding.startYearChartView.visibility = View.GONE
}
val genreChartModel = getGenreChartModel(anime)
if (genreChartModel.first != null) {
binding.genreChartView.visibility = View.VISIBLE
val aaOptions = buildOptions(genreChartModel.first!!, true, """
function () {
return 'Genre: ' +
this.x +
'<br/> ' +
' ${getTypeName()} ' +
this.y
}
""".trimIndent()
)
val min = genreChartModel.second.first
val max = genreChartModel.second.second
aaOptions.yAxis = AAYAxis().min(min).max(max).tickInterval(if (max > 100) 20 else 10)
binding.genreChartView.aa_drawChartWithChartOptions(aaOptions)
} else {
binding.genreChartView.visibility = View.GONE
}
}
private fun buildOptions(
aaChartModel: AAChartModel,
polar: Boolean = true,
formatting: String? = null
): AAOptions {
val aaOptions = aaChartModel.aa_toAAOptions()
aaOptions.chart?.zoomType = "xy"
aaOptions.chart?.pinchType = "xy"
aaOptions.chart?.polar = polar
aaOptions.tooltip?.apply {
headerFormat
if (formatting != null) {
formatter(formatting)
} else {
formatter(
"""
function () {
return this.point.name
+ ': <br/> '
+ '<b> '
+ this.y
+ ', '
+ (this.percentage).toFixed(2)
+ '%'
}
""".trimIndent()
)
}
}
aaOptions.legend?.apply {
enabled(true)
.labelFormat = "{name}: {y}"
}
aaOptions.plotOptions?.series?.connectNulls(true)
setColors(aaOptions)
return aaOptions
}
private fun getFormatChartModel(anime: Boolean): AAChartModel? {
val names: List<String> = if (anime) { val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.formats?.map { it.format } ?: emptyList() stats?.data?.user?.statistics?.anime?.formats?.map { it.format } ?: emptyList()
} else { } else {
@ -267,19 +134,21 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.formats?.map { it.meanScore } StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.formats?.map { it.meanScore }
} ?: emptyList() } ?: emptyList()
} }
if (names.isEmpty() || values.isEmpty()) if (names.isNotEmpty() || values.isNotEmpty()) {
return null val formatChart = ChartBuilder.buildChart(
val primaryColor = primaryColor activity,
val palette = generateColorPalette(primaryColor, names.size) ChartType.OneDimensional,
return AAChartModel() AAChartType.Pie,
.chartType(AAChartType.Pie) statType,
.subtitle(getTypeName()) type,
.zoomType(AAChartZoomType.XY) names,
.dataLabelsEnabled(true) values
.series(getElements(names, values, palette)) )
adapter.add(ChartItem("Format", formatChart))
}
} }
private fun getStatusChartModel(anime: Boolean): AAChartModel? { private fun loadStatusChart(anime: Boolean) {
val names: List<String> = if (anime) { val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.statuses?.map { it.status } ?: emptyList() stats?.data?.user?.statistics?.anime?.statuses?.map { it.status } ?: emptyList()
} else { } else {
@ -298,18 +167,21 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.statuses?.map { it.meanScore } StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.statuses?.map { it.meanScore }
} ?: emptyList() } ?: emptyList()
} }
if (names.isEmpty() || values.isEmpty()) if (names.isNotEmpty() || values.isNotEmpty()) {
return null val statusChart = ChartBuilder.buildChart(
val palette = generateColorPalette(primaryColor, names.size) activity,
return AAChartModel() ChartType.OneDimensional,
.chartType(AAChartType.Funnel) AAChartType.Funnel,
.subtitle(getTypeName()) statType,
.zoomType(AAChartZoomType.XY) type,
.dataLabelsEnabled(true) names,
.series(getElements(names, values, palette)) values
)
adapter.add(ChartItem("Status", statusChart))
}
} }
private fun getScoreChartModel(anime: Boolean): AAChartModel? { private fun loadScoreChart(anime: Boolean) {
val names: List<Int> = if (anime) { val names: List<Int> = if (anime) {
stats?.data?.user?.statistics?.anime?.scores?.map { it.score } ?: emptyList() stats?.data?.user?.statistics?.anime?.scores?.map { it.score } ?: emptyList()
} else { } else {
@ -328,25 +200,28 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.scores?.map { it.meanScore } StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.scores?.map { it.meanScore }
} ?: emptyList() } ?: emptyList()
} }
if (names.isEmpty() || values.isEmpty()) if (names.isNotEmpty() || values.isNotEmpty()) {
return null val scoreChart = ChartBuilder.buildChart(
val palette = generateColorPalette(primaryColor, names.size) activity,
return AAChartModel() ChartType.TwoDimensional,
.chartType(AAChartType.Column) AAChartType.Column,
.subtitle(getTypeName()) statType,
.zoomType(AAChartZoomType.XY) type,
.dataLabelsEnabled(false) names,
.yAxisTitle(getTypeName()) values,
.xAxisTickInterval(10) xAxisName = "Score",
.stacking(AAChartStackingType.Normal) )
.series(getElements(names, values, palette)) adapter.add(ChartItem("Score", scoreChart))
}
} }
private fun getLengthChartModel(anime: Boolean): AAChartModel? { private fun loadLengthChart(anime: Boolean) {
val names: List<String> = if (anime) { val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.lengths?.map { it.length?: "unknown" } ?: emptyList() stats?.data?.user?.statistics?.anime?.lengths?.map { it.length ?: "unknown" }
?: emptyList()
} else { } else {
stats?.data?.user?.statistics?.manga?.lengths?.map { it.length?: "unknown" } ?: emptyList() stats?.data?.user?.statistics?.manga?.lengths?.map { it.length ?: "unknown" }
?: emptyList()
} }
val values: List<Number> = if (anime) { val values: List<Number> = if (anime) {
when (statType) { when (statType) {
@ -361,23 +236,28 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.lengths?.map { it.meanScore } StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.lengths?.map { it.meanScore }
} ?: emptyList() } ?: emptyList()
} }
//clear nulls from names if (names.isNotEmpty() || values.isNotEmpty()) {
if (names.isEmpty() || values.isEmpty()) val lengthChart = ChartBuilder.buildChart(
return null activity,
val palette = generateColorPalette(primaryColor, names.size) ChartType.OneDimensional,
return AAChartModel() AAChartType.Pyramid,
.chartType(AAChartType.Pyramid) statType,
.subtitle(getTypeName()) type,
.zoomType(AAChartZoomType.XY) names,
.dataLabelsEnabled(true) values,
.series(getElements(names, values, palette)) xAxisName = "Length",
)
adapter.add(ChartItem("Length", lengthChart))
}
} }
private fun getReleaseYearChartModel(anime: Boolean): AAChartModel? { private fun loadReleaseYearChart(anime: Boolean) {
val names: List<Number> = if (anime) { val names: List<Number> = if (anime) {
stats?.data?.user?.statistics?.anime?.releaseYears?.map { it.releaseYear } ?: emptyList() stats?.data?.user?.statistics?.anime?.releaseYears?.map { it.releaseYear }
?: emptyList()
} else { } else {
stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.releaseYear } ?: emptyList() stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.releaseYear }
?: emptyList()
} }
val values: List<Number> = if (anime) { val values: List<Number> = if (anime) {
when (statType) { when (statType) {
@ -392,22 +272,22 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.meanScore } StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.meanScore }
} ?: emptyList() } ?: emptyList()
} }
if (names.isEmpty() || values.isEmpty()) if (names.isNotEmpty() || values.isNotEmpty()) {
return null val releaseYearChart = ChartBuilder.buildChart(
val palette = generateColorPalette(primaryColor, names.size) activity,
val hexColorsArray: Array<Any> = palette.map { String.format("#%06X", 0xFFFFFF and it) }.toTypedArray() ChartType.TwoDimensional,
return AAChartModel() AAChartType.Bubble,
.chartType(AAChartType.Bubble) statType,
.subtitle(getTypeName()) type,
.zoomType(AAChartZoomType.XY) names,
.dataLabelsEnabled(false) values,
.yAxisTitle(getTypeName()) xAxisName = "Year",
.stacking(AAChartStackingType.Normal) )
.series(getElementsSimple(names, values)) adapter.add(ChartItem("Release Year", releaseYearChart))
.colorsTheme(hexColorsArray) }
} }
private fun getStartYearChartModel(anime: Boolean): AAChartModel? { private fun loadStartYearChart(anime: Boolean) {
val names: List<Number> = if (anime) { val names: List<Number> = if (anime) {
stats?.data?.user?.statistics?.anime?.startYears?.map { it.startYear } ?: emptyList() stats?.data?.user?.statistics?.anime?.startYears?.map { it.startYear } ?: emptyList()
} else { } else {
@ -426,22 +306,22 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.startYears?.map { it.meanScore } StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.startYears?.map { it.meanScore }
} ?: emptyList() } ?: emptyList()
} }
if (names.isEmpty() || values.isEmpty()) if (names.isNotEmpty() || values.isNotEmpty()) {
return null val startYearChart = ChartBuilder.buildChart(
val palette = generateColorPalette(primaryColor, names.size) activity,
val hexColorsArray: Array<Any> = palette.map { String.format("#%06X", 0xFFFFFF and it) }.toTypedArray() ChartType.TwoDimensional,
return AAChartModel() AAChartType.Bar,
.chartType(AAChartType.Bar) statType,
.subtitle(getTypeName()) type,
.zoomType(AAChartZoomType.XY) names,
.dataLabelsEnabled(false) values,
.yAxisTitle(getTypeName()) xAxisName = "Year",
.stacking(AAChartStackingType.Normal) )
.series(getElementsSimple(names, values)) adapter.add(ChartItem("Start Year", startYearChart))
.colorsTheme(hexColorsArray) }
} }
private fun getGenreChartModel(anime: Boolean): Pair<AAChartModel?, Pair<Int, Int>> { private fun loadGenreChart(anime: Boolean) {
val names: List<String> = if (anime) { val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.genres?.map { it.genre } ?: emptyList() stats?.data?.user?.statistics?.anime?.genres?.map { it.genre } ?: emptyList()
} else { } else {
@ -460,163 +340,219 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.genres?.map { it.meanScore } StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.genres?.map { it.meanScore }
} ?: emptyList() } ?: emptyList()
} }
if (names.isEmpty() || values.isEmpty()) if (names.isNotEmpty() || values.isNotEmpty()) {
return Pair(null, Pair(0, 0)) val genreChart = ChartBuilder.buildChart(
val palette = generateColorPalette(primaryColor, names.size) activity,
val hexColorsArray: Array<Any> = palette.map { String.format("#%06X", 0xFFFFFF and it) }.toTypedArray() ChartType.TwoDimensional,
return Pair(AAChartModel() AAChartType.Areaspline,
.chartType(AAChartType.Area) statType,
.subtitle(getTypeName()) type,
.zoomType(AAChartZoomType.XY) names.take(15),
.dataLabelsEnabled(false) values.take(15),
.legendEnabled(false) xAxisName = "Genre",
.yAxisTitle(getTypeName()) polar = true,
.stacking(AAChartStackingType.Normal) categories = names
.series(getElementsSimple(names, values))
.colorsTheme(hexColorsArray)
.categories(names.toTypedArray()),
Pair(values.minOf { it.toInt() }, values.maxOf { it.toInt() }))
}
enum class StatType {
COUNT, TIME, MEAN_SCORE
}
enum class MediaType {
ANIME, MANGA
}
private fun getTypeName(): String {
return when (statType) {
StatType.COUNT -> "Count"
StatType.TIME -> if (type == MediaType.ANIME) "Hours Watched" else "Chapters Read"
StatType.MEAN_SCORE -> "Mean Score"
}
}
private fun getElements(
names: List<Any>,
statData: List<Number>,
colors: List<Int>
): Array<Any> {
val statDataElements = mutableListOf<AADataElement>()
for (i in statData.indices) {
val element = AADataElement()
.y(statData[i])
.color(
AAColor.rgbaColor(
Color.red(colors[i]),
Color.green(colors[i]),
Color.blue(colors[i]),
0.9f
)
)
if (names[i] is Number) {
element.x(names[i] as Number)
element.dataLabels(AADataLabels()
.enabled(false)
.format("{point.y}")
.backgroundColor(AAColor.rgbaColor(255, 255, 255, 0.0f))
) )
adapter.add(ChartItem("Genre", genreChart))
}
}
private fun loadTagChart(anime: Boolean) {
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.tags?.map { it.tag.name } ?: emptyList()
} else { } else {
element.x(i) stats?.data?.user?.statistics?.manga?.tags?.map { it.tag.name } ?: emptyList()
element.name(names[i] as String)
} }
statDataElements.add(element) val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.tags?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.tags?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.tags?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.tags?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.tags?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.tags?.map { it.meanScore }
} ?: emptyList()
} }
return arrayOf( if (names.isNotEmpty() || values.isNotEmpty()) {
AASeriesElement().name("Score").color(primaryColor) val min = values.minOf { it.toInt() }
.data(statDataElements.toTypedArray()) val max = values.maxOf { it.toInt() }
val tagChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Areaspline,
statType,
type,
names,
values,
xAxisName = "Tag",
polar = false,
categories = names,
scrollPos = 0.0f
) )
tagChart.yAxis = AAYAxis().min(min).max(max).tickInterval(if (max > 100) 20 else 10)
adapter.add(ChartItem("Tag", tagChart))
}
} }
private fun getElementsSimple( private fun loadCountryChart(anime: Boolean) {
names: List<Any>, val names: List<String> = if (anime) {
statData: List<Any> stats?.data?.user?.statistics?.anime?.countries?.map { it.country } ?: emptyList()
): Array<Any> { } else {
val statValues = mutableListOf<Array<Any>>() stats?.data?.user?.statistics?.manga?.countries?.map { it.country } ?: emptyList()
for (i in statData.indices) {
statValues.add(arrayOf(names[i], statData[i], statData[i]))
} }
return arrayOf( val values: List<Number> = if (anime) {
AASeriesElement().name("Score") when (statType) {
.data(statValues.toTypedArray()) StatType.COUNT -> stats?.data?.user?.statistics?.anime?.countries?.map { it.count }
.dataLabels(AADataLabels() StatType.TIME -> stats?.data?.user?.statistics?.anime?.countries?.map { it.minutesWatched / 60 }
.enabled(false) StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.countries?.map { it.meanScore }
) } ?: emptyList()
.colorByPoint(true) } else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.countries?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.countries?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.countries?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
val countryChart = ChartBuilder.buildChart(
activity,
ChartType.OneDimensional,
AAChartType.Pie,
statType,
type,
names,
values,
xAxisName = "Country",
polar = false,
categories = names,
scrollPos = null
) )
adapter.add(ChartItem("Country", countryChart))
}
} }
private fun setColors(aaOptions: AAOptions) { private fun loadVoiceActorsChart(anime: Boolean) {
val backgroundColor = TypedValue() val names: List<String> = if (anime) {
activity.theme.resolveAttribute(com.google.android.material.R.attr.colorSurfaceVariant, backgroundColor, true) stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList()
val backgroundStyle = AAStyle().color( } else {
AAColor.rgbaColor( stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList()
Color.red(backgroundColor.data), }
Color.green(backgroundColor.data), val values: List<Number> = if (anime) {
Color.blue(backgroundColor.data), when (statType) {
1f StatType.COUNT -> stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
val voiceActorsChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Column,
statType,
type,
names,
values,
xAxisName = "Voice Actor",
polar = false,
categories = names,
scrollPos = 0.0f
) )
) adapter.add(ChartItem("Voice Actor", voiceActorsChart))
val colorOnBackground = TypedValue() }
activity.theme.resolveAttribute(
com.google.android.material.R.attr.colorOnSurface,
colorOnBackground,
true
)
val onBackgroundStyle = AAStyle().color(
AAColor.rgbaColor(
Color.red(colorOnBackground.data),
Color.green(colorOnBackground.data),
Color.blue(colorOnBackground.data),
0.9f
)
)
aaOptions.chart?.backgroundColor(backgroundStyle.color)
aaOptions.tooltip?.backgroundColor(
AAColor.rgbaColor(
Color.red(primaryColor),
Color.green(primaryColor),
Color.blue(primaryColor),
0.9f
)
)
aaOptions.title?.style(onBackgroundStyle)
aaOptions.subtitle?.style(onBackgroundStyle)
aaOptions.tooltip?.style(backgroundStyle)
aaOptions.credits?.style(onBackgroundStyle)
aaOptions.xAxis?.labels?.style(onBackgroundStyle)
aaOptions.yAxis?.labels?.style(onBackgroundStyle)
aaOptions.plotOptions?.series?.dataLabels?.style(onBackgroundStyle)
aaOptions.plotOptions?.series?.dataLabels?.backgroundColor(backgroundStyle.color)
aaOptions.legend?.itemStyle(AAItemStyle().color(onBackgroundStyle.color))
aaOptions.touchEventEnabled(true)
} }
private fun generateColorPalette( private fun loadStaffChart(anime: Boolean) {
baseColor: Int, val names: List<String> = if (anime) {
size: Int, stats?.data?.user?.statistics?.anime?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList()
hueDelta: Float = 8f, } else {
saturationDelta: Float = 2.02f, stats?.data?.user?.statistics?.manga?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList()
valueDelta: Float = 2.02f }
): List<Int> { val values: List<Number> = if (anime) {
val palette = mutableListOf<Int>() when (statType) {
val hsv = FloatArray(3) StatType.COUNT -> stats?.data?.user?.statistics?.anime?.staff?.map { it.count }
Color.colorToHSV(baseColor, hsv) StatType.TIME -> stats?.data?.user?.statistics?.anime?.staff?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.staff?.map { it.meanScore }
for (i in 0 until size) { } ?: emptyList()
val newHue = (hsv[0] + hueDelta * i) % 360 // Ensure hue stays within the 0-360 range } else {
val newSaturation = (hsv[1] + saturationDelta * i).coerceIn(0f, 1f) when (statType) {
val newValue = (hsv[2] + valueDelta * i).coerceIn(0f, 1f) StatType.COUNT -> stats?.data?.user?.statistics?.manga?.staff?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.staff?.map { it.chaptersRead }
val newHsv = floatArrayOf(newHue, newSaturation, newValue) StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.staff?.map { it.meanScore }
palette.add(Color.HSVToColor(newHsv)) } ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
val staffChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Line,
statType,
type,
names,
values,
xAxisName = "Staff",
polar = false,
categories = names,
scrollPos = 0.0f
)
adapter.add(ChartItem("Staff", staffChart))
}
} }
return palette private fun loadStudioChart(anime: Boolean) {
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.studios?.map { it.studio.name } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.studios?.map { it.studio.name } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.studios?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.studios?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.studios?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.studios?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.studios?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.studios?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
val studioChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Spline,
statType,
type,
names.take(15),
values.take(15),
xAxisName = "Studio",
polar = true,
categories = names,
scrollPos = null
)
adapter.add(ChartItem("Studio", studioChart))
}
}
companion object {
fun newInstance(user: Query.UserProfile): StatsFragment {
val args = Bundle().apply {
putSerializable("user", user)
}
return StatsFragment().apply {
arguments = args
}
}
} }
} }

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h560q33,0 56.5,23.5T840,200v560q0,33 -23.5,56.5T760,840L600,840v-80h160v-480L200,280v480h160v80L200,840ZM440,840v-246l-64,64 -56,-58 160,-160 160,160 -56,58 -64,-64v246h-80Z"/>
</vector>

View file

@ -97,385 +97,9 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
<LinearLayout <androidx.recyclerview.widget.RecyclerView
android:id="@+id/chartsContainer" android:id="@+id/statisticList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> tools:listitem="@layout/item_chart" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Format"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/formatChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Score"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/scoreChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Status"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/statusChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Length"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/lengthChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Release Year"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/releaseYearChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Start Year"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/startYearChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Genre"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/genreChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Tag"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/tagChartChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Country of Origin"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/countryChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Voice Actor"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/voiceActorChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Staff"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/staffChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Studio"
android:textColor="?attr/colorPrimary"
android:textSize="16sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/studioChartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/chartLayout"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/typeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_semi_bold"
android:text="Format"
android:textColor="?attr/colorPrimary"
android:textSize="16sp"
tools:ignore="HardcodedText" />
<ImageButton
android:id="@+id/openButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end"
android:background="?android:colorBackground"
android:contentDescription="Open"
android:src="@drawable/ic_open_24"
app:tint="?attr/colorPrimary"
tools:ignore="HardcodedText" />
</FrameLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp">
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/chartView"
android:layout_width="match_parent"
android:layout_height="350dp"
android:background="?android:colorBackground" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>