mirror of
https://github.com/dzeiocom/charts.git
synced 2025-04-23 02:52:10 +00:00
feat: Add basic Stacked charts support (#22)
This commit is contained in:
parent
6afe0a938e
commit
ed06aa697e
@ -144,8 +144,14 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (serie in series) {
|
if (type == ChartType.STACKED) {
|
||||||
serie.onDraw(canvas, rect)
|
for (serie in series.reversed()) {
|
||||||
|
serie.onDraw(canvas, rect)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (serie in series) {
|
||||||
|
serie.onDraw(canvas, rect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,12 @@ import android.graphics.Color
|
|||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
|
import com.dzeio.charts.ChartType
|
||||||
import com.dzeio.charts.ChartViewInterface
|
import com.dzeio.charts.ChartViewInterface
|
||||||
|
import com.dzeio.charts.Entry
|
||||||
import com.dzeio.charts.utils.drawDottedLine
|
import com.dzeio.charts.utils.drawDottedLine
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
class YAxis(
|
class YAxis(
|
||||||
private val view: ChartViewInterface
|
private val view: ChartViewInterface
|
||||||
@ -37,7 +40,7 @@ class YAxis(
|
|||||||
|
|
||||||
override var labelCount = 5
|
override var labelCount = 5
|
||||||
|
|
||||||
private var min: Float? = 0f
|
private var min: Float? = null
|
||||||
private var max: Float? = null
|
private var max: Float? = null
|
||||||
|
|
||||||
@Deprecated("use the new global function", replaceWith = ReplaceWith("YAxisInterface.addLine"))
|
@Deprecated("use the new global function", replaceWith = ReplaceWith("YAxisInterface.addLine"))
|
||||||
@ -68,6 +71,22 @@ class YAxis(
|
|||||||
if (view.series.isEmpty()) {
|
if (view.series.isEmpty()) {
|
||||||
return this.lines.keys.maxOrNull() ?: 0f
|
return this.lines.keys.maxOrNull() ?: 0f
|
||||||
}
|
}
|
||||||
|
if (view.type == ChartType.STACKED) {
|
||||||
|
val nList: ArrayList<Float> = 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
|
val seriesMax = view.series
|
||||||
.maxOf { serie ->
|
.maxOf { serie ->
|
||||||
if (serie.getDisplayedEntries().isEmpty()) {
|
if (serie.getDisplayedEntries().isEmpty()) {
|
||||||
@ -85,6 +104,22 @@ class YAxis(
|
|||||||
if (view.series.isEmpty()) {
|
if (view.series.isEmpty()) {
|
||||||
return this.lines.keys.minOrNull() ?: 0f
|
return this.lines.keys.minOrNull() ?: 0f
|
||||||
}
|
}
|
||||||
|
if (view.type == ChartType.STACKED) {
|
||||||
|
val nList: ArrayList<Float> = 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
|
return view.series
|
||||||
.minOf { serie ->
|
.minOf { serie ->
|
||||||
if (serie.getDisplayedEntries().isEmpty()) {
|
if (serie.getDisplayedEntries().isEmpty()) {
|
||||||
@ -168,4 +203,35 @@ class YAxis(
|
|||||||
addLine(height, Line(true))
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.dzeio.charts.axis
|
|||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
|
import com.dzeio.charts.Entry
|
||||||
|
|
||||||
sealed interface YAxisInterface {
|
sealed interface YAxisInterface {
|
||||||
|
|
||||||
@ -110,4 +111,24 @@ sealed interface YAxisInterface {
|
|||||||
* @param y the Y position of the line
|
* @param y the Y position of the line
|
||||||
*/
|
*/
|
||||||
fun removeLine(y: Float)
|
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
|
||||||
}
|
}
|
||||||
|
@ -44,17 +44,12 @@ class BarSerie(
|
|||||||
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
|
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
|
||||||
val displayedEntries = getDisplayedEntries()
|
val displayedEntries = getDisplayedEntries()
|
||||||
val barWidth = view.xAxis.getEntryWidth(drawableSpace).toFloat()
|
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(
|
val zero = view.yAxis.getPositionOnRect(0f, drawableSpace)
|
||||||
drawableSpace.top, drawableSpace.bottom
|
|
||||||
)
|
|
||||||
|
|
||||||
for (entry in displayedEntries) {
|
for (entry in displayedEntries) {
|
||||||
// calculated height in percent from 0 to 100
|
// 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)
|
||||||
.coerceIn(drawableSpace.top, drawableSpace.bottom)
|
|
||||||
var posX = drawableSpace.left + view.xAxis.getPositionOnRect(
|
var posX = drawableSpace.left + view.xAxis.getPositionOnRect(
|
||||||
entry,
|
entry,
|
||||||
drawableSpace
|
drawableSpace
|
||||||
|
@ -34,15 +34,13 @@ class LineSerie(
|
|||||||
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
|
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
|
||||||
val displayedEntries = getDisplayedEntries()
|
val displayedEntries = getDisplayedEntries()
|
||||||
displayedEntries.sortBy { it.x }
|
displayedEntries.sortBy { it.x }
|
||||||
val max = view.yAxis.getYMax()
|
|
||||||
val min = view.yAxis.getYMin()
|
|
||||||
|
|
||||||
var previousPosX: Float? = null
|
var previousPosX: Float? = null
|
||||||
var previousPosY: Float? = null
|
var previousPosY: Float? = null
|
||||||
|
|
||||||
for (entry in displayedEntries) {
|
for (entry in displayedEntries) {
|
||||||
// calculated height in percent from 0 to 100
|
// 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 +
|
val posX = (drawableSpace.left +
|
||||||
view.xAxis.getPositionOnRect(entry, drawableSpace) +
|
view.xAxis.getPositionOnRect(entry, drawableSpace) +
|
||||||
view.xAxis.getEntryWidth(drawableSpace) / 2f).toFloat()
|
view.xAxis.getEntryWidth(drawableSpace) / 2f).toFloat()
|
||||||
|
@ -37,15 +37,15 @@ class MainFragment : Fragment() {
|
|||||||
val serie2 = BarSerie(this)
|
val serie2 = BarSerie(this)
|
||||||
|
|
||||||
// transform the chart into a grouped chart
|
// transform the chart into a grouped chart
|
||||||
type = ChartType.GROUPED
|
type = ChartType.STACKED
|
||||||
|
|
||||||
// utils function to use Material3 auto colors
|
// utils function to use Material3 auto colors
|
||||||
materielTheme(this, requireView())
|
materielTheme(this, requireView())
|
||||||
serie2.barPaint.color = Color.RED
|
serie2.barPaint.color = Color.RED
|
||||||
|
|
||||||
// give the serie it's entries
|
// give the serie it's entries
|
||||||
serie1.entries = generateRandomDataset(10)
|
serie1.entries = generateRandomDataset(1)
|
||||||
serie2.entries = generateRandomDataset(10)
|
serie2.entries = generateRandomDataset(1)
|
||||||
|
|
||||||
// refresh the Chart
|
// refresh the Chart
|
||||||
refresh()
|
refresh()
|
||||||
@ -164,7 +164,7 @@ class MainFragment : Fragment() {
|
|||||||
private fun generateRandomDataset(size: Int = 100, min: Int = 0, max: Int = 100): ArrayList<Entry> {
|
private fun generateRandomDataset(size: Int = 100, min: Int = 0, max: Int = 100): ArrayList<Entry> {
|
||||||
val dataset: ArrayList<Entry> = arrayListOf()
|
val dataset: ArrayList<Entry> = arrayListOf()
|
||||||
|
|
||||||
for (i in 0 .. size) {
|
for (i in 0 until size) {
|
||||||
dataset.add(Entry(
|
dataset.add(Entry(
|
||||||
i.toDouble(),
|
i.toDouble(),
|
||||||
Random.nextInt(min, max).toFloat()
|
Random.nextInt(min, max).toFloat()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user