diff --git a/library/src/main/java/com/dzeio/charts/ChartView.kt b/library/src/main/java/com/dzeio/charts/ChartView.kt index 63258a8..562f5d3 100644 --- a/library/src/main/java/com/dzeio/charts/ChartView.kt +++ b/library/src/main/java/com/dzeio/charts/ChartView.kt @@ -144,8 +144,14 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet ) } - for (serie in series) { - serie.onDraw(canvas, rect) + if (type == ChartType.STACKED) { + for (serie in series.reversed()) { + serie.onDraw(canvas, rect) + } + } else { + for (serie in series) { + serie.onDraw(canvas, rect) + } } super.onDraw(canvas) } diff --git a/library/src/main/java/com/dzeio/charts/axis/YAxis.kt b/library/src/main/java/com/dzeio/charts/axis/YAxis.kt index 68b66b4..97441ac 100644 --- a/library/src/main/java/com/dzeio/charts/axis/YAxis.kt +++ b/library/src/main/java/com/dzeio/charts/axis/YAxis.kt @@ -5,9 +5,12 @@ import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF +import com.dzeio.charts.ChartType import com.dzeio.charts.ChartViewInterface +import com.dzeio.charts.Entry import com.dzeio.charts.utils.drawDottedLine import kotlin.math.roundToInt +import kotlin.math.sign class YAxis( private val view: ChartViewInterface @@ -37,7 +40,7 @@ class YAxis( override var labelCount = 5 - private var min: Float? = 0f + private var min: Float? = null private var max: Float? = null @Deprecated("use the new global function", replaceWith = ReplaceWith("YAxisInterface.addLine")) @@ -68,6 +71,22 @@ class YAxis( if (view.series.isEmpty()) { return this.lines.keys.maxOrNull() ?: 0f } + if (view.type == ChartType.STACKED) { + val nList: ArrayList = arrayListOf() + + for (serie in view.series) { + val size = serie.entries.size + while (nList.size < size) { + nList.add(0f) + } + for (index in 0 until serie.entries.size) { + val entry = serie.entries[index] + nList[index] += entry.y + } + } + + return nList.maxOf { it } + } val seriesMax = view.series .maxOf { serie -> if (serie.getDisplayedEntries().isEmpty()) { @@ -85,6 +104,22 @@ class YAxis( if (view.series.isEmpty()) { return this.lines.keys.minOrNull() ?: 0f } + if (view.type == ChartType.STACKED) { + val nList: ArrayList = arrayListOf() + + for (serie in view.series) { + val size = serie.entries.size + while (nList.size < size) { + nList.add(0f) + } + for (index in 0 until serie.entries.size) { + val entry = serie.entries[index] + nList[index] += entry.y + } + } + + return nList.minOf { it } + } return view.series .minOf { serie -> if (serie.getDisplayedEntries().isEmpty()) { @@ -168,4 +203,35 @@ class YAxis( addLine(height, Line(true)) } } + + override fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Float { + if (view.type == ChartType.STACKED) { + val serie = view.series.find { it.entries.contains(entry) } + val index = view.series.indexOf(serie) + return getPositionOnRect(entry, drawableSpace, index) + } + return getPositionOnRect(entry.y, drawableSpace) + } + + private fun getPositionOnRect(entry: Entry, drawableSpace: RectF, index: Int): Float { + if (index > 0) { + val entry2 = view.series[index - 1].entries.find { it.x == entry.x } + if (entry2 != null) { + // make a new """Entry""" containing the new Y + val isReverse = sign(entry2.y) != sign(entry.y) + val tmp = Entry(entry.x, if (isReverse) entry.y else entry.y + entry2.y) + return getPositionOnRect(tmp, drawableSpace, index - 1) + } + } + return getPositionOnRect(entry.y, drawableSpace) + } + + override fun getPositionOnRect(point: Float, drawableSpace: RectF): Float { + val min = getYMin() + val max = getYMax() + + return (1 - (point - min) / (max - min)) * + drawableSpace.height() + + drawableSpace.top + } } diff --git a/library/src/main/java/com/dzeio/charts/axis/YAxisInterface.kt b/library/src/main/java/com/dzeio/charts/axis/YAxisInterface.kt index 9a7f469..2807da5 100644 --- a/library/src/main/java/com/dzeio/charts/axis/YAxisInterface.kt +++ b/library/src/main/java/com/dzeio/charts/axis/YAxisInterface.kt @@ -3,6 +3,7 @@ package com.dzeio.charts.axis import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF +import com.dzeio.charts.Entry sealed interface YAxisInterface { @@ -110,4 +111,24 @@ sealed interface YAxisInterface { * @param y the Y position of the line */ fun removeLine(y: Float) + + /** + * get the position of an [entry] Y position in the [drawableSpace] + * + * if the chart type is stacked it will automatically calculate the position depending on it + * + * @param entry the entry to search to position + * @param drawableSpace the space in which it should appear + * @return the float position (can be out of the [drawableSpace]) + */ + fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Float + + /** + * get the position of a [point] in the [drawableSpace] + * + * @param point the point to search to position + * @param drawableSpace the space in which it should appear + * @return the float position (can be out of the [drawableSpace]) + */ + fun getPositionOnRect(point: Float, drawableSpace: RectF): Float } diff --git a/library/src/main/java/com/dzeio/charts/series/BarSerie.kt b/library/src/main/java/com/dzeio/charts/series/BarSerie.kt index dfbb5d5..02337e2 100644 --- a/library/src/main/java/com/dzeio/charts/series/BarSerie.kt +++ b/library/src/main/java/com/dzeio/charts/series/BarSerie.kt @@ -44,17 +44,12 @@ class BarSerie( override fun onDraw(canvas: Canvas, drawableSpace: RectF) { val displayedEntries = getDisplayedEntries() val barWidth = view.xAxis.getEntryWidth(drawableSpace).toFloat() - val max = view.yAxis.getYMax() - val min = view.yAxis.getYMin() - val zero = ((1 - -min / (max - min)) * drawableSpace.height() + drawableSpace.top).coerceIn( - drawableSpace.top, drawableSpace.bottom - ) + val zero = view.yAxis.getPositionOnRect(0f, drawableSpace) for (entry in displayedEntries) { // calculated height in percent from 0 to 100 - val top = ((1 - (entry.y - min) / (max - min)) * drawableSpace.height() + drawableSpace.top) - .coerceIn(drawableSpace.top, drawableSpace.bottom) + val top = view.yAxis.getPositionOnRect(entry, drawableSpace) var posX = drawableSpace.left + view.xAxis.getPositionOnRect( entry, drawableSpace diff --git a/library/src/main/java/com/dzeio/charts/series/LineSerie.kt b/library/src/main/java/com/dzeio/charts/series/LineSerie.kt index b322f42..467cd1a 100644 --- a/library/src/main/java/com/dzeio/charts/series/LineSerie.kt +++ b/library/src/main/java/com/dzeio/charts/series/LineSerie.kt @@ -34,15 +34,13 @@ class LineSerie( override fun onDraw(canvas: Canvas, drawableSpace: RectF) { val displayedEntries = getDisplayedEntries() displayedEntries.sortBy { it.x } - val max = view.yAxis.getYMax() - val min = view.yAxis.getYMin() var previousPosX: Float? = null var previousPosY: Float? = null for (entry in displayedEntries) { // calculated height in percent from 0 to 100 - val top = (1 - (entry.y - min) / (max - min)) * drawableSpace.height() + drawableSpace.top + val top = view.yAxis.getPositionOnRect(entry, drawableSpace) val posX = (drawableSpace.left + view.xAxis.getPositionOnRect(entry, drawableSpace) + view.xAxis.getEntryWidth(drawableSpace) / 2f).toFloat() diff --git a/sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt b/sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt index 7932bfe..b9f0e1f 100644 --- a/sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt +++ b/sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt @@ -37,15 +37,15 @@ class MainFragment : Fragment() { val serie2 = BarSerie(this) // transform the chart into a grouped chart - type = ChartType.GROUPED + type = ChartType.STACKED // 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) + serie1.entries = generateRandomDataset(1) + serie2.entries = generateRandomDataset(1) // refresh the Chart refresh() @@ -164,7 +164,7 @@ class MainFragment : Fragment() { private fun generateRandomDataset(size: Int = 100, min: Int = 0, max: Int = 100): ArrayList { val dataset: ArrayList = arrayListOf() - for (i in 0 .. size) { + for (i in 0 until size) { dataset.add(Entry( i.toDouble(), Random.nextInt(min, max).toFloat() @@ -233,4 +233,4 @@ class MainFragment : Fragment() { } } } -} \ No newline at end of file +}