Skip to content

Commit

Permalink
Make HorizontalAxis & VerticalAxis open
Browse files Browse the repository at this point in the history
  • Loading branch information
Gowsky committed Apr 6, 2024
1 parent 34b30c8 commit 4b3579c
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ import kotlin.math.min
* @see Axis
* @see BaseAxis
*/
public class HorizontalAxis<Position : AxisPosition.Horizontal>(
public open class HorizontalAxis<Position : AxisPosition.Horizontal>(
override val position: Position,
) : BaseAxis<Position>() {
private val AxisPosition.Horizontal.textVerticalPosition: VerticalPosition
protected val AxisPosition.Horizontal.textVerticalPosition: VerticalPosition
get() = if (isBottom) VerticalPosition.Bottom else VerticalPosition.Top

/**
Expand Down Expand Up @@ -145,45 +145,47 @@ public class HorizontalAxis<Position : AxisPosition.Horizontal>(

if (clipRestoreCount >= 0) canvas.restoreToCount(clipRestoreCount)

drawGuidelines(baseCanvasX, fullXRange, labelValues, lineValues)
drawGuidelines(context, baseCanvasX, fullXRange, labelValues, lineValues)
}

private fun CartesianDrawContext.drawGuidelines(
protected open fun drawGuidelines(
context: CartesianDrawContext,
baseCanvasX: Float,
fullXRange: ClosedFloatingPointRange<Float>,
labelValues: List<Float>,
lineValues: List<Float>?,
) {
val guideline = guideline ?: return
val clipRestoreCount = canvas.save()
canvas.clipRect(chartBounds)
): Unit =
with(context) {
val guideline = guideline ?: return
val clipRestoreCount = canvas.save()
canvas.clipRect(chartBounds)

if (lineValues == null) {
labelValues.forEach { x ->
val canvasX =
baseCanvasX + (x - chartValues.minX) / chartValues.xStep * horizontalDimensions.xSpacing *
layoutDirectionMultiplier
if (lineValues == null) {
labelValues.forEach { x ->
val canvasX =
baseCanvasX + (x - chartValues.minX) / chartValues.xStep * horizontalDimensions.xSpacing *
layoutDirectionMultiplier

guideline
.takeUnless { x.isBoundOf(fullXRange) }
?.drawVertical(this, chartBounds.top, chartBounds.bottom, canvasX)
}
} else {
lineValues.forEach { x ->
val canvasX =
baseCanvasX + (x - chartValues.minX) / chartValues.xStep * horizontalDimensions.xSpacing *
layoutDirectionMultiplier + getLinesCorrectionX(x, fullXRange)
guideline
.takeUnless { x.isBoundOf(fullXRange) }
?.drawVertical(this, chartBounds.top, chartBounds.bottom, canvasX)
}
} else {
lineValues.forEach { x ->
val canvasX =
baseCanvasX + (x - chartValues.minX) / chartValues.xStep * horizontalDimensions.xSpacing *
layoutDirectionMultiplier + getLinesCorrectionX(x, fullXRange)

guideline
.takeUnless { x.isBoundOf(fullXRange) }
?.drawVertical(this, chartBounds.top, chartBounds.bottom, canvasX)
guideline
.takeUnless { x.isBoundOf(fullXRange) }
?.drawVertical(this, chartBounds.top, chartBounds.bottom, canvasX)
}
}
}

if (clipRestoreCount >= 0) canvas.restoreToCount(clipRestoreCount)
}
if (clipRestoreCount >= 0) canvas.restoreToCount(clipRestoreCount)
}

private fun CartesianDrawContext.getLinesCorrectionX(
protected fun CartesianDrawContext.getLinesCorrectionX(
entryX: Float,
fullXRange: ClosedFloatingPointRange<Float>,
): Float =
Expand Down Expand Up @@ -249,7 +251,7 @@ public class HorizontalAxis<Position : AxisPosition.Horizontal>(
}
}

private fun CartesianMeasureContext.getFullXRange(
protected fun CartesianMeasureContext.getFullXRange(
horizontalDimensions: HorizontalDimensions,
): ClosedFloatingPointRange<Float> =
with(horizontalDimensions) {
Expand All @@ -258,7 +260,7 @@ public class HorizontalAxis<Position : AxisPosition.Horizontal>(
start..end
}

private fun getDesiredHeight(
protected open fun getDesiredHeight(
context: CartesianMeasureContext,
horizontalDimensions: HorizontalDimensions,
maxLabelWidth: Float,
Expand Down Expand Up @@ -296,7 +298,7 @@ public class HorizontalAxis<Position : AxisPosition.Horizontal>(
}
}

private fun CartesianMeasureContext.getMaxLabelWidth(
protected fun CartesianMeasureContext.getMaxLabelWidth(
horizontalDimensions: HorizontalDimensions,
fullXRange: ClosedFloatingPointRange<Float>,
): Float {
Expand All @@ -310,7 +312,7 @@ public class HorizontalAxis<Position : AxisPosition.Horizontal>(
.orZero
}

private fun CartesianMeasureContext.getMaxLabelHeight(
protected fun CartesianMeasureContext.getMaxLabelHeight(
horizontalDimensions: HorizontalDimensions,
fullXRange: ClosedFloatingPointRange<Float>,
maxLabelWidth: Float,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ private const val TITLE_ABS_ROTATION_DEGREES = 90f
* @see Axis
* @see BaseAxis
*/
public class VerticalAxis<Position : AxisPosition.Vertical>(
public open class VerticalAxis<Position : AxisPosition.Vertical>(
override val position: Position,
) : BaseAxis<Position>() {
private val areLabelsOutsideAtStartOrInsideAtEnd
protected val areLabelsOutsideAtStartOrInsideAtEnd: Boolean
get() =
horizontalLabelPosition == Outside && position is AxisPosition.Vertical.Start ||
horizontalLabelPosition == Inside && position is AxisPosition.Vertical.End

private val textHorizontalPosition: HorizontalPosition
protected val textHorizontalPosition: HorizontalPosition
get() = if (areLabelsOutsideAtStartOrInsideAtEnd) HorizontalPosition.Start else HorizontalPosition.End

/**
Expand Down Expand Up @@ -132,6 +132,7 @@ public class VerticalAxis<Position : AxisPosition.Vertical>(

label ?: return@forEach
drawLabel(
context = this,
label = label,
labelText = valueFormatter.format(labelValue, chartValues, position),
labelX = labelX,
Expand All @@ -158,48 +159,50 @@ public class VerticalAxis<Position : AxisPosition.Vertical>(
horizontalDimensions: MutableHorizontalDimensions,
): Unit = Unit

private fun CartesianDrawContext.drawLabel(
protected open fun drawLabel(
context: CartesianDrawContext,
label: TextComponent,
labelText: CharSequence,
labelX: Float,
tickCenterY: Float,
) {
val textBounds =
label.getTextBounds(this, labelText, rotationDegrees = labelRotationDegrees).apply {
translate(
x = labelX,
y = tickCenterY - centerY(),
): Unit =
with(context) {
val textBounds =
label.getTextBounds(this, labelText, rotationDegrees = labelRotationDegrees).apply {
translate(
x = labelX,
y = tickCenterY - centerY(),
)
}

if (
horizontalLabelPosition == Outside ||
isNotInRestrictedBounds(
left = textBounds.left,
top = textBounds.top,
right = textBounds.right,
bottom = textBounds.bottom,
)
) {
label.drawText(
context = this,
text = labelText,
textX = labelX,
textY = tickCenterY,
horizontalPosition = textHorizontalPosition,
verticalPosition = verticalLabelPosition.textPosition,
rotationDegrees = labelRotationDegrees,
maxTextWidth =
when (sizeConstraint) {
// Let the `TextComponent` use as much width as it needs, based on the measuring phase.
is SizeConstraint.Auto -> Int.MAX_VALUE
else -> (bounds.width() - tickLength - axisThickness).toInt()
},
)
}

if (
horizontalLabelPosition == Outside ||
isNotInRestrictedBounds(
left = textBounds.left,
top = textBounds.top,
right = textBounds.right,
bottom = textBounds.bottom,
)
) {
label.drawText(
context = this,
text = labelText,
textX = labelX,
textY = tickCenterY,
horizontalPosition = textHorizontalPosition,
verticalPosition = verticalLabelPosition.textPosition,
rotationDegrees = labelRotationDegrees,
maxTextWidth =
when (sizeConstraint) {
// Let the `TextComponent` use as much width as it needs, based on the measuring phase.
is SizeConstraint.Auto -> Int.MAX_VALUE
else -> (bounds.width() - tickLength - axisThickness).toInt()
},
)
}
}

private fun CartesianMeasureContext.getTickLeftX(): Float {
protected fun CartesianMeasureContext.getTickLeftX(): Float {
val onLeft = position.isLeft(isLtr = isLtr)
val base = if (onLeft) bounds.right else bounds.left
return when {
Expand All @@ -217,7 +220,7 @@ public class VerticalAxis<Position : AxisPosition.Vertical>(
outInsets: HorizontalInsets,
): Unit =
with(context) {
val desiredWidth = getDesiredWidth(availableHeight)
val desiredWidth = getDesiredWidth(this, availableHeight)

outInsets.set(
start = if (position.isStart) desiredWidth else 0f,
Expand Down Expand Up @@ -254,59 +257,68 @@ public class VerticalAxis<Position : AxisPosition.Vertical>(
/**
* Calculates the optimal width for this [VerticalAxis], accounting for the value of [sizeConstraint].
*/
private fun CartesianMeasureContext.getDesiredWidth(height: Float) =
when (val constraint = sizeConstraint) {
is SizeConstraint.Auto -> {
val titleComponentWidth =
title?.let { title ->
titleComponent?.getWidth(
context = this,
text = title,
rotationDegrees = TITLE_ABS_ROTATION_DEGREES,
height = bounds.height().toInt(),
protected open fun getDesiredWidth(
context: CartesianMeasureContext,
height: Float,
): Float =
with(context) {
when (val constraint = sizeConstraint) {
is SizeConstraint.Auto -> {
val titleComponentWidth =
title?.let { title ->
titleComponent?.getWidth(
context = this,
text = title,
rotationDegrees = TITLE_ABS_ROTATION_DEGREES,
height = bounds.height().toInt(),
)
}.orZero
val labelSpace =
when (horizontalLabelPosition) {
Outside -> getMaxLabelWidth(height)
Inside -> 0f
}
(labelSpace + titleComponentWidth + axisThickness + tickLength)
.coerceIn(
minimumValue = constraint.minSizeDp.pixels,
maximumValue = constraint.maxSizeDp.pixels,
)
}.orZero
val labelSpace =
when (horizontalLabelPosition) {
Outside -> getMaxLabelWidth(height)
Inside -> 0f
}
(labelSpace + titleComponentWidth + axisThickness + tickLength)
.coerceIn(minimumValue = constraint.minSizeDp.pixels, maximumValue = constraint.maxSizeDp.pixels)
}

is SizeConstraint.Exact -> constraint.sizeDp.pixels
is SizeConstraint.Fraction -> canvasBounds.width() * constraint.fraction
is SizeConstraint.TextWidth ->
label?.getWidth(
context = this,
text = constraint.text,
rotationDegrees = labelRotationDegrees,
).orZero + tickLength + axisThickness.half
}

is SizeConstraint.Exact -> constraint.sizeDp.pixels
is SizeConstraint.Fraction -> canvasBounds.width() * constraint.fraction
is SizeConstraint.TextWidth ->
label?.getWidth(
context = this,
text = constraint.text,
rotationDegrees = labelRotationDegrees,
).orZero + tickLength + axisThickness.half
}

private fun CartesianMeasureContext.getMaxLabelHeight() =
protected fun CartesianMeasureContext.getMaxLabelHeight(): Float =
label?.let { label ->
itemPlacer
.getHeightMeasurementLabelValues(this, position)
.maxOfOrNull { value -> label.getHeight(this, valueFormatter.format(value, chartValues, position)) }
}.orZero

private fun CartesianMeasureContext.getMaxLabelWidth(axisHeight: Float) =
protected fun CartesianMeasureContext.getMaxLabelWidth(axisHeight: Float): Float =
label?.let { label ->
itemPlacer
.getWidthMeasurementLabelValues(this, axisHeight, getMaxLabelHeight(), position)
.maxOfOrNull { value -> label.getWidth(this, valueFormatter.format(value, chartValues, position)) }
}.orZero

private fun CartesianDrawContext.getLineCanvasYCorrection(
protected fun CartesianDrawContext.getLineCanvasYCorrection(
thickness: Float,
y: Float,
) = if (y == chartValues.getYRange(position).maxY && itemPlacer.getShiftTopLines(this)) {
-thickness.half
} else {
thickness.half
}
): Float =
if (y == chartValues.getYRange(position).maxY && itemPlacer.getShiftTopLines(this)) {
-thickness.half
} else {
thickness.half
}

/**
* Defines the horizontal position of each of a vertical axis’s labels relative to the axis line.
Expand Down

0 comments on commit 4b3579c

Please sign in to comment.