Skip to content

Commit

Permalink
CandlestickCartesianLayer: Convert Candles to interface, and rena…
Browse files Browse the repository at this point in the history
…me it to `CandleProvider`
  • Loading branch information
patrickmichalik committed Mar 30, 2024
1 parent bb2a1b0 commit e73df6c
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 236 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ import androidx.compose.ui.tooling.preview.Preview
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottomAxis
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberStartAxis
import com.patrykandpatrick.vico.compose.cartesian.layer.hollow
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberCandlestickCartesianLayer
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberHollowCandles
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
import com.patrykandpatrick.vico.core.cartesian.layer.CandlestickCartesianLayer
import com.patrykandpatrick.vico.core.cartesian.model.CandlestickCartesianLayerModel
import com.patrykandpatrick.vico.core.cartesian.model.CartesianChartModel

Expand All @@ -36,7 +37,7 @@ fun CandlestickLinePreview() {
CartesianChartHost(
chart =
rememberCartesianChart(
rememberCandlestickCartesianLayer(rememberHollowCandles()),
rememberCandlestickCartesianLayer(CandlestickCartesianLayer.CandleProvider.hollow()),
startAxis = rememberStartAxis(),
bottomAxis = rememberBottomAxis(),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import com.patrykandpatrick.vico.core.common.DrawingModelInterpolator
/** Creates and remembers a [CandlestickCartesianLayer]. */
@Composable
public fun rememberCandlestickCartesianLayer(
candles: CandlestickCartesianLayer.Candles = rememberFilledCandles(),
candles: CandlestickCartesianLayer.CandleProvider = CandlestickCartesianLayer.CandleProvider.filled(),
minCandleBodyHeight: Dp = Defaults.MIN_CANDLE_BODY_HEIGHT_DP.dp,
candleSpacing: Dp = Defaults.CANDLE_SPACING_DP.dp,
verticalAxisPosition: AxisPosition.Vertical? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.patrykandpatrick.vico.compose.common.style.getDefaultColors
import com.patrykandpatrick.vico.core.cartesian.layer.CandlestickCartesianLayer
import com.patrykandpatrick.vico.core.cartesian.layer.CandlestickCartesianLayer.Candle
import com.patrykandpatrick.vico.core.cartesian.layer.asWick
import com.patrykandpatrick.vico.core.cartesian.layer.filled
import com.patrykandpatrick.vico.core.cartesian.layer.hollow
import com.patrykandpatrick.vico.core.common.Defaults
import com.patrykandpatrick.vico.core.common.component.LineComponent

Expand Down Expand Up @@ -69,39 +71,19 @@ public fun rememberCandle(
bottomWick: LineComponent = topWick,
): Candle = remember(body, topWick, bottomWick) { Candle(body, topWick, bottomWick) }

/**
* TODO
*/
/** A [CandlestickCartesianLayer.CandleProvider.Companion.filled] alias with default arguments. */
@Composable
public fun rememberFilledCandles(
@Stable
public fun CandlestickCartesianLayer.CandleProvider.Companion.filled(
bullish: Candle = Candle.sharpFilledCandle(Color(getDefaultColors().candlestickGreen)),
neutral: Candle = bullish.copyWithColor(Color(getDefaultColors().candlestickGray)),
bearish: Candle = bullish.copyWithColor(Color(getDefaultColors().candlestickRed)),
): CandlestickCartesianLayer.Candles =
remember(
bullish,
neutral,
bearish,
) {
CandlestickCartesianLayer.Candles(
absolutelyBullishRelativelyBullish = bullish,
absolutelyBullishRelativelyNeutral = bullish,
absolutelyBullishRelativelyBearish = bullish,
absolutelyNeutralRelativelyBullish = neutral,
absolutelyNeutralRelativelyNeutral = neutral,
absolutelyNeutralRelativelyBearish = neutral,
absolutelyBearishRelativelyBullish = bearish,
absolutelyBearishRelativelyNeutral = bearish,
absolutelyBearishRelativelyBearish = bearish,
)
}
): CandlestickCartesianLayer.CandleProvider = CandlestickCartesianLayer.CandleProvider.filled(bullish, neutral, bearish)

/**
* TODO
*/
/** A [CandlestickCartesianLayer.CandleProvider.Companion.hollow] alias with default arguments. */
@Composable
@Stable
public fun rememberHollowCandles(
public fun CandlestickCartesianLayer.CandleProvider.Companion.hollow(
absolutelyBullishRelativelyBullish: Candle = Candle.sharpHollowCandle(Color(getDefaultColors().candlestickGreen)),
absolutelyBullishRelativelyNeutral: Candle =
absolutelyBullishRelativelyBullish.copyWithColor(Color(getDefaultColors().candlestickGray)),
Expand All @@ -115,8 +97,8 @@ public fun rememberHollowCandles(
absolutelyBearishRelativelyBullish.copyWithColor(Color(getDefaultColors().candlestickGray)),
absolutelyBearishRelativelyBearish: Candle =
absolutelyBearishRelativelyBullish.copyWithColor(Color(getDefaultColors().candlestickRed)),
): CandlestickCartesianLayer.Candles =
remember(
): CandlestickCartesianLayer.CandleProvider =
CandlestickCartesianLayer.CandleProvider.hollow(
absolutelyBullishRelativelyBullish,
absolutelyBullishRelativelyNeutral,
absolutelyBullishRelativelyBearish,
Expand All @@ -126,16 +108,4 @@ public fun rememberHollowCandles(
absolutelyBearishRelativelyBullish,
absolutelyBearishRelativelyNeutral,
absolutelyBearishRelativelyBearish,
) {
CandlestickCartesianLayer.Candles(
absolutelyBullishRelativelyBullish,
absolutelyBullishRelativelyNeutral,
absolutelyBullishRelativelyBearish,
absolutelyNeutralRelativelyBullish,
absolutelyNeutralRelativelyNeutral,
absolutelyNeutralRelativelyBearish,
absolutelyBearishRelativelyBullish,
absolutelyBearishRelativelyNeutral,
absolutelyBearishRelativelyBearish,
)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.patrykandpatrick.vico.core.cartesian.axis.AxisPosition
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
import com.patrykandpatrick.vico.core.cartesian.dimensions.MutableHorizontalDimensions
import com.patrykandpatrick.vico.core.cartesian.draw.CartesianChartDrawContext
import com.patrykandpatrick.vico.core.cartesian.layer.CandlestickCartesianLayer.Candle
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
import com.patrykandpatrick.vico.core.cartesian.marker.put
import com.patrykandpatrick.vico.core.cartesian.model.CandlestickCartesianLayerDrawingModel
Expand All @@ -44,15 +45,15 @@ import com.patrykandpatrick.vico.core.common.extension.half
/**
* [CandlestickCartesianLayer] displays data as vertical bars. It can draw multiple columns per segment.
*
* @param candles TODO
* @param candles provides the [Candle]s.
* @param minCandleBodyHeightDp TODO
* @param candleSpacingDp the horizontal padding between the edges of chart segments and the columns they contain.
* segments that contain a single column only.
* @param verticalAxisPosition the position of the [VerticalAxis] with which the [ColumnCartesianLayer] should be
* associated. Use this for independent [CartesianLayer] scaling.
*/
public open class CandlestickCartesianLayer(
public var candles: Candles,
public var candles: CandleProvider,
public var minCandleBodyHeightDp: Float = Defaults.MIN_CANDLE_BODY_HEIGHT_DP,
public var candleSpacingDp: Float = Defaults.CANDLE_SPACING_DP,
public var verticalAxisPosition: AxisPosition.Vertical? = null,
Expand All @@ -73,7 +74,8 @@ public open class CandlestickCartesianLayer(
public val topWick: LineComponent = body.asWick(),
public val bottomWick: LineComponent = topWick,
) {
internal val thicknessDp
/** The width of the [Candle] (in dp). */
public val widthDp: Float
get() =
maxOf(
body.thicknessDp,
Expand Down Expand Up @@ -117,20 +119,20 @@ public open class CandlestickCartesianLayer(
val drawingStart: Float =
bounds.getStart(isLtr = isLtr) + (
horizontalDimensions.startPadding -
candles.maxThicknessDp.half.pixels * zoom
candles.getWidestCandle(model.extraStore).widthDp.half.pixels * zoom
) * layoutDirectionMultiplier - horizontalScroll

var bodyCenterX: Float
var candle: Candle
val minBodyHeight = minCandleBodyHeightDp.pixels

model.series.forEachInIndexed(range = chartValues.minX..chartValues.maxX) { index, entry, _ ->
candle = candles.getCandle(entry)
candle = candles.getCandle(entry, model.extraStore)
val candleInfo = drawingModel?.entries?.get(entry.x) ?: entry.toCandleInfo(yRange)

val xSpacingMultiplier = (entry.x - chartValues.minX) / chartValues.xStep
bodyCenterX = drawingStart + layoutDirectionMultiplier * horizontalDimensions.xSpacing *
xSpacingMultiplier + candle.thicknessDp.half.pixels * zoom
xSpacingMultiplier + candle.widthDp.half.pixels * zoom

var bodyBottomY = bounds.bottom - candleInfo.bodyBottomY * bounds.height()
var bodyTopY = bounds.bottom - candleInfo.bodyTopY * bounds.height()
Expand Down Expand Up @@ -217,7 +219,7 @@ public open class CandlestickCartesianLayer(
model: CandlestickCartesianLayerModel,
) {
with(context) {
val candleWidth = candles.maxThicknessDp.pixels
val candleWidth = candles.getWidestCandle(model.extraStore).widthDp.pixels
val xSpacing = candleWidth + candleSpacingDp.pixels
when (val horizontalLayout = horizontalLayout) {
is HorizontalLayout.Segmented -> horizontalDimensions.ensureSegmentedValues(xSpacing, chartValues)
Expand Down Expand Up @@ -270,69 +272,85 @@ public open class CandlestickCartesianLayer(
return CandlestickCartesianLayerDrawingModel(series.associate { it.x to it.toCandleInfo(yRange) })
}

/**
* TODO
*
* @param absolutelyBullishRelativelyBullish TODO
* @param absolutelyBullishRelativelyNeutral TODO
* @param absolutelyBullishRelativelyBearish TODO
* @param absolutelyNeutralRelativelyBullish TODO
* @param absolutelyNeutralRelativelyNeutral TODO
* @param absolutelyNeutralRelativelyBearish TODO
* @param absolutelyBearishRelativelyBullish TODO
* @param absolutelyBearishRelativelyNeutral TODO
* @param absolutelyBearishRelativelyBearish TODO
*/
public class Candles(
public val absolutelyBullishRelativelyBullish: Candle,
public val absolutelyBullishRelativelyNeutral: Candle,
public val absolutelyBullishRelativelyBearish: Candle,
public val absolutelyNeutralRelativelyBullish: Candle,
public val absolutelyNeutralRelativelyNeutral: Candle,
public val absolutelyNeutralRelativelyBearish: Candle,
public val absolutelyBearishRelativelyBullish: Candle,
public val absolutelyBearishRelativelyNeutral: Candle,
public val absolutelyBearishRelativelyBearish: Candle,
) {
internal val maxThicknessDp
get() =
maxOf(
absolutelyBullishRelativelyBullish.body.thicknessDp,
absolutelyBullishRelativelyNeutral.body.thicknessDp,
absolutelyBullishRelativelyBearish.body.thicknessDp,
absolutelyNeutralRelativelyBullish.body.thicknessDp,
absolutelyNeutralRelativelyNeutral.body.thicknessDp,
absolutelyNeutralRelativelyBearish.body.thicknessDp,
absolutelyBearishRelativelyBullish.body.thicknessDp,
absolutelyBearishRelativelyNeutral.body.thicknessDp,
absolutelyBearishRelativelyBearish.body.thicknessDp,
)
/** Provides [Candle]s to [CandlestickCartesianLayer]s. */
public interface CandleProvider {
/** Returns the [Candle] for the given [CandlestickCartesianLayerModel.Entry]. */
public fun getCandle(
entry: CandlestickCartesianLayerModel.Entry,
extraStore: ExtraStore,
): Candle

/** Returns the widest [Candle]. */
public fun getWidestCandle(extraStore: ExtraStore): Candle

/** Provides access to [CandleProvider] factory functions. */
public companion object {
internal data class Filled(val bullish: Candle, val neutral: Candle, val bearish: Candle) : CandleProvider {
private val candles = listOf(bullish, neutral, bearish)

override fun getCandle(
entry: CandlestickCartesianLayerModel.Entry,
extraStore: ExtraStore,
) = when (entry.absoluteChange) {
Change.Bullish -> bullish
Change.Neutral -> neutral
Change.Bearish -> bearish
}

internal fun getCandle(entry: CandlestickCartesianLayerModel.Entry) =
when (entry.absoluteChange) {
Change.Bullish ->
when (entry.relativeChange) {
Change.Bullish -> absolutelyBullishRelativelyBullish
Change.Bearish -> absolutelyBullishRelativelyBearish
Change.Neutral -> absolutelyBullishRelativelyNeutral
}

Change.Bearish ->
when (entry.relativeChange) {
Change.Bullish -> absolutelyBearishRelativelyBullish
Change.Bearish -> absolutelyBearishRelativelyBearish
Change.Neutral -> absolutelyBearishRelativelyNeutral
}

Change.Neutral ->
when (entry.relativeChange) {
Change.Bullish -> absolutelyNeutralRelativelyBullish
Change.Bearish -> absolutelyNeutralRelativelyBearish
Change.Neutral -> absolutelyNeutralRelativelyNeutral
}
override fun getWidestCandle(extraStore: ExtraStore) = candles.maxBy { it.widthDp }
}

public companion object
internal data class Hollow(
val absolutelyBullishRelativelyBullish: Candle,
val absolutelyBullishRelativelyNeutral: Candle,
val absolutelyBullishRelativelyBearish: Candle,
val absolutelyNeutralRelativelyBullish: Candle,
val absolutelyNeutralRelativelyNeutral: Candle,
val absolutelyNeutralRelativelyBearish: Candle,
val absolutelyBearishRelativelyBullish: Candle,
val absolutelyBearishRelativelyNeutral: Candle,
val absolutelyBearishRelativelyBearish: Candle,
) : CandleProvider {
private val candles =
listOf(
absolutelyBullishRelativelyBullish,
absolutelyBullishRelativelyNeutral,
absolutelyBullishRelativelyBearish,
absolutelyNeutralRelativelyBullish,
absolutelyNeutralRelativelyNeutral,
absolutelyNeutralRelativelyBearish,
absolutelyBearishRelativelyBullish,
absolutelyBearishRelativelyNeutral,
absolutelyBearishRelativelyBearish,
)

override fun getCandle(
entry: CandlestickCartesianLayerModel.Entry,
extraStore: ExtraStore,
) = when (entry.absoluteChange) {
Change.Bullish ->
when (entry.relativeChange) {
Change.Bullish -> absolutelyBullishRelativelyBullish
Change.Bearish -> absolutelyBullishRelativelyBearish
Change.Neutral -> absolutelyBullishRelativelyNeutral
}
Change.Bearish ->
when (entry.relativeChange) {
Change.Bullish -> absolutelyBearishRelativelyBullish
Change.Bearish -> absolutelyBearishRelativelyBearish
Change.Neutral -> absolutelyBearishRelativelyNeutral
}
Change.Neutral ->
when (entry.relativeChange) {
Change.Bullish -> absolutelyNeutralRelativelyBullish
Change.Bearish -> absolutelyNeutralRelativelyBearish
Change.Neutral -> absolutelyNeutralRelativelyNeutral
}
}

override fun getWidestCandle(extraStore: ExtraStore) = candles.maxBy { it.widthDp }
}
}
}
}

Expand All @@ -344,3 +362,38 @@ public fun LineComponent.asWick(): LineComponent =
thicknessDp = Defaults.WICK_DEFAULT_WIDTH_DP,
strokeWidthDp = 0f,
)

/** Switches between three [Candle]s based on [CandlestickCartesianLayerModel.Entry.absoluteChange]. */
public fun CandlestickCartesianLayer.CandleProvider.Companion.filled(
bullish: Candle,
neutral: Candle,
bearish: Candle,
): CandlestickCartesianLayer.CandleProvider =
CandlestickCartesianLayer.CandleProvider.Companion.Filled(bullish, neutral, bearish)

/**
* Switches between nine [Candle]s based on [CandlestickCartesianLayerModel.Entry.absoluteChange] and
* [CandlestickCartesianLayerModel.Entry.relativeChange].
*/
public fun CandlestickCartesianLayer.CandleProvider.Companion.hollow(
absolutelyBullishRelativelyBullish: Candle,
absolutelyBullishRelativelyNeutral: Candle,
absolutelyBullishRelativelyBearish: Candle,
absolutelyNeutralRelativelyBullish: Candle,
absolutelyNeutralRelativelyNeutral: Candle,
absolutelyNeutralRelativelyBearish: Candle,
absolutelyBearishRelativelyBullish: Candle,
absolutelyBearishRelativelyNeutral: Candle,
absolutelyBearishRelativelyBearish: Candle,
): CandlestickCartesianLayer.CandleProvider =
CandlestickCartesianLayer.CandleProvider.Companion.Hollow(
absolutelyBullishRelativelyBullish,
absolutelyBullishRelativelyNeutral,
absolutelyBullishRelativelyBearish,
absolutelyNeutralRelativelyBullish,
absolutelyNeutralRelativelyNeutral,
absolutelyNeutralRelativelyBearish,
absolutelyBearishRelativelyBullish,
absolutelyBearishRelativelyNeutral,
absolutelyBearishRelativelyBearish,
)
Loading

0 comments on commit e73df6c

Please sign in to comment.