feat: normalize genres

This commit is contained in:
rebelonion 2024-03-05 00:25:40 -06:00
parent 5218d5cd28
commit db979de829
2 changed files with 117 additions and 45 deletions

View file

@ -18,7 +18,6 @@ import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAScrollablePlotAre
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAStyle 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.github.aachartmodel.aainfographics.aatools.AAColor
import com.github.aachartmodel.aainfographics.aatools.AAGradientColor
class ChartBuilder { class ChartBuilder {
companion object { companion object {
@ -37,7 +36,7 @@ class ChartBuilder {
data class ChartPacket( data class ChartPacket(
val username: String, val username: String,
val names: List<Any>, val names: List<Any>,
val statData: List<Number> var statData: List<Number>
) )
fun buildChart( fun buildChart(
@ -47,11 +46,12 @@ class ChartBuilder {
statType: StatType, statType: StatType,
mediaType: MediaType, mediaType: MediaType,
chartPackets: List<ChartPacket>, chartPackets: List<ChartPacket>,
xAxisName: String = "X Axis", xAxisName: String,
xAxisTickInterval: Int? = null, xAxisTickInterval: Int? = null,
polar: Boolean = false, polar: Boolean = false,
passedCategories: List<String>? = null, passedCategories: List<String>? = null,
scrollPos: Float? = null, scrollPos: Float? = null,
normalize: Boolean = false
): AAOptions { ): AAOptions {
val typedValue = TypedValue() val typedValue = TypedValue()
context.theme.resolveAttribute( context.theme.resolveAttribute(
@ -69,6 +69,11 @@ class ChartBuilder {
aaChartType = AAChartType.Column aaChartType = AAChartType.Column
categories = chartPackets[0].names.map { it.toString() } categories = chartPackets[0].names.map { it.toString() }
} }
if (normalize && chartPackets.size > 1) {
chartPackets.forEach {
it.statData = normalizeData(it.statData)
}
}
val namesMax = chartPackets.maxOf { it.names.size } val namesMax = chartPackets.maxOf { it.names.size }
val palette = ColorEditor.generateColorPalette(primaryColor, namesMax) val palette = ColorEditor.generateColorPalette(primaryColor, namesMax)
@ -76,20 +81,25 @@ class ChartBuilder {
ChartType.OneDimensional -> { ChartType.OneDimensional -> {
val chart = AAChartModel() val chart = AAChartModel()
.chartType(aaChartType) .chartType(aaChartType)
.subtitle(getTypeName(statType, mediaType)) .subtitle(
getTypeName(
statType,
mediaType
) + if (normalize && chartPackets.size > 1) " (Normalized)" else ""
)
.zoomType(AAChartZoomType.None) .zoomType(AAChartZoomType.None)
.dataLabelsEnabled(true) .dataLabelsEnabled(true)
val elements: MutableList<Any> = mutableListOf() val elements: MutableList<Any> = mutableListOf()
chartPackets.forEachIndexed { index, chartPacket -> chartPackets.forEachIndexed { index, chartPacket ->
val element = AASeriesElement() val element = AASeriesElement()
.name(chartPacket.username) .name(chartPacket.username)
.data( .data(
get1DElements( get1DElements(
chartPacket.names, chartPacket.names,
chartPacket.statData, chartPacket.statData,
palette palette
)
) )
)
if (index == 0) { if (index == 0) {
element.color(primaryColor) element.color(primaryColor)
} else { } else {
@ -109,31 +119,66 @@ class ChartBuilder {
palette.map { String.format("#%06X", 0xFFFFFF and it) }.toTypedArray() palette.map { String.format("#%06X", 0xFFFFFF and it) }.toTypedArray()
val chart = AAChartModel() val chart = AAChartModel()
.chartType(aaChartType) .chartType(aaChartType)
.subtitle(getTypeName(statType, mediaType)) .subtitle(
getTypeName(
statType,
mediaType
) + if (normalize && chartPackets.size > 1) " (Normalized)" else ""
)
.zoomType(AAChartZoomType.None) .zoomType(AAChartZoomType.None)
.dataLabelsEnabled(false) .dataLabelsEnabled(false)
.yAxisTitle(getTypeName(statType, mediaType)) .yAxisTitle(
if (chartPackets.size == 1) { getTypeName(
chart.colorsTheme(hexColorsArray) statType,
} mediaType
) + if (normalize && chartPackets.size > 1) " (Normalized)" else ""
)
if (chartPackets.size == 1) {
chart.colorsTheme(hexColorsArray)
}
val elements: MutableList<AASeriesElement> = mutableListOf() val elements: MutableList<AASeriesElement> = mutableListOf()
chartPackets.forEachIndexed { index, chartPacket -> chartPackets.forEachIndexed { index, chartPacket ->
val element = get2DElements( val element = get2DElements(
chartPacket.names, chartPacket.names,
chartPacket.statData, chartPacket.statData,
chartPackets.size == 1 chartPackets.size == 1
) )
element.name(chartPacket.username) element.name(chartPacket.username)
if (index == 0) { if (index == 0) {
element.color(AAColor.rgbaColor(Color.red(primaryColor), Color.green(primaryColor), Color.blue(primaryColor), 0.9f)) element.color(
AAColor.rgbaColor(
Color.red(primaryColor),
Color.green(primaryColor),
Color.blue(primaryColor),
0.9f
)
)
} else { } else {
element.color(AAColor.rgbaColor(Color.red(ColorEditor.oppositeColor(primaryColor)), Color.green(ColorEditor.oppositeColor(primaryColor)), Color.blue(ColorEditor.oppositeColor(primaryColor)), 0.9f)) element.color(
AAColor.rgbaColor(
Color.red(
ColorEditor.oppositeColor(
primaryColor
)
),
Color.green(ColorEditor.oppositeColor(primaryColor)),
Color.blue(ColorEditor.oppositeColor(primaryColor)),
0.9f
)
)
} }
if (chartPackets.size == 1) { if (chartPackets.size == 1) {
element.fillColor(AAColor.rgbaColor(Color.red(primaryColor), Color.green(primaryColor), Color.blue(primaryColor), 0.9f)) element.fillColor(
AAColor.rgbaColor(
Color.red(primaryColor),
Color.green(primaryColor),
Color.blue(primaryColor),
0.9f
)
)
} }
elements.add(element) elements.add(element)
} }
@ -205,12 +250,12 @@ class ChartBuilder {
statValues.add(arrayOf(names[i], statData[i], statData[i])) statValues.add(arrayOf(names[i], statData[i], statData[i]))
} }
return AASeriesElement() return AASeriesElement()
.data(statValues.toTypedArray()) .data(statValues.toTypedArray())
.dataLabels( .dataLabels(
AADataLabels() AADataLabels()
.enabled(false) .enabled(false)
) )
.colorByPoint(colorByPoint) .colorByPoint(colorByPoint)
} }
private fun get1DElements( private fun get1DElements(
@ -255,6 +300,14 @@ class ChartBuilder {
} }
} }
private fun normalizeData(data: List<Number>): List<Number> {
if (data.isEmpty()) {
return data
}
val max = data.maxOf { it.toDouble() }
return data.map { (it.toDouble() / max) * 100 }
}
private fun setColors(aaOptions: AAOptions, context: Context, primaryColor: Int) { private fun setColors(aaOptions: AAOptions, context: Context, primaryColor: Int) {
val backgroundColor = TypedValue() val backgroundColor = TypedValue()
context.theme.resolveAttribute( context.theme.resolveAttribute(
@ -281,7 +334,7 @@ class ChartBuilder {
Color.red(colorOnBackground.data), Color.red(colorOnBackground.data),
Color.green(colorOnBackground.data), Color.green(colorOnBackground.data),
Color.blue(colorOnBackground.data), Color.blue(colorOnBackground.data),
0.9f 1.0f
) )
) )
@ -292,7 +345,7 @@ class ChartBuilder {
Color.red(backgroundColor.data), Color.red(backgroundColor.data),
Color.green(backgroundColor.data), Color.green(backgroundColor.data),
Color.blue(backgroundColor.data), Color.blue(backgroundColor.data),
0.9f 1.0f
) )
) )
aaOptions.title?.style(onBackgroundStyle) aaOptions.title?.style(onBackgroundStyle)

View file

@ -12,10 +12,10 @@ 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 ani.dantotsu.profile.ChartBuilder.Companion.ChartType
import ani.dantotsu.profile.ChartBuilder.Companion.StatType
import ani.dantotsu.profile.ChartBuilder.Companion.MediaType
import ani.dantotsu.profile.ChartBuilder.Companion.ChartPacket import ani.dantotsu.profile.ChartBuilder.Companion.ChartPacket
import ani.dantotsu.profile.ChartBuilder.Companion.ChartType
import ani.dantotsu.profile.ChartBuilder.Companion.MediaType
import ani.dantotsu.profile.ChartBuilder.Companion.StatType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -147,7 +147,7 @@ class StatsFragment :
private fun loadFormatChart(anime: Boolean) { private fun loadFormatChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>() val chartPackets = mutableListOf<ChartPacket>()
stats.forEach {stat -> stats.forEach { stat ->
val names: List<String> = if (anime) { val names: List<String> = if (anime) {
stat?.statistics?.anime?.formats?.map { it.format } ?: emptyList() stat?.statistics?.anime?.formats?.map { it.format } ?: emptyList()
} else { } else {
@ -167,7 +167,7 @@ class StatsFragment :
} ?: emptyList() } ?: emptyList()
} }
if (names.isNotEmpty() && values.isNotEmpty()) { if (names.isNotEmpty() && values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name?:"Unknown", names, values)) chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
} }
} }
if (chartPackets.isNotEmpty()) { if (chartPackets.isNotEmpty()) {
@ -178,6 +178,7 @@ class StatsFragment :
statType, statType,
type, type,
chartPackets, chartPackets,
xAxisName = "Format",
) )
adapter.add(ChartItem("Format", formatChart, activity)) adapter.add(ChartItem("Format", formatChart, activity))
} }
@ -215,7 +216,8 @@ class StatsFragment :
AAChartType.Funnel, AAChartType.Funnel,
statType, statType,
type, type,
chartPackets chartPackets,
xAxisName = "Status",
) )
adapter.add(ChartItem("Status", statusChart, activity)) adapter.add(ChartItem("Status", statusChart, activity))
} }
@ -225,9 +227,19 @@ class StatsFragment :
val chartPackets = mutableListOf<ChartPacket>() val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat -> stats.forEach { stat ->
val names: List<Int> = if (anime) { val names: List<Int> = if (anime) {
stat?.statistics?.anime?.scores?.map { convertScore(it.score, stat.mediaListOptions.scoreFormat) } ?: emptyList() stat?.statistics?.anime?.scores?.map {
convertScore(
it.score,
stat.mediaListOptions.scoreFormat
)
} ?: emptyList()
} else { } else {
stat?.statistics?.manga?.scores?.map { convertScore(it.score, stat.mediaListOptions.scoreFormat) } ?: emptyList() stat?.statistics?.manga?.scores?.map {
convertScore(
it.score,
stat.mediaListOptions.scoreFormat
)
} ?: emptyList()
} }
val values: List<Number> = if (anime) { val values: List<Number> = if (anime) {
when (statType) { when (statType) {
@ -337,6 +349,7 @@ class StatsFragment :
type, type,
chartPackets, chartPackets,
xAxisName = "Year", xAxisName = "Year",
scrollPos = 0.0f
) )
adapter.add(ChartItem("Release Year", releaseYearChart, activity)) adapter.add(ChartItem("Release Year", releaseYearChart, activity))
} }
@ -429,6 +442,7 @@ class StatsFragment :
xAxisName = "Genre", xAxisName = "Genre",
polar = true, polar = true,
passedCategories = chartPackets[0].names as List<String>, passedCategories = chartPackets[0].names as List<String>,
normalize = true
) )
adapter.add(ChartItem("Genre", genreChart, activity)) adapter.add(ChartItem("Genre", genreChart, activity))
} }
@ -546,9 +560,11 @@ class StatsFragment :
val chartPackets = mutableListOf<ChartPacket>() val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat -> stats.forEach { stat ->
val names: List<String> = if (anime) { val names: List<String> = if (anime) {
stat?.statistics?.anime?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList() stat?.statistics?.anime?.voiceActors?.map { it.voiceActor.name.full ?: "unknown" }
?: emptyList()
} else { } else {
stat?.statistics?.manga?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList() stat?.statistics?.manga?.voiceActors?.map { it.voiceActor.name.full ?: "unknown" }
?: emptyList()
} }
val values: List<Number> = if (anime) { val values: List<Number> = if (anime) {
when (statType) { when (statType) {
@ -644,7 +660,8 @@ class StatsFragment :
xAxisName = "Studio", xAxisName = "Studio",
polar = true, polar = true,
passedCategories = chartPackets[0].names as List<String>, passedCategories = chartPackets[0].names as List<String>,
scrollPos = null scrollPos = null,
normalize = true
) )
adapter.add(ChartItem("Studio", studioChart, activity)) adapter.add(ChartItem("Studio", studioChart, activity))
} }
@ -654,9 +671,11 @@ class StatsFragment :
val chartPackets = mutableListOf<ChartPacket>() val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat -> stats.forEach { stat ->
val names: List<String> = if (anime) { val names: List<String> = if (anime) {
stat?.statistics?.anime?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList() stat?.statistics?.anime?.staff?.map { it.staff.name.full ?: "unknown" }
?: emptyList()
} else { } else {
stat?.statistics?.manga?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList() stat?.statistics?.manga?.staff?.map { it.staff.name.full ?: "unknown" }
?: emptyList()
} }
val values: List<Number> = if (anime) { val values: List<Number> = if (anime) {
when (statType) { when (statType) {