Skip to content

Commit

Permalink
Add initial impl. of legends
Browse files Browse the repository at this point in the history
  • Loading branch information
mahozad committed Jul 19, 2021
1 parent 7bfa973 commit 17d74f8
Show file tree
Hide file tree
Showing 2 changed files with 284 additions and 0 deletions.
267 changes: 267 additions & 0 deletions piechart/src/main/kotlin/ir/mahozad/android/Component.kt
@@ -0,0 +1,267 @@
package ir.mahozad.android

import android.graphics.*
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import androidx.annotation.Dimension
import androidx.annotation.FloatRange
import kotlin.math.min

/**
* Using the *composite* pattern.
*/
internal interface Component {
val width: Float
val height: Float
fun layOut(top: Float, left: Float)
fun draw(canvas: Canvas)
}

internal class Text(
private val string: String,
@Dimension size: Float,
@ColorInt color: Int,
font: Typeface
) : Component {

private val paint = updatePaintForLabel(Paint(), size, color, font)
private val dimensions = calculateLabelBounds(string, paint)
private val bounds = RectF(0f, 0f, 0f, 0f)
private var descent = paint.descent()
override val width = dimensions.width()
override val height = dimensions.height()

override fun layOut(top: Float, left: Float) {
val size = calculateLabelBounds(string, paint)
val width = size.width()
val height = size.height()
bounds.set(left, top, left + width, top + height)
}

override fun draw(canvas: Canvas) {
// The x denotes the horizontal *center* of the text because it is center aligned
val x = bounds.centerX()
val y = bounds.bottom - descent
canvas.drawText(string, x, y, paint)
}
}

internal class Icon(
private val drawable: Drawable,
override val height: Float,
@ColorInt private val tint: Int? = null
) : Component {

override val width = calculateLabelIconWidth(drawable, height)

override fun layOut(top: Float, left: Float) {
val right = left + width
val bottom = top + height
drawable.bounds = Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
tint?.let { drawable.setTint(it) }
}

override fun draw(canvas: Canvas) {
drawable.draw(canvas)
}
}

/**
* Either a row or a column.
*/
internal data class Line(val components: List<Component>, val width: Float, val height: Float)

enum class Alignment { START, CENTER, END }
enum class LayoutDirection { HORIZONTAL, VERTICAL }
data class Padding(
val top: Float,
val end: Float,
val start: Float,
val bottom: Float,
)

internal class Root(
private val startCoordinates: Coordinates? = null,
private val parentMaxWidth: Float,
private val parentMaxHeight: Float,
private val padding: Padding = Padding(0f, 0f, 0f, 0f),
private val children: List<Component>,
private val layoutDirection: LayoutDirection,
private val childrenAlignment: Alignment,
private val maxRows: Int = 1,
private val maxColumns: Int = 1,
private val hasBorder: Boolean = false,
private val hasBackground: Boolean = false,
private val borderDashIntervals: FloatArray? = null,
@ColorInt private val borderColor: Int = Color.BLACK,
@ColorInt private val backgroundColor: Int = Color.TRANSPARENT,
/**
* Note that these two are convenience properties because the alpha can be specified in the color itself as well.
*/
@FloatRange(from = 0.0, to = 1.0) private val backgroundOpacity: Float = 1f,
@FloatRange(from = 0.0, to = 1.0) private val borderOpacity: Float = 1f,
@Dimension private val cornerRadius: Float = 0f,
@Dimension private val borderThickness: Float = 0f
) : Component {

private val bounds = RectF(0f, 0f, 0f, 0f)
private val paint = Paint()
private val rows: List<Line> by lazy {
val rows = mutableListOf<Line>()
val rowWidth = padding.start + padding.end
for (child in children) {
val row = listOf(child)
}
emptyList()
}

override val width by lazy {
if (layoutDirection == LayoutDirection.HORIZONTAL) {
val totalChildrenWidth = children.sumOf { it.width.toDouble() }.toFloat()
min(parentMaxWidth, totalChildrenWidth)
} else {
val childrenMaxWidth = children.maxOf { it.width }
min(parentMaxWidth, childrenMaxWidth)
}
}
override val height by lazy {
if (layoutDirection == LayoutDirection.HORIZONTAL) {
val childrenMaxHeight = children.maxOf { it.height }
min(parentMaxHeight, childrenMaxHeight)
} else {
val totalChildrenHeight = children.sumOf { it.height.toDouble() }.toFloat()
min(parentMaxHeight, totalChildrenHeight)
}
}

override fun layOut(top: Float, left: Float) {
// if (layoutDirection == LayoutDirection.HORIZONTAL) {
// for (row in rows) {
// for (component in row.components) {
// val startCoordinates = 0f /* TODO */
// component.layOut(startCoordinates, row.height)
// }
// }
// }
var newTop = top
var newLeft = left
for (child in children) {
child.layOut(newTop, newLeft)
if (layoutDirection == LayoutDirection.HORIZONTAL) {
newLeft += child.width /* + margin */
} else {
newTop += child.height
}
}
}

override fun draw(canvas: Canvas) {
if (hasBorder) {
paint.style = Paint.Style.STROKE
paint.color = borderColor
paint.alpha = (borderOpacity * 255).toInt()
paint.strokeWidth = borderThickness
paint.pathEffect = DashPathEffect(borderDashIntervals, 0f)
canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint)
}
if (hasBackground) {
paint.style = Paint.Style.FILL
paint.color = backgroundColor
paint.alpha = (backgroundOpacity * 255).toInt()
canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint)
}
for (child in children) {
child.draw(canvas)
}
}
}

internal class Container(
private val padding: Padding = Padding(0f, 0f, 0f, 0f),
private val parentMaxWidth: Float,
private val parentMaxHeight: Float,
private val children: List<Component>,
private val layoutDirection: LayoutDirection,
private val childrenAlignment: Alignment,
private val maxRows: Int = 1,
private val maxColumns: Int = 1,
private val hasBorder: Boolean = false,
private val hasBackground: Boolean = false,
private val borderDashIntervals: FloatArray? = null,
@ColorInt private val borderColor: Int = Color.BLACK,
@ColorInt private val backgroundColor: Int = Color.TRANSPARENT,
/**
* Note that these two are convenience properties because the alpha can be specified in the color itself as well.
*/
@FloatRange(from = 0.0, to = 1.0) private val backgroundOpacity: Float = 1f,
@FloatRange(from = 0.0, to = 1.0) private val borderOpacity: Float = 1f,

@Dimension private val cornerRadius: Float = 0f,
@Dimension private val borderThickness: Float = 0f
) : Component {

private val bounds = RectF(0f, 0f, 0f, 0f)
private val paint = Paint()
private val dashedBorder = Path()

private val rows: List<Line> by lazy {
val rows = mutableListOf<Line>()
val rowWidth = padding.start + padding.end
for (child in children) {
val row = listOf(child)
}
emptyList()
}

override val width by lazy {
if (layoutDirection == LayoutDirection.HORIZONTAL) {
val totalChildrenWidth = children.sumOf { it.width.toDouble() }.toFloat()
min(parentMaxWidth, totalChildrenWidth)
} else {
val childrenMaxWidth = children.maxOf { it.width }
min(parentMaxWidth, childrenMaxWidth)
}
}
override val height by lazy {
if (layoutDirection == LayoutDirection.HORIZONTAL) {
val childrenMaxHeight = children.maxOf { it.height }
min(parentMaxHeight, childrenMaxHeight)
} else {
val totalChildrenHeight = children.sumOf { it.height.toDouble() }.toFloat()
min(parentMaxHeight, totalChildrenHeight)
}
}

override fun layOut(top: Float, left: Float) {
var newTop = top
var newLeft = left
for (child in children) {
child.layOut(newTop, newLeft)
if (layoutDirection == LayoutDirection.HORIZONTAL) {
newLeft += child.width /* + margin */
} else {
newTop += child.height
}
}
}

override fun draw(canvas: Canvas) {
if (hasBorder) {
paint.style = Paint.Style.STROKE
paint.color = borderColor
paint.alpha = (borderOpacity * 255).toInt()
paint.strokeWidth = borderThickness
paint.pathEffect = DashPathEffect(borderDashIntervals, 0f)
canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint)
}
if (hasBackground) {
paint.style = Paint.Style.FILL
paint.color = backgroundColor
paint.alpha = (backgroundOpacity * 255).toInt()
canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint)
}
for (child in children) {
child.draw(canvas)
}
}
}
17 changes: 17 additions & 0 deletions piechart/src/main/kotlin/ir/mahozad/android/PieChart.kt
Expand Up @@ -277,6 +277,7 @@ class PieChart(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val totalDrawableRect = RectF()
private var pieRadius = 0f
private var center = Coordinates(0f, 0f)
private lateinit var legendsBox: Component

/**
* Attributes are a powerful way of controlling the behavior and appearance of views,
Expand Down Expand Up @@ -350,6 +351,20 @@ class PieChart(context: Context, attrs: AttributeSet) : View(context, attrs) {
pieEnclosingRect.set(RectF(left, top, right, bottom))
totalDrawableRect.set(pieEnclosingRect)




val legendsTitle = Text("Title", 50f, Color.BLACK, DEFAULT)
val drawable1 = resources.getDrawable(R.drawable.ic_circle, null)
val icon1 = Icon(drawable1, 100f)
val label1 = Text("legend1", 50f, Color.BLACK, DEFAULT)
val legend1 = Container(parentMaxWidth = width.toFloat(), parentMaxHeight = height.toFloat(),children = listOf(icon1, label1), childrenAlignment = Alignment.CENTER, layoutDirection = LayoutDirection.HORIZONTAL)
legendsBox = Root(Coordinates(100f, 100f), width.toFloat(), height.toFloat(), hasBackground = true, backgroundColor = Color.argb(200, 150, 100, 10), children = listOf(legendsTitle, legend1), childrenAlignment = Alignment.CENTER, layoutDirection = LayoutDirection.VERTICAL)
legendsBox.layOut(0f, 600f)




if (labelType == OUTSIDE) {
val defaults = Defaults(outsideLabelsMargin, labelsSize, labelsColor, labelsFont, labelIconsHeight, labelIconsMargin, labelIconsPlacement)
pieEnclosingRect.set(calculatePieNewBoundsForOutsideLabel(context, pieEnclosingRect, slices, drawDirection, startAngle, defaults, shouldCenterPie))
Expand Down Expand Up @@ -585,6 +600,8 @@ class PieChart(context: Context, attrs: AttributeSet) : View(context, attrs) {
mainPaint.color = ContextCompat.getColor(context, android.R.color.black) // or better Color.BLACK
mainPaint.alpha = (overlayAlpha * 255).toInt()
canvas.drawPath(overlay, mainPaint)

legendsBox.draw(canvas)
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
Expand Down

0 comments on commit 17d74f8

Please sign in to comment.