diff --git a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/Marker.kt b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/Marker.kt index 3bafd717a..fb7291540 100644 --- a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/Marker.kt +++ b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/Marker.kt @@ -28,21 +28,20 @@ import com.patrykandpatrick.vico.compose.common.component.rememberLayeredCompone import com.patrykandpatrick.vico.compose.common.component.rememberShapeComponent import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent import com.patrykandpatrick.vico.compose.common.component.shadow -import com.patrykandpatrick.vico.compose.common.dimensions import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.insets import com.patrykandpatrick.vico.compose.common.shape.markerCorneredShape import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMargins import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker import com.patrykandpatrick.vico.core.common.Fill -import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.LayeredComponent import com.patrykandpatrick.vico.core.common.component.Shadow import com.patrykandpatrick.vico.core.common.component.ShapeComponent import com.patrykandpatrick.vico.core.common.component.TextComponent -import com.patrykandpatrick.vico.core.common.shape.Corner import com.patrykandpatrick.vico.core.common.shape.CorneredShape @Composable @@ -50,19 +49,19 @@ internal fun rememberMarker( labelPosition: DefaultCartesianMarker.LabelPosition = DefaultCartesianMarker.LabelPosition.Top, showIndicator: Boolean = true, ): CartesianMarker { - val labelBackgroundShape = markerCorneredShape(Corner.FullyRounded) + val labelBackgroundShape = markerCorneredShape(CorneredShape.Corner.Rounded) val labelBackground = rememberShapeComponent( fill = fill(MaterialTheme.colorScheme.surfaceBright), shape = labelBackgroundShape, shadow = - shadow(radius = LABEL_BACKGROUND_SHADOW_RADIUS_DP.dp, dy = LABEL_BACKGROUND_SHADOW_DY_DP.dp), + shadow(radius = LABEL_BACKGROUND_SHADOW_RADIUS_DP.dp, y = LABEL_BACKGROUND_SHADOW_DY_DP.dp), ) val label = rememberTextComponent( color = MaterialTheme.colorScheme.onSurface, textAlignment = Layout.Alignment.ALIGN_CENTER, - padding = dimensions(8.dp, 4.dp), + padding = insets(8.dp, 4.dp), background = labelBackground, minWidth = TextComponent.MinWidth.fixed(40.dp), ) @@ -72,14 +71,14 @@ internal fun rememberMarker( val indicatorRearComponent = rememberShapeComponent(shape = CorneredShape.Pill) val indicator = rememberLayeredComponent( - rear = indicatorRearComponent, + back = indicatorRearComponent, front = rememberLayeredComponent( - rear = indicatorCenterComponent, + back = indicatorCenterComponent, front = indicatorFrontComponent, - padding = dimensions(5.dp), + padding = insets(5.dp), ), - padding = dimensions(10.dp), + padding = insets(10.dp), ) val guideline = rememberAxisGuidelineComponent() return remember(label, labelPosition, indicator, showIndicator, guideline) { @@ -91,20 +90,20 @@ internal fun rememberMarker( if (showIndicator) { { color -> LayeredComponent( - rear = + back = ShapeComponent(Fill(ColorUtils.setAlphaComponent(color, 38)), CorneredShape.Pill), front = LayeredComponent( - rear = + back = ShapeComponent( fill = Fill(color), shape = CorneredShape.Pill, shadow = Shadow(radiusDp = 12f, color = color), ), front = indicatorFrontComponent, - padding = dimensions(5.dp), + padding = insets(5.dp), ), - padding = dimensions(10.dp), + padding = insets(10.dp), ) } } else { @@ -113,24 +112,24 @@ internal fun rememberMarker( indicatorSizeDp = 36f, guideline = guideline, ) { - override fun updateInsets( + override fun updateLayerMargins( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerMargins: CartesianLayerMargins, + layerDimensions: CartesianLayerDimensions, model: CartesianChartModel, - insets: Insets, ) { with(context) { - val baseShadowInsetDp = + val baseShadowMarginDp = CLIPPING_FREE_SHADOW_RADIUS_MULTIPLIER * LABEL_BACKGROUND_SHADOW_RADIUS_DP - var topInset = (baseShadowInsetDp - LABEL_BACKGROUND_SHADOW_DY_DP).pixels - var bottomInset = (baseShadowInsetDp + LABEL_BACKGROUND_SHADOW_DY_DP).pixels + var topMargin = (baseShadowMarginDp - LABEL_BACKGROUND_SHADOW_DY_DP).pixels + var bottomMargin = (baseShadowMarginDp + LABEL_BACKGROUND_SHADOW_DY_DP).pixels when (labelPosition) { LabelPosition.Top, - LabelPosition.AbovePoint -> topInset += label.getHeight(context) + tickSizeDp.pixels - LabelPosition.Bottom -> bottomInset += label.getHeight(context) + tickSizeDp.pixels + LabelPosition.AbovePoint -> topMargin += label.getHeight(context) + tickSizeDp.pixels + LabelPosition.Bottom -> bottomMargin += label.getHeight(context) + tickSizeDp.pixels LabelPosition.AroundPoint -> {} } - insets.ensureValuesAtLeast(top = topInset, bottom = bottomInset) + layerMargins.ensureValuesAtLeast(top = topMargin, bottom = bottomMargin) } } } diff --git a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart1.kt b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart1.kt index 56b56365e..134451bc6 100644 --- a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart1.kt +++ b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart1.kt @@ -31,7 +31,6 @@ import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLine import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLineCartesianLayer import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart import com.patrykandpatrick.vico.compose.cartesian.rememberVicoZoomState -import com.patrykandpatrick.vico.compose.common.data.rememberExtraLambda import com.patrykandpatrick.vico.compose.common.fill import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis @@ -84,7 +83,7 @@ private fun ComposeChart1(modelProducer: CartesianChartModelProducer, modifier: ), marker = marker, layerPadding = { cartesianLayerPadding(scalableStart = 16.dp, scalableEnd = 16.dp) }, - persistentMarkers = rememberExtraLambda(marker) { marker at PERSISTENT_MARKER_X }, + persistentMarkers = { marker at PERSISTENT_MARKER_X }, ), modelProducer = modelProducer, modifier = modifier, diff --git a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart2.kt b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart2.kt index 0e256813e..79f574cd6 100644 --- a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart2.kt +++ b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart2.kt @@ -31,8 +31,8 @@ import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent import com.patrykandpatrick.vico.compose.common.component.shapeComponent -import com.patrykandpatrick.vico.compose.common.dimensions import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.insets import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer @@ -40,8 +40,8 @@ import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.cartesian.data.columnSeries import com.patrykandpatrick.vico.core.cartesian.decoration.HorizontalLine import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.ShapeComponent import com.patrykandpatrick.vico.core.common.component.TextComponent @@ -140,9 +140,9 @@ private fun rememberComposeHorizontalLine(): HorizontalLine { val line = rememberLineComponent(fill, HORIZONTAL_LINE_THICKNESS_DP.dp) val labelComponent = rememberTextComponent( - margins = dimensions(HORIZONTAL_LINE_LABEL_MARGIN_DP.dp), + margins = insets(HORIZONTAL_LINE_LABEL_MARGIN_DP.dp), padding = - dimensions( + insets( HORIZONTAL_LINE_LABEL_HORIZONTAL_PADDING_DP.dp, HORIZONTAL_LINE_LABEL_VERTICAL_PADDING_DP.dp, ), @@ -157,9 +157,9 @@ private fun getViewHorizontalLine() = line = LineComponent(Fill(HORIZONTAL_LINE_COLOR), HORIZONTAL_LINE_THICKNESS_DP), labelComponent = TextComponent( - margins = Dimensions(HORIZONTAL_LINE_LABEL_MARGIN_DP), + margins = Insets(HORIZONTAL_LINE_LABEL_MARGIN_DP), padding = - Dimensions( + Insets( HORIZONTAL_LINE_LABEL_HORIZONTAL_PADDING_DP, HORIZONTAL_LINE_LABEL_VERTICAL_PADDING_DP, ), diff --git a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart3.kt b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart3.kt index f5954eb21..86fde64f1 100644 --- a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart3.kt +++ b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart3.kt @@ -37,8 +37,8 @@ import com.patrykandpatrick.vico.compose.cartesian.rememberVicoZoomState import com.patrykandpatrick.vico.compose.common.component.rememberShapeComponent import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent import com.patrykandpatrick.vico.compose.common.component.shapeComponent -import com.patrykandpatrick.vico.compose.common.dimensions import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.insets import com.patrykandpatrick.vico.compose.common.shader.horizontalGradient import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis @@ -48,7 +48,7 @@ import com.patrykandpatrick.vico.core.cartesian.data.lineSeries import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker import com.patrykandpatrick.vico.core.common.data.ExtraStore -import com.patrykandpatrick.vico.core.common.shader.DynamicShader +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider import com.patrykandpatrick.vico.core.common.shape.CorneredShape import com.patrykandpatrick.vico.databinding.Chart3Binding import com.patrykandpatrick.vico.sample.showcase.Defaults @@ -94,7 +94,7 @@ private fun ComposeChart3(modelProducer: CartesianChartModelProducer, modifier: fill = LineCartesianLayer.LineFill.single( fill( - DynamicShader.horizontalGradient( + ShaderProvider.horizontalGradient( arrayOf(Color(LINE_COLOR_1), Color(LINE_COLOR_2)) ) ) @@ -111,8 +111,8 @@ private fun ComposeChart3(modelProducer: CartesianChartModelProducer, modifier: titleComponent = rememberTextComponent( color = Color.Black, - margins = dimensions(end = 4.dp), - padding = dimensions(8.dp, 2.dp), + margins = insets(end = 4.dp), + padding = insets(8.dp, 2.dp), background = rememberShapeComponent(fill(Color(LINE_COLOR_1)), CorneredShape.Pill), ), title = stringResource(R.string.y_axis), @@ -124,8 +124,8 @@ private fun ComposeChart3(modelProducer: CartesianChartModelProducer, modifier: titleComponent = rememberTextComponent( color = Color.White, - margins = dimensions(top = 4.dp), - padding = dimensions(8.dp, 2.dp), + margins = insets(top = 4.dp), + padding = insets(8.dp, 2.dp), background = shapeComponent(fill(bottomAxisLabelBackgroundColor), CorneredShape.Pill), ), @@ -155,7 +155,7 @@ private fun ViewChart3(modelProducer: CartesianChartModelProducer, modifier: Mod LineCartesianLayer.Line( fill = LineCartesianLayer.LineFill.single( - fill(DynamicShader.horizontalGradient(LINE_COLOR_1, LINE_COLOR_2)) + fill(ShaderProvider.horizontalGradient(LINE_COLOR_1, LINE_COLOR_2)) ), stroke = LineCartesianLayer.LineStroke.Dashed(), ) diff --git a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart6.kt b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart6.kt index 4fd4a46e4..bfe5cea59 100644 --- a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart6.kt +++ b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart6.kt @@ -34,8 +34,8 @@ import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent import com.patrykandpatrick.vico.compose.common.component.rememberShapeComponent import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent import com.patrykandpatrick.vico.compose.common.component.shapeComponent -import com.patrykandpatrick.vico.compose.common.dimensions import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.insets import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer @@ -43,7 +43,7 @@ import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.cartesian.data.columnSeries import com.patrykandpatrick.vico.core.cartesian.decoration.HorizontalBox import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer -import com.patrykandpatrick.vico.core.common.Dimensions +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.component.ShapeComponent import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.shape.CorneredShape @@ -140,9 +140,9 @@ private fun rememberComposeHorizontalBox(): HorizontalBox { val box = rememberShapeComponent(fill(color.copy(HORIZONTAL_BOX_ALPHA))) val labelComponent = rememberTextComponent( - margins = dimensions(HORIZONTAL_BOX_LABEL_MARGIN_DP.dp), + margins = insets(HORIZONTAL_BOX_LABEL_MARGIN_DP.dp), padding = - dimensions( + insets( HORIZONTAL_BOX_LABEL_HORIZONTAL_PADDING_DP.dp, HORIZONTAL_BOX_LABEL_VERTICAL_PADDING_DP.dp, ), @@ -158,9 +158,9 @@ private fun getViewHorizontalBox(): HorizontalBox { box = ShapeComponent(fill(color.copy(HORIZONTAL_BOX_ALPHA))), labelComponent = TextComponent( - margins = Dimensions(HORIZONTAL_BOX_LABEL_MARGIN_DP), + margins = Insets(HORIZONTAL_BOX_LABEL_MARGIN_DP), padding = - Dimensions( + Insets( HORIZONTAL_BOX_LABEL_HORIZONTAL_PADDING_DP, HORIZONTAL_BOX_LABEL_VERTICAL_PADDING_DP, ), diff --git a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart7.kt b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart7.kt index 8cf5aef8d..b2bccd99c 100644 --- a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart7.kt +++ b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart7.kt @@ -37,9 +37,8 @@ import com.patrykandpatrick.vico.compose.cartesian.rememberVicoZoomState import com.patrykandpatrick.vico.compose.common.component.rememberShapeComponent import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent import com.patrykandpatrick.vico.compose.common.component.shapeComponent -import com.patrykandpatrick.vico.compose.common.data.rememberExtraLambda -import com.patrykandpatrick.vico.compose.common.dimensions import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.insets import com.patrykandpatrick.vico.compose.common.rememberVerticalLegend import com.patrykandpatrick.vico.compose.common.shape.rounded import com.patrykandpatrick.vico.compose.common.vicoTheme @@ -151,8 +150,8 @@ private fun ViewChart7(modelProducer: CartesianChartModelProducer, modifier: Mod private fun rememberStartAxisLabel() = rememberAxisLabelComponent( color = Color.Black, - margins = dimensions(4.dp), - padding = dimensions(8.dp, 2.dp), + margins = insets(4.dp), + padding = insets(8.dp, 2.dp), background = rememberShapeComponent(fill(Color(0xfffab94d)), CorneredShape.rounded(4.dp)), ) @@ -161,19 +160,18 @@ private fun rememberLegend(): Legend - add( - LegendItem( - icon = shapeComponent(fill(color), CorneredShape.Pill), - labelComponent = labelComponent, - label = resources.getString(R.string.series_x, index + 1), - ) + items = { + chartColors.forEachIndexed { index, color -> + add( + LegendItem( + icon = shapeComponent(fill(color), CorneredShape.Pill), + labelComponent = labelComponent, + label = resources.getString(R.string.series_x, index + 1), ) - } - }, - padding = dimensions(top = 8.dp), + ) + } + }, + padding = insets(top = 8.dp), ) } diff --git a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart9.kt b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart9.kt index 0b46461ff..4b9b6d66a 100644 --- a/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart9.kt +++ b/sample/src/main/java/com/patrykandpatrick/vico/sample/showcase/charts/Chart9.kt @@ -39,8 +39,8 @@ import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent import com.patrykandpatrick.vico.compose.common.component.rememberShapeComponent import com.patrykandpatrick.vico.compose.common.component.shapeComponent -import com.patrykandpatrick.vico.compose.common.dimensions import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.insets import com.patrykandpatrick.vico.compose.common.shader.component import com.patrykandpatrick.vico.compose.common.shader.verticalGradient import com.patrykandpatrick.vico.compose.common.shape.dashedShape @@ -49,10 +49,10 @@ import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.lineSeries import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.component.ShapeComponent -import com.patrykandpatrick.vico.core.common.shader.DynamicShader +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider import com.patrykandpatrick.vico.core.common.shape.CorneredShape import com.patrykandpatrick.vico.core.common.shape.Shape import com.patrykandpatrick.vico.databinding.Chart9Binding @@ -107,34 +107,38 @@ private fun ComposeChart9(modelProducer: CartesianChartModelProducer, modifier: LineCartesianLayer.AreaFill.double( topFill = fill( - DynamicShader.compose( - DynamicShader.component( + ShaderProvider.compose( + ShaderProvider.component( component = shapeComponent( fill = fill(colors[0]), shape = CorneredShape.Pill, - margins = dimensions(1.dp), + margins = insets(1.dp), ), componentSize = 6.dp, ), - DynamicShader.verticalGradient(arrayOf(Color.Black, Color.Transparent)), + ShaderProvider.verticalGradient( + arrayOf(Color.Black, Color.Transparent) + ), PorterDuff.Mode.DST_IN, ) ), bottomFill = fill( - DynamicShader.compose( - DynamicShader.component( + ShaderProvider.compose( + ShaderProvider.component( component = shapeComponent( fill = fill(colors[1]), shape = Shape.Rectangle, - margins = dimensions(horizontal = 2.dp), + margins = insets(horizontal = 2.dp), ), componentSize = 5.dp, - checkeredArrangement = false, + checker = false, + ), + ShaderProvider.verticalGradient( + arrayOf(Color.Transparent, Color.Black) ), - DynamicShader.verticalGradient(arrayOf(Color.Transparent, Color.Black)), PorterDuff.Mode.DST_IN, ) ), @@ -148,8 +152,8 @@ private fun ComposeChart9(modelProducer: CartesianChartModelProducer, modifier: label = rememberAxisLabelComponent( color = MaterialTheme.colorScheme.onBackground, - margins = dimensions(end = 8.dp), - padding = dimensions(6.dp, 2.dp), + margins = insets(end = 8.dp), + padding = insets(6.dp, 2.dp), background = rememberShapeComponent( fill = Fill.Transparent, @@ -203,17 +207,17 @@ private fun ViewChart9(modelProducer: CartesianChartModelProducer, modifier: Mod LineCartesianLayer.AreaFill.double( topFill = Fill( - DynamicShader.compose( - DynamicShader.component( + ShaderProvider.compose( + ShaderProvider.component( component = ShapeComponent( fill = fill(colors[0]), shape = CorneredShape.Pill, - margins = Dimensions(allDp = 1f), + margins = Insets(allDp = 1f), ), componentSizeDp = 6f, ), - DynamicShader.verticalGradient( + ShaderProvider.verticalGradient( android.graphics.Color.BLACK, android.graphics.Color.TRANSPARENT, ), @@ -222,18 +226,18 @@ private fun ViewChart9(modelProducer: CartesianChartModelProducer, modifier: Mod ), bottomFill = Fill( - DynamicShader.compose( - DynamicShader.component( + ShaderProvider.compose( + ShaderProvider.component( component = ShapeComponent( fill = fill(colors[1]), shape = Shape.Rectangle, - margins = Dimensions(horizontalDp = 2f, verticalDp = 0f), + margins = Insets(horizontalDp = 2f), ), componentSizeDp = 5f, - checkeredArrangement = false, + checker = false, ), - DynamicShader.verticalGradient( + ShaderProvider.verticalGradient( android.graphics.Color.TRANSPARENT, android.graphics.Color.BLACK, ), diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChart.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChart.kt index 63ceb24b3..fc0da5fbd 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChart.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChart.kt @@ -23,13 +23,13 @@ import com.patrykandpatrick.vico.compose.cartesian.layer.rememberColumnCartesian import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLineCartesianLayer import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerPadding import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext import com.patrykandpatrick.vico.core.cartesian.FadingEdges import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.decoration.Decoration import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener import com.patrykandpatrick.vico.core.common.Legend diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartHost.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartHost.kt index 376e878cb..b209cf24f 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartHost.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartHost.kt @@ -41,7 +41,7 @@ import com.patrykandpatrick.vico.compose.cartesian.data.component2 import com.patrykandpatrick.vico.compose.cartesian.data.component3 import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges @@ -67,9 +67,8 @@ import kotlinx.coroutines.launch * @param zoomState houses information on the [CartesianChart]’s zoom factor. Allows for zoom * customization. * @param animationSpec the [AnimationSpec] for difference animations. - * @param runInitialAnimation whether to display an animation when the chart is created. In this - * animation, the value of each chart entry is animated from zero to the actual value. This - * animation isn’t run in previews. + * @param animateIn whether to run an initial animation when the [CartesianChartHost] enters + * composition. The animation is skipped for previews. * @param placeholder shown when no [CartesianChartModel] is available. */ @Composable @@ -80,12 +79,11 @@ public fun CartesianChartHost( scrollState: VicoScrollState = rememberVicoScrollState(), zoomState: VicoZoomState = rememberDefaultVicoZoomState(scrollState.scrollEnabled), animationSpec: AnimationSpec? = defaultCartesianDiffAnimationSpec, - runInitialAnimation: Boolean = true, + animateIn: Boolean = true, placeholder: @Composable BoxScope.() -> Unit = {}, ) { val mutableRanges = remember(chart) { MutableCartesianChartRanges() } - val modelWrapper by - modelProducer.collectAsState(chart, animationSpec, runInitialAnimation, mutableRanges) + val modelWrapper by modelProducer.collectAsState(chart, animationSpec, animateIn, mutableRanges) val (model, previousModel, ranges) = modelWrapper CartesianChartHostBox(modifier) { @@ -138,7 +136,7 @@ internal fun CartesianChartHostImpl( previousModel: CartesianChartModel? = null, ) { val canvasBounds = remember { RectF() } - val markerTouchPoint = remember { mutableStateOf(null) } + val pointerPosition = remember { mutableStateOf(null) } val measuringContext = rememberCartesianMeasuringContext( canvasBounds = canvasBounds, @@ -149,15 +147,16 @@ internal fun CartesianChartHostImpl( layerPadding = remember(chart.layerPadding, model.extraStore) { chart.layerPadding(model.extraStore) }, spToPx = with(LocalContext.current) { ::spToPx }, + pointerPosition = pointerPosition.value, ) val coroutineScope = rememberCoroutineScope() var previousModelID by remember { ValueWrapper(model.id) } - val horizontalDimensions = remember { MutableHorizontalDimensions() } + val layerDimensions = remember { MutableCartesianLayerDimensions() } LaunchedEffect(scrollState.pointerXDeltas) { scrollState.pointerXDeltas.collect { delta -> - markerTouchPoint.value?.let { point -> markerTouchPoint.value = point.copy(point.x + delta) } + pointerPosition.value?.let { point -> pointerPosition.value = point.copy(point.x + delta) } } } @@ -169,7 +168,7 @@ internal fun CartesianChartHostImpl( .chartTouchEvent( setTouchPoint = remember(chart.marker == null) { - if (chart.marker != null) markerTouchPoint.component2() else null + if (chart.marker != null) pointerPosition.component2() else null }, isScrollEnabled = scrollState.scrollEnabled, scrollState = scrollState, @@ -192,13 +191,13 @@ internal fun CartesianChartHostImpl( if (canvas.width == 0 || canvas.height == 0) return@Canvas canvasBounds.set(left = 0, top = 0, right = size.width, bottom = size.height) - horizontalDimensions.clear() - chart.prepare(measuringContext, horizontalDimensions, canvasBounds) + layerDimensions.clear() + chart.prepare(measuringContext, layerDimensions) if (chart.layerBounds.isEmpty) return@Canvas - zoomState.update(measuringContext, horizontalDimensions, chart.layerBounds) - scrollState.update(measuringContext, chart.layerBounds, horizontalDimensions) + zoomState.update(measuringContext, layerDimensions, chart.layerBounds) + scrollState.update(measuringContext, chart.layerBounds, layerDimensions) if (model.id != previousModelID) { coroutineScope.launch { scrollState.autoScroll(model, previousModel) } @@ -207,16 +206,15 @@ internal fun CartesianChartHostImpl( val drawingContext = CartesianDrawingContext( - measuringContext = measuringContext, - canvas = canvas, - markerTouchPoint = markerTouchPoint.value, - horizontalDimensions = horizontalDimensions, - layerBounds = chart.layerBounds, - scroll = scrollState.value, - zoom = zoomState.value, + measuringContext, + canvas, + layerDimensions, + chart.layerBounds, + scrollState.value, + zoomState.value, ) - chart.draw(drawingContext, markerTouchPoint.value) + chart.draw(drawingContext) measuringContext.reset() } } diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartModelProducer.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartModelProducer.kt index a9de5c4f1..928f0ed99 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartModelProducer.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianChartModelProducer.kt @@ -51,7 +51,7 @@ internal val defaultCartesianDiffAnimationSpec: AnimationSpec = internal fun CartesianChartModelProducer.collectAsState( chart: CartesianChart, animationSpec: AnimationSpec?, - runInitialAnimation: Boolean, + animateIn: Boolean, ranges: MutableCartesianChartRanges, ): State { var previousHashCode by remember { ValueWrapper(null) } @@ -63,7 +63,7 @@ internal fun CartesianChartModelProducer.collectAsState( val scope = rememberCoroutineScope() val isInPreview = LocalInspectionMode.current val chartState = rememberWrappedValue(chart) - DisposableEffect(chart.id, runInitialAnimation, isInPreview) { + DisposableEffect(chart.id, animateIn, isInPreview) { var mainAnimationJob: Job? = null var animationFrameJob: Job? = null var finalAnimationFrameJob: Job? = null @@ -74,7 +74,7 @@ internal fun CartesianChartModelProducer.collectAsState( if ( animationSpec != null && !isInPreview && - (modelWrapperState.value.model != null || runInitialAnimation) + (modelWrapperState.value.model != null || animateIn) ) { isAnimationRunning = true mainAnimationJob = diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianLayerPadding.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianLayerPadding.kt index e87b86825..c33ad0063 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianLayerPadding.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianLayerPadding.kt @@ -18,7 +18,7 @@ package com.patrykandpatrick.vico.compose.cartesian import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerPadding +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding /** Creates a [CartesianLayerPadding] instance. */ public fun cartesianLayerPadding( diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianMeasuringContext.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianMeasuringContext.kt index 2c9581998..586af507f 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianMeasuringContext.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/CartesianMeasuringContext.kt @@ -22,10 +22,12 @@ import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerPadding +import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext import com.patrykandpatrick.vico.core.cartesian.MutableCartesianMeasuringContext import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding +import com.patrykandpatrick.vico.core.common.Point @Composable internal fun rememberCartesianMeasuringContext( @@ -36,27 +38,31 @@ internal fun rememberCartesianMeasuringContext( zoomEnabled: Boolean, layerPadding: CartesianLayerPadding, spToPx: (Float) -> Float, -): MutableCartesianMeasuringContext = - remember { - MutableCartesianMeasuringContext( - canvasBounds = canvasBounds, - density = 0f, - isLtr = true, - model = model, - ranges = ranges, - scrollEnabled = scrollEnabled, - zoomEnabled = zoomEnabled, - layerPadding = layerPadding, - spToPx = spToPx, - ) - } - .apply { - this.density = LocalDensity.current.density - this.isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr - this.model = model - this.ranges = ranges - this.scrollEnabled = scrollEnabled - this.zoomEnabled = zoomEnabled - this.layerPadding = layerPadding - this.spToPx = spToPx - } + pointerPosition: Point?, +): CartesianMeasuringContext { + val density = LocalDensity.current.density + val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr + return remember( + canvasBounds, + model, + ranges, + scrollEnabled, + zoomEnabled, + layerPadding, + spToPx, + pointerPosition, + ) { + MutableCartesianMeasuringContext( + canvasBounds, + density, + isLtr, + model, + ranges, + scrollEnabled, + zoomEnabled, + layerPadding, + pointerPosition, + spToPx, + ) + } +} diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/FadingEdges.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/FadingEdges.kt index 960dae518..c5d4782dd 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/FadingEdges.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/FadingEdges.kt @@ -27,52 +27,33 @@ import com.patrykandpatrick.vico.core.cartesian.FadingEdges import com.patrykandpatrick.vico.core.common.Defaults.FADING_EDGE_VISIBILITY_THRESHOLD_DP import com.patrykandpatrick.vico.core.common.Defaults.FADING_EDGE_WIDTH_DP -/** - * Creates and remembers a [FadingEdges] instance. - * - * @param startEdgeWidth the width of the fade overlay for the start edge (in dp). - * @param endEdgeWidth the width of the fade overlay for the end edge (in dp). - * @param visibilityThreshold the scroll distance over which the overlays fade in and out (in dp). - * @param visibilityEasing used for the fading edges’ fade-in and fade-out animations. This is a - * mapping of the degree to which [visibilityThreshold] has been satisfied to the opacity of the - * fading edges. - * @see FadingEdges - */ +/** Creates and remembers a [FadingEdges] instance. */ @Composable public fun rememberFadingEdges( - startEdgeWidth: Dp = FadingEdgesDefaults.edgeWidth, - endEdgeWidth: Dp = FadingEdgesDefaults.edgeWidth, + startWidth: Dp = FadingEdgesDefaults.edgeWidth, + endWidth: Dp = FadingEdgesDefaults.edgeWidth, visibilityThreshold: Dp = FadingEdgesDefaults.visibilityThreshold, visibilityEasing: Easing = FadingEdgesDefaults.visibilityEasing, ): FadingEdges = - remember(startEdgeWidth, endEdgeWidth, visibilityThreshold, visibilityEasing) { + remember(startWidth, endWidth, visibilityThreshold, visibilityEasing) { FadingEdges( - startEdgeWidth.value, - endEdgeWidth.value, + startWidth.value, + endWidth.value, visibilityThreshold.value, TimeInterpolator(visibilityEasing::transform), ) } -/** - * Creates and remembers a [FadingEdges] instance. - * - * @param edgeWidth the width of the fade overlay. - * @param visibilityThreshold the scroll distance over which the overlays fade in and out (in dp). - * @param visibilityEasing used for the fading edges’ fade-in and fade-out animations. This is a - * mapping of the degree to which [visibilityThreshold] has been satisfied to the opacity of the - * fading edges. - * @see FadingEdges - */ +/** Creates and remembers a [FadingEdges] instance. */ @Composable public fun rememberFadingEdges( - edgeWidth: Dp = FadingEdgesDefaults.edgeWidth, + width: Dp = FadingEdgesDefaults.edgeWidth, visibilityThreshold: Dp = FadingEdgesDefaults.visibilityThreshold, visibilityEasing: Easing = FadingEdgesDefaults.visibilityEasing, ): FadingEdges = rememberFadingEdges( - startEdgeWidth = edgeWidth, - endEdgeWidth = edgeWidth, + startWidth = width, + endWidth = width, visibilityThreshold = visibilityThreshold, visibilityEasing = visibilityEasing, ) diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoScrollState.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoScrollState.kt index f95ababb4..0f67112e0 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoScrollState.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoScrollState.kt @@ -33,11 +33,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import com.patrykandpatrick.vico.core.cartesian.AutoScrollCondition import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions import com.patrykandpatrick.vico.core.cartesian.Scroll import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.getDelta import com.patrykandpatrick.vico.core.cartesian.getMaxScrollDistance +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions import com.patrykandpatrick.vico.core.common.rangeWith import kotlinx.coroutines.flow.MutableSharedFlow @@ -54,7 +54,7 @@ public class VicoScrollState { private val _maxValue = mutableFloatStateOf(0f) private var initialScrollHandled: Boolean private var context: CartesianMeasuringContext? = null - private var horizontalDimensions: HorizontalDimensions? = null + private var layerDimensions: CartesianLayerDimensions? = null private var bounds: RectF? = null internal val scrollEnabled: Boolean internal val pointerXDeltas = MutableSharedFlow(extraBufferCapacity = 1) @@ -134,33 +134,33 @@ public class VicoScrollState { ) private inline fun withUpdated( - block: (CartesianMeasuringContext, HorizontalDimensions, RectF) -> Unit + block: (CartesianMeasuringContext, CartesianLayerDimensions, RectF) -> Unit ) { val context = this.context - val horizontalDimensions = this.horizontalDimensions + val layerDimensions = this.layerDimensions val bounds = this.bounds - if (context != null && horizontalDimensions != null && bounds != null) { - block(context, horizontalDimensions, bounds) + if (context != null && layerDimensions != null && bounds != null) { + block(context, layerDimensions, bounds) } } internal fun update( context: CartesianMeasuringContext, bounds: RectF, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, ) { this.context = context - this.horizontalDimensions = horizontalDimensions + this.layerDimensions = layerDimensions this.bounds = bounds - maxValue = context.getMaxScrollDistance(bounds.width(), horizontalDimensions) + maxValue = context.getMaxScrollDistance(bounds.width(), layerDimensions) if (!initialScrollHandled) { - value = initialScroll.getValue(context, horizontalDimensions, bounds, maxValue) + value = initialScroll.getValue(context, layerDimensions, bounds, maxValue) initialScrollHandled = true } } internal suspend fun autoScroll(model: CartesianChartModel, oldModel: CartesianChartModel?) { - if (!autoScrollCondition.shouldPerformAutoScroll(model, oldModel)) return + if (!autoScrollCondition.shouldScroll(oldModel, model)) return if (scrollableState.isScrollInProgress) scrollableState.stopScroll(MutatePriority.PreventUserInput) animateScroll(autoScroll, autoScrollAnimationSpec) @@ -168,24 +168,22 @@ public class VicoScrollState { internal fun clearUpdated() { context = null - horizontalDimensions = null + layerDimensions = null bounds = null } /** Triggers a scroll. */ public suspend fun scroll(scroll: Scroll) { - withUpdated { context, horizontalDimensions, bounds -> - scrollableState.scrollBy( - scroll.getDelta(context, horizontalDimensions, bounds, maxValue, value) - ) + withUpdated { context, layerDimensions, bounds -> + scrollableState.scrollBy(scroll.getDelta(context, layerDimensions, bounds, maxValue, value)) } } /** Triggers an animated scroll. */ public suspend fun animateScroll(scroll: Scroll, animationSpec: AnimationSpec = spring()) { - withUpdated { context, horizontalDimensions, bounds -> + withUpdated { context, layerDimensions, bounds -> scrollableState.animateScrollBy( - scroll.getDelta(context, horizontalDimensions, bounds, maxValue, value), + scroll.getDelta(context, layerDimensions, bounds, maxValue, value), animationSpec, ) } diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoZoomState.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoZoomState.kt index 08238e3de..7cbfd7906 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoZoomState.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/VicoZoomState.kt @@ -26,7 +26,7 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.Scroll import com.patrykandpatrick.vico.core.cartesian.Zoom import com.patrykandpatrick.vico.core.cartesian.scale @@ -98,14 +98,14 @@ public class VicoZoomState { internal fun update( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + layerDimensions: MutableCartesianLayerDimensions, bounds: RectF, ) { - val minValue = minZoom.getValue(context, horizontalDimensions, bounds) - val maxValue = maxZoom.getValue(context, horizontalDimensions, bounds) + val minValue = minZoom.getValue(context, layerDimensions, bounds) + val maxValue = maxZoom.getValue(context, layerDimensions, bounds) valueRange = minValue..maxValue - if (!overridden) value = initialZoom.getValue(context, horizontalDimensions, bounds) - horizontalDimensions.scale(value) + if (!overridden) value = initialZoom.getValue(context, layerDimensions, bounds) + layerDimensions.scale(value) } internal fun zoom(factor: Float, centroidX: Float, scroll: Float, bounds: RectF): Scroll { @@ -133,9 +133,9 @@ public class VicoZoomState { @Composable public fun rememberVicoZoomState( zoomEnabled: Boolean = true, - initialZoom: Zoom = remember { Zoom.max(Zoom.static(), Zoom.Content) }, + initialZoom: Zoom = remember { Zoom.max(Zoom.fixed(), Zoom.Content) }, minZoom: Zoom = Zoom.Content, - maxZoom: Zoom = remember { Zoom.max(Zoom.static(Defaults.MAX_ZOOM), Zoom.Content) }, + maxZoom: Zoom = remember { Zoom.max(Zoom.fixed(Defaults.MAX_ZOOM), Zoom.Content) }, ): VicoZoomState = rememberSaveable( zoomEnabled, @@ -154,5 +154,5 @@ public fun rememberVicoZoomState( internal fun rememberDefaultVicoZoomState(scrollEnabled: Boolean) = rememberVicoZoomState( initialZoom = - if (scrollEnabled) remember { Zoom.max(Zoom.static(), Zoom.Content) } else Zoom.Content + if (scrollEnabled) remember { Zoom.max(Zoom.fixed(), Zoom.Content) } else Zoom.Content ) diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/AxisComponents.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/AxisComponents.kt index ff31ff989..4fa6a7251 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/AxisComponents.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/AxisComponents.kt @@ -29,14 +29,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent -import com.patrykandpatrick.vico.compose.common.dimensions import com.patrykandpatrick.vico.compose.common.fill +import com.patrykandpatrick.vico.compose.common.insets import com.patrykandpatrick.vico.compose.common.shape.dashedShape import com.patrykandpatrick.vico.compose.common.vicoTheme import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.Shadow @@ -53,10 +53,10 @@ public fun rememberAxisLabelComponent( lineHeight: TextUnit? = null, lineCount: Int = Defaults.AXIS_LABEL_MAX_LINES, truncateAt: TextUtils.TruncateAt = TextUtils.TruncateAt.END, - margins: Dimensions = - dimensions(Defaults.AXIS_LABEL_HORIZONTAL_MARGIN.dp, Defaults.AXIS_LABEL_VERTICAL_MARGIN.dp), - padding: Dimensions = - dimensions(Defaults.AXIS_LABEL_HORIZONTAL_PADDING.dp, Defaults.AXIS_LABEL_VERTICAL_PADDING.dp), + margins: Insets = + insets(Defaults.AXIS_LABEL_HORIZONTAL_MARGIN.dp, Defaults.AXIS_LABEL_VERTICAL_MARGIN.dp), + padding: Insets = + insets(Defaults.AXIS_LABEL_HORIZONTAL_PADDING.dp, Defaults.AXIS_LABEL_VERTICAL_PADDING.dp), background: Component? = null, minWidth: TextComponent.MinWidth = TextComponent.MinWidth.fixed(), ): TextComponent = @@ -80,7 +80,7 @@ public fun rememberAxisLineComponent( fill: Fill = fill(vicoTheme.lineColor), thickness: Dp = Defaults.AXIS_LINE_WIDTH.dp, shape: Shape = Shape.Rectangle, - margins: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, strokeFill: Fill = Fill.Transparent, strokeThickness: Dp = 0.dp, shadow: Shadow? = null, @@ -93,7 +93,7 @@ public fun rememberAxisTickComponent( fill: Fill = fill(vicoTheme.lineColor), thickness: Dp = Defaults.AXIS_LINE_WIDTH.dp, shape: Shape = Shape.Rectangle, - margins: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, strokeFill: Fill = Fill.Transparent, strokeThickness: Dp = 0.dp, shadow: Shadow? = null, @@ -106,7 +106,7 @@ public fun rememberAxisGuidelineComponent( fill: Fill = fill(vicoTheme.lineColor), thickness: Dp = Defaults.AXIS_GUIDELINE_WIDTH.dp, shape: Shape = dashedShape(), - margins: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, strokeFill: Fill = Fill.Transparent, strokeThickness: Dp = 0.dp, shadow: Shadow? = null, diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/BaseAxis.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/BaseAxis.kt index c3d44520d..9c99438db 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/BaseAxis.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/BaseAxis.kt @@ -22,13 +22,13 @@ import com.patrykandpatrick.vico.core.cartesian.axis.BaseAxis /** Creates a [BaseAxis.Size.Auto] instance. */ public fun BaseAxis.Size.Companion.auto( - minSize: Dp = 0.dp, - maxSize: Dp = Float.MAX_VALUE.dp, -): BaseAxis.Size.Auto = BaseAxis.Size.Auto(minSize.value, maxSize.value) + min: Dp = 0.dp, + max: Dp = Float.MAX_VALUE.dp, +): BaseAxis.Size.Auto = BaseAxis.Size.Auto(min.value, max.value) -/** Creates a [BaseAxis.Size.Exact] instance. */ -public fun BaseAxis.Size.Companion.exact(size: Dp): BaseAxis.Size.Exact = - BaseAxis.Size.Exact(size.value) +/** Creates a [BaseAxis.Size.Fixed] instance. */ +public fun BaseAxis.Size.Companion.fixed(value: Dp): BaseAxis.Size.Fixed = + BaseAxis.Size.Fixed(value.value) /** Creates a [BaseAxis.Size.Fraction] instance. */ public fun BaseAxis.Size.Companion.fraction(fraction: Float): BaseAxis.Size.Fraction = diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/VerticalAxis.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/VerticalAxis.kt index b95a03124..4aa959e43 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/VerticalAxis.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/axis/VerticalAxis.kt @@ -25,6 +25,7 @@ import com.patrykandpatrick.vico.core.cartesian.axis.BaseAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.common.Defaults +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.TextComponent @@ -36,8 +37,7 @@ public fun VerticalAxis.Companion.rememberStart( labelRotationDegrees: Float = Defaults.AXIS_LABEL_ROTATION_DEGREES, horizontalLabelPosition: VerticalAxis.HorizontalLabelPosition = VerticalAxis.HorizontalLabelPosition.Outside, - verticalLabelPosition: VerticalAxis.VerticalLabelPosition = - VerticalAxis.VerticalLabelPosition.Center, + verticalLabelPosition: Position.Vertical = Position.Vertical.Center, valueFormatter: CartesianValueFormatter = remember { CartesianValueFormatter.decimal() }, tick: LineComponent? = rememberAxisTickComponent(), tickLength: Dp = Defaults.AXIS_TICK_LENGTH.dp, @@ -87,8 +87,7 @@ public fun VerticalAxis.Companion.rememberEnd( labelRotationDegrees: Float = Defaults.AXIS_LABEL_ROTATION_DEGREES, horizontalLabelPosition: VerticalAxis.HorizontalLabelPosition = VerticalAxis.HorizontalLabelPosition.Outside, - verticalLabelPosition: VerticalAxis.VerticalLabelPosition = - VerticalAxis.VerticalLabelPosition.Center, + verticalLabelPosition: Position.Vertical = Position.Vertical.Center, valueFormatter: CartesianValueFormatter = remember { CartesianValueFormatter.decimal() }, tick: LineComponent? = rememberAxisTickComponent(), tickLength: Dp = Defaults.AXIS_TICK_LENGTH.dp, diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/CandlestickCartesianLayer.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/CandlestickCartesianLayer.kt index fdf3a913d..0f1fa8606 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/CandlestickCartesianLayer.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/CandlestickCartesianLayer.kt @@ -33,7 +33,7 @@ import com.patrykandpatrick.vico.core.common.setValue /** Creates and remembers a [CandlestickCartesianLayer]. */ @Composable public fun rememberCandlestickCartesianLayer( - candles: CandlestickCartesianLayer.CandleProvider = + candleProvider: CandlestickCartesianLayer.CandleProvider = CandlestickCartesianLayer.CandleProvider.absolute(), minCandleBodyHeight: Dp = Defaults.MIN_CANDLE_BODY_HEIGHT_DP.dp, candleSpacing: Dp = Defaults.CANDLE_SPACING_DP.dp, @@ -42,7 +42,7 @@ public fun rememberCandlestickCartesianLayer( verticalAxisPosition: Axis.Position.Vertical? = null, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - CandlestickCartesianLayerDrawingModel.CandleInfo, + CandlestickCartesianLayerDrawingModel.Entry, CandlestickCartesianLayerDrawingModel, > = CartesianLayerDrawingModelInterpolator.default(), @@ -51,7 +51,7 @@ public fun rememberCandlestickCartesianLayer( ValueWrapper(null) } return remember( - candles, + candleProvider, minCandleBodyHeight, candleSpacing, scaleCandleWicks, @@ -61,7 +61,7 @@ public fun rememberCandlestickCartesianLayer( ) { val candlestickCartesianLayer = candlestickCartesianLayerWrapper?.copy( - candles, + candleProvider, minCandleBodyHeight.value, candleSpacing.value, scaleCandleWicks, @@ -70,7 +70,7 @@ public fun rememberCandlestickCartesianLayer( drawingModelInterpolator, ) ?: CandlestickCartesianLayer( - candles, + candleProvider, minCandleBodyHeight.value, candleSpacing.value, scaleCandleWicks, diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/ColumnCartesianLayer.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/ColumnCartesianLayer.kt index 17ae525dd..1e0de13fb 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/ColumnCartesianLayer.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/ColumnCartesianLayer.kt @@ -30,8 +30,8 @@ import com.patrykandpatrick.vico.core.cartesian.data.ColumnCartesianLayerDrawing import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer.MergeMode import com.patrykandpatrick.vico.core.common.Defaults +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.ValueWrapper -import com.patrykandpatrick.vico.core.common.VerticalPosition import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.CartesianLayerDrawingModelInterpolator import com.patrykandpatrick.vico.core.common.data.ExtraStore @@ -55,14 +55,14 @@ public fun rememberColumnCartesianLayer( columnCollectionSpacing: Dp = Defaults.COLUMN_COLLECTION_SPACING.dp, mergeMode: (ExtraStore) -> MergeMode = { MergeMode.grouped() }, dataLabel: TextComponent? = null, - dataLabelVerticalPosition: VerticalPosition = VerticalPosition.Top, + dataLabelPosition: Position.Vertical = Position.Vertical.Top, dataLabelValueFormatter: CartesianValueFormatter = remember { CartesianValueFormatter.decimal() }, dataLabelRotationDegrees: Float = 0f, rangeProvider: CartesianLayerRangeProvider = remember { CartesianLayerRangeProvider.auto() }, verticalAxisPosition: Axis.Position.Vertical? = null, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - ColumnCartesianLayerDrawingModel.ColumnInfo, + ColumnCartesianLayerDrawingModel.Entry, ColumnCartesianLayerDrawingModel, > = remember { @@ -75,7 +75,7 @@ public fun rememberColumnCartesianLayer( columnCollectionSpacing, mergeMode, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelValueFormatter, dataLabelRotationDegrees, rangeProvider, @@ -88,7 +88,7 @@ public fun rememberColumnCartesianLayer( columnCollectionSpacing.value, mergeMode, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelValueFormatter, dataLabelRotationDegrees, rangeProvider, @@ -100,7 +100,7 @@ public fun rememberColumnCartesianLayer( columnCollectionSpacing.value, mergeMode, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelValueFormatter, dataLabelRotationDegrees, rangeProvider, diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/LineCartesianLayer.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/LineCartesianLayer.kt index d36933d54..26bcbe1ae 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/LineCartesianLayer.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/layer/LineCartesianLayer.kt @@ -31,8 +31,8 @@ import com.patrykandpatrick.vico.core.cartesian.data.LineCartesianLayerDrawingMo import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer import com.patrykandpatrick.vico.core.cartesian.layer.getDefaultAreaFill import com.patrykandpatrick.vico.core.common.Defaults +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.ValueWrapper -import com.patrykandpatrick.vico.core.common.VerticalPosition import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.CartesianLayerDrawingModelInterpolator @@ -53,7 +53,7 @@ public fun rememberLineCartesianLayer( verticalAxisPosition: Axis.Position.Vertical? = null, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - LineCartesianLayerDrawingModel.PointInfo, + LineCartesianLayerDrawingModel.Entry, LineCartesianLayerDrawingModel, > = remember { @@ -102,7 +102,7 @@ public fun LineCartesianLayer.Companion.rememberLine( LineCartesianLayer.PointConnector.cubic() }, dataLabel: TextComponent? = null, - dataLabelVerticalPosition: VerticalPosition = VerticalPosition.Top, + dataLabelPosition: Position.Vertical = Position.Vertical.Top, dataLabelValueFormatter: CartesianValueFormatter = remember { CartesianValueFormatter.decimal() }, dataLabelRotationDegrees: Float = 0f, ): LineCartesianLayer.Line = @@ -113,7 +113,7 @@ public fun LineCartesianLayer.Companion.rememberLine( pointProvider, pointConnector, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelRotationDegrees, dataLabelRotationDegrees, ) { @@ -124,7 +124,7 @@ public fun LineCartesianLayer.Companion.rememberLine( pointProvider, pointConnector, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelValueFormatter, dataLabelRotationDegrees, ) diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/marker/DefaultCartesianMarker.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/marker/DefaultCartesianMarker.kt index 2b36c3dcf..011d48aa8 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/marker/DefaultCartesianMarker.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/cartesian/marker/DefaultCartesianMarker.kt @@ -21,9 +21,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerValueFormatter import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker -import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarkerValueFormatter import com.patrykandpatrick.vico.core.common.Defaults import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.component.LineComponent @@ -33,24 +31,15 @@ import com.patrykandpatrick.vico.core.common.component.TextComponent @Composable public fun rememberDefaultCartesianMarker( label: TextComponent, - valueFormatter: CartesianMarkerValueFormatter = remember { - DefaultCartesianMarkerValueFormatter() + valueFormatter: DefaultCartesianMarker.ValueFormatter = remember { + DefaultCartesianMarker.ValueFormatter.default() }, labelPosition: DefaultCartesianMarker.LabelPosition = DefaultCartesianMarker.LabelPosition.Top, indicator: ((Color) -> Component)? = null, indicatorSize: Dp = Defaults.MARKER_INDICATOR_SIZE.dp, - setIndicatorColor: ((Int) -> Unit)? = null, guideline: LineComponent? = null, ): DefaultCartesianMarker = - remember( - label, - valueFormatter, - labelPosition, - indicator, - indicatorSize, - setIndicatorColor, - guideline, - ) { + remember(label, valueFormatter, labelPosition, indicator, indicatorSize, guideline) { DefaultCartesianMarker( label = label, valueFormatter = valueFormatter, diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Fill.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Fill.kt index 9ef516965..0bf8f8a3a 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Fill.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Fill.kt @@ -19,10 +19,10 @@ package com.patrykandpatrick.vico.compose.common import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.patrykandpatrick.vico.core.common.Fill -import com.patrykandpatrick.vico.core.common.shader.DynamicShader +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider /** Creates a [Color][Fill]. */ public fun fill(color: Color): Fill = Fill(color.toArgb()) -/** Creates a [DynamicShader][Fill]. */ -public fun fill(shader: DynamicShader): Fill = Fill(shader) +/** Creates a [ShaderProvider][Fill]. */ +public fun fill(shaderProvider: ShaderProvider): Fill = Fill(shaderProvider) diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Dimensions.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Insets.kt similarity index 53% rename from vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Dimensions.kt rename to vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Insets.kt index 735f68e16..631f221f5 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Dimensions.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Insets.kt @@ -18,19 +18,15 @@ package com.patrykandpatrick.vico.compose.common import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.patrykandpatrick.vico.core.common.Dimensions +import com.patrykandpatrick.vico.core.common.Insets -/** Creates a [Dimensions] instance with a common value for each coordinate. */ -public fun dimensions(all: Dp = 0.dp): Dimensions = Dimensions(all.value) +/** Creates an [Insets] instance. */ +public fun insets(all: Dp = 0.dp): Insets = Insets(all.value) -/** Creates a [Dimensions] instance using the provided measurements. */ -public fun dimensions(horizontal: Dp = 0.dp, vertical: Dp = 0.dp): Dimensions = - Dimensions(horizontal.value, vertical.value) +/** Creates an [Insets] instance. */ +public fun insets(horizontal: Dp = 0.dp, vertical: Dp = 0.dp): Insets = + Insets(horizontal.value, vertical.value) -/** Creates a [Dimensions] instance using the provided measurements. */ -public fun dimensions( - start: Dp = 0.dp, - top: Dp = 0.dp, - end: Dp = 0.dp, - bottom: Dp = 0.dp, -): Dimensions = Dimensions(start.value, top.value, end.value, bottom.value) +/** Creates an [Insets] instance. */ +public fun insets(start: Dp = 0.dp, top: Dp = 0.dp, end: Dp = 0.dp, bottom: Dp = 0.dp): Insets = + Insets(start.value, top.value, end.value, bottom.value) diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Legends.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Legends.kt index 06ed6470e..a7a800240 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Legends.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/Legends.kt @@ -22,9 +22,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.patrykandpatrick.vico.core.common.AdditionScope import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.DrawingContext import com.patrykandpatrick.vico.core.common.HorizontalLegend +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.LegendItem import com.patrykandpatrick.vico.core.common.MeasuringContext import com.patrykandpatrick.vico.core.common.VerticalLegend @@ -37,7 +37,7 @@ public fun rememberVerticalLegend( iconSize: Dp = Defaults.LEGEND_ICON_SIZE.dp, iconLabelSpacing: Dp = Defaults.LEGEND_ICON_LABEL_SPACING.dp, rowSpacing: Dp = Defaults.LEGEND_ROW_SPACING.dp, - padding: Dimensions = Dimensions.Empty, + padding: Insets = Insets.Zero, ): VerticalLegend = remember(items, iconSize, iconLabelSpacing, rowSpacing, padding) { VerticalLegend(items, iconSize.value, iconLabelSpacing.value, rowSpacing.value, padding) @@ -51,7 +51,7 @@ public fun rememberHorizontalLegend( iconLabelSpacing: Dp = Defaults.LEGEND_ICON_LABEL_SPACING.dp, rowSpacing: Dp = Defaults.LEGEND_ROW_SPACING.dp, columnSpacing: Dp = Defaults.LEGEND_COLUMN_SPACING.dp, - padding: Dimensions = Dimensions.Empty, + padding: Insets = Insets.Zero, ): HorizontalLegend = remember(items, iconSize, iconLabelSpacing, rowSpacing, columnSpacing, padding) { HorizontalLegend( diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/component/Components.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/component/Components.kt index 9f9983a20..a594a6cb1 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/component/Components.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/component/Components.kt @@ -30,8 +30,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.patrykandpatrick.vico.compose.common.pixelSize import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.LayeredComponent import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.component.LineComponent @@ -46,7 +46,7 @@ public fun rememberLineComponent( fill: Fill = Fill.Black, thickness: Dp = Defaults.LINE_COMPONENT_THICKNESS_DP.dp, shape: Shape = Shape.Rectangle, - margins: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, strokeFill: Fill = Fill.Transparent, strokeThickness: Dp = 0.dp, shadow: Shadow? = null, @@ -59,7 +59,7 @@ public fun rememberLineComponent( public fun shapeComponent( fill: Fill = Fill.Black, shape: Shape = Shape.Rectangle, - margins: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, strokeFill: Fill = Fill.Transparent, strokeThickness: Dp = 0.dp, shadow: Shadow? = null, @@ -70,7 +70,7 @@ public fun shapeComponent( public fun rememberShapeComponent( fill: Fill = Fill.Black, shape: Shape = Shape.Rectangle, - margins: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, strokeFill: Fill = Fill.Transparent, strokeThickness: Dp = 0.dp, shadow: Shadow? = null, @@ -82,12 +82,12 @@ public fun rememberShapeComponent( /** Creates and remembers a [LayeredComponent]. */ @Composable public fun rememberLayeredComponent( - rear: Component, + back: Component, front: Component, - padding: Dimensions = Dimensions.Empty, - margins: Dimensions = Dimensions.Empty, + padding: Insets = Insets.Zero, + margins: Insets = Insets.Zero, ): LayeredComponent = - remember(rear, front, padding, margins) { LayeredComponent(rear, front, padding, margins) } + remember(back, front, padding, margins) { LayeredComponent(back, front, padding, margins) } /** Creates and remembers a [TextComponent]. */ @Composable @@ -99,8 +99,8 @@ public fun rememberTextComponent( lineHeight: TextUnit? = null, lineCount: Int = Defaults.TEXT_COMPONENT_LINE_COUNT, truncateAt: TextUtils.TruncateAt = TextUtils.TruncateAt.END, - margins: Dimensions = Dimensions.Empty, - padding: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, + padding: Insets = Insets.Zero, background: Component? = null, minWidth: TextComponent.MinWidth = TextComponent.MinWidth.fixed(), ): TextComponent = @@ -133,8 +133,8 @@ public fun rememberTextComponent( } /** Creates a [Shadow]. */ -public fun shadow(radius: Dp, dx: Dp = 0.dp, dy: Dp = 0.dp, color: Color? = null): Shadow = - Shadow(radius.value, dx.value, dy.value, color?.toArgb() ?: Defaults.SHADOW_COLOR) +public fun shadow(radius: Dp, x: Dp = 0.dp, y: Dp = 0.dp, color: Color? = null): Shadow = + Shadow(radius.value, x.value, y.value, color?.toArgb() ?: Defaults.SHADOW_COLOR) /** A [Dp] version of [TextComponent.MinWidth.fixed]. */ public fun TextComponent.MinWidth.Companion.fixed(value: Dp = 0.dp): TextComponent.MinWidth = diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/data/ExtraStore.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/data/ExtraStore.kt deleted file mode 100644 index c468e5d5a..000000000 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/data/ExtraStore.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.compose.common.data - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import com.patrykandpatrick.vico.core.common.data.ExtraStore - -/** - * Remembers an [ExtraStore] lambda. When either initially composed or recomposed with new [keys] - * values, this function saves and returns [lambda]. When recomposed with unchanged [keys] values, - * it ignores [lambda] and returns the lambda that was last saved. - */ -@Composable -public fun rememberExtraLambda( - vararg keys: Any?, - lambda: R.(ExtraStore) -> Unit, -): R.(ExtraStore) -> Unit = remember(*keys) { lambda } diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shader/DynamicShader.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shader/ShaderProvider.kt similarity index 54% rename from vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shader/DynamicShader.kt rename to vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shader/ShaderProvider.kt index ccf087975..77a376610 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shader/DynamicShader.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shader/ShaderProvider.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:Suppress("UnusedReceiverParameter") - package com.patrykandpatrick.vico.compose.common.shader import android.graphics.Shader @@ -28,45 +26,36 @@ import androidx.compose.ui.unit.Dp import androidx.core.graphics.translationMatrix import com.patrykandpatrick.vico.core.common.DrawingContext import com.patrykandpatrick.vico.core.common.component.Component -import com.patrykandpatrick.vico.core.common.shader.CacheableDynamicShader -import com.patrykandpatrick.vico.core.common.shader.DynamicShader -import com.patrykandpatrick.vico.core.common.shader.LinearGradientShader +import com.patrykandpatrick.vico.core.common.shader.CachingShaderProvider +import com.patrykandpatrick.vico.core.common.shader.LinearGradientShaderProvider +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider -/** A [Dp] version of [DynamicShader.component]. */ -public fun DynamicShader.Companion.component( +/** A [Dp] version of [ShaderProvider.component]. */ +public fun ShaderProvider.Companion.component( component: Component, componentSize: Dp, - checkeredArrangement: Boolean = true, - tileXMode: Shader.TileMode = Shader.TileMode.REPEAT, - tileYMode: Shader.TileMode = tileXMode, -): DynamicShader = - component(component, componentSize.value, checkeredArrangement, tileXMode, tileYMode) + checker: Boolean = true, + xTileMode: Shader.TileMode = Shader.TileMode.REPEAT, + yTileMode: Shader.TileMode = xTileMode, +): ShaderProvider = component(component, componentSize.value, checker, xTileMode, yTileMode) -/** - * Creates a [DynamicShader] with a horizontal gradient. [colors] houses the gradient colors, and - * [positions] specifies the color offsets (between 0 and 1), with `null` producing an even - * distribution. - */ -public fun DynamicShader.Companion.horizontalGradient( +/** A [Color] version of [ShaderProvider.horizontalGradient]. */ +public fun ShaderProvider.Companion.horizontalGradient( colors: Array, positions: FloatArray? = null, -): DynamicShader = - LinearGradientShader(IntArray(colors.size) { colors[it].toArgb() }, positions, true) +): ShaderProvider = + LinearGradientShaderProvider(IntArray(colors.size) { colors[it].toArgb() }, positions, true) -/** - * Creates a [DynamicShader] with a vertical gradient. [colors] houses the gradient colors, and - * [positions] specifies the color offsets (between 0 and 1), with `null` producing an even - * distribution. - */ -public fun DynamicShader.Companion.verticalGradient( +/** A [Color] version of [ShaderProvider.verticalGradient]. */ +public fun ShaderProvider.Companion.verticalGradient( colors: Array, positions: FloatArray? = null, -): DynamicShader = - LinearGradientShader(IntArray(colors.size) { colors[it].toArgb() }, positions, false) +): ShaderProvider = + LinearGradientShaderProvider(IntArray(colors.size) { colors[it].toArgb() }, positions, false) -/** Converts this [Brush] to a [DynamicShader]. */ -public fun Brush.toDynamicShader(): DynamicShader = - object : CacheableDynamicShader() { +/** Converts this [Brush] to a [ShaderProvider]. */ +public fun Brush.toShaderProvider(): ShaderProvider = + object : CachingShaderProvider() { override fun createShader( context: DrawingContext, left: Float, diff --git a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shape/Shape.kt b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shape/Shape.kt index 5091971e4..cd02b4724 100644 --- a/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shape/Shape.kt +++ b/vico/compose/src/main/java/com/patrykandpatrick/vico/compose/common/shape/Shape.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.unit.dp import com.patrykandpatrick.vico.core.common.Defaults import com.patrykandpatrick.vico.core.common.Defaults.MARKER_TICK_SIZE import com.patrykandpatrick.vico.core.common.MeasuringContext -import com.patrykandpatrick.vico.core.common.shape.Corner import com.patrykandpatrick.vico.core.common.shape.CorneredShape import com.patrykandpatrick.vico.core.common.shape.DashedShape import com.patrykandpatrick.vico.core.common.shape.MarkerCorneredShape @@ -148,25 +147,25 @@ public fun CorneredShape.Companion.cut(all: Dp = 0.dp): CorneredShape = cut(all. /** Creates a [MarkerCorneredShape]. */ public fun markerCorneredShape( - topLeft: Corner, - topRight: Corner, - bottomRight: Corner, - bottomLeft: Corner, - tickSizeDp: Dp = MARKER_TICK_SIZE.dp, + topLeft: CorneredShape.Corner, + topRight: CorneredShape.Corner, + bottomRight: CorneredShape.Corner, + bottomLeft: CorneredShape.Corner, + tickSize: Dp = MARKER_TICK_SIZE.dp, ): MarkerCorneredShape = - MarkerCorneredShape(topLeft, topRight, bottomRight, bottomLeft, tickSizeDp.value) + MarkerCorneredShape(topLeft, topRight, bottomRight, bottomLeft, tickSize.value) /** Creates a [MarkerCorneredShape]. */ public fun markerCorneredShape( - all: Corner, - tickSizeDp: Dp = MARKER_TICK_SIZE.dp, -): MarkerCorneredShape = MarkerCorneredShape(all, tickSizeDp.value) + all: CorneredShape.Corner, + tickSize: Dp = MARKER_TICK_SIZE.dp, +): MarkerCorneredShape = MarkerCorneredShape(all, tickSize.value) /** Creates a [MarkerCorneredShape]. */ public fun markerCorneredShape( - corneredShape: CorneredShape, - tickSizeDp: Dp = MARKER_TICK_SIZE.dp, -): MarkerCorneredShape = MarkerCorneredShape(corneredShape, tickSizeDp.value) + base: CorneredShape, + tickSize: Dp = MARKER_TICK_SIZE.dp, +): MarkerCorneredShape = MarkerCorneredShape(base, tickSize.value) /** Creates a [DashedShape]. */ public fun dashedShape( diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/AutoScrollCondition.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/AutoScrollCondition.kt index a4d3d2295..958281166 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/AutoScrollCondition.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/AutoScrollCondition.kt @@ -23,10 +23,7 @@ public fun interface AutoScrollCondition { /** * Given a chart’s new and old models, defines whether an automatic scroll should be performed. */ - public fun shouldPerformAutoScroll( - newModel: CartesianChartModel, - oldModel: CartesianChartModel?, - ): Boolean + public fun shouldScroll(oldModel: CartesianChartModel?, newModel: CartesianChartModel): Boolean public companion object { /** Prevents any automatic scrolling from occurring. */ @@ -36,10 +33,9 @@ public fun interface AutoScrollCondition { * Triggers an automatic scroll when the size of the model increases (that is, the contents of * the chart become wider). */ - public val OnModelSizeIncreased: AutoScrollCondition = - AutoScrollCondition { newModel, oldModel -> - oldModel != null && - (newModel.models.size > oldModel.models.size || newModel.width > oldModel.width) - } + public val OnModelGrowth: AutoScrollCondition = AutoScrollCondition { oldModel, newModel -> + oldModel != null && + (newModel.models.size > oldModel.models.size || newModel.width > oldModel.width) + } } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianChart.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianChart.kt index f22aaec37..905f5ba3d 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianChart.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianChart.kt @@ -30,12 +30,15 @@ import com.patrykandpatrick.vico.core.cartesian.data.MutableCartesianChartRanges import com.patrykandpatrick.vico.core.cartesian.decoration.Decoration import com.patrykandpatrick.vico.core.cartesian.layer.CandlestickCartesianLayer import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMarginUpdater +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMargins +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.HorizontalCartesianLayerMargins import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener -import com.patrykandpatrick.vico.core.common.HorizontalInsets -import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.Legend import com.patrykandpatrick.vico.core.common.Point import com.patrykandpatrick.vico.core.common.data.CacheStore @@ -86,19 +89,19 @@ public open class CartesianChart( protected val decorations: List = emptyList(), protected val persistentMarkers: (PersistentMarkerScope.(ExtraStore) -> Unit)? = null, protected val getXStep: ((CartesianChartModel) -> Double) = { it.getXDeltaGcd() }, -) : CartesianLayerInsetter { +) : CartesianLayerMarginUpdater { private val persistentMarkerMap = mutableMapOf() private val persistentMarkerScope = PersistentMarkerScope { persistentMarkerMap[it.toDouble()] = this } private var previousPersistentMarkerHashCode: Int? = null - private val insets = Insets() + private val layerMargins = CartesianLayerMargins() + private val layerCanvas = Canvas() private val axisManager = AxisManager() private val _markerTargets = sortedMapOf>() private var previousMarkerTargetHashCode: Int? = null - protected val layerCanvas: Canvas = Canvas() - private val drawingModelAndLayerConsumer = + private val drawingConsumer = object : ModelAndLayerConsumer { lateinit var context: CartesianDrawingContext @@ -110,48 +113,53 @@ public open class CartesianChart( } } - private val horizontalDimensionUpdateModelAndLayerConsumer = + private val layerDimensionUpdateConsumer = object : ModelAndLayerConsumer { lateinit var context: CartesianMeasuringContext - lateinit var horizontalDimensions: MutableHorizontalDimensions + lateinit var layerDimensions: MutableCartesianLayerDimensions override fun invoke(model: T?, layer: CartesianLayer) { - layer.updateHorizontalDimensions(context, horizontalDimensions, model ?: return) + layer.updateDimensions(context, layerDimensions, model ?: return) } } - private val rangeUpdateModelAndLayerConsumer = + private val rangeUpdateConsumer = object : ModelAndLayerConsumer { lateinit var ranges: MutableCartesianChartRanges override fun invoke(model: T?, layer: CartesianLayer) { - layer.updateRanges(ranges, model ?: return) + layer.updateChartRanges(ranges, model ?: return) } } - private val insetUpdateModelAndLayerConsumer = + private val layerMarginUpdateConsumer = object : ModelAndLayerConsumer { lateinit var context: CartesianMeasuringContext - lateinit var horizontalDimensions: HorizontalDimensions - lateinit var insets: Insets + lateinit var layerDimensions: CartesianLayerDimensions + lateinit var layerMargins: CartesianLayerMargins override fun invoke(model: T?, layer: CartesianLayer) { - layer.updateInsets(context, horizontalDimensions, model ?: return, insets) + layer.updateLayerMargins(context, layerMargins, layerDimensions, model ?: return) } } - private val horizontalInsetUpdateModelAndLayerConsumer = + private val horizontalLayerMarginUpdateConsumer = object : ModelAndLayerConsumer { lateinit var context: CartesianMeasuringContext - var freeHeight: Float = 0f - lateinit var insets: HorizontalInsets + lateinit var horizontalLayerMargins: HorizontalCartesianLayerMargins + var layerHeight: Float = 0f override fun invoke(model: T?, layer: CartesianLayer) { - layer.updateHorizontalInsets(context, freeHeight, model ?: return, insets) + layer.updateHorizontalLayerMargins( + context, + horizontalLayerMargins, + layerHeight, + model ?: return, + ) } } - private val transformationPreparationModelAndLayerConsumer = + private val transformationPreparationConsumer = object : ModelAndLayerConsumer { lateinit var extraStore: MutableExtraStore lateinit var ranges: CartesianChartRanges @@ -200,52 +208,56 @@ public open class CartesianChart( layerBounds.set(left, top, right, bottom) } - /** Prepares the [CartesianChart] for drawing. */ + /** @suppress */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun prepare( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, - canvasBounds: RectF, + layerDimensions: MutableCartesianLayerDimensions, ) { with(context) { _markerTargets.clear() - insets.clear() + layerMargins.clear() val persistentMarkerHashCode = Objects.hash(persistentMarkers, model.extraStore) if (persistentMarkerHashCode != previousPersistentMarkerHashCode) { updatePersistentMarkers(model.extraStore) previousPersistentMarkerHashCode = persistentMarkerHashCode } model.forEachWithLayer( - horizontalDimensionUpdateModelAndLayerConsumer.apply { + layerDimensionUpdateConsumer.apply { this.context = context - this.horizontalDimensions = horizontalDimensions + this.layerDimensions = layerDimensions } ) - startAxis?.updateHorizontalDimensions(context, horizontalDimensions) - topAxis?.updateHorizontalDimensions(context, horizontalDimensions) - endAxis?.updateHorizontalDimensions(context, horizontalDimensions) - bottomAxis?.updateHorizontalDimensions(context, horizontalDimensions) - val insetters = buildList { + startAxis?.updateLayerDimensions(context, layerDimensions) + topAxis?.updateLayerDimensions(context, layerDimensions) + endAxis?.updateLayerDimensions(context, layerDimensions) + bottomAxis?.updateLayerDimensions(context, layerDimensions) + val marginUpdaters = buildList { add(this@CartesianChart) addAll(axisManager.axisCache) marker?.let(::add) addAll(persistentMarkerMap.values) } - insetters.forEach { it.updateInsets(context, horizontalDimensions, model, insets) } + marginUpdaters.forEach { updater -> + updater.updateLayerMargins(context, layerMargins, layerDimensions, model) + } val legendHeight = legend?.getHeight(context, canvasBounds.width()).orZero - val freeHeight = canvasBounds.height() - insets.vertical - legendHeight - insetters.forEach { it.updateHorizontalInsets(context, freeHeight, model, insets) } + val freeHeight = canvasBounds.height() - layerMargins.vertical - legendHeight + marginUpdaters.forEach { updater -> + updater.updateHorizontalLayerMargins(context, layerMargins, freeHeight, model) + } setLayerBounds( - canvasBounds.left + insets.getLeft(isLtr), - canvasBounds.top + insets.top, - canvasBounds.right - insets.getRight(isLtr), - canvasBounds.bottom - insets.bottom - legendHeight, + canvasBounds.left + layerMargins.getLeft(isLtr), + canvasBounds.top + layerMargins.top, + canvasBounds.right - layerMargins.getRight(isLtr), + canvasBounds.bottom - layerMargins.bottom - legendHeight, ) - axisManager.setAxesBounds(context, canvasBounds, layerBounds, insets) + axisManager.setAxesBounds(context, canvasBounds, layerBounds, layerMargins) legend?.setBounds( left = canvasBounds.left, - top = layerBounds.bottom + insets.bottom, + top = layerBounds.bottom + layerMargins.bottom, right = canvasBounds.right, - bottom = layerBounds.bottom + insets.bottom + legendHeight, + bottom = layerBounds.bottom + layerMargins.bottom + legendHeight, ) } } @@ -255,16 +267,17 @@ public open class CartesianChart( persistentMarkers?.invoke(persistentMarkerScope, extraStore) } - /** Draws the [CartesianChart]. */ - public fun draw(context: CartesianDrawingContext, pointerPosition: Point?) { + /** @suppress */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public fun draw(context: CartesianDrawingContext) { with(context) { val canvasSaveCount = if (fadingEdges != null) canvas.saveLayer() else -1 axisManager.drawUnderLayers(context) decorations.forEach { it.drawUnderLayers(context) } val layerBitmap = getBitmap(cacheKeyNamespace) layerCanvas.setBitmap(layerBitmap) - withOtherCanvas(layerCanvas) { - model.forEachWithLayer(drawingModelAndLayerConsumer.apply { this.context = context }) + withCanvas(layerCanvas) { + model.forEachWithLayer(drawingConsumer.apply { this.context = context }) } forEachPersistentMarker { marker, targets -> marker.drawUnderLayers(context, targets) } val markerTargets = getMarkerTargets(context, pointerPosition) @@ -282,57 +295,60 @@ public open class CartesianChart( } } - /** Updates [ranges] in accordance with [model]. */ + /** @suppress */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun updateRanges(ranges: MutableCartesianChartRanges, model: CartesianChartModel) { ranges.xStep = getXStep(model) - model.forEachWithLayer(rangeUpdateModelAndLayerConsumer.apply { this.ranges = ranges }) + model.forEachWithLayer(rangeUpdateConsumer.apply { this.ranges = ranges }) } - override fun updateInsets( + override fun updateLayerMargins( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerMargins: CartesianLayerMargins, + layerDimensions: CartesianLayerDimensions, model: CartesianChartModel, - insets: Insets, ) { context.model.forEachWithLayer( - insetUpdateModelAndLayerConsumer.apply { + layerMarginUpdateConsumer.apply { this.context = context - this.horizontalDimensions = horizontalDimensions - this.insets = insets + this.layerDimensions = layerDimensions + this.layerMargins = layerMargins } ) } - override fun updateHorizontalInsets( + override fun updateHorizontalLayerMargins( context: CartesianMeasuringContext, - freeHeight: Float, + horizontalLayerMargins: HorizontalCartesianLayerMargins, + layerHeight: Float, model: CartesianChartModel, - insets: HorizontalInsets, ) { context.model.forEachWithLayer( - horizontalInsetUpdateModelAndLayerConsumer.apply { + horizontalLayerMarginUpdateConsumer.apply { this.context = context - this.freeHeight = freeHeight - this.insets = insets + this.horizontalLayerMargins = horizontalLayerMargins + this.layerHeight = layerHeight } ) } - /** Prepares the [CartesianLayer]s for a difference animation. */ + /** @suppress */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun prepareForTransformation( model: CartesianChartModel?, extraStore: MutableExtraStore, ranges: CartesianChartRanges, ) { model?.forEachWithLayer( - transformationPreparationModelAndLayerConsumer.apply { + transformationPreparationConsumer.apply { this.extraStore = extraStore this.ranges = ranges } ) ?: layers.forEach { it.prepareForTransformation(null, ranges, extraStore) } } - /** Carries out the pending difference animation. */ + /** @suppress */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public suspend fun transform(extraStore: MutableExtraStore, fraction: Float) { layers.forEach { it.transform(extraStore, fraction) } } @@ -349,7 +365,7 @@ public open class CartesianChart( } } - private inline fun CartesianDrawingContext.forEachPersistentMarker( + private inline fun forEachPersistentMarker( block: (CartesianMarker, List) -> Unit ) { persistentMarkerMap.forEach { (x, marker) -> diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianDrawingContext.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianDrawingContext.kt index ef90a8ed5..cfea5e3fc 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianDrawingContext.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianDrawingContext.kt @@ -20,8 +20,8 @@ import android.graphics.Canvas import android.graphics.RectF import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions import com.patrykandpatrick.vico.core.common.DrawingContext -import com.patrykandpatrick.vico.core.common.Point import kotlin.math.ceil /** A [DrawingContext] extension with [CartesianChart]-specific data. */ @@ -29,11 +29,8 @@ public interface CartesianDrawingContext : DrawingContext, CartesianMeasuringCon /** The bounds of the [CartesianLayer] area. */ public val layerBounds: RectF - /** Holds information on the [CartesianChart]’s horizontal dimensions. */ - public val horizontalDimensions: HorizontalDimensions - - /** The point inside the chart’s coordinates where physical touch is occurring. */ - public val markerTouchPoint: Point? + /** Stores shared [CartesianLayer] dimensions. */ + public val layerDimensions: CartesianLayerDimensions /** The scroll value (in pixels). */ public val scroll: Float @@ -46,24 +43,23 @@ public interface CartesianDrawingContext : DrawingContext, CartesianMeasuringCon @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun CartesianMeasuringContext.getMaxScrollDistance( chartWidth: Float, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, ): Float = ceil( - (layoutDirectionMultiplier * (horizontalDimensions.getContentWidth(this) - chartWidth)).run { + (layoutDirectionMultiplier * (layerDimensions.getContentWidth(this) - chartWidth)).run { if (isLtr) coerceAtLeast(0f) else coerceAtMost(0f) } ) internal fun CartesianDrawingContext.getMaxScrollDistance() = - getMaxScrollDistance(layerBounds.width(), horizontalDimensions) + getMaxScrollDistance(layerBounds.width(), layerDimensions) /** @suppress */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun CartesianDrawingContext( measuringContext: CartesianMeasuringContext, canvas: Canvas, - markerTouchPoint: Point?, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, layerBounds: RectF, scroll: Float, zoom: Float, @@ -73,15 +69,13 @@ public fun CartesianDrawingContext( override var canvas: Canvas = canvas - override val markerTouchPoint: Point? = markerTouchPoint - - override val horizontalDimensions: HorizontalDimensions = horizontalDimensions + override val layerDimensions: CartesianLayerDimensions = layerDimensions override val scroll: Float = scroll override val zoom: Float = zoom - override fun withOtherCanvas(canvas: Canvas, block: () -> Unit) { + override fun withCanvas(canvas: Canvas, block: () -> Unit) { val originalCanvas = this.canvas this.canvas = canvas block() diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianMeasuringContext.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianMeasuringContext.kt index 0d85ed69e..3e69ff1b8 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianMeasuringContext.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianMeasuringContext.kt @@ -19,7 +19,9 @@ package com.patrykandpatrick.vico.core.cartesian import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding import com.patrykandpatrick.vico.core.common.MeasuringContext +import com.patrykandpatrick.vico.core.common.Point /** A [MeasuringContext] extension with [CartesianChart]-specific data. */ public interface CartesianMeasuringContext : MeasuringContext { @@ -37,4 +39,7 @@ public interface CartesianMeasuringContext : MeasuringContext { /** Stores the [CartesianLayer] padding values. */ public val layerPadding: CartesianLayerPadding + + /** The pointer position. */ + public val pointerPosition: Point? } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/FadingEdges.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/FadingEdges.kt index 9567b3a70..c169088d1 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/FadingEdges.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/FadingEdges.kt @@ -38,8 +38,8 @@ private const val NO_FADE: Int = 0x00000000 * This effect indicates that there’s more content beyond a given edge, and the user can scroll to * reveal it. * - * @param startEdgeWidthDp the width of the fade overlay for the start edge (in dp). - * @param endEdgeWidthDp the width of the fade overlay for the end edge (in dp). + * @param startWidthDp the width of the fade overlay for the start edge (in dp). + * @param endWidthDp the width of the fade overlay for the end edge (in dp). * @param visibilityThresholdDp the scroll distance over which the overlays fade in and out (in dp). * @param visibilityInterpolator used for the fading edges’ fade-in and fade-out animations. This is * a mapping of the degree to which [visibilityThresholdDp] has been satisfied to the opacity of @@ -47,8 +47,8 @@ private const val NO_FADE: Int = 0x00000000 */ @Immutable public open class FadingEdges( - protected val startEdgeWidthDp: Float = FADING_EDGE_WIDTH_DP, - protected val endEdgeWidthDp: Float = FADING_EDGE_WIDTH_DP, + protected val startWidthDp: Float = FADING_EDGE_WIDTH_DP, + protected val endWidthDp: Float = FADING_EDGE_WIDTH_DP, protected val visibilityThresholdDp: Float = FADING_EDGE_VISIBILITY_THRESHOLD_DP, protected val visibilityInterpolator: TimeInterpolator = AccelerateDecelerateInterpolator(), ) { @@ -59,7 +59,7 @@ public open class FadingEdges( /** * Creates a [FadingEdges] instance with fading edges of equal width. * - * @param edgeWidthDp the width of the fade overlay (in dp). + * @param widthDp the width of the fade overlays (in dp). * @param visibilityThresholdDp the scroll distance over which the overlays fade in and out (in * dp). * @param visibilityInterpolator used for the fading edges’ fade-in and fade-out animations. This @@ -67,47 +67,46 @@ public open class FadingEdges( * of the fading edges. */ public constructor( - edgeWidthDp: Float = FADING_EDGE_WIDTH_DP, + widthDp: Float = FADING_EDGE_WIDTH_DP, visibilityThresholdDp: Float = FADING_EDGE_VISIBILITY_THRESHOLD_DP, visibilityInterpolator: TimeInterpolator = AccelerateDecelerateInterpolator(), ) : this( - startEdgeWidthDp = edgeWidthDp, - endEdgeWidthDp = edgeWidthDp, + startWidthDp = widthDp, + endWidthDp = widthDp, visibilityThresholdDp = visibilityThresholdDp, visibilityInterpolator = visibilityInterpolator, ) init { - require(value = startEdgeWidthDp >= 0) { "`startEdgeWidthDp` must be greater than 0." } - require(value = endEdgeWidthDp >= 0) { "`endEdgeWidthDp` must be greater than 0." } + require(value = startWidthDp >= 0) { "`startWidthDp` must be nonnegative." } + require(value = endWidthDp >= 0) { "`endWidthDp` must be nonnegative." } paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) } - /** Applies the fade. */ - public fun draw(context: CartesianDrawingContext) { + internal fun draw(context: CartesianDrawingContext) { with(context) { val maxScroll = getMaxScrollDistance() var fadeAlphaFraction: Float - if (scrollEnabled && startEdgeWidthDp > 0f && scroll > 0f) { + if (scrollEnabled && startWidthDp > 0f && scroll > 0f) { fadeAlphaFraction = (scroll / visibilityThresholdDp.pixels).coerceAtMost(1f) drawFadingEdge( left = layerBounds.left, top = layerBounds.top, - right = layerBounds.left + startEdgeWidthDp.pixels, + right = layerBounds.left + startWidthDp.pixels, bottom = layerBounds.bottom, direction = -1, alpha = (visibilityInterpolator.getInterpolation(fadeAlphaFraction) * FULL_ALPHA).toInt(), ) } - if (scrollEnabled && endEdgeWidthDp > 0f && scroll < maxScroll) { + if (scrollEnabled && endWidthDp > 0f && scroll < maxScroll) { fadeAlphaFraction = ((maxScroll - scroll) / visibilityThresholdDp.pixels).coerceAtMost(1f) drawFadingEdge( - left = layerBounds.right - endEdgeWidthDp.pixels, + left = layerBounds.right - endWidthDp.pixels, top = layerBounds.top, right = layerBounds.right, bottom = layerBounds.bottom, @@ -146,14 +145,14 @@ public open class FadingEdges( override fun equals(other: Any?): Boolean = this === other || other is FadingEdges && - startEdgeWidthDp == other.startEdgeWidthDp && - endEdgeWidthDp == other.endEdgeWidthDp && + startWidthDp == other.startWidthDp && + endWidthDp == other.endWidthDp && visibilityThresholdDp == other.visibilityThresholdDp && visibilityInterpolator == other.visibilityInterpolator override fun hashCode(): Int { - var result = startEdgeWidthDp.hashCode() - result = 31 * result + endEdgeWidthDp.hashCode() + var result = startWidthDp.hashCode() + result = 31 * result + endWidthDp.hashCode() result = 31 * result + visibilityThresholdDp.hashCode() result = 31 * result + visibilityInterpolator.hashCode() return result diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableHorizontalDimensions.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableCartesianLayerDimensions.kt similarity index 86% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableHorizontalDimensions.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableCartesianLayerDimensions.kt index c2d555342..9e2037f9a 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableHorizontalDimensions.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableCartesianLayerDimensions.kt @@ -17,23 +17,23 @@ package com.patrykandpatrick.vico.core.cartesian import androidx.annotation.RestrictTo +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions -/** An implementation of [HorizontalDimensions] whose every property is mutable. */ -public data class MutableHorizontalDimensions( +/** An implementation of [CartesianLayerDimensions] whose every property is mutable. */ +public class MutableCartesianLayerDimensions( override var xSpacing: Float = 0f, override var scalableStartPadding: Float = 0f, override var scalableEndPadding: Float = 0f, override var unscalableStartPadding: Float = 0f, override var unscalableEndPadding: Float = 0f, -) : HorizontalDimensions { - /** Updates the stored values. */ - public fun set( +) : CartesianLayerDimensions { + internal fun set( xSpacing: Float, scalableStartPadding: Float, scalableEndPadding: Float, unscalableStartPadding: Float, unscalableEndPadding: Float, - ): MutableHorizontalDimensions = apply { + ) { this.xSpacing = xSpacing this.scalableStartPadding = scalableStartPadding this.scalableEndPadding = scalableEndPadding @@ -48,7 +48,7 @@ public data class MutableHorizontalDimensions( scalableEndPadding: Float = 0f, unscalableStartPadding: Float = 0f, unscalableEndPadding: Float = 0f, - ): MutableHorizontalDimensions = + ) { set( this.xSpacing.coerceAtLeast(xSpacing), this.scalableStartPadding.coerceAtLeast(scalableStartPadding), @@ -56,6 +56,7 @@ public data class MutableHorizontalDimensions( this.unscalableStartPadding.coerceAtLeast(unscalableStartPadding), this.unscalableEndPadding.coerceAtLeast(unscalableEndPadding), ) + } /** Clears the stored values. */ public fun clear() { @@ -69,7 +70,7 @@ public data class MutableHorizontalDimensions( /** @suppress */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public fun MutableHorizontalDimensions.scale(factor: Float): MutableHorizontalDimensions = +public fun MutableCartesianLayerDimensions.scale(factor: Float) { set( factor * xSpacing, factor * scalableStartPadding, @@ -77,3 +78,4 @@ public fun MutableHorizontalDimensions.scale(factor: Float): MutableHorizontalDi unscalableStartPadding, unscalableEndPadding, ) +} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableCartesianMeasuringContext.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableCartesianMeasuringContext.kt index faae42808..45be54b39 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableCartesianMeasuringContext.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/MutableCartesianMeasuringContext.kt @@ -20,7 +20,9 @@ import android.graphics.RectF import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding import com.patrykandpatrick.vico.core.common.MutableMeasuringContext +import com.patrykandpatrick.vico.core.common.Point /** @suppress */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @@ -33,6 +35,7 @@ public class MutableCartesianMeasuringContext( override var scrollEnabled: Boolean, override var zoomEnabled: Boolean, override var layerPadding: CartesianLayerPadding, + override var pointerPosition: Point?, spToPx: (Float) -> Float, ) : MutableMeasuringContext( @@ -41,6 +44,4 @@ public class MutableCartesianMeasuringContext( isLtr = isLtr, spToPx = spToPx, ), - CartesianMeasuringContext { - override fun spToPx(sp: Float): Float = spToPx.invoke(sp) -} + CartesianMeasuringContext diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Scroll.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Scroll.kt index 220f2e101..e7cdc08f6 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Scroll.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Scroll.kt @@ -20,6 +20,7 @@ import android.graphics.RectF import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.Scroll.Absolute import com.patrykandpatrick.vico.core.cartesian.Scroll.Relative +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions /** Represents a [CartesianChart] scroll value or delta. */ public sealed interface Scroll { @@ -28,7 +29,7 @@ public sealed interface Scroll { /** Returns the scroll value. */ public fun getValue( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, bounds: RectF, maxValue: Float, ): Float @@ -49,10 +50,10 @@ public sealed interface Scroll { * ([bias] = 0) and the end edge ([bias] = 1) of the [CartesianChart]. */ public fun x(x: Double, bias: Float = 0f): Absolute = - Absolute { context, horizontalDimensions, bounds, _ -> - horizontalDimensions.startPadding + + Absolute { context, layerDimensions, bounds, _ -> + layerDimensions.startPadding + ((x - context.ranges.minX) / context.ranges.xStep).toFloat() * - horizontalDimensions.xSpacing - bias * bounds.width() + layerDimensions.xSpacing - bias * bounds.width() } } } @@ -62,7 +63,7 @@ public sealed interface Scroll { /** Returns the scroll delta. */ public fun getDelta( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, bounds: RectF, maxValue: Float, ): Float @@ -73,8 +74,8 @@ public sealed interface Scroll { public fun pixels(pixels: Float): Relative = Relative { _, _, _, _ -> pixels } /** Scrolls by the specified number of _x_ units. */ - public fun x(x: Double): Relative = Relative { context, horizontalDimensions, _, _ -> - (x / context.ranges.xStep).toFloat() * horizontalDimensions.xSpacing + public fun x(x: Double): Relative = Relative { context, layerDimensions, _, _ -> + (x / context.ranges.xStep).toFloat() * layerDimensions.xSpacing } } } @@ -84,12 +85,12 @@ public sealed interface Scroll { @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun Scroll.getDelta( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, bounds: RectF, maxValue: Float, value: Float, ): Float = when (this) { - is Absolute -> getValue(context, horizontalDimensions, bounds, maxValue) - value - is Relative -> getDelta(context, horizontalDimensions, bounds, maxValue) + is Absolute -> getValue(context, layerDimensions, bounds, maxValue) - value + is Relative -> getDelta(context, layerDimensions, bounds, maxValue) } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Zoom.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Zoom.kt index 9a71660e9..44aa8d654 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Zoom.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/Zoom.kt @@ -17,6 +17,7 @@ package com.patrykandpatrick.vico.core.cartesian import android.graphics.RectF +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions import kotlin.math.max import kotlin.math.min @@ -25,43 +26,43 @@ public fun interface Zoom { /** Returns the zoom factor. */ public fun getValue( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, bounds: RectF, ): Float /** Houses [Zoom] singletons and factory functions. */ public companion object { /** Ensures all of the [CartesianChart]’s content is visible. */ - public val Content: Zoom = Zoom { context, horizontalDimensions, bounds -> - val scalableContentWidth = horizontalDimensions.getScalableContentWidth(context) + public val Content: Zoom = Zoom { context, layerDimensions, bounds -> + val scalableContentWidth = layerDimensions.getScalableContentWidth(context) if (scalableContentWidth == 0f) { 1f } else { - (bounds.width() - horizontalDimensions.unscalablePadding) / scalableContentWidth + (bounds.width() - layerDimensions.unscalablePadding) / scalableContentWidth } } /** Uses a zoom factor of [value]. */ - public fun static(value: Float = 1f): Zoom = Zoom { _, _, _ -> value } + public fun fixed(value: Float = 1f): Zoom = Zoom { _, _, _ -> value } /** Ensures the specified number of _x_ units is visible. */ - public fun x(x: Double): Zoom = Zoom { context, horizontalDimensions, bounds -> - bounds.width() * (context.ranges.xStep / x).toFloat() / horizontalDimensions.xSpacing + public fun x(x: Double): Zoom = Zoom { context, layerDimensions, bounds -> + bounds.width() * (context.ranges.xStep / x).toFloat() / layerDimensions.xSpacing } /** Uses the smaller of [a]’s zoom factor and [b]’s zoom factor. */ - public fun min(a: Zoom, b: Zoom): Zoom = Zoom { context, horizontalDimensions, bounds -> + public fun min(a: Zoom, b: Zoom): Zoom = Zoom { context, layerDimensions, bounds -> min( - a.getValue(context, horizontalDimensions, bounds), - b.getValue(context, horizontalDimensions, bounds), + a.getValue(context, layerDimensions, bounds), + b.getValue(context, layerDimensions, bounds), ) } /** Uses the greater of [a]’s zoom factor and [b]’s zoom factor. */ - public fun max(a: Zoom, b: Zoom): Zoom = Zoom { context, horizontalDimensions, bounds -> + public fun max(a: Zoom, b: Zoom): Zoom = Zoom { context, layerDimensions, bounds -> max( - a.getValue(context, horizontalDimensions, bounds), - b.getValue(context, horizontalDimensions, bounds), + a.getValue(context, layerDimensions, bounds), + b.getValue(context, layerDimensions, bounds), ) } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/Axis.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/Axis.kt index b86fc56ed..1a8d74edc 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/Axis.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/Axis.kt @@ -20,17 +20,18 @@ import android.graphics.RectF import androidx.compose.runtime.Immutable import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerInsetter import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMarginUpdater import com.patrykandpatrick.vico.core.common.Bounded import com.patrykandpatrick.vico.core.common.MeasuringContext /** Draws an axis. */ @Immutable -public interface Axis

: Bounded, CartesianLayerInsetter { +public interface Axis

: + Bounded, CartesianLayerMarginUpdater { /** The position of the [Axis]. */ public val position: P @@ -43,10 +44,10 @@ public interface Axis

: Bounded, CartesianLayerInsetter.setTopAxisBounds( context: CartesianMeasuringContext, canvasBounds: RectF, - insets: Insets, + layerMargins: CartesianLayerMargins, ) { with(context) { setBounds( - left = canvasBounds.left + if (isLtr) insets.start else insets.end, + left = canvasBounds.left + if (isLtr) layerMargins.start else layerMargins.end, top = canvasBounds.top, - right = canvasBounds.right - if (isLtr) insets.end else insets.start, - bottom = canvasBounds.top + insets.top, + right = canvasBounds.right - if (isLtr) layerMargins.end else layerMargins.start, + bottom = canvasBounds.top + layerMargins.top, ) } } @@ -79,13 +79,13 @@ internal class AxisManager { context: CartesianMeasuringContext, canvasBounds: RectF, layerBounds: RectF, - insets: Insets, + layerMargins: CartesianLayerMargins, ) { with(context) { setBounds( - left = if (isLtr) canvasBounds.right - insets.end else canvasBounds.left, + left = if (isLtr) canvasBounds.right - layerMargins.end else canvasBounds.left, top = layerBounds.top, - right = if (isLtr) canvasBounds.right else canvasBounds.left + insets.end, + right = if (isLtr) canvasBounds.right else canvasBounds.left + layerMargins.end, bottom = layerBounds.bottom, ) } @@ -95,14 +95,14 @@ internal class AxisManager { context: CartesianMeasuringContext, canvasBounds: RectF, layerBounds: RectF, - insets: Insets, + layerMargins: CartesianLayerMargins, ) { with(context) { setBounds( - left = canvasBounds.left + if (isLtr) insets.start else insets.end, + left = canvasBounds.left + if (isLtr) layerMargins.start else layerMargins.end, top = layerBounds.bottom, - right = canvasBounds.right - if (isLtr) insets.end else insets.start, - bottom = layerBounds.bottom + insets.bottom, + right = canvasBounds.right - if (isLtr) layerMargins.end else layerMargins.start, + bottom = layerBounds.bottom + layerMargins.bottom, ) } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/BaseAxis.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/BaseAxis.kt index adb476918..4e550449f 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/BaseAxis.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/BaseAxis.kt @@ -118,27 +118,21 @@ public abstract class BaseAxis

( */ @Immutable public sealed class Size { - /** - * The axis will measure itself and use as much space as it needs, but no less than [minSizeDp], - * and no more than [maxSizeDp]. - */ - public class Auto( - public val minSizeDp: Float = 0f, - public val maxSizeDp: Float = Float.MAX_VALUE, - ) : Size() { + /** Allows a [BaseAxis] to use its preferred size from [[minDp], [maxDp]]. */ + public class Auto(public val minDp: Float = 0f, public val maxDp: Float = Float.MAX_VALUE) : + Size() { override fun equals(other: Any?): Boolean = - this === other || - other is Auto && minSizeDp == other.minSizeDp && maxSizeDp == other.maxSizeDp + this === other || other is Auto && minDp == other.minDp && maxDp == other.maxDp - override fun hashCode(): Int = 31 * minSizeDp.hashCode() + maxSizeDp.hashCode() + override fun hashCode(): Int = 31 * minDp.hashCode() + maxDp.hashCode() } - /** The axis size will be exactly [sizeDp]. */ - public class Exact(public val sizeDp: Float) : Size() { + /** Instructs a [BaseAxis] to take a size of [valueDp] dp. */ + public class Fixed(public val valueDp: Float) : Size() { override fun equals(other: Any?): Boolean = - this === other || other is Exact && sizeDp == other.sizeDp + this === other || other is Fixed && valueDp == other.valueDp - override fun hashCode(): Int = sizeDp.hashCode() + override fun hashCode(): Int = valueDp.hashCode() } /** diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/DefaultVerticalAxisItemPlacer.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/DefaultVerticalAxisItemPlacer.kt index 88a1d9a6d..e6a7daffb 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/DefaultVerticalAxisItemPlacer.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/DefaultVerticalAxisItemPlacer.kt @@ -18,6 +18,7 @@ package com.patrykandpatrick.vico.core.cartesian.axis import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.data.CacheStore import com.patrykandpatrick.vico.core.common.data.ExtraStore import com.patrykandpatrick.vico.core.common.getDivisors @@ -57,33 +58,33 @@ internal class DefaultVerticalAxisItemPlacer( return listOf(yRange.minY, (yRange.minY + yRange.maxY).half, yRange.maxY) } - override fun getTopVerticalAxisInset( + override fun getTopLayerMargin( context: CartesianMeasuringContext, - verticalLabelPosition: VerticalAxis.VerticalLabelPosition, + verticalLabelPosition: Position.Vertical, maxLabelHeight: Float, maxLineThickness: Float, ) = when { !mode.insetsRequired(context) -> 0f - verticalLabelPosition == VerticalAxis.VerticalLabelPosition.Top -> + verticalLabelPosition == Position.Vertical.Top -> maxLabelHeight + (if (shiftTopLines) maxLineThickness else -maxLineThickness).half - verticalLabelPosition == VerticalAxis.VerticalLabelPosition.Center -> + verticalLabelPosition == Position.Vertical.Center -> (max(maxLabelHeight, maxLineThickness) + if (shiftTopLines) maxLineThickness else -maxLineThickness) .half else -> if (shiftTopLines) maxLineThickness else 0f } - override fun getBottomVerticalAxisInset( + override fun getBottomLayerMargin( context: CartesianMeasuringContext, - verticalLabelPosition: VerticalAxis.VerticalLabelPosition, + verticalLabelPosition: Position.Vertical, maxLabelHeight: Float, maxLineThickness: Float, ): Float = when { !mode.insetsRequired(context) -> 0f - verticalLabelPosition == VerticalAxis.VerticalLabelPosition.Top -> maxLineThickness - verticalLabelPosition == VerticalAxis.VerticalLabelPosition.Center -> + verticalLabelPosition == Position.Vertical.Top -> maxLineThickness + verticalLabelPosition == Position.Vertical.Center -> (max(maxLabelHeight, maxLineThickness) + maxLineThickness).half else -> maxLabelHeight + maxLineThickness.half } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxis.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxis.kt index 6b96ca463..377a3c058 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxis.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxis.kt @@ -19,15 +19,15 @@ package com.patrykandpatrick.vico.core.cartesian.axis import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.cartesian.data.formatForAxis import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer -import com.patrykandpatrick.vico.core.common.Insets -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMargins +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.ExtraStore @@ -74,11 +74,11 @@ protected constructor( titleComponent, title, ) { - protected val Axis.Position.Horizontal.textVerticalPosition: VerticalPosition + protected val Axis.Position.Horizontal.textVerticalPosition: Position.Vertical get() = when (this) { - Axis.Position.Horizontal.Top -> VerticalPosition.Top - Axis.Position.Horizontal.Bottom -> VerticalPosition.Bottom + Axis.Position.Horizontal.Top -> Position.Vertical.Top + Axis.Position.Horizontal.Bottom -> Position.Vertical.Bottom } /** @suppress */ @@ -119,37 +119,25 @@ protected constructor( bounds.top } val tickBottom = tickTop + lineThickness + tickLength - val fullXRange = getFullXRange(horizontalDimensions) - val maxLabelWidth = getMaxLabelWidth(horizontalDimensions, fullXRange) + val fullXRange = getFullXRange(layerDimensions) + val maxLabelWidth = getMaxLabelWidth(layerDimensions, fullXRange) canvas.clipRect( bounds.left - - itemPlacer.getStartHorizontalAxisInset( - this, - horizontalDimensions, - tickThickness, - maxLabelWidth, - ), + itemPlacer.getStartLayerMargin(this, layerDimensions, tickThickness, maxLabelWidth), min(bounds.top, layerBounds.top), bounds.right + - itemPlacer.getEndHorizontalAxisInset( - this, - horizontalDimensions, - tickThickness, - maxLabelWidth, - ), + itemPlacer.getEndLayerMargin(this, layerDimensions, tickThickness, maxLabelWidth), max(bounds.bottom, layerBounds.bottom), ) val textY = if (position == Axis.Position.Horizontal.Top) tickTop else tickBottom val baseCanvasX = - bounds.getStart(isLtr) - scroll + - horizontalDimensions.startPadding * layoutDirectionMultiplier + bounds.getStart(isLtr) - scroll + layerDimensions.startPadding * layoutDirectionMultiplier val firstVisibleX = fullXRange.start + - scroll / horizontalDimensions.xSpacing * ranges.xStep * layoutDirectionMultiplier - val lastVisibleX = - firstVisibleX + bounds.width() / horizontalDimensions.xSpacing * ranges.xStep + scroll / layerDimensions.xSpacing * ranges.xStep * layoutDirectionMultiplier + val lastVisibleX = firstVisibleX + bounds.width() / layerDimensions.xSpacing * ranges.xStep val visibleXRange = firstVisibleX..lastVisibleX val labelValues = itemPlacer.getLabelValues(this, visibleXRange, fullXRange, maxLabelWidth) val lineValues = itemPlacer.getLineValues(this, visibleXRange, fullXRange, maxLabelWidth) @@ -158,12 +146,12 @@ protected constructor( val canvasX = baseCanvasX + ((x - ranges.minX) / ranges.xStep).toFloat() * - horizontalDimensions.xSpacing * + layerDimensions.xSpacing * layoutDirectionMultiplier val previousX = labelValues.getOrNull(index - 1) ?: (fullXRange.start.doubled - x) val nextX = labelValues.getOrNull(index + 1) ?: (fullXRange.endInclusive.doubled - x) val maxWidth = - ceil(min(x - previousX, nextX - x) / ranges.xStep * horizontalDimensions.xSpacing).toInt() + ceil(min(x - previousX, nextX - x) / ranges.xStep * layerDimensions.xSpacing).toInt() label?.draw( context = this, @@ -180,9 +168,9 @@ protected constructor( if (lineValues == null) { tick?.drawVertical( context = this, + x = canvasX + getLinesCorrectionX(x, fullXRange), top = tickTop, bottom = tickBottom, - centerX = canvasX + getLinesCorrectionX(x, fullXRange), ) } } @@ -190,14 +178,14 @@ protected constructor( lineValues?.forEach { x -> tick?.drawVertical( context = this, - top = tickTop, - bottom = tickBottom, - centerX = + x = baseCanvasX + ((x - ranges.minX) / ranges.xStep).toFloat() * - horizontalDimensions.xSpacing * + layerDimensions.xSpacing * layoutDirectionMultiplier + getLinesCorrectionX(x, fullXRange), + top = tickTop, + bottom = tickBottom, ) } @@ -212,7 +200,7 @@ protected constructor( context = this, left = layerBounds.left - lineExtensionLength, right = layerBounds.right + lineExtensionLength, - centerY = + y = if (position == Axis.Position.Horizontal.Top) { bounds.bottom - lineThickness.half } else { @@ -227,9 +215,9 @@ protected constructor( y = if (position == Axis.Position.Horizontal.Top) bounds.top else bounds.bottom, verticalPosition = if (position == Axis.Position.Horizontal.Top) { - VerticalPosition.Bottom + Position.Vertical.Bottom } else { - VerticalPosition.Top + Position.Vertical.Top }, maxWidth = bounds.width().toInt(), text = title, @@ -259,25 +247,25 @@ protected constructor( val canvasX = baseCanvasX + ((x - ranges.minX) / ranges.xStep).toFloat() * - horizontalDimensions.xSpacing * + layerDimensions.xSpacing * layoutDirectionMultiplier guideline .takeUnless { x.isBoundOf(fullXRange) } - ?.drawVertical(this, layerBounds.top, layerBounds.bottom, canvasX) + ?.drawVertical(this, canvasX, layerBounds.top, layerBounds.bottom) } } else { lineValues.forEach { x -> val canvasX = baseCanvasX + ((x - ranges.minX) / ranges.xStep).toFloat() * - horizontalDimensions.xSpacing * + layerDimensions.xSpacing * layoutDirectionMultiplier + getLinesCorrectionX(x, fullXRange) guideline .takeUnless { x.isBoundOf(fullXRange) } - ?.drawVertical(this, layerBounds.top, layerBounds.bottom, canvasX) + ?.drawVertical(this, canvasX, layerBounds.top, layerBounds.bottom) } } @@ -297,14 +285,14 @@ protected constructor( override fun drawOverLayers(context: CartesianDrawingContext) {} - override fun updateHorizontalDimensions( + override fun updateLayerDimensions( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + layerDimensions: MutableCartesianLayerDimensions, ) { val label = label ?: return val ranges = context.ranges val maxLabelWidth = - context.getMaxLabelWidth(horizontalDimensions, context.getFullXRange(horizontalDimensions)) + context.getMaxLabelWidth(layerDimensions, context.getFullXRange(layerDimensions)) val firstLabelValue = itemPlacer.getFirstLabelValue(context, maxLabelWidth) val lastLabelValue = itemPlacer.getLastLabelValue(context, maxLabelWidth) if (firstLabelValue != null) { @@ -325,9 +313,9 @@ protected constructor( .half if (!context.zoomEnabled) { unscalableStartPadding -= - (firstLabelValue - ranges.minX).toFloat() * horizontalDimensions.xSpacing + (firstLabelValue - ranges.minX).toFloat() * layerDimensions.xSpacing } - horizontalDimensions.ensureValuesAtLeast(unscalableStartPadding = unscalableStartPadding) + layerDimensions.ensureValuesAtLeast(unscalableStartPadding = unscalableStartPadding) } if (lastLabelValue != null) { val text = @@ -347,45 +335,40 @@ protected constructor( .half if (!context.zoomEnabled) { unscalableEndPadding -= - ((ranges.maxX - lastLabelValue) * horizontalDimensions.xSpacing).toFloat() + ((ranges.maxX - lastLabelValue) * layerDimensions.xSpacing).toFloat() } - horizontalDimensions.ensureValuesAtLeast(unscalableEndPadding = unscalableEndPadding) + layerDimensions.ensureValuesAtLeast(unscalableEndPadding = unscalableEndPadding) } } - override fun updateInsets( + override fun updateLayerMargins( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerMargins: CartesianLayerMargins, + layerDimensions: CartesianLayerDimensions, model: CartesianChartModel, - insets: Insets, ) { val maxLabelWidth = - context.getMaxLabelWidth(horizontalDimensions, context.getFullXRange(horizontalDimensions)) - val height = getHeight(context, horizontalDimensions, maxLabelWidth) - insets.ensureValuesAtLeast( - itemPlacer.getStartHorizontalAxisInset( - context, - horizontalDimensions, - context.tickThickness, - maxLabelWidth, - ), - itemPlacer.getEndHorizontalAxisInset( + context.getMaxLabelWidth(layerDimensions, context.getFullXRange(layerDimensions)) + val height = getHeight(context, layerDimensions, maxLabelWidth) + layerMargins.ensureValuesAtLeast( + itemPlacer.getStartLayerMargin( context, - horizontalDimensions, + layerDimensions, context.tickThickness, maxLabelWidth, ), + itemPlacer.getEndLayerMargin(context, layerDimensions, context.tickThickness, maxLabelWidth), ) when (position) { - Axis.Position.Horizontal.Top -> insets.ensureValuesAtLeast(top = height) - Axis.Position.Horizontal.Bottom -> insets.ensureValuesAtLeast(bottom = height) + Axis.Position.Horizontal.Top -> layerMargins.ensureValuesAtLeast(top = height) + Axis.Position.Horizontal.Bottom -> layerMargins.ensureValuesAtLeast(bottom = height) } } protected fun CartesianMeasuringContext.getFullXRange( - horizontalDimensions: HorizontalDimensions + layerDimensions: CartesianLayerDimensions ): ClosedFloatingPointRange = - with(horizontalDimensions) { + with(layerDimensions) { val start = ranges.minX - startPadding / xSpacing * ranges.xStep val end = ranges.maxX + endPadding / xSpacing * ranges.xStep start..end @@ -393,15 +376,15 @@ protected constructor( protected open fun getHeight( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, maxLabelWidth: Float, ): Float = with(context) { - val fullXRange = getFullXRange(horizontalDimensions) + val fullXRange = getFullXRange(layerDimensions) when (size) { is Size.Auto -> { - val labelHeight = getMaxLabelHeight(horizontalDimensions, fullXRange, maxLabelWidth) + val labelHeight = getMaxLabelHeight(layerDimensions, fullXRange, maxLabelWidth) val titleComponentHeight = title ?.let { title -> @@ -417,9 +400,9 @@ protected constructor( (if (position == Axis.Position.Horizontal.Bottom) lineThickness else 0f) + tickLength) .coerceAtMost(canvasBounds.height() / MAX_HEIGHT_DIVISOR) - .coerceIn(size.minSizeDp.pixels, size.maxSizeDp.pixels) + .coerceIn(size.minDp.pixels, size.maxDp.pixels) } - is Size.Exact -> size.sizeDp.pixels + is Size.Fixed -> size.valueDp.pixels is Size.Fraction -> canvasBounds.height() * size.fraction is Size.Text -> label @@ -429,12 +412,12 @@ protected constructor( } protected fun CartesianMeasuringContext.getMaxLabelWidth( - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, fullXRange: ClosedFloatingPointRange, ): Float { val label = label ?: return 0f return itemPlacer - .getWidthMeasurementLabelValues(this, horizontalDimensions, fullXRange) + .getWidthMeasurementLabelValues(this, layerDimensions, fullXRange) .maxOfOrNull { value -> val text = valueFormatter.formatForAxis(context = this, value = value, verticalAxisPosition = null) @@ -449,13 +432,13 @@ protected constructor( } protected fun CartesianMeasuringContext.getMaxLabelHeight( - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, fullXRange: ClosedFloatingPointRange, maxLabelWidth: Float, ): Float { val label = label ?: return 0f return itemPlacer - .getHeightMeasurementLabelValues(this, horizontalDimensions, fullXRange, maxLabelWidth) + .getHeightMeasurementLabelValues(this, layerDimensions, fullXRange, maxLabelWidth) .maxOf { value -> val text = valueFormatter.formatForAxis(context = this, value = value, verticalAxisPosition = null) @@ -549,7 +532,7 @@ protected constructor( */ public fun getWidthMeasurementLabelValues( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, fullXRange: ClosedFloatingPointRange, ): List @@ -560,7 +543,7 @@ protected constructor( */ public fun getHeightMeasurementLabelValues( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, fullXRange: ClosedFloatingPointRange, maxLabelWidth: Float, ): List @@ -577,18 +560,18 @@ protected constructor( maxLabelWidth: Float, ): List? = null - /** Returns the start inset required by the [HorizontalAxis]. */ - public fun getStartHorizontalAxisInset( + /** Returns the start [CartesianLayer]-area margin required by the [HorizontalAxis]. */ + public fun getStartLayerMargin( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, tickThickness: Float, maxLabelWidth: Float, ): Float - /** Returns the end inset required by the [HorizontalAxis]. */ - public fun getEndHorizontalAxisInset( + /** Returns the end [CartesianLayer]-area margin required by the [HorizontalAxis]. */ + public fun getEndLayerMargin( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, tickThickness: Float, maxLabelWidth: Float, ): Float diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxisItemPlacers.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxisItemPlacers.kt index 661c4edfb..3039d9460 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxisItemPlacers.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/HorizontalAxisItemPlacers.kt @@ -18,8 +18,8 @@ package com.patrykandpatrick.vico.core.cartesian.axis import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions import com.patrykandpatrick.vico.core.common.data.ExtraStore import com.patrykandpatrick.vico.core.common.half import com.patrykandpatrick.vico.core.common.roundedToNearest @@ -71,7 +71,7 @@ internal abstract class BaseHorizontalAxisItemPlacer(private val shiftExtremeLin override fun getHeightMeasurementLabelValues( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, fullXRange: ClosedFloatingPointRange, maxLabelWidth: Float, ) = context.ranges.measuredLabelValues @@ -122,7 +122,7 @@ internal class AlignedHorizontalAxisItemPlacer( spacing = spacing * if (addExtremeLabelPadding && maxLabelWidth != 0f) { - ceil(maxLabelWidth / (context.horizontalDimensions.xSpacing * spacing)).toInt() + ceil(maxLabelWidth / (context.layerDimensions.xSpacing * spacing)).toInt() } else { 1 }, @@ -131,28 +131,28 @@ internal class AlignedHorizontalAxisItemPlacer( override fun getWidthMeasurementLabelValues( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, fullXRange: ClosedFloatingPointRange, ) = if (addExtremeLabelPadding) context.ranges.measuredLabelValues else emptyList() - override fun getStartHorizontalAxisInset( + override fun getStartLayerMargin( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, tickThickness: Float, maxLabelWidth: Float, ): Float { val tickSpace = if (shiftExtremeLines) tickThickness else tickThickness.half - return (tickSpace - horizontalDimensions.unscalableStartPadding).coerceAtLeast(0f) + return (tickSpace - layerDimensions.unscalableStartPadding).coerceAtLeast(0f) } - override fun getEndHorizontalAxisInset( + override fun getEndLayerMargin( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, tickThickness: Float, maxLabelWidth: Float, ): Float { val tickSpace = if (shiftExtremeLines) tickThickness else tickThickness.half - return (tickSpace - horizontalDimensions.unscalableEndPadding).coerceAtLeast(0f) + return (tickSpace - layerDimensions.unscalableEndPadding).coerceAtLeast(0f) } } @@ -167,7 +167,7 @@ internal class SegmentedHorizontalAxisItemPlacer(private val shiftExtremeLines: override fun getWidthMeasurementLabelValues( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, fullXRange: ClosedFloatingPointRange, ) = emptyList() @@ -192,16 +192,16 @@ internal class SegmentedHorizontalAxisItemPlacer(private val shiftExtremeLines: values } - override fun getStartHorizontalAxisInset( + override fun getStartLayerMargin( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, tickThickness: Float, maxLabelWidth: Float, ) = if (shiftExtremeLines) tickThickness else tickThickness.half - override fun getEndHorizontalAxisInset( + override fun getEndLayerMargin( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, tickThickness: Float, maxLabelWidth: Float, ) = if (shiftExtremeLines) tickThickness else tickThickness.half diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/VerticalAxis.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/VerticalAxis.kt index ac0b2c09a..cd9c23f99 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/VerticalAxis.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/axis/VerticalAxis.kt @@ -19,19 +19,17 @@ package com.patrykandpatrick.vico.core.cartesian.axis import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis.HorizontalLabelPosition.Inside import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis.HorizontalLabelPosition.Outside -import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis.VerticalLabelPosition.Center import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.cartesian.data.formatForAxis import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer -import com.patrykandpatrick.vico.core.common.HorizontalInsets -import com.patrykandpatrick.vico.core.common.HorizontalPosition -import com.patrykandpatrick.vico.core.common.Insets -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMargins +import com.patrykandpatrick.vico.core.cartesian.layer.HorizontalCartesianLayerMargins +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.ExtraStore @@ -61,7 +59,7 @@ protected constructor( label: TextComponent?, labelRotationDegrees: Float, public val horizontalLabelPosition: HorizontalLabelPosition, - public val verticalLabelPosition: VerticalLabelPosition, + public val verticalLabelPosition: Position.Vertical, valueFormatter: CartesianValueFormatter, tick: LineComponent?, tickLengthDp: Float, @@ -88,9 +86,13 @@ protected constructor( position == Axis.Position.Vertical.Start && horizontalLabelPosition == Outside || position == Axis.Position.Vertical.End && horizontalLabelPosition == Inside - protected val textHorizontalPosition: HorizontalPosition + protected val textHorizontalPosition: Position.Horizontal get() = - if (areLabelsOutsideAtStartOrInsideAtEnd) HorizontalPosition.Start else HorizontalPosition.End + if (areLabelsOutsideAtStartOrInsideAtEnd) { + Position.Horizontal.Start + } else { + Position.Horizontal.End + } protected var maxLabelWidth: Float? = null @@ -102,7 +104,7 @@ protected constructor( label: TextComponent?, labelRotationDegrees: Float, horizontalLabelPosition: HorizontalLabelPosition, - verticalLabelPosition: VerticalLabelPosition, + verticalLabelPosition: Position.Vertical, tick: LineComponent?, tickLengthDp: Float, guideline: LineComponent?, @@ -153,20 +155,20 @@ protected constructor( context = context, left = layerBounds.left, right = layerBounds.right, - centerY = centerY, + y = centerY, ) } val lineExtensionLength = if (itemPlacer.getShiftTopLines(this)) tickThickness else 0f line?.drawVertical( context = context, - top = bounds.top - lineExtensionLength, - bottom = bounds.bottom + lineExtensionLength, - centerX = + x = if (position.isLeft(this)) { bounds.right - lineThickness.half } else { bounds.left + lineThickness.half }, + top = bounds.top - lineExtensionLength, + bottom = bounds.bottom + lineExtensionLength, ) } } @@ -191,7 +193,7 @@ protected constructor( context = context, left = tickLeftX, right = tickRightX, - centerY = tickCenterY, + y = tickCenterY, ) label ?: return@forEach @@ -212,11 +214,11 @@ protected constructor( y = bounds.centerY(), horizontalPosition = if (position == Axis.Position.Vertical.Start) { - HorizontalPosition.End + Position.Horizontal.End } else { - HorizontalPosition.Start + Position.Horizontal.Start }, - verticalPosition = VerticalPosition.Center, + verticalPosition = Position.Vertical.Center, rotationDegrees = if (position == Axis.Position.Vertical.Start) { -TITLE_ABS_ROTATION_DEGREES @@ -229,9 +231,9 @@ protected constructor( } } - override fun updateHorizontalDimensions( + override fun updateLayerDimensions( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + layerDimensions: MutableCartesianLayerDimensions, ): Unit = Unit protected open fun drawLabel( @@ -262,7 +264,7 @@ protected constructor( x = labelX, y = tickCenterY, horizontalPosition = textHorizontalPosition, - verticalPosition = verticalLabelPosition.textPosition, + verticalPosition = verticalLabelPosition, rotationDegrees = labelRotationDegrees, maxWidth = (maxLabelWidth ?: (layerBounds.width().half - tickLength)).toInt(), ) @@ -281,38 +283,38 @@ protected constructor( } } - override fun updateHorizontalInsets( + override fun updateHorizontalLayerMargins( context: CartesianMeasuringContext, - freeHeight: Float, + horizontalLayerMargins: HorizontalCartesianLayerMargins, + layerHeight: Float, model: CartesianChartModel, - insets: HorizontalInsets, ) { - val width = getWidth(context, freeHeight) + val width = getWidth(context, layerHeight) when (position) { - Axis.Position.Vertical.Start -> insets.ensureValuesAtLeast(start = width) - Axis.Position.Vertical.End -> insets.ensureValuesAtLeast(end = width) + Axis.Position.Vertical.Start -> horizontalLayerMargins.ensureValuesAtLeast(start = width) + Axis.Position.Vertical.End -> horizontalLayerMargins.ensureValuesAtLeast(end = width) } } - override fun updateInsets( + override fun updateLayerMargins( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerMargins: CartesianLayerMargins, + layerDimensions: CartesianLayerDimensions, model: CartesianChartModel, - insets: Insets, ): Unit = with(context) { val maxLabelHeight = getMaxLabelHeight() val maxLineThickness = max(lineThickness, tickThickness) - insets.ensureValuesAtLeast( + layerMargins.ensureValuesAtLeast( top = - itemPlacer.getTopVerticalAxisInset( + itemPlacer.getTopLayerMargin( context, verticalLabelPosition, maxLabelHeight, maxLineThickness, ), bottom = - itemPlacer.getBottomVerticalAxisInset( + itemPlacer.getBottomLayerMargin( context, verticalLabelPosition, maxLabelHeight, @@ -342,11 +344,11 @@ protected constructor( Inside -> 0f } (labelSpace + titleComponentWidth + lineThickness).coerceIn( - minimumValue = size.minSizeDp.pixels, - maximumValue = size.maxSizeDp.pixels, + size.minDp.pixels, + size.maxDp.pixels, ) } - is Size.Exact -> size.sizeDp.pixels + is Size.Fixed -> size.valueDp.pixels is Size.Fraction -> canvasBounds.width() * size.fraction is Size.Text -> label @@ -399,7 +401,7 @@ protected constructor( label: TextComponent? = this.label, labelRotationDegrees: Float = this.labelRotationDegrees, horizontalLabelPosition: HorizontalLabelPosition = this.horizontalLabelPosition, - verticalLabelPosition: VerticalLabelPosition = this.verticalLabelPosition, + verticalLabelPosition: Position.Vertical = this.verticalLabelPosition, valueFormatter: CartesianValueFormatter = this.valueFormatter, tick: LineComponent? = this.tick, tickLengthDp: Float = this.tickLengthDp, @@ -449,19 +451,6 @@ protected constructor( Inside, } - /** - * Defines the vertical position of each of a horizontal axis’s labels relative to the label’s - * corresponding tick. - * - * @param textPosition the label position. - * @see VerticalPosition - */ - public enum class VerticalLabelPosition(public val textPosition: VerticalPosition) { - Center(VerticalPosition.Center), - Top(VerticalPosition.Top), - Bottom(VerticalPosition.Bottom), - } - /** Determines for what _y_ values a [VerticalAxis] displays labels, ticks, and guidelines. */ public interface ItemPlacer { /** @@ -510,18 +499,18 @@ protected constructor( position: Axis.Position.Vertical, ): List? = null - /** Returns the top inset required by the [VerticalAxis]. */ - public fun getTopVerticalAxisInset( + /** Returns the top [CartesianLayer]-area margin required by the [VerticalAxis]. */ + public fun getTopLayerMargin( context: CartesianMeasuringContext, - verticalLabelPosition: VerticalLabelPosition, + verticalLabelPosition: Position.Vertical, maxLabelHeight: Float, maxLineThickness: Float, ): Float - /** Returns the bottom inset required by the [VerticalAxis]. */ - public fun getBottomVerticalAxisInset( + /** Returns the bottom [CartesianLayer]-area margin required by the [VerticalAxis]. */ + public fun getBottomLayerMargin( context: CartesianMeasuringContext, - verticalLabelPosition: VerticalLabelPosition, + verticalLabelPosition: Position.Vertical, maxLabelHeight: Float, maxLineThickness: Float, ): Float @@ -566,7 +555,7 @@ protected constructor( label: TextComponent? = null, labelRotationDegrees: Float = 0f, horizontalLabelPosition: HorizontalLabelPosition = Outside, - verticalLabelPosition: VerticalLabelPosition = Center, + verticalLabelPosition: Position.Vertical = Position.Vertical.Center, valueFormatter: CartesianValueFormatter = CartesianValueFormatter.decimal(), tick: LineComponent? = null, tickLengthDp: Float = 0f, @@ -599,7 +588,7 @@ protected constructor( label: TextComponent? = null, labelRotationDegrees: Float = 0f, horizontalLabelPosition: HorizontalLabelPosition = Outside, - verticalLabelPosition: VerticalLabelPosition = Center, + verticalLabelPosition: Position.Vertical = Position.Vertical.Center, valueFormatter: CartesianValueFormatter = CartesianValueFormatter.decimal(), tick: LineComponent? = null, tickLengthDp: Float = 0f, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerDrawingModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerDrawingModel.kt index d982133f0..55ca43fa9 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerDrawingModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerDrawingModel.kt @@ -25,17 +25,17 @@ import com.patrykandpatrick.vico.core.common.orZero * Houses drawing information for a [CandlestickCartesianLayer]. [opacity] is the columns’ opacity. */ public class CandlestickCartesianLayerDrawingModel( - public val entries: Map, + public val entries: Map, public val opacity: Float = 1f, -) : CartesianLayerDrawingModel(listOf(entries)) { +) : CartesianLayerDrawingModel(listOf(entries)) { override fun transform( - drawingInfo: List>, - from: CartesianLayerDrawingModel?, + entries: List>, + from: CartesianLayerDrawingModel?, fraction: Float, - ): CartesianLayerDrawingModel { + ): CartesianLayerDrawingModel { val oldOpacity = (from as CandlestickCartesianLayerDrawingModel?)?.opacity.orZero return CandlestickCartesianLayerDrawingModel( - entries = drawingInfo.first(), + entries = entries.first(), opacity = oldOpacity.lerp(opacity, fraction), ) } @@ -58,19 +58,22 @@ public class CandlestickCartesianLayerDrawingModel( * @property bottomWickY the position of the bottom wick’s bottom edge. * @property topWickY the position of the top wick’s top edge. */ - public class CandleInfo( + public class Entry( public val bodyBottomY: Float, public val bodyTopY: Float, public val bottomWickY: Float, public val topWickY: Float, - ) : DrawingInfo { - override fun transform(from: DrawingInfo?, fraction: Float): DrawingInfo { - val old = from as? CandleInfo + ) : CartesianLayerDrawingModel.Entry { + override fun transform( + from: CartesianLayerDrawingModel.Entry?, + fraction: Float, + ): CartesianLayerDrawingModel.Entry { + val old = from as? Entry val oldBodyBottomY = old?.bodyBottomY.orZero val oldBodyTopY = old?.bodyTopY.orZero val oldBottomWickY = old?.bottomWickY.orZero val oldTopWickY = old?.topWickY.orZero - return CandleInfo( + return Entry( oldBodyBottomY.lerp(bodyBottomY, fraction), oldBodyTopY.lerp(bodyTopY, fraction), oldBottomWickY.lerp(bottomWickY, fraction), @@ -80,7 +83,7 @@ public class CandlestickCartesianLayerDrawingModel( override fun equals(other: Any?): Boolean = this === other || - other is CandleInfo && + other is Entry && bodyBottomY == other.bodyBottomY && bodyTopY == other.bodyTopY && bottomWickY == other.bottomWickY && diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerModel.kt index ebf7910ce..36f5f8001 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CandlestickCartesianLayerModel.kt @@ -96,6 +96,24 @@ public class CandlestickCartesianLayerModel : CartesianLayerModel { return result } + /** Represents an [Entry]’s absolute or relative price change. */ + public enum class Change { + Bullish, + Bearish, + Neutral; + + internal companion object { + private fun forPrices(old: Float, new: Float) = + when { + new > old -> Bullish + new < old -> Bearish + else -> Neutral + } + + fun forPrices(old: Number, new: Number) = forPrices(old.toFloat(), new.toFloat()) + } + } + /** * Houses a single candle’s data. * @@ -165,29 +183,6 @@ public class CandlestickCartesianLayerModel : CartesianLayerModel { result = 31 * result + relativeChange.hashCode() return result } - - /** Represents an [Entry]’s absolute or relative price change. */ - public enum class Change { - /** Represents a price increase. */ - Bullish, - - /** Represents a price decrease. */ - Bearish, - - /** Represents no change in price. */ - Neutral; - - internal companion object { - private fun forPrices(old: Float, new: Float) = - when { - new > old -> Bullish - new < old -> Bearish - else -> Neutral - } - - fun forPrices(old: Number, new: Number) = forPrices(old.toFloat(), new.toFloat()) - } - } } /** @@ -218,9 +213,8 @@ public class CandlestickCartesianLayerModel : CartesianLayerModel { closing = closingPrice, low = low.elementAt(index), high = high.elementAt(index), - absoluteChange = Entry.Change.forPrices(old = openingPrice, new = closingPrice), - relativeChange = - Entry.Change.forPrices(old = previousClosingPrice ?: 0, new = closingPrice), + absoluteChange = Change.forPrices(old = openingPrice, new = closingPrice), + relativeChange = Change.forPrices(old = previousClosingPrice ?: 0, new = closingPrice), ) ) previousClosingPrice = closingPrice diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModel.kt index 69ddb782b..d83deeb63 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModel.kt @@ -16,6 +16,7 @@ package com.patrykandpatrick.vico.core.cartesian.data +import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.common.data.CartesianLayerDrawingModel import com.patrykandpatrick.vico.core.common.data.ExtraStore @@ -83,8 +84,9 @@ public class CartesianChartModel { /** Creates an immutable copy of this [CartesianChartModel]. */ public fun toImmutable(): CartesianChartModel = this + /** @suppress */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public companion object { - /** An empty [CartesianChartModel]. */ public val Empty: CartesianChartModel = CartesianChartModel(models = emptyList(), id = 0, width = 0.0, extraStore = ExtraStore.Empty) } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModelProducer.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModelProducer.kt index 976039724..a4993778b 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModelProducer.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartModelProducer.kt @@ -23,9 +23,9 @@ import com.patrykandpatrick.vico.core.common.data.ExtraStore import com.patrykandpatrick.vico.core.common.data.MutableExtraStore import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -33,61 +33,61 @@ import kotlinx.coroutines.withContext /** Creates [CartesianChartModel]s and handles difference animations. */ public class CartesianChartModelProducer { - private var partials = emptyList() - private var extraStore = MutableExtraStore() + private var lastPartials = emptyList() + private var lastTransactionExtraStore = MutableExtraStore() private var cachedModel: CartesianChartModel? = null - private val updateMutex = Mutex() - private val registrationMutex = Mutex() + private var cachedModelPartialHashCode: Int? = null + private val mutex = Mutex() private val updateReceivers = ConcurrentHashMap() private suspend fun update( partials: List, - extraStore: MutableExtraStore, + transactionExtraStore: MutableExtraStore, ) { withContext(Dispatchers.Default) { - updateMutex.lock() - registrationMutex.lock() - val immutablePartials = partials.toList() - if ( - immutablePartials == this@CartesianChartModelProducer.partials && - extraStore == this@CartesianChartModelProducer.extraStore - ) { - updateMutex.unlock() - registrationMutex.unlock() - return@withContext - } - this@CartesianChartModelProducer.partials = partials.toList() - this@CartesianChartModelProducer.extraStore = extraStore - cachedModel = null - coroutineScope { - updateReceivers.values.forEach { launch { it.handleUpdate() } } - registrationMutex.unlock() + mutex.withLock { + val immutablePartials = partials.toList() + if ( + immutablePartials == this@CartesianChartModelProducer.lastPartials && + transactionExtraStore == this@CartesianChartModelProducer.lastTransactionExtraStore + ) { + return@withContext + } + updateReceivers.values + .map { launch { it.handleUpdate(immutablePartials, transactionExtraStore) } } + .joinAll() + lastPartials = immutablePartials + lastTransactionExtraStore = transactionExtraStore } - updateMutex.unlock() } } - private fun getModel(extraStore: ExtraStore) = - if (partials.isEmpty()) { - null - } else { - val mergedExtraStore = this.extraStore + extraStore - cachedModel?.copy(mergedExtraStore) - ?: CartesianChartModel(partials.map { it.complete(mergedExtraStore) }, mergedExtraStore) - .also { cachedModel = it } + private fun getModel(partials: List, extraStore: ExtraStore) = + when { + partials.isEmpty() -> { + cachedModel = null + cachedModelPartialHashCode = null + null + } + partials.hashCode() == cachedModelPartialHashCode -> cachedModel?.copy(extraStore) + else -> + CartesianChartModel(partials.map { it.complete(extraStore) }, extraStore).also { model -> + cachedModel = model + cachedModelPartialHashCode = partials.hashCode() + } } private suspend fun transformModel( key: Any, fraction: Float, model: CartesianChartModel?, + transactionExtraStore: ExtraStore, ranges: CartesianChartRanges, ) { with(updateReceivers[key] ?: return) { withContext(Dispatchers.Default) { - transform(extraStore, fraction) - val transformedModel = - model?.copy(this@CartesianChartModelProducer.extraStore + extraStore.copy()) + transform(hostExtraStore, fraction) + val transformedModel = model?.copy(transactionExtraStore + hostExtraStore.copy()) currentCoroutineContext().ensureActive() onModelCreated(transformedModel, ranges) } @@ -118,9 +118,9 @@ public class CartesianChartModelProducer { transform, updateRanges, ) - registrationMutex.withLock { + mutex.withLock { updateReceivers[key] = receiver - receiver.handleUpdate() + receiver.handleUpdate(lastPartials, lastTransactionExtraStore) } } } @@ -135,8 +135,6 @@ public class CartesianChartModelProducer { updateReceivers.remove(key) } - public fun createTransaction(): Transaction = Transaction() - /** Creates a [Transaction], runs [block], and calls [Transaction.commit]. */ public suspend fun runTransaction(block: Transaction.() -> Unit) { Transaction().also(block).commit() @@ -179,18 +177,23 @@ public class CartesianChartModelProducer { val cancelAnimation: suspend () -> Unit, val startAnimation: (transformModel: suspend (key: Any, fraction: Float) -> Unit) -> Unit, val onModelCreated: (CartesianChartModel?, CartesianChartRanges) -> Unit, - val extraStore: MutableExtraStore, + val hostExtraStore: MutableExtraStore, val prepareForTransformation: (CartesianChartModel?, MutableExtraStore, CartesianChartRanges) -> Unit, val transform: suspend (MutableExtraStore, Float) -> Unit, val updateRanges: (CartesianChartModel?) -> CartesianChartRanges, ) { - suspend fun handleUpdate() { + suspend fun handleUpdate( + partials: List, + transactionExtraStore: ExtraStore, + ) { cancelAnimation() - val model = getModel(extraStore) + val model = getModel(partials, transactionExtraStore + hostExtraStore) val ranges = updateRanges(model) - prepareForTransformation(model, extraStore, ranges) - startAnimation { key, fraction -> transformModel(key, fraction, model, ranges) } + prepareForTransformation(model, hostExtraStore, ranges) + startAnimation { key, fraction -> + transformModel(key, fraction, model, transactionExtraStore, ranges) + } } } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartRanges.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartRanges.kt index 72a333e5c..6e2cec06a 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartRanges.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/CartesianChartRanges.kt @@ -16,6 +16,7 @@ package com.patrykandpatrick.vico.core.cartesian.data +import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.axis.Axis @@ -52,7 +53,8 @@ public interface CartesianChartRanges { public val length: Double } - /** An empty [CartesianChartRanges] implementation. */ + /** @suppress */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public object Empty : CartesianChartRanges { private const val ERROR_MESSAGE = "`CartesianRanges.Empty` shouldn’t be used." diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerDrawingModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerDrawingModel.kt index 2147dd026..6e5ec3a01 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerDrawingModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerDrawingModel.kt @@ -23,16 +23,16 @@ import com.patrykandpatrick.vico.core.common.orZero /** Houses drawing information for a [ColumnCartesianLayer]. [opacity] is the columns’ opacity. */ public class ColumnCartesianLayerDrawingModel( - private val entries: List>, + private val entries: List>, public val opacity: Float = 1f, -) : CartesianLayerDrawingModel(entries) { +) : CartesianLayerDrawingModel(entries) { override fun transform( - drawingInfo: List>, - from: CartesianLayerDrawingModel?, + entries: List>, + from: CartesianLayerDrawingModel?, fraction: Float, - ): CartesianLayerDrawingModel { + ): CartesianLayerDrawingModel { val oldOpacity = (from as ColumnCartesianLayerDrawingModel?)?.opacity.orZero - return ColumnCartesianLayerDrawingModel(drawingInfo, oldOpacity.lerp(opacity, fraction)) + return ColumnCartesianLayerDrawingModel(entries, oldOpacity.lerp(opacity, fraction)) } override fun equals(other: Any?): Boolean = @@ -47,14 +47,17 @@ public class ColumnCartesianLayerDrawingModel( * Houses positional information for a [ColumnCartesianLayer]’s column. [height] expresses the * column’s height as a fraction of the [ColumnCartesianLayer]’s height. */ - public class ColumnInfo(public val height: Float) : DrawingInfo { - override fun transform(from: DrawingInfo?, fraction: Float): DrawingInfo { - val oldHeight = (from as? ColumnInfo)?.height.orZero - return ColumnInfo(oldHeight.lerp(height, fraction)) + public class Entry(public val height: Float) : CartesianLayerDrawingModel.Entry { + override fun transform( + from: CartesianLayerDrawingModel.Entry?, + fraction: Float, + ): CartesianLayerDrawingModel.Entry { + val oldHeight = (from as? Entry)?.height.orZero + return Entry(oldHeight.lerp(height, fraction)) } override fun equals(other: Any?): Boolean = - this === other || other is ColumnInfo && height == other.height + this === other || other is Entry && height == other.height override fun hashCode(): Int = height.hashCode() } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerModel.kt index 764aacca0..e929000a7 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/ColumnCartesianLayerModel.kt @@ -161,7 +161,7 @@ public class ColumnCartesianLayerModel : CartesianLayerModel { } /** Facilitates the creation of [ColumnCartesianLayerModel]s and [Partial]s. */ - public class BuilderScope { + public class BuilderScope internal constructor() { internal val series = mutableListOf>() /** diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerDrawingModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerDrawingModel.kt index 29bb30ecd..2db7184e0 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerDrawingModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerDrawingModel.kt @@ -23,39 +23,42 @@ import com.patrykandpatrick.vico.core.common.orZero /** Houses [LineCartesianLayer] drawing information. [opacity] is the lines’ opacity. */ public class LineCartesianLayerDrawingModel( - private val pointInfo: List>, + private val entries: List>, public val opacity: Float = 1f, -) : CartesianLayerDrawingModel(pointInfo) { +) : CartesianLayerDrawingModel(entries) { override fun transform( - drawingInfo: List>, - from: CartesianLayerDrawingModel?, + entries: List>, + from: CartesianLayerDrawingModel?, fraction: Float, - ): CartesianLayerDrawingModel = + ): CartesianLayerDrawingModel = LineCartesianLayerDrawingModel( - drawingInfo, + entries, (from as LineCartesianLayerDrawingModel?)?.opacity.orZero.lerp(opacity, fraction), ) override fun equals(other: Any?): Boolean = this === other || other is LineCartesianLayerDrawingModel && - pointInfo == other.pointInfo && + entries == other.entries && opacity == other.opacity - override fun hashCode(): Int = 31 * pointInfo.hashCode() + opacity.hashCode() + override fun hashCode(): Int = 31 * entries.hashCode() + opacity.hashCode() /** * Houses positional information for a [LineCartesianLayer]’s point. [y] expresses the distance of * the point from the bottom of the [LineCartesianLayer] as a fraction of the * [LineCartesianLayer]’s height. */ - public class PointInfo(public val y: Float) : DrawingInfo { - override fun transform(from: DrawingInfo?, fraction: Float): DrawingInfo { - val oldY = (from as? PointInfo)?.y.orZero - return PointInfo(oldY.lerp(y, fraction)) + public class Entry(public val y: Float) : CartesianLayerDrawingModel.Entry { + override fun transform( + from: CartesianLayerDrawingModel.Entry?, + fraction: Float, + ): CartesianLayerDrawingModel.Entry { + val oldY = (from as? Entry)?.y.orZero + return Entry(oldY.lerp(y, fraction)) } - override fun equals(other: Any?): Boolean = this === other || other is PointInfo && y == other.y + override fun equals(other: Any?): Boolean = this === other || other is Entry && y == other.y override fun hashCode(): Int = y.hashCode() } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerModel.kt index 8b15fef17..d55937b55 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/data/LineCartesianLayerModel.kt @@ -133,7 +133,7 @@ public class LineCartesianLayerModel : CartesianLayerModel { } /** Facilitates the creation of [LineCartesianLayerModel]s and [Partial]s. */ - public class BuilderScope { + public class BuilderScope internal constructor() { internal val series = mutableListOf>() /** diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalBox.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalBox.kt index aaef0581b..45a1c3815 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalBox.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalBox.kt @@ -20,8 +20,7 @@ import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis -import com.patrykandpatrick.vico.core.common.HorizontalPosition -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.ShapeComponent import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.ExtraStore @@ -50,8 +49,8 @@ public class HorizontalBox( private val box: ShapeComponent, private val labelComponent: TextComponent? = null, private val label: (ExtraStore) -> CharSequence = { getLabel(y(it)) }, - private val horizontalLabelPosition: HorizontalPosition = HorizontalPosition.Start, - private val verticalLabelPosition: VerticalPosition = VerticalPosition.Top, + private val horizontalLabelPosition: Position.Horizontal = Position.Horizontal.Start, + private val verticalLabelPosition: Position.Vertical = Position.Vertical.Top, private val labelRotationDegrees: Float = 0f, private val verticalAxisPosition: Axis.Position.Vertical? = null, ) : Decoration { @@ -68,9 +67,9 @@ public class HorizontalBox( .toFloat() val labelY = when (verticalLabelPosition) { - VerticalPosition.Top -> topY - VerticalPosition.Center -> (topY + bottomY).half - VerticalPosition.Bottom -> bottomY + Position.Vertical.Top -> topY + Position.Vertical.Center -> (topY + bottomY).half + Position.Vertical.Bottom -> bottomY } box.draw(context, layerBounds.left, topY, layerBounds.right, bottomY) labelComponent?.draw( @@ -78,9 +77,9 @@ public class HorizontalBox( text = label, x = when (horizontalLabelPosition) { - HorizontalPosition.Start -> layerBounds.getStart(isLtr) - HorizontalPosition.Center -> layerBounds.centerX() - HorizontalPosition.End -> layerBounds.getEnd(isLtr) + Position.Horizontal.Start -> layerBounds.getStart(isLtr) + Position.Horizontal.Center -> layerBounds.centerX() + Position.Horizontal.End -> layerBounds.getEnd(isLtr) }, y = labelY, horizontalPosition = -horizontalLabelPosition, @@ -93,7 +92,7 @@ public class HorizontalBox( text = label, rotationDegrees = labelRotationDegrees, ), - y = labelY, + referenceY = labelY, ), maxWidth = layerBounds.width().toInt(), rotationDegrees = labelRotationDegrees, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalLine.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalLine.kt index 3019a75fa..e76b59143 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalLine.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/decoration/HorizontalLine.kt @@ -20,8 +20,7 @@ import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis -import com.patrykandpatrick.vico.core.common.HorizontalPosition -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.ExtraStore @@ -50,8 +49,8 @@ public class HorizontalLine( private val line: LineComponent, private val labelComponent: TextComponent? = null, private val label: (ExtraStore) -> CharSequence = { getLabel(y(it)) }, - private val horizontalLabelPosition: HorizontalPosition = HorizontalPosition.Start, - private val verticalLabelPosition: VerticalPosition = VerticalPosition.Top, + private val horizontalLabelPosition: Position.Horizontal = Position.Horizontal.Start, + private val verticalLabelPosition: Position.Vertical = Position.Vertical.Top, private val labelRotationDegrees: Float = 0f, private val verticalAxisPosition: Axis.Position.Vertical? = null, ) : Decoration { @@ -67,29 +66,29 @@ public class HorizontalLine( val clippingFreeVerticalLabelPosition = verticalLabelPosition.inBounds( bounds = layerBounds, - distanceFromPoint = line.thicknessDp.half.pixels, componentHeight = labelComponent.getHeight( context = context, text = label, rotationDegrees = labelRotationDegrees, ), - y = canvasY, + referenceY = canvasY, + referenceDistance = line.thicknessDp.half.pixels, ) labelComponent.draw( context = context, text = label, x = when (horizontalLabelPosition) { - HorizontalPosition.Start -> layerBounds.getStart(isLtr) - HorizontalPosition.Center -> layerBounds.centerX() - HorizontalPosition.End -> layerBounds.getEnd(isLtr) + Position.Horizontal.Start -> layerBounds.getStart(isLtr) + Position.Horizontal.Center -> layerBounds.centerX() + Position.Horizontal.End -> layerBounds.getEnd(isLtr) }, y = when (clippingFreeVerticalLabelPosition) { - VerticalPosition.Top -> canvasY - line.thicknessDp.half.pixels - VerticalPosition.Center -> canvasY - VerticalPosition.Bottom -> canvasY + line.thicknessDp.half.pixels + Position.Vertical.Top -> canvasY - line.thicknessDp.half.pixels + Position.Vertical.Center -> canvasY + Position.Vertical.Bottom -> canvasY + line.thicknessDp.half.pixels }, horizontalPosition = -horizontalLabelPosition, verticalPosition = clippingFreeVerticalLabelPosition, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/AreaFills.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/AreaFills.kt index 0f8181956..e86664a6a 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/AreaFills.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/AreaFills.kt @@ -28,7 +28,8 @@ import com.patrykandpatrick.vico.core.common.copyColor import com.patrykandpatrick.vico.core.common.data.ExtraStore import com.patrykandpatrick.vico.core.common.getEnd import com.patrykandpatrick.vico.core.common.getStart -import com.patrykandpatrick.vico.core.common.shader.DynamicShader +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider +import com.patrykandpatrick.vico.core.common.shader.getShader internal abstract class BaseAreaFill(open val splitY: (ExtraStore) -> Number) : LineCartesianLayer.AreaFill { @@ -113,7 +114,7 @@ internal data class SingleAreaFill( override fun onAreasCreated(context: CartesianDrawingContext, fillBounds: RectF) { with(context) { paint.color = fill.color - paint.shader = fill.shader?.provideShader(this, fillBounds) + paint.shader = fill.shaderProvider?.getShader(this, fillBounds) canvas.drawPath(areaPath, paint) } } @@ -129,7 +130,7 @@ internal data class DoubleAreaFill( override fun onTopAreasCreated(context: CartesianDrawingContext, path: Path, fillBounds: RectF) { with(context) { paint.color = topFill.color - paint.shader = topFill.shader?.provideShader(this, fillBounds) + paint.shader = topFill.shaderProvider?.getShader(this, fillBounds) canvas.drawPath(path, paint) } } @@ -141,7 +142,7 @@ internal data class DoubleAreaFill( ) { with(context) { paint.color = bottomFill.color - paint.shader = bottomFill.shader?.provideShader(this, fillBounds) + paint.shader = bottomFill.shaderProvider?.getShader(this, fillBounds) canvas.drawPath(path, paint) } } @@ -155,14 +156,14 @@ private fun LineCartesianLayer.AreaFill.Companion.default( double( topFill = Fill( - DynamicShader.verticalGradient( + ShaderProvider.verticalGradient( topColor.copyColor(DefaultAlpha.LINE_BACKGROUND_SHADER_START), topColor.copyColor(DefaultAlpha.LINE_BACKGROUND_SHADER_END), ) ), bottomFill = Fill( - DynamicShader.verticalGradient( + ShaderProvider.verticalGradient( bottomColor.copyColor(DefaultAlpha.LINE_BACKGROUND_SHADER_END), bottomColor.copyColor(DefaultAlpha.LINE_BACKGROUND_SHADER_START), ) @@ -174,9 +175,9 @@ private fun LineCartesianLayer.AreaFill.Companion.default( @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun LineCartesianLayer.LineFill.getDefaultAreaFill(): LineCartesianLayer.AreaFill? = when { - this is SingleLineFill && fill.shader == null -> + this is SingleLineFill && fill.shaderProvider == null -> LineCartesianLayer.AreaFill.default(topColor = fill.color, bottomColor = fill.color) - this is DoubleLineFill && topFill.shader == null && bottomFill.shader == null -> + this is DoubleLineFill && topFill.shaderProvider == null && bottomFill.shaderProvider == null -> LineCartesianLayer.AreaFill.default(topFill.color, bottomFill.color, splitY) else -> null } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/BaseCartesianLayer.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/BaseCartesianLayer.kt index d589ac7ad..92f74f51d 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/BaseCartesianLayer.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/BaseCartesianLayer.kt @@ -18,24 +18,23 @@ package com.patrykandpatrick.vico.core.cartesian.layer import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.data.CartesianLayerModel -import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.inClip /** A base [CartesianLayer] implementation. */ public abstract class BaseCartesianLayer : CartesianLayer { - private val insets: Insets = Insets() + private val margins: CartesianLayerMargins = CartesianLayerMargins() protected abstract fun drawInternal(context: CartesianDrawingContext, model: T) override fun draw(context: CartesianDrawingContext, model: T) { with(context) { - insets.clear() - updateInsets(this, horizontalDimensions, model, insets) + margins.clear() + updateLayerMargins(this, margins, layerDimensions, model) canvas.inClip( - left = layerBounds.left - insets.getLeft(isLtr), - top = layerBounds.top - insets.top, - right = layerBounds.right + insets.getRight(isLtr), - bottom = layerBounds.bottom + insets.bottom, + left = layerBounds.left - margins.getLeft(isLtr), + top = layerBounds.top - margins.top, + right = layerBounds.right + margins.getRight(isLtr), + bottom = layerBounds.bottom + margins.bottom, ) { drawInternal(context, model) } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CandlestickCartesianLayer.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CandlestickCartesianLayer.kt index 75a5d832a..e87a74c0f 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CandlestickCartesianLayer.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CandlestickCartesianLayer.kt @@ -20,12 +20,12 @@ import androidx.annotation.RestrictTo import androidx.compose.runtime.Stable import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CandlestickCartesianLayerDrawingModel import com.patrykandpatrick.vico.core.cartesian.data.CandlestickCartesianLayerModel -import com.patrykandpatrick.vico.core.cartesian.data.CandlestickCartesianLayerModel.Entry.Change +import com.patrykandpatrick.vico.core.cartesian.data.CandlestickCartesianLayerModel.Change import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges import com.patrykandpatrick.vico.core.cartesian.data.CartesianLayerRangeProvider import com.patrykandpatrick.vico.core.cartesian.data.MutableCartesianChartRanges @@ -48,7 +48,7 @@ import kotlin.math.max /** * Draws the content of candlestick charts. * - * @property candles provides the [Candle]s. + * @property candleProvider provides the [Candle]s. * @property minCandleBodyHeightDp the minimum height of the candle bodies (in dp). * @property candleSpacingDp the spacing between neighboring candles. * @property scaleCandleWicks whether the candle wicks should be scaled based on the zoom factor. @@ -60,7 +60,7 @@ import kotlin.math.max @Stable public open class CandlestickCartesianLayer protected constructor( - protected val candles: CandleProvider, + protected val candleProvider: CandleProvider, protected val minCandleBodyHeightDp: Float, protected val candleSpacingDp: Float, protected val scaleCandleWicks: Boolean, @@ -68,7 +68,7 @@ protected constructor( protected val verticalAxisPosition: Axis.Position.Vertical?, protected val drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - CandlestickCartesianLayerDrawingModel.CandleInfo, + CandlestickCartesianLayerDrawingModel.Entry, CandlestickCartesianLayerDrawingModel, >, protected val drawingModelKey: ExtraStore.Key, @@ -96,14 +96,11 @@ protected constructor( private val _markerTargets = mutableMapOf>() - /** Holds information on the [CandlestickCartesianLayer]’s horizontal dimensions. */ - protected val horizontalDimensions: MutableHorizontalDimensions = MutableHorizontalDimensions() - override val markerTargets: Map> = _markerTargets /** Creates a [CandlestickCartesianLayer]. */ public constructor( - candles: CandleProvider, + candleProvider: CandleProvider, minCandleBodyHeightDp: Float = Defaults.MIN_CANDLE_BODY_HEIGHT_DP, candleSpacingDp: Float = Defaults.CANDLE_SPACING_DP, scaleCandleWicks: Boolean = false, @@ -111,12 +108,12 @@ protected constructor( verticalAxisPosition: Axis.Position.Vertical? = null, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - CandlestickCartesianLayerDrawingModel.CandleInfo, + CandlestickCartesianLayerDrawingModel.Entry, CandlestickCartesianLayerDrawingModel, > = CartesianLayerDrawingModelInterpolator.default(), ) : this( - candles, + candleProvider, minCandleBodyHeightDp, candleSpacingDp, scaleCandleWicks, @@ -141,24 +138,24 @@ protected constructor( drawingModel: CandlestickCartesianLayerDrawingModel?, ) { val yRange = ranges.getYRange(verticalAxisPosition) - val halfMaxCandleWidth = candles.getWidestCandle(model.extraStore).widthDp.half.pixels + val halfMaxCandleWidth = candleProvider.getWidestCandle(model.extraStore).widthDp.half.pixels val drawingStart = layerBounds.getStart(isLtr) + - (horizontalDimensions.startPadding - halfMaxCandleWidth * zoom) * - layoutDirectionMultiplier - scroll + (layerDimensions.startPadding - halfMaxCandleWidth * zoom) * layoutDirectionMultiplier - + scroll var bodyCenterX: Float var candle: Candle val minBodyHeight = minCandleBodyHeightDp.pixels model.series.forEachIn(ranges.minX..ranges.maxX) { entry, _ -> - candle = candles.getCandle(entry, model.extraStore) + candle = candleProvider.getCandle(entry, model.extraStore) val candleInfo = drawingModel?.entries?.get(entry.x) ?: entry.toCandleInfo(yRange) val xSpacingMultiplier = ((entry.x - ranges.minX) / ranges.xStep).toFloat() bodyCenterX = drawingStart + - layoutDirectionMultiplier * horizontalDimensions.xSpacing * xSpacingMultiplier + + layoutDirectionMultiplier * layerDimensions.xSpacing * xSpacingMultiplier + halfMaxCandleWidth * zoom var bodyBottomY = layerBounds.bottom - candleInfo.bodyBottomY * layerBounds.height() @@ -174,11 +171,9 @@ protected constructor( if ( candle.body.intersectsVertical( context = this, - top = bodyTopY, - bottom = bodyBottomY, - centerX = bodyCenterX, - boundingBox = layerBounds, - thicknessScale = zoom, + x = bodyCenterX, + bounds = layerBounds, + thicknessFactor = zoom, ) ) { updateMarkerTargets( @@ -191,22 +186,22 @@ protected constructor( candle, ) - candle.body.drawVertical(this, bodyTopY, bodyBottomY, bodyCenterX, zoom) + candle.body.drawVertical(this, bodyCenterX, bodyTopY, bodyBottomY, zoom) candle.topWick.drawVertical( context = this, + x = bodyCenterX, top = topWickY, bottom = bodyTopY, - centerX = bodyCenterX, - thicknessScale = if (scaleCandleWicks) zoom else 1f, + thicknessFactor = if (scaleCandleWicks) zoom else 1f, ) candle.bottomWick.drawVertical( context = this, + x = bodyCenterX, top = bodyBottomY, bottom = bottomWickY, - centerX = bodyCenterX, - thicknessScale = if (scaleCandleWicks) zoom else 1f, + thicknessFactor = if (scaleCandleWicks) zoom else 1f, ) } } @@ -269,11 +264,11 @@ protected constructor( ) } - override fun updateRanges( - ranges: MutableCartesianChartRanges, + override fun updateChartRanges( + chartRanges: MutableCartesianChartRanges, model: CandlestickCartesianLayerModel, ) { - ranges.tryUpdate( + chartRanges.tryUpdate( rangeProvider.getMinX(model.minX, model.maxX, model.extraStore), rangeProvider.getMaxX(model.minX, model.maxX, model.extraStore), rangeProvider.getMinY(model.minY, model.maxY, model.extraStore), @@ -282,15 +277,15 @@ protected constructor( ) } - override fun updateHorizontalDimensions( + override fun updateDimensions( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + dimensions: MutableCartesianLayerDimensions, model: CandlestickCartesianLayerModel, ) { with(context) { - val candleWidth = candles.getWidestCandle(model.extraStore).widthDp.pixels + val candleWidth = candleProvider.getWidestCandle(model.extraStore).widthDp.pixels val xSpacing = candleWidth + candleSpacingDp.pixels - horizontalDimensions.ensureValuesAtLeast( + dimensions.ensureValuesAtLeast( xSpacing = xSpacing, scalableStartPadding = candleWidth.half + layerPadding.scalableStartDp.pixels, scalableEndPadding = candleWidth.half + layerPadding.scalableEndDp.pixels, @@ -319,7 +314,7 @@ protected constructor( private fun CandlestickCartesianLayerModel.Entry.toCandleInfo( yRange: CartesianChartRanges.YRange ) = - CandlestickCartesianLayerDrawingModel.CandleInfo( + CandlestickCartesianLayerDrawingModel.Entry( bodyBottomY = ((minOf(opening, closing) - yRange.minY) / yRange.length).toFloat(), bodyTopY = ((max(opening, closing) - yRange.minY) / yRange.length).toFloat(), bottomWickY = ((low - yRange.minY) / yRange.length).toFloat(), @@ -337,7 +332,7 @@ protected constructor( /** Creates a new [CandlestickCartesianLayer] based on this one. */ public fun copy( - candles: CandleProvider = this.candles, + candleProvider: CandleProvider = this.candleProvider, minCandleBodyHeightDp: Float = this.minCandleBodyHeightDp, candleSpacingDp: Float = this.candleSpacingDp, scaleCandleWicks: Boolean = this.scaleCandleWicks, @@ -345,13 +340,13 @@ protected constructor( verticalAxisPosition: Axis.Position.Vertical? = this.verticalAxisPosition, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - CandlestickCartesianLayerDrawingModel.CandleInfo, + CandlestickCartesianLayerDrawingModel.Entry, CandlestickCartesianLayerDrawingModel, > = this.drawingModelInterpolator, ): CandlestickCartesianLayer = CandlestickCartesianLayer( - candles, + candleProvider, minCandleBodyHeightDp, candleSpacingDp, scaleCandleWicks, @@ -363,7 +358,7 @@ protected constructor( override fun equals(other: Any?): Boolean = other is CandlestickCartesianLayer && - candles == other.candles && + candleProvider == other.candleProvider && minCandleBodyHeightDp == other.minCandleBodyHeightDp && candleSpacingDp == other.candleSpacingDp && scaleCandleWicks == other.scaleCandleWicks && @@ -373,7 +368,7 @@ protected constructor( override fun hashCode(): Int = Objects.hash( - candles, + candleProvider, minCandleBodyHeightDp, candleSpacingDp, scaleCandleWicks, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayer.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayer.kt index 2699fcd7b..ac883825e 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayer.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayer.kt @@ -18,9 +18,8 @@ package com.patrykandpatrick.vico.core.cartesian.layer import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerInsetter import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges import com.patrykandpatrick.vico.core.cartesian.data.CartesianLayerModel import com.patrykandpatrick.vico.core.cartesian.data.MutableCartesianChartRanges @@ -31,22 +30,22 @@ import com.patrykandpatrick.vico.core.common.data.MutableExtraStore * Visualizes data on a Cartesian plane. [CartesianLayer]s are combined and drawn by * [CartesianChart]s. */ -public interface CartesianLayer : CartesianLayerInsetter { +public interface CartesianLayer : CartesianLayerMarginUpdater { /** Links _x_ values to [CartesianMarker.Target]s. */ public val markerTargets: Map> /** Draws the [CartesianLayer]. */ public fun draw(context: CartesianDrawingContext, model: M) - /** Updates [horizontalDimensions] to match this [CartesianLayer]’s dimensions. */ - public fun updateHorizontalDimensions( + /** Updates [dimensions] to match this [CartesianLayer]’s dimensions. */ + public fun updateDimensions( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + dimensions: MutableCartesianLayerDimensions, model: M, ) - /** Updates [ranges] in accordance with [model]. */ - public fun updateRanges(ranges: MutableCartesianChartRanges, model: M) + /** Updates [chartRanges] in accordance with [model]. */ + public fun updateChartRanges(chartRanges: MutableCartesianChartRanges, model: M) /** Prepares the [CartesianLayer] for a difference animation. */ public fun prepareForTransformation( diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/HorizontalDimensions.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerDimensions.kt similarity index 92% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/HorizontalDimensions.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerDimensions.kt index fb5280217..287e72d48 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/HorizontalDimensions.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerDimensions.kt @@ -14,10 +14,12 @@ * limitations under the License. */ -package com.patrykandpatrick.vico.core.cartesian +package com.patrykandpatrick.vico.core.cartesian.layer -/** Holds information on a [CartesianChart]’s horizontal dimensions. */ -public interface HorizontalDimensions { +import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext + +/** Stores shared [CartesianLayer] dimensions. */ +public interface CartesianLayerDimensions { /** The distance between neighboring major _x_ values (in pixels). This can be scaled. */ public val xSpacing: Float diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianLayerInsetter.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerMarginUpdater.kt similarity index 56% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianLayerInsetter.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerMarginUpdater.kt index c0ed1093f..d08073032 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianLayerInsetter.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerMarginUpdater.kt @@ -14,32 +14,28 @@ * limitations under the License. */ -package com.patrykandpatrick.vico.core.cartesian +package com.patrykandpatrick.vico.core.cartesian.layer -import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer -import com.patrykandpatrick.vico.core.common.HorizontalInsets -import com.patrykandpatrick.vico.core.common.Insets +import com.patrykandpatrick.vico.core.cartesian.CartesianChart +import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext /** * Enables a [CartesianChart] component to make room for itself around the [CartesianLayer] area. */ -public interface CartesianLayerInsetter { - /** Ensures that there are sufficient insets. */ - public fun updateInsets( +public interface CartesianLayerMarginUpdater { + /** Ensures that there are sufficient [CartesianLayer]-area margins. */ + public fun updateLayerMargins( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerMargins: CartesianLayerMargins, + layerDimensions: CartesianLayerDimensions, model: M, - insets: Insets, ) {} - /** - * Ensures that there are sufficient horizontal insets. [freeHeight] is the height of the - * [CartesianLayer] area. - */ - public fun updateHorizontalInsets( + /** Ensures that there are sufficient horizontal [CartesianLayer]-area margins. */ + public fun updateHorizontalLayerMargins( context: CartesianMeasuringContext, - freeHeight: Float, + horizontalLayerMargins: HorizontalCartesianLayerMargins, + layerHeight: Float, model: M, - insets: HorizontalInsets, ) {} } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerMargins.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerMargins.kt new file mode 100644 index 000000000..3c759eeec --- /dev/null +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerMargins.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2024 by Patryk Goworowski and Patrick Michalik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.patrykandpatrick.vico.core.cartesian.layer + +/** + * Stores the sizes of [CartesianLayer]-area margins. + * + * @see CartesianLayerMarginUpdater + */ +public class CartesianLayerMargins : HorizontalCartesianLayerMargins { + /** The start margin’s size. */ + public override var start: Float = 0f + private set + + /** The top margin’s size. */ + public var top: Float = 0f + private set + + /** The end margin’s size. */ + public override var end: Float = 0f + private set + + /** The bottom margin’s size. */ + public var bottom: Float = 0f + private set + + /** The sum of [top] and [bottom]. */ + public val vertical: Float + get() = top + bottom + + /** The largest of [start], [top], [end], and [bottom]. */ + public val max: Float + get() = maxOf(start, top, end, bottom) + + override fun ensureValuesAtLeast(start: Float, end: Float) { + this.start = this.start.coerceAtLeast(start) + this.end = this.end.coerceAtLeast(end) + } + + /** Ensures that the stored values are no smaller than those provided. */ + public fun ensureValuesAtLeast( + start: Float = this.start, + top: Float = this.top, + end: Float = this.end, + bottom: Float = this.bottom, + ) { + this.start = this.start.coerceAtLeast(start) + this.top = this.top.coerceAtLeast(top) + this.end = this.end.coerceAtLeast(end) + this.bottom = this.bottom.coerceAtLeast(bottom) + } + + /** Clears the stored values. */ + public fun clear() { + start = 0f + top = 0f + end = 0f + bottom = 0f + } +} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianLayerPadding.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerPadding.kt similarity index 67% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianLayerPadding.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerPadding.kt index 49c832486..426ef448d 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/CartesianLayerPadding.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/CartesianLayerPadding.kt @@ -14,21 +14,24 @@ * limitations under the License. */ -package com.patrykandpatrick.vico.core.cartesian +package com.patrykandpatrick.vico.core.cartesian.layer import androidx.compose.runtime.Immutable -import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer /** - * Stores [CartesianLayer] padding values. [scalableStartDp] and [scalableEndDp] are multiplied by - * the zoom factor, unlike [unscalableStartDp] and [unscalableEndDp]. + * Stores [CartesianLayer] padding sizes. Scalable padding depends on the zoom factor. + * + * @property scalableStartDp the size of the scalable start padding (in dp). + * @property scalableEndDp the size of the scalable end padding (in dp). + * @property unscalableStartDp the size of the unscalable start padding (in dp). + * @property unscalableEndDp the size of the unscalable end padding (in dp). */ @Immutable public class CartesianLayerPadding( - internal val scalableStartDp: Float = 0f, - internal val scalableEndDp: Float = 0f, - internal val unscalableStartDp: Float = 0f, - internal val unscalableEndDp: Float = 0f, + public val scalableStartDp: Float = 0f, + public val scalableEndDp: Float = 0f, + public val unscalableStartDp: Float = 0f, + public val unscalableEndDp: Float = 0f, ) { override fun equals(other: Any?): Boolean = this === other || diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/ColumnCartesianLayer.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/ColumnCartesianLayer.kt index fb51938c6..2c6c7e320 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/ColumnCartesianLayer.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/ColumnCartesianLayer.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges @@ -34,7 +34,7 @@ import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker import com.patrykandpatrick.vico.core.cartesian.marker.ColumnCartesianLayerMarkerTarget import com.patrykandpatrick.vico.core.cartesian.marker.MutableColumnCartesianLayerMarkerTarget import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.CartesianLayerDrawingModelInterpolator @@ -59,8 +59,9 @@ import kotlin.math.min * @property columnCollectionSpacingDp the spacing between neighboring column collections (in dp). * @property mergeMode defines how columns should be drawn in column collections. * @property dataLabel the [TextComponent] for the data labels. Use `null` for no data labels. - * @property dataLabelVerticalPosition the vertical position of each data label relative to its - * column’s top edge. + * @property dataLabelPosition the vertical position of the data labels relative to the top edges of + * positive columns. For negative columns, this is inverted and interpreted relative to the bottom + * edges of the columns. * @property dataLabelValueFormatter the [CartesianValueFormatter] for the data labels. * @property dataLabelRotationDegrees the rotation of the data labels (in degrees). * @property rangeProvider defines the _x_ and _y_ ranges. @@ -76,7 +77,7 @@ protected constructor( protected val columnCollectionSpacingDp: Float = Defaults.COLUMN_COLLECTION_SPACING, protected val mergeMode: (ExtraStore) -> MergeMode = { MergeMode.Grouped() }, protected val dataLabel: TextComponent? = null, - protected val dataLabelVerticalPosition: VerticalPosition = VerticalPosition.Top, + protected val dataLabelPosition: Position.Vertical = Position.Vertical.Top, protected val dataLabelValueFormatter: CartesianValueFormatter = CartesianValueFormatter.decimal(), protected val dataLabelRotationDegrees: Float = 0f, @@ -84,7 +85,7 @@ protected constructor( protected val verticalAxisPosition: Axis.Position.Vertical? = null, protected val drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - ColumnCartesianLayerDrawingModel.ColumnInfo, + ColumnCartesianLayerDrawingModel.Entry, ColumnCartesianLayerDrawingModel, > = CartesianLayerDrawingModelInterpolator.default(), @@ -95,9 +96,6 @@ protected constructor( protected val stackInfo: MutableMap = mutableMapOf() - /** Holds information on the [ColumnCartesianLayer]’s horizontal dimensions. */ - protected val horizontalDimensions: MutableHorizontalDimensions = MutableHorizontalDimensions() - override val markerTargets: Map> = _markerTargets /** Creates a [ColumnCartesianLayer]. */ @@ -106,14 +104,14 @@ protected constructor( columnCollectionSpacingDp: Float = Defaults.COLUMN_COLLECTION_SPACING, mergeMode: (ExtraStore) -> MergeMode = { MergeMode.Grouped() }, dataLabel: TextComponent? = null, - dataLabelVerticalPosition: VerticalPosition = VerticalPosition.Top, + dataLabelPosition: Position.Vertical = Position.Vertical.Top, dataLabelValueFormatter: CartesianValueFormatter = CartesianValueFormatter.decimal(), dataLabelRotationDegrees: Float = 0f, rangeProvider: CartesianLayerRangeProvider = CartesianLayerRangeProvider.auto(), verticalAxisPosition: Axis.Position.Vertical? = null, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - ColumnCartesianLayerDrawingModel.ColumnInfo, + ColumnCartesianLayerDrawingModel.Entry, ColumnCartesianLayerDrawingModel, > = CartesianLayerDrawingModelInterpolator.default(), @@ -122,7 +120,7 @@ protected constructor( columnCollectionSpacingDp, mergeMode, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelValueFormatter, dataLabelRotationDegrees, rangeProvider, @@ -169,7 +167,7 @@ protected constructor( val column = columnProvider.getColumn(entry, index, model.extraStore) columnCenterX = drawingStart + - (horizontalDimensions.xSpacing * xSpacingMultiplier + + (layerDimensions.xSpacing * xSpacingMultiplier + columnProvider .getWidestSeriesColumn(index, model.extraStore) .thicknessDp @@ -199,11 +197,9 @@ protected constructor( if ( column.intersectsVertical( context = this, - top = columnTop, - bottom = columnBottom, - centerX = columnCenterX, - boundingBox = layerBounds, - thicknessScale = zoom, + x = columnCenterX, + bounds = layerBounds, + thicknessFactor = zoom, ) ) { updateMarkerTargets( @@ -214,7 +210,7 @@ protected constructor( column = column, mergeMode = mergeMode, ) - column.drawVertical(this, columnTop, columnBottom, columnCenterX, zoom) + column.drawVertical(this, columnCenterX, columnTop, columnBottom, zoom) } if (mergeMode is MergeMode.Grouped) { @@ -299,14 +295,14 @@ protected constructor( mergeMode == MergeMode.Stacked || mergeMode is MergeMode.Grouped && modelEntriesSize == 1 var maxWidth = when { - canUseXSpacing -> horizontalDimensions.xSpacing + canUseXSpacing -> layerDimensions.xSpacing mergeMode is MergeMode.Grouped -> (columnThicknessDp + min(columnCollectionSpacingDp, mergeMode.columnSpacingDp).half) .pixels * zoom else -> error(message = "Encountered an unexpected `MergeMode`.") } - if (isFirst) maxWidth = maxWidth.coerceAtMost(horizontalDimensions.startPadding.doubled) - if (isLast) maxWidth = maxWidth.coerceAtMost(horizontalDimensions.endPadding.doubled) + if (isFirst) maxWidth = maxWidth.coerceAtMost(layerDimensions.startPadding.doubled) + if (isLast) maxWidth = maxWidth.coerceAtMost(layerDimensions.endPadding.doubled) val text = dataLabelValueFormatter.format(this, dataLabelValue, verticalAxisPosition) val dataLabelWidth = textComponent @@ -319,12 +315,10 @@ protected constructor( return } - val labelVerticalPosition = - if (dataLabelValue < 0f) -dataLabelVerticalPosition else dataLabelVerticalPosition + val labelVerticalPosition = if (dataLabelValue < 0f) -dataLabelPosition else dataLabelPosition val verticalPosition = labelVerticalPosition.inBounds( - y = y, bounds = layerBounds, componentHeight = textComponent.getHeight( @@ -333,6 +327,7 @@ protected constructor( maxWidth = maxWidth.toInt(), rotationDegrees = dataLabelRotationDegrees, ), + referenceY = y, ) textComponent.draw( context = this, @@ -378,11 +373,14 @@ protected constructor( } } - override fun updateRanges(ranges: MutableCartesianChartRanges, model: ColumnCartesianLayerModel) { + override fun updateChartRanges( + chartRanges: MutableCartesianChartRanges, + model: ColumnCartesianLayerModel, + ) { val mergeMode = mergeMode(model.extraStore) val minY = mergeMode.getMinY(model) val maxY = mergeMode.getMaxY(model) - ranges.tryUpdate( + chartRanges.tryUpdate( rangeProvider.getMinX(model.minX, model.maxX, model.extraStore), rangeProvider.getMaxX(model.minX, model.maxX, model.extraStore), rangeProvider.getMinY(minY, maxY, model.extraStore), @@ -391,9 +389,9 @@ protected constructor( ) } - override fun updateHorizontalDimensions( + override fun updateDimensions( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + dimensions: MutableCartesianLayerDimensions, model: ColumnCartesianLayerModel, ) { with(context) { @@ -403,7 +401,7 @@ protected constructor( mergeMode(model.extraStore), ) val xSpacing = columnCollectionWidth + columnCollectionSpacingDp.pixels - horizontalDimensions.ensureValuesAtLeast( + dimensions.ensureValuesAtLeast( xSpacing = xSpacing, scalableStartPadding = columnCollectionWidth.half + layerPadding.scalableStartDp.pixels, scalableEndPadding = columnCollectionWidth.half + layerPadding.scalableEndDp.pixels, @@ -442,7 +440,7 @@ protected constructor( MergeMode.Stacked -> 0f } return layerBounds.getStart(isLtr) + - (horizontalDimensions.startPadding + + (layerDimensions.startPadding + (mergeModeComponent - getColumnCollectionWidth(entryCollectionCount, mergeMode).half) * zoom) * layoutDirectionMultiplier } @@ -512,7 +510,7 @@ protected constructor( .map { series -> series.associate { entry -> entry.x to - ColumnCartesianLayerDrawingModel.ColumnInfo( + ColumnCartesianLayerDrawingModel.Entry( height = (abs(entry.y) / ranges.getYRange(verticalAxisPosition).length).toFloat() ) } @@ -525,14 +523,14 @@ protected constructor( columnCollectionSpacingDp: Float = this.columnCollectionSpacingDp, mergeMode: (ExtraStore) -> MergeMode = this.mergeMode, dataLabel: TextComponent? = this.dataLabel, - dataLabelVerticalPosition: VerticalPosition = this.dataLabelVerticalPosition, + dataLabelPosition: Position.Vertical = this.dataLabelPosition, dataLabelValueFormatter: CartesianValueFormatter = this.dataLabelValueFormatter, dataLabelRotationDegrees: Float = this.dataLabelRotationDegrees, rangeProvider: CartesianLayerRangeProvider = this.rangeProvider, verticalAxisPosition: Axis.Position.Vertical? = this.verticalAxisPosition, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - ColumnCartesianLayerDrawingModel.ColumnInfo, + ColumnCartesianLayerDrawingModel.Entry, ColumnCartesianLayerDrawingModel, > = this.drawingModelInterpolator, @@ -542,7 +540,7 @@ protected constructor( columnCollectionSpacingDp, mergeMode, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelValueFormatter, dataLabelRotationDegrees, rangeProvider, @@ -558,7 +556,7 @@ protected constructor( columnCollectionSpacingDp == other.columnCollectionSpacingDp && mergeMode == other.mergeMode && dataLabel == other.dataLabel && - dataLabelVerticalPosition == other.dataLabelVerticalPosition && + dataLabelPosition == other.dataLabelPosition && dataLabelValueFormatter == other.dataLabelValueFormatter && dataLabelRotationDegrees == other.dataLabelRotationDegrees && rangeProvider == other.rangeProvider && @@ -571,7 +569,7 @@ protected constructor( columnCollectionSpacingDp, mergeMode, dataLabel, - dataLabelVerticalPosition, + dataLabelPosition, dataLabelValueFormatter, dataLabelRotationDegrees, rangeProvider, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalInsets.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/HorizontalCartesianLayerMargins.kt similarity index 71% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalInsets.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/HorizontalCartesianLayerMargins.kt index 8734f0e54..48b5412d5 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalInsets.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/HorizontalCartesianLayerMargins.kt @@ -14,31 +14,29 @@ * limitations under the License. */ -package com.patrykandpatrick.vico.core.common - -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerInsetter +package com.patrykandpatrick.vico.core.cartesian.layer /** - * Used to apply horizontal insets. + * Stores the sizes of horizontal [CartesianLayer]-area margins. * - * @see CartesianLayerInsetter - * @see Insets + * @see CartesianLayerMargins + * @see CartesianLayerMarginUpdater */ -public interface HorizontalInsets { - /** The start inset’s size (in pixels). */ +public interface HorizontalCartesianLayerMargins { + /** The start margin’s size. */ public val start: Float - /** The end inset’s size (in pixels). */ + /** The end margin’s size. */ public val end: Float /** The sum of [start] and [end]. */ public val horizontal: Float get() = start + end - /** Returns the left inset’s size (in pixels). */ + /** Returns the left margin’s size. */ public fun getLeft(isLtr: Boolean): Float = if (isLtr) start else end - /** Returns the right inset’s size (in pixels). */ + /** Returns the right margin’s size. */ public fun getRight(isLtr: Boolean): Float = if (isLtr) end else start /** Ensures that the stored values are no smaller than the provided ones. */ diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineCartesianLayer.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineCartesianLayer.kt index cafd74340..20210bb35 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineCartesianLayer.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineCartesianLayer.kt @@ -27,8 +27,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges @@ -44,8 +43,7 @@ import com.patrykandpatrick.vico.core.cartesian.marker.LineCartesianLayerMarkerT import com.patrykandpatrick.vico.core.cartesian.marker.MutableLineCartesianLayerMarkerTarget import com.patrykandpatrick.vico.core.common.Defaults import com.patrykandpatrick.vico.core.common.Fill -import com.patrykandpatrick.vico.core.common.Insets -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.data.CacheStore @@ -85,7 +83,7 @@ protected constructor( protected val verticalAxisPosition: Axis.Position.Vertical? = null, protected val drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - LineCartesianLayerDrawingModel.PointInfo, + LineCartesianLayerDrawingModel.Entry, LineCartesianLayerDrawingModel, > = CartesianLayerDrawingModelInterpolator.default(), @@ -100,8 +98,7 @@ protected constructor( * @property pointProvider provides the [Point]s. * @property pointConnector connects the line’s points, thus defining its shape. * @property dataLabel used for the data labels. - * @property dataLabelVerticalPosition the vertical position of the data labels relative to the - * points. + * @property dataLabelPosition the vertical position of the data labels relative to the points. * @property dataLabelValueFormatter formats the data-label values. * @property dataLabelRotationDegrees the data-label rotation (in degrees). */ @@ -112,7 +109,7 @@ protected constructor( public val pointProvider: PointProvider? = null, public val pointConnector: PointConnector = PointConnector.cubic(), public val dataLabel: TextComponent? = null, - public val dataLabelVerticalPosition: VerticalPosition = VerticalPosition.Top, + public val dataLabelPosition: Position.Vertical = Position.Vertical.Top, public val dataLabelValueFormatter: CartesianValueFormatter = CartesianValueFormatter.decimal(), public val dataLabelRotationDegrees: Float = 0f, ) { @@ -132,7 +129,7 @@ protected constructor( val halfThickness = stroke.thicknessDp.pixels.half areaFill?.draw(context, path, halfThickness, verticalAxisPosition) lineCanvas.drawPath(path, linePaint) - withOtherCanvas(fillCanvas) { fill.draw(context, halfThickness, verticalAxisPosition) } + withCanvas(fillCanvas) { fill.draw(context, halfThickness, verticalAxisPosition) } } } } @@ -377,7 +374,7 @@ protected constructor( verticalAxisPosition: Axis.Position.Vertical? = null, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - LineCartesianLayerDrawingModel.PointInfo, + LineCartesianLayerDrawingModel.Entry, LineCartesianLayerDrawingModel, > = CartesianLayerDrawingModelInterpolator.default(), @@ -406,7 +403,7 @@ protected constructor( var prevY = layerBounds.bottom val drawingStartAlignmentCorrection = - layoutDirectionMultiplier * horizontalDimensions.startPadding + layoutDirectionMultiplier * layerDimensions.startPadding val drawingStart = layerBounds.getStart(isLtr = isLtr) + drawingStartAlignmentCorrection - scroll @@ -476,7 +473,7 @@ protected constructor( series: List, seriesIndex: Int, drawingStart: Float, - pointInfoMap: Map?, + pointInfoMap: Map?, ) { forEachPointInBounds( series = series, @@ -489,8 +486,8 @@ protected constructor( line.dataLabel .takeIf { chartEntry.x != ranges.minX && chartEntry.x != ranges.maxX || - chartEntry.x == ranges.minX && horizontalDimensions.startPadding > 0 || - chartEntry.x == ranges.maxX && horizontalDimensions.endPadding > 0 + chartEntry.x == ranges.minX && layerDimensions.startPadding > 0 || + chartEntry.x == ranges.maxX && layerDimensions.endPadding > 0 } ?.let { textComponent -> val distanceFromLine = max(line.stroke.thicknessDp, point?.sizeDp.orZero).half.pixels @@ -498,9 +495,8 @@ protected constructor( val text = line.dataLabelValueFormatter.format(this, chartEntry.y, verticalAxisPosition) val maxWidth = getMaxDataLabelWidth(chartEntry, x, previousX, nextX) val verticalPosition = - line.dataLabelVerticalPosition.inBounds( + line.dataLabelPosition.inBounds( bounds = layerBounds, - distanceFromPoint = distanceFromLine, componentHeight = textComponent.getHeight( context = this, @@ -508,14 +504,15 @@ protected constructor( maxWidth = maxWidth, rotationDegrees = line.dataLabelRotationDegrees, ), - y = y, + referenceY = y, + referenceDistance = distanceFromLine, ) val dataLabelY = y + when (verticalPosition) { - VerticalPosition.Top -> -distanceFromLine - VerticalPosition.Center -> 0f - VerticalPosition.Bottom -> distanceFromLine + Position.Vertical.Top -> -distanceFromLine + Position.Vertical.Center -> 0f + Position.Vertical.Bottom -> distanceFromLine } textComponent.draw( context = this, @@ -539,17 +536,17 @@ protected constructor( when { previousX != null && nextX != null -> min(x - previousX, nextX - x) previousX == null && nextX == null -> - min(horizontalDimensions.startPadding, horizontalDimensions.endPadding).doubled + min(layerDimensions.startPadding, layerDimensions.endPadding).doubled nextX != null -> { - ((entry.x - ranges.minX) / ranges.xStep * horizontalDimensions.xSpacing + - horizontalDimensions.startPadding) + ((entry.x - ranges.minX) / ranges.xStep * layerDimensions.xSpacing + + layerDimensions.startPadding) .doubled .toFloat() .coerceAtMost(nextX - x) } else -> { - ((ranges.maxX - entry.x) / ranges.xStep * horizontalDimensions.xSpacing + - horizontalDimensions.endPadding) + ((ranges.maxX - entry.x) / ranges.xStep * layerDimensions.xSpacing + + layerDimensions.endPadding) .doubled .toFloat() .coerceAtMost(x - previousX!!) @@ -564,7 +561,7 @@ protected constructor( protected open fun CartesianDrawingContext.forEachPointInBounds( series: List, drawingStart: Float, - pointInfoMap: Map?, + pointInfoMap: Map?, drawFullLineLength: Boolean = false, action: ( @@ -583,9 +580,7 @@ protected constructor( fun getDrawX(entry: LineCartesianLayerModel.Entry): Float = drawingStart + - layoutDirectionMultiplier * - horizontalDimensions.xSpacing * - ((entry.x - minX) / xStep).toFloat() + layoutDirectionMultiplier * layerDimensions.xSpacing * ((entry.x - minX) / xStep).toFloat() fun getDrawY(entry: LineCartesianLayerModel.Entry): Float { val yRange = ranges.getYRange(verticalAxisPosition) @@ -613,9 +608,9 @@ protected constructor( } } - override fun updateHorizontalDimensions( + override fun updateDimensions( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + dimensions: MutableCartesianLayerDimensions, model: LineCartesianLayerModel, ) { with(context) { @@ -631,7 +626,7 @@ protected constructor( } .pixels val xSpacing = maxPointSize + pointSpacingDp.pixels - horizontalDimensions.ensureValuesAtLeast( + dimensions.ensureValuesAtLeast( xSpacing = xSpacing, scalableStartPadding = layerPadding.scalableStartDp.pixels, scalableEndPadding = layerPadding.scalableEndDp.pixels, @@ -641,8 +636,11 @@ protected constructor( } } - override fun updateRanges(ranges: MutableCartesianChartRanges, model: LineCartesianLayerModel) { - ranges.tryUpdate( + override fun updateChartRanges( + chartRanges: MutableCartesianChartRanges, + model: LineCartesianLayerModel, + ) { + chartRanges.tryUpdate( rangeProvider.getMinX(model.minX, model.maxX, model.extraStore), rangeProvider.getMaxX(model.minX, model.maxX, model.extraStore), rangeProvider.getMinY(model.minY, model.maxY, model.extraStore), @@ -651,14 +649,14 @@ protected constructor( ) } - override fun updateInsets( + override fun updateLayerMargins( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerMargins: CartesianLayerMargins, + layerDimensions: CartesianLayerDimensions, model: LineCartesianLayerModel, - insets: Insets, ) { with(context) { - val verticalInset = + val verticalMargin = (0.. series.associate { entry -> entry.x to - LineCartesianLayerDrawingModel.PointInfo( + LineCartesianLayerDrawingModel.Entry( ((entry.y - yRange.minY) / yRange.length).toFloat() ) } @@ -713,7 +711,7 @@ protected constructor( verticalAxisPosition: Axis.Position.Vertical? = this.verticalAxisPosition, drawingModelInterpolator: CartesianLayerDrawingModelInterpolator< - LineCartesianLayerDrawingModel.PointInfo, + LineCartesianLayerDrawingModel.Entry, LineCartesianLayerDrawingModel, > = this.drawingModelInterpolator, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineFills.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineFills.kt index 69c79d463..ba3260a53 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineFills.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/layer/LineFills.kt @@ -32,7 +32,7 @@ internal data class SingleLineFill(val fill: Fill) : LineCartesianLayer.LineFill ) { with(context) { paint.shader = - fill.shader?.provideShader( + fill.shaderProvider?.getShader( this, layerBounds.left, layerBounds.top, @@ -60,7 +60,7 @@ internal data class DoubleLineFill( val canvasSplitY = getCanvasSplitY(splitY, halfLineThickness, verticalAxisPosition) paint.color = topFill.color paint.shader = - topFill.shader?.provideShader( + topFill.shaderProvider?.getShader( this, layerBounds.left, layerBounds.top - halfLineThickness, @@ -76,7 +76,7 @@ internal data class DoubleLineFill( ) paint.color = bottomFill.color paint.shader = - bottomFill.shader?.provideShader( + bottomFill.shaderProvider?.getShader( this, layerBounds.left, canvasSplitY, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/CartesianMarker.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/CartesianMarker.kt index cfe3e2dff..3ddc4874f 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/CartesianMarker.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/CartesianMarker.kt @@ -19,13 +19,13 @@ package com.patrykandpatrick.vico.core.cartesian.marker import androidx.compose.runtime.Immutable import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerInsetter import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMarginUpdater /** Marks [CartesianChart] objects. */ @Immutable -public interface CartesianMarker : CartesianLayerInsetter { +public interface CartesianMarker : CartesianLayerMarginUpdater { /** Draws content under the [CartesianLayer]s. */ public fun drawUnderLayers(context: CartesianDrawingContext, targets: List) {} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/CartesianMarkerValueFormatter.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/CartesianMarkerValueFormatter.kt deleted file mode 100644 index 9b7348f91..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/CartesianMarkerValueFormatter.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.cartesian.marker - -import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext - -/** Formats [CartesianMarker] values for display. */ -public fun interface CartesianMarkerValueFormatter { - /** Returns a label for the given [CartesianMarker.Target]s. */ - public fun format( - context: CartesianDrawingContext, - targets: List, - ): CharSequence -} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/DefaultCartesianMarker.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/DefaultCartesianMarker.kt index 03abdbe42..aeb3cd8c1 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/DefaultCartesianMarker.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/DefaultCartesianMarker.kt @@ -17,14 +17,19 @@ package com.patrykandpatrick.vico.core.cartesian.marker import android.graphics.RectF +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.style.ForegroundColorSpan import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel +import com.patrykandpatrick.vico.core.cartesian.data.CartesianLayerModel +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerMargins import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.Insets -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position +import com.patrykandpatrick.vico.core.common.appendCompat import com.patrykandpatrick.vico.core.common.averageOf import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.component.LineComponent @@ -35,6 +40,7 @@ import com.patrykandpatrick.vico.core.common.doubled import com.patrykandpatrick.vico.core.common.half import com.patrykandpatrick.vico.core.common.orZero import com.patrykandpatrick.vico.core.common.shape.MarkerCorneredShape +import java.text.DecimalFormat import kotlin.math.ceil import kotlin.math.min @@ -50,14 +56,12 @@ import kotlin.math.min */ public open class DefaultCartesianMarker( protected val label: TextComponent, - protected val valueFormatter: CartesianMarkerValueFormatter = - DefaultCartesianMarkerValueFormatter(), + protected val valueFormatter: ValueFormatter = ValueFormatter.default(), protected val labelPosition: LabelPosition = LabelPosition.Top, protected val indicator: ((Int) -> Component)? = null, protected val indicatorSizeDp: Float = Defaults.MARKER_INDICATOR_SIZE, protected val guideline: LineComponent? = null, ) : CartesianMarker { - protected val tempBounds: RectF = RectF() protected val markerCorneredShape: MarkerCorneredShape? = (label.background as? ShapeComponent)?.shape as? MarkerCorneredShape @@ -131,29 +135,23 @@ public open class DefaultCartesianMarker( with(context) { val text = valueFormatter.format(context, targets) val targetX = targets.averageOf { it.canvasX } - val labelBounds = - label.getBounds( - context = context, - text = text, - maxWidth = layerBounds.width().toInt(), - outRect = tempBounds, - ) + val labelBounds = label.getBounds(context, text, layerBounds.width().toInt()) val halfOfTextWidth = labelBounds.width().half val x = overrideXPositionToFit(targetX, layerBounds, halfOfTextWidth) markerCorneredShape?.tickX = targetX val tickPosition: MarkerCorneredShape.TickPosition val y: Float - val verticalPosition: VerticalPosition + val verticalPosition: Position.Vertical when (labelPosition) { LabelPosition.Top -> { tickPosition = MarkerCorneredShape.TickPosition.Bottom y = context.layerBounds.top - tickSizeDp.pixels - verticalPosition = VerticalPosition.Top + verticalPosition = Position.Vertical.Top } LabelPosition.Bottom -> { tickPosition = MarkerCorneredShape.TickPosition.Top y = context.layerBounds.bottom + tickSizeDp.pixels - verticalPosition = VerticalPosition.Bottom + verticalPosition = Position.Vertical.Bottom } LabelPosition.AroundPoint, LabelPosition.AbovePoint -> { @@ -175,7 +173,7 @@ public open class DefaultCartesianMarker( if (flip) MarkerCorneredShape.TickPosition.Top else MarkerCorneredShape.TickPosition.Bottom y = topPointY + (if (flip) 1 else -1) * tickSizeDp.pixels - verticalPosition = if (flip) VerticalPosition.Bottom else VerticalPosition.Top + verticalPosition = if (flip) Position.Vertical.Bottom else Position.Vertical.Top } } markerCorneredShape?.tickPosition = tickPosition @@ -205,22 +203,22 @@ public open class DefaultCartesianMarker( targets .map { it.canvasX } .toSet() - .forEach { x -> guideline?.drawVertical(this, layerBounds.top, layerBounds.bottom, x) } + .forEach { x -> guideline?.drawVertical(this, x, layerBounds.top, layerBounds.bottom) } } - override fun updateInsets( + override fun updateLayerMargins( context: CartesianMeasuringContext, - horizontalDimensions: HorizontalDimensions, + layerMargins: CartesianLayerMargins, + layerDimensions: CartesianLayerDimensions, model: CartesianChartModel, - insets: Insets, ) { with(context) { when (labelPosition) { LabelPosition.Top, LabelPosition.AbovePoint -> - insets.ensureValuesAtLeast(top = label.getHeight(context) + tickSizeDp.pixels) + layerMargins.ensureValuesAtLeast(top = label.getHeight(context) + tickSizeDp.pixels) LabelPosition.Bottom -> - insets.ensureValuesAtLeast(bottom = label.getHeight(context) + tickSizeDp.pixels) + layerMargins.ensureValuesAtLeast(bottom = label.getHeight(context) + tickSizeDp.pixels) LabelPosition.AroundPoint -> Unit // Will be inside the chart } } @@ -266,7 +264,103 @@ public open class DefaultCartesianMarker( AbovePoint, } + /** Formats [CartesianMarker] values for display. */ + public fun interface ValueFormatter { + /** Returns a label for the given [CartesianMarker.Target]s. */ + public fun format( + context: CartesianDrawingContext, + targets: List, + ): CharSequence + + /** Houses a [ValueFormatter] factory function. */ + public companion object { + /** + * Creates an instance of the default [ValueFormatter] implementation. The labels produced + * include the [CartesianLayerModel.Entry] _y_ values, which are formatted via [decimalFormat] + * and, if [colorCode] is true, color-coded. + */ + public fun default( + decimalFormat: DecimalFormat = DecimalFormat("#.##;−#.##"), + colorCode: Boolean = true, + ): ValueFormatter = DefaultValueFormatter(decimalFormat, colorCode) + } + } + protected companion object { public val keyNamespace: CacheStore.KeyNamespace = CacheStore.KeyNamespace() } } + +internal class DefaultValueFormatter( + private val decimalFormat: DecimalFormat, + private val colorCode: Boolean, +) : DefaultCartesianMarker.ValueFormatter { + private fun SpannableStringBuilder.append(y: Double, color: Int? = null) { + if (colorCode && color != null) { + appendCompat( + decimalFormat.format(y), + ForegroundColorSpan(color), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) + } else { + append(decimalFormat.format(y)) + } + } + + private fun SpannableStringBuilder.append(target: CartesianMarker.Target, shorten: Boolean) { + when (target) { + is CandlestickCartesianLayerMarkerTarget -> { + if (shorten) { + append(target.entry.closing, target.closingColor) + } else { + append("O ") + append(target.entry.opening, target.openingColor) + append(", C ") + append(target.entry.closing, target.closingColor) + append(", L ") + append(target.entry.low, target.lowColor) + append(", H ") + append(target.entry.high, target.highColor) + } + } + is ColumnCartesianLayerMarkerTarget -> { + val includeSum = target.columns.size > 1 + if (includeSum) { + append(target.columns.sumOf { it.entry.y }) + append(" (") + } + target.columns.forEachIndexed { index, column -> + append(column.entry.y, column.color) + if (index != target.columns.lastIndex) append(", ") + } + if (includeSum) append(")") + } + is LineCartesianLayerMarkerTarget -> { + target.points.forEachIndexed { index, point -> + append(point.entry.y, point.color) + if (index != target.points.lastIndex) append(", ") + } + } + else -> throw IllegalArgumentException("Unexpected `CartesianMarker.Target` implementation.") + } + } + + override fun format( + context: CartesianDrawingContext, + targets: List, + ): CharSequence = + SpannableStringBuilder().apply { + targets.forEachIndexed { index, target -> + append(target = target, shorten = targets.size > 1) + if (index != targets.lastIndex) append(", ") + } + } + + override fun equals(other: Any?): Boolean = + this === other || + other is DefaultValueFormatter && + decimalFormat == other.decimalFormat && + colorCode == other.colorCode + + override fun hashCode(): Int = 31 * decimalFormat.hashCode() + colorCode.hashCode() +} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/DefaultCartesianMarkerValueFormatter.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/DefaultCartesianMarkerValueFormatter.kt deleted file mode 100644 index b32ab582e..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/cartesian/marker/DefaultCartesianMarkerValueFormatter.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.cartesian.marker - -import android.text.Spannable -import android.text.SpannableStringBuilder -import android.text.style.ForegroundColorSpan -import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext -import com.patrykandpatrick.vico.core.cartesian.data.CartesianLayerModel -import com.patrykandpatrick.vico.core.common.appendCompat -import java.text.DecimalFormat - -/** - * The default [CartesianMarkerValueFormatter]. The labels produced include the - * [CartesianLayerModel.Entry] instances’ _y_ values, which are formatted via [decimalFormat] and, - * if [colorCode] is `true`, color-coded. - */ -public open class DefaultCartesianMarkerValueFormatter( - private val decimalFormat: DecimalFormat = DecimalFormat("#.##;−#.##"), - private val colorCode: Boolean = true, -) : CartesianMarkerValueFormatter { - protected open fun SpannableStringBuilder.append(y: Double, color: Int? = null) { - if (colorCode && color != null) { - appendCompat( - decimalFormat.format(y), - ForegroundColorSpan(color), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, - ) - } else { - append(decimalFormat.format(y)) - } - } - - protected open fun SpannableStringBuilder.append( - target: CartesianMarker.Target, - shorten: Boolean, - ) { - when (target) { - is CandlestickCartesianLayerMarkerTarget -> { - if (shorten) { - append(target.entry.closing, target.closingColor) - } else { - append("O ") - append(target.entry.opening, target.openingColor) - append(", C ") - append(target.entry.closing, target.closingColor) - append(", L ") - append(target.entry.low, target.lowColor) - append(", H ") - append(target.entry.high, target.highColor) - } - } - is ColumnCartesianLayerMarkerTarget -> { - val includeSum = target.columns.size > 1 - if (includeSum) { - append(target.columns.sumOf { it.entry.y }) - append(" (") - } - target.columns.forEachIndexed { index, column -> - append(column.entry.y, column.color) - if (index != target.columns.lastIndex) append(", ") - } - if (includeSum) append(")") - } - is LineCartesianLayerMarkerTarget -> { - target.points.forEachIndexed { index, point -> - append(point.entry.y, point.color) - if (index != target.points.lastIndex) append(", ") - } - } - else -> throw IllegalArgumentException("Unexpected `CartesianMarker.Target` implementation.") - } - } - - override fun format( - context: CartesianDrawingContext, - targets: List, - ): CharSequence = - SpannableStringBuilder().apply { - targets.forEachIndexed { index, target -> - append(target = target, shorten = targets.size > 1) - if (index != targets.lastIndex) append(", ") - } - } - - override fun equals(other: Any?): Boolean = - this === other || - other is DefaultCartesianMarkerValueFormatter && - decimalFormat == other.decimalFormat && - colorCode == other.colorCode - - override fun hashCode(): Int = 31 * decimalFormat.hashCode() + colorCode.hashCode() -} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Bounded.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Bounded.kt index 01fedf371..ccba74124 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Bounded.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Bounded.kt @@ -27,9 +27,4 @@ public interface Bounded { public fun setBounds(left: Number, top: Number, right: Number, bottom: Number) { bounds.set(left, top, right, bottom) } - - /** Sets the coordinates of the bounds to the provided bounds. */ - public fun setBounds(bounds: RectF) { - this.bounds.set(bounds) - } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Dimensions.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Dimensions.kt deleted file mode 100644 index 69c38bdf2..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Dimensions.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.common - -import androidx.compose.runtime.Immutable - -/** - * Defines the size of each edge of a rectangle. Used to store measurements such as padding or - * margin values. - * - * @param startDp the value for the start edge in the dp unit. - * @param topDp the value for the top edge in the dp unit. - * @param endDp the value for the end edge in the dp unit. - * @param bottomDp the value for the bottom edge in the dp unit. - */ -@Immutable -public data class Dimensions( - public val startDp: Float = 0f, - public val topDp: Float = 0f, - public val endDp: Float = 0f, - public val bottomDp: Float = 0f, -) { - /** The sum of [startDp] and [endDp]. */ - public val horizontalDp: Float - get() = startDp + endDp - - /** The sum of [topDp] and [bottomDp]. */ - public val verticalDp: Float - get() = topDp + bottomDp - - /** Creates a [Dimensions] instance using the provided measurements. */ - public constructor( - horizontalDp: Float = 0f, - verticalDp: Float = 0f, - ) : this(horizontalDp, verticalDp, horizontalDp, verticalDp) - - /** Creates a [Dimensions] instance using the provided measurements. */ - public constructor(allDp: Float = 0f) : this(allDp, allDp, allDp, allDp) - - /** - * Returns the dimension of the left edge depending on the layout orientation. - * - * @param isLtr whether the device layout is left-to-right. - */ - public fun getLeftDp(isLtr: Boolean): Float = if (isLtr) startDp else endDp - - /** - * Returns the dimension of the right edge depending on the layout orientation. - * - * @param isLtr whether the device layout is left-to-right. - */ - public fun getRightDp(isLtr: Boolean): Float = if (isLtr) endDp else startDp - - public companion object { - /** A [Dimensions] instance with all coordinates set to 0. */ - public val Empty: Dimensions = Dimensions(0f, 0f, 0f, 0f) - } -} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/DrawingContext.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/DrawingContext.kt index 84f50031b..b23ac42c6 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/DrawingContext.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/DrawingContext.kt @@ -32,7 +32,7 @@ public interface DrawingContext : MeasuringContext { * Updates the value of [DrawingContext.canvas] to [canvas], runs [block], and restores the * previous [DrawingContext.canvas] value. */ - public fun withOtherCanvas(canvas: Canvas, block: () -> Unit) + public fun withCanvas(canvas: Canvas, block: () -> Unit) } /** @suppress */ @@ -55,7 +55,7 @@ public fun DrawingContext( override val cacheStore: CacheStore = CacheStore() - override fun withOtherCanvas(canvas: Canvas, block: () -> Unit) { + override fun withCanvas(canvas: Canvas, block: () -> Unit) { val originalCanvas = this.canvas this.canvas = canvas block() diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Fill.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Fill.kt index 2c9cf5dcd..8d5dc3aa6 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Fill.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Fill.kt @@ -20,25 +20,27 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import com.patrykandpatrick.vico.core.common.data.CacheStore -import com.patrykandpatrick.vico.core.common.shader.DynamicShader +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider /** * Stores fill properties. * - * @property color the color. If [shader] is not `null`, this is [Color.BLACK]. - * @property shader the [DynamicShader]. + * @property color the color. If [shaderProvider] is not null, this is [Color.BLACK]. + * @property shaderProvider the [ShaderProvider]. */ -public class Fill private constructor(public val color: Int, public val shader: DynamicShader?) { +public class Fill +private constructor(public val color: Int, public val shaderProvider: ShaderProvider?) { /** Creates a color [Fill]. */ - public constructor(color: Int) : this(color = color, shader = null) + public constructor(color: Int) : this(color = color, shaderProvider = null) - /** Creates a [DynamicShader][Fill]. */ - public constructor(shader: DynamicShader) : this(Color.BLACK, shader) + /** Creates a [ShaderProvider][Fill]. */ + public constructor(shaderProvider: ShaderProvider) : this(Color.BLACK, shaderProvider) override fun equals(other: Any?): Boolean = - this === other || other is Fill && color == other.color && shader == other.shader + this === other || + other is Fill && color == other.color && shaderProvider == other.shaderProvider - override fun hashCode(): Int = 31 * color + shader?.hashCode().orZero + override fun hashCode(): Int = 31 * color + shaderProvider?.hashCode().orZero /** Houses [Fill] singletons. */ public companion object { @@ -60,12 +62,12 @@ internal fun Fill.extractColor( height: Float, side: Int = 1, ): Int = - if (shader != null) { + if (shaderProvider != null) { val bitmap = context.getBitmap(cacheKeyNamespace) canvas.setBitmap(bitmap) val correctedHeight = if (height <= 0f) canvas.height.toFloat() else height.coerceAtLeast(1f) val correctedWidth = if (width <= 0f) canvas.width.toFloat() else width.coerceAtLeast(1f) - paint.shader = shader.provideShader(context, 0f, 0f, correctedWidth, correctedHeight) + paint.shader = shaderProvider.getShader(context, 0f, 0f, correctedWidth, correctedHeight) canvas.drawRect(0f, 0f, correctedWidth, correctedHeight, paint) bitmap.getPixel( correctedWidth.half.toInt(), diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalLegend.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalLegend.kt index 137dd457a..d7e883c0c 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalLegend.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalLegend.kt @@ -37,7 +37,7 @@ public open class HorizontalLegend( protected val iconLabelSpacingDp: Float = Defaults.LEGEND_ICON_LABEL_SPACING, protected val rowSpacingDp: Float = Defaults.LEGEND_ROW_SPACING, protected val columnSpacingDp: Float = Defaults.LEGEND_COLUMN_SPACING, - protected val padding: Dimensions = Dimensions.Empty, + protected val padding: Insets = Insets.Zero, ) : Legend { private val itemManager = LegendItemManager(items) private val heights = mutableListOf() @@ -54,14 +54,14 @@ public open class HorizontalLegend( max( itemManager.itemList .first() - .getLabelHeight(context, maxWidth, iconLabelSpacingDp, iconSizeDp), + .getLabelHeight(context, iconSizeDp, iconLabelSpacingDp, maxWidth), iconSizeDp.pixels, ) heights.add(height) buildLines(context, maxWidth) { item -> val currentHeight = max( - item.getLabelHeight(context, maxWidth, iconLabelSpacingDp, iconSizeDp), + item.getLabelHeight(context, iconSizeDp, iconLabelSpacingDp, maxWidth), iconSizeDp.pixels, ) heights.add(currentHeight) @@ -86,7 +86,7 @@ public open class HorizontalLegend( var currentStart = 0f val currentLineHeight = heights.getOrElse(index) { - item.first().getLabelHeight(context, availableWidth, iconLabelSpacingDp, iconSizeDp) + item.first().getLabelHeight(context, iconSizeDp, iconLabelSpacingDp, availableWidth) } val centerY = currentTop + currentLineHeight.half @@ -109,18 +109,18 @@ public open class HorizontalLegend( text = it.label, x = startX + currentStart, y = centerY, - horizontalPosition = HorizontalPosition.End, - verticalPosition = VerticalPosition.Center, + horizontalPosition = Position.Horizontal.End, + verticalPosition = Position.Vertical.Center, maxWidth = (bounds.width() - (iconSizeDp + iconLabelSpacingDp + padding.horizontalDp).pixels) .toInt(), ) currentStart += if (isLtr) { - it.getLabelWidth(context, availableWidth, iconLabelSpacingDp, iconSizeDp) + + it.getLabelWidth(context, iconSizeDp, iconLabelSpacingDp, availableWidth) + columnSpacingDp.pixels } else { - -(it.getLabelWidth(context, availableWidth, iconLabelSpacingDp, iconSizeDp) + + -(it.getLabelWidth(context, iconSizeDp, iconLabelSpacingDp, availableWidth) + columnSpacingDp.pixels + iconSizeDp.pixels) } @@ -142,7 +142,7 @@ public open class HorizontalLegend( lines.add(mutableListOf()) itemManager.itemList.forEach { remainWidth -= - it.getWidth(context, availableWidth, iconLabelSpacingDp, iconSizeDp) + + it.getWidth(context, iconSizeDp, iconLabelSpacingDp, availableWidth) + columnSpacingDp.pixels if (remainWidth >= 0 || remainWidth == availableWidth) { lines[currentLine].add(it) @@ -152,7 +152,7 @@ public open class HorizontalLegend( currentLine++ remainWidth = availableWidth - - it.getWidth(context, availableWidth, iconLabelSpacingDp, iconSizeDp) - + it.getWidth(context, iconSizeDp, iconLabelSpacingDp, availableWidth) - columnSpacingDp.pixels lines.add(mutableListOf(it)) callback(it) diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalPosition.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalPosition.kt deleted file mode 100644 index a7b58430e..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/HorizontalPosition.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.common - -/** Defines the horizontal position of a drawn object relative to a given point. */ -public enum class HorizontalPosition { - Start, - Center, - End, -} - -internal operator fun HorizontalPosition.unaryMinus() = - when (this) { - HorizontalPosition.Start -> HorizontalPosition.End - HorizontalPosition.Center -> HorizontalPosition.Center - HorizontalPosition.End -> HorizontalPosition.Start - } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Insets.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Insets.kt index f09c93d0c..e9da8550d 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Insets.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Insets.kt @@ -16,61 +16,67 @@ package com.patrykandpatrick.vico.core.common -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerInsetter +import androidx.compose.runtime.Immutable /** - * Used to apply insets. + * Stores inset sizes for the sides of a rectangle. Used for margins and padding. * - * @see CartesianLayerInsetter + * @param startDp the start inset’s size (in dp). + * @param topDp the top inset’s size (in dp). + * @param endDp the end inset’s size (in dp). + * @param bottomDp the bottom inset’s size (in dp). */ -public class Insets : HorizontalInsets { - /** The start inset’s size (in pixels). */ - public override var start: Float = 0f - private set +@Immutable +public class Insets( + public val startDp: Float = 0f, + public val topDp: Float = 0f, + public val endDp: Float = 0f, + public val bottomDp: Float = 0f, +) { + /** The sum of [startDp] and [endDp]. */ + public val horizontalDp: Float + get() = startDp + endDp - /** The top inset’s size (in pixels). */ - public var top: Float = 0f - private set + /** The sum of [topDp] and [bottomDp]. */ + public val verticalDp: Float + get() = topDp + bottomDp - /** The end inset’s size (in pixels). */ - public override var end: Float = 0f - private set + /** Creates an [Insets] instance with [startDp] = [endDp] and [topDp] = [bottomDp]. */ + public constructor( + horizontalDp: Float = 0f, + verticalDp: Float = 0f, + ) : this(horizontalDp, verticalDp, horizontalDp, verticalDp) - /** The bottom inset’s size (in pixels). */ - public var bottom: Float = 0f - private set + /** Creates an [Insets] instance with a common size for all four insets. */ + public constructor(allDp: Float = 0f) : this(allDp, allDp, allDp, allDp) - /** The sum of [top] and [bottom]. */ - public val vertical: Float - get() = top + bottom + /** Returns the left inset’s size. */ + public fun getLeft(context: MeasuringContext): Float = + with(context) { (if (isLtr) startDp else endDp).pixels } - /** The largest of [start], [top], [end], and [bottom]. */ - public val max: Float - get() = maxOf(start, top, end, bottom) + /** Returns the right inset’s size. */ + public fun getRight(context: MeasuringContext): Float = + with(context) { (if (isLtr) endDp else startDp).pixels } - override fun ensureValuesAtLeast(start: Float, end: Float) { - this.start = this.start.coerceAtLeast(start) - this.end = this.end.coerceAtLeast(end) - } + override fun equals(other: Any?): Boolean = + this === other || + other is Insets && + startDp == other.startDp && + topDp == other.topDp && + endDp == other.endDp && + bottomDp == other.bottomDp - /** Ensures that the stored values are no smaller than the provided ones. */ - public fun ensureValuesAtLeast( - start: Float = this.start, - top: Float = this.top, - end: Float = this.end, - bottom: Float = this.bottom, - ) { - this.start = this.start.coerceAtLeast(start) - this.top = this.top.coerceAtLeast(top) - this.end = this.end.coerceAtLeast(end) - this.bottom = this.bottom.coerceAtLeast(bottom) + override fun hashCode(): Int { + var result = startDp.hashCode() + result = 31 * result + topDp.hashCode() + result = 31 * result + endDp.hashCode() + result = 31 * result + bottomDp.hashCode() + return result } - /** Clears the stored values. */ - public fun clear() { - start = 0f - top = 0f - end = 0f - bottom = 0f + /** Houses an [Insets] singleton. */ + public companion object { + /** An [Insets] instance with a size of zero for all four insets. */ + public val Zero: Insets = Insets(0f, 0f, 0f, 0f) } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LayeredComponent.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LayeredComponent.kt index 5237d86ab..40d660225 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LayeredComponent.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LayeredComponent.kt @@ -19,14 +19,18 @@ package com.patrykandpatrick.vico.core.common import com.patrykandpatrick.vico.core.common.component.Component /** - * Draws two [Component]s, [rear] and [front], on top of each other. [padding] defines the padding - * between them. + * Draws two [Component]s on top of each other. + * + * @property back the back [Component]. + * @property front the front [Component]. + * @property padding the padding between [back] and [front]. + * @property margins the margins around the [LayeredComponent]. */ public open class LayeredComponent( - protected val rear: Component, + protected val back: Component, protected val front: Component, - protected val padding: Dimensions = Dimensions.Empty, - protected val margins: Dimensions = Dimensions.Empty, + protected val padding: Insets = Insets.Zero, + protected val margins: Insets = Insets.Zero, ) : Component { override fun draw( context: DrawingContext, @@ -36,17 +40,17 @@ public open class LayeredComponent( bottom: Float, ): Unit = with(context) { - val leftWithMargin = left + margins.getLeftDp(isLtr).pixels + val leftWithMargin = left + margins.getLeft(context) val topWithMargin = top + margins.topDp.pixels - val rightWithMargin = right - margins.getRightDp(isLtr).pixels + val rightWithMargin = right - margins.getRight(context) val bottomWithMargin = bottom - margins.bottomDp.pixels - rear.draw(context, leftWithMargin, topWithMargin, rightWithMargin, bottomWithMargin) + back.draw(context, leftWithMargin, topWithMargin, rightWithMargin, bottomWithMargin) front.draw( context, - leftWithMargin + padding.getLeftDp(isLtr).pixels, + leftWithMargin + padding.getLeft(context), topWithMargin + padding.topDp.pixels, - rightWithMargin - padding.getRightDp(isLtr).pixels, + rightWithMargin - padding.getRight(context), bottomWithMargin - padding.bottomDp.pixels, ) } @@ -54,13 +58,13 @@ public open class LayeredComponent( override fun equals(other: Any?): Boolean = this === other || other is LayeredComponent && - rear == other.rear && + back == other.back && front == other.front && padding == other.padding && margins == other.margins override fun hashCode(): Int { - var result = rear.hashCode() + var result = back.hashCode() result = 31 * result + front.hashCode() result = 31 * result + padding.hashCode() result = 31 * result + margins.hashCode() diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LegendItem.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LegendItem.kt index a9a86c210..4ed216910 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LegendItem.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/LegendItem.kt @@ -34,60 +34,57 @@ public open class LegendItem( /** * Measures the height of the label. * - * @param maxWidth the maximum width. - * @param iconPaddingDp the padding between the icon and the label. - * @param iconSizeDp the size of the icon. + * @param iconSizeDp the [LegendItem.icon] size (in dp). + * @param iconLabelSpacingDp the spacing between [LegendItem.icon] and [LegendItem.labelComponent] + * (in dp). + * @param maxWidth the maximum [LegendItem] width. */ public fun getLabelHeight( context: MeasuringContext, - maxWidth: Float, - iconPaddingDp: Float, iconSizeDp: Float, + iconLabelSpacingDp: Float, + maxWidth: Float, ): Float = - with(context) { - labelComponent.getHeight( - context = context, - text = label, - maxWidth = (maxWidth - iconSizeDp.pixels - iconPaddingDp.pixels).toInt(), - ) - } + labelComponent.getHeight( + context = context, + text = label, + maxWidth = (maxWidth - context.run { iconSizeDp.pixels + iconLabelSpacingDp.pixels }).toInt(), + ) /** * Measures the width of the label. * - * @param maxWidth the maximum width. - * @param iconPaddingDp the padding between the icon and the label. - * @param iconSizeDp the size of the icon. + * @param iconSizeDp the [LegendItem.icon] size (in dp). + * @param iconLabelSpacingDp the spacing between [LegendItem.icon] and [LegendItem.labelComponent] + * (in dp). + * @param maxWidth the maximum [LegendItem] width. */ public fun getLabelWidth( context: MeasuringContext, - maxWidth: Float, - iconPaddingDp: Float, iconSizeDp: Float, + iconLabelSpacingDp: Float, + maxWidth: Float, ): Float = - with(context) { - labelComponent.getWidth( - context = context, - text = label, - maxWidth = (maxWidth - iconSizeDp.pixels - iconPaddingDp.pixels).toInt(), - ) - } + labelComponent.getWidth( + context = context, + text = label, + maxWidth = (maxWidth - context.run { iconSizeDp.pixels + iconLabelSpacingDp.pixels }).toInt(), + ) /** * Measures the width of this [LegendItem]. * - * @param maxWidth the maximum width. - * @param iconPaddingDp the padding between the icon and the label. - * @param iconSizeDp the size of the icon. + * @param iconSizeDp the [LegendItem.icon] size (in dp). + * @param iconLabelSpacingDp the spacing between [LegendItem.icon] and [LegendItem.labelComponent] + * (in dp). + * @param maxWidth the maximum [LegendItem] width. */ public fun getWidth( context: MeasuringContext, - maxWidth: Float, - iconPaddingDp: Float, iconSizeDp: Float, + iconLabelSpacingDp: Float, + maxWidth: Float, ): Float = - with(context) { - getLabelWidth(context, maxWidth, iconPaddingDp, iconSizeDp) + - (iconSizeDp + iconPaddingDp).pixels - } + getLabelWidth(context, iconSizeDp, iconLabelSpacingDp, maxWidth) + + context.run { (iconSizeDp + iconLabelSpacingDp).pixels } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MeasuringContext.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MeasuringContext.kt index e43740ee6..330684ac1 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MeasuringContext.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MeasuringContext.kt @@ -51,9 +51,9 @@ public interface MeasuringContext { /** Caches drawing data. */ public val cacheStore: CacheStore - /** `1f` if [isLtr] is `true`, and `-1f` otherwise. */ - public val layoutDirectionMultiplier: Float - get() = if (isLtr) 1f else -1f + /** 1 if [isLtr] is true; −1 otherwise. */ + public val layoutDirectionMultiplier: Int + get() = if (isLtr) 1 else -1 /** Removes all temporary data. */ public fun reset() { diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MutableMeasuringContext.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MutableMeasuringContext.kt index 14a80590a..ff21a57a4 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MutableMeasuringContext.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/MutableMeasuringContext.kt @@ -20,12 +20,13 @@ import android.graphics.RectF import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.common.data.CacheStore -/** A [MeasuringContext] implementation that facilitates the mutation of some of its properties. */ +/** @suppress */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public open class MutableMeasuringContext( override val canvasBounds: RectF, override var density: Float, override var isLtr: Boolean, - @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public var spToPx: (Float) -> Float, + private var spToPx: (Float) -> Float, ) : MeasuringContext { override val cacheStore: CacheStore = CacheStore() diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Point.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Point.kt index a095c5843..2eba8f060 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Point.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Point.kt @@ -16,12 +16,6 @@ package com.patrykandpatrick.vico.core.common -/** Creates a new [Point] with the provided coordinates. */ -public fun Point(x: Float, y: Float): Point = Point(packFloats(x, y)) - -/** Creates a new [Point] with the provided coordinates. */ -public fun Point(x: Int, y: Int): Point = Point(packInts(x, y)) - /** Represents a point in a coordinate system. */ @JvmInline public value class Point internal constructor(private val packedValue: Long) { @@ -33,11 +27,7 @@ public value class Point internal constructor(private val packedValue: Long) { public val y: Float get() = unpackFloat2(packedValue) - /** @see x */ - public operator fun component1(): Float = x - - /** @see y */ - public operator fun component2(): Float = y + public constructor(x: Float, y: Float) : this(packFloats(x, y)) /** * Copies this [Point], updating one or both of the coordinates. If providing new values for both diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Position.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Position.kt new file mode 100644 index 000000000..5c0825aa1 --- /dev/null +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/Position.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2024 by Patryk Goworowski and Patrick Michalik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.patrykandpatrick.vico.core.common + +import android.graphics.RectF + +/** Defines the relative position of an object. */ +public object Position { + /** Defines the relative horizontal position of an object. */ + public enum class Horizontal { + Start, + Center, + End, + } + + /** Defines the relative vertical position of an object. */ + public enum class Vertical { + Top, + Center, + Bottom, + } +} + +internal operator fun Position.Horizontal.unaryMinus() = + when (this) { + Position.Horizontal.Start -> Position.Horizontal.End + Position.Horizontal.Center -> Position.Horizontal.Center + Position.Horizontal.End -> Position.Horizontal.Start + } + +internal operator fun Position.Vertical.unaryMinus() = + when (this) { + Position.Vertical.Top -> Position.Vertical.Bottom + Position.Vertical.Center -> Position.Vertical.Center + Position.Vertical.Bottom -> Position.Vertical.Top + } + +internal fun Position.Vertical.inBounds( + bounds: RectF, + componentHeight: Float, + referenceY: Float, + referenceDistance: Float = 0f, +): Position.Vertical { + val topFits = referenceY - referenceDistance - componentHeight >= bounds.top + val centerFits = + referenceY - componentHeight.half >= bounds.top && + referenceY + componentHeight.half <= bounds.bottom + val bottomFits = referenceY + referenceDistance + componentHeight <= bounds.bottom + return when (this) { + Position.Vertical.Top -> if (topFits) this else Position.Vertical.Bottom + Position.Vertical.Bottom -> if (bottomFits) this else Position.Vertical.Top + Position.Vertical.Center -> + when { + centerFits -> this + topFits -> Position.Vertical.Top + else -> Position.Vertical.Bottom + } + } +} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/RectF.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/RectF.kt index 440b67310..bf8efc05a 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/RectF.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/RectF.kt @@ -77,52 +77,3 @@ internal fun RectF.translate(x: Float, y: Float): RectF = apply { internal fun RectF.getStart(isLtr: Boolean): Float = if (isLtr) left else right internal fun RectF.getEnd(isLtr: Boolean): Float = if (isLtr) right else left - -internal fun RectF.updateBounds( - left: Float = this.left, - top: Float = this.top, - right: Float = this.right, - bottom: Float = this.bottom, -) { - set(left, top, right, bottom) -} - -internal fun RectF.updateBy( - left: Float = 0f, - top: Float = 0f, - right: Float = 0f, - bottom: Float = 0f, -) { - set( - left = this.left + left, - top = this.top + top, - right = this.right + right, - bottom = this.bottom + bottom, - ) -} - -internal fun RectF.updateIfExceeds(x: Float, y: Float) { - updateBounds( - left = left.coerceAtMost(x), - top = top.coerceAtMost(y), - right = right.coerceAtLeast(x), - bottom = bottom.coerceAtLeast(y), - ) -} - -internal val RectF.radius: Float - get() { - require(width() == height()) { "RectF must be a square." } - return width().half - } - -internal val RectF.centerPoint: Point - get() = Point(centerX(), centerY()) - -internal operator fun RectF.component1(): Float = left - -internal operator fun RectF.component2(): Float = top - -internal operator fun RectF.component3(): Float = right - -internal operator fun RectF.component4(): Float = bottom diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/StaticLayout.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/StaticLayout.kt index dbc139a90..59e91266e 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/StaticLayout.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/StaticLayout.kt @@ -70,13 +70,8 @@ internal fun StaticLayout.setLineCount(count: Int) = apply { setFieldValue(LINE_COUNT_FIELD, count) } -internal fun Layout.getBounds(outBounds: RectF): RectF = - outBounds.apply { - left = 0f - top = 0f - right = widestLineWidth - bottom = heightWithSpacingAddition - } +internal val Layout.bounds + get() = RectF(0f, 0f, widestLineWidth, heightWithSpacingAddition) internal val Layout.widestLineWidth: Float get() = (0.. getLineWidth(lineIndex) } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/VerticalLegend.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/VerticalLegend.kt index 06090c9d2..d76b35347 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/VerticalLegend.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/VerticalLegend.kt @@ -34,7 +34,7 @@ public open class VerticalLegend( protected val iconSizeDp: Float = Defaults.LEGEND_ICON_SIZE, protected val iconLabelSpacingDp: Float = Defaults.LEGEND_ICON_LABEL_SPACING, protected val rowSpacingDp: Float = Defaults.LEGEND_ROW_SPACING, - protected val padding: Dimensions = Dimensions.Empty, + protected val padding: Insets = Insets.Zero, ) : Legend { private val itemManager = LegendItemManager(items) private val heights: HashMap = HashMap() @@ -48,7 +48,7 @@ public open class VerticalLegend( sum + max( iconSizeDp.pixels, - item.getLabelHeight(context, maxWidth, iconLabelSpacingDp, iconSizeDp), + item.getLabelHeight(context, iconSizeDp, iconLabelSpacingDp, maxWidth), ) .also { height -> heights[item] = height } } + (padding.verticalDp + rowSpacingDp * (itemManager.itemList.size - 1)).pixels @@ -88,7 +88,7 @@ public open class VerticalLegend( text = item.label, x = startX, y = centerY, - horizontalPosition = HorizontalPosition.End, + horizontalPosition = Position.Horizontal.End, maxWidth = (bounds.width() - (iconSizeDp + iconLabelSpacingDp + padding.horizontalDp).pixels) .toInt(), diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/VerticalPosition.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/VerticalPosition.kt deleted file mode 100644 index 74abe9e84..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/VerticalPosition.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.common - -import android.graphics.RectF - -/** Defines the vertical position of a drawn object relative to a given point. */ -public enum class VerticalPosition { - Top, - Center, - Bottom, -} - -internal operator fun VerticalPosition.unaryMinus() = - when (this) { - VerticalPosition.Top -> VerticalPosition.Bottom - VerticalPosition.Center -> VerticalPosition.Center - VerticalPosition.Bottom -> VerticalPosition.Top - } - -internal fun VerticalPosition.inBounds( - bounds: RectF, - distanceFromPoint: Float = 0f, - componentHeight: Float, - y: Float, -): VerticalPosition { - val topFits = y - distanceFromPoint - componentHeight >= bounds.top - val centerFits = - y - componentHeight.half >= bounds.top && y + componentHeight.half <= bounds.bottom - val bottomFits = y + distanceFromPoint + componentHeight <= bounds.bottom - - return when (this) { - VerticalPosition.Top -> if (topFits) this else VerticalPosition.Bottom - VerticalPosition.Bottom -> if (bottomFits) this else VerticalPosition.Top - VerticalPosition.Center -> - when { - centerFits -> this - topFits -> VerticalPosition.Top - else -> VerticalPosition.Bottom - } - } -} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/LineComponent.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/LineComponent.kt index 5902167b7..b83e6dc33 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/LineComponent.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/LineComponent.kt @@ -18,10 +18,11 @@ package com.patrykandpatrick.vico.core.common.component import android.graphics.RectF import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.DrawingContext import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.MeasuringContext +import com.patrykandpatrick.vico.core.common.half import com.patrykandpatrick.vico.core.common.shape.Shape /** @@ -39,7 +40,7 @@ public open class LineComponent( fill: Fill, public val thicknessDp: Float = Defaults.LINE_COMPONENT_THICKNESS_DP, shape: Shape = Shape.Rectangle, - margins: Dimensions = Dimensions.Empty, + margins: Insets = Insets.Zero, strokeFill: Fill = Fill.Transparent, strokeThicknessDp: Float = 0f, shadow: Shadow? = null, @@ -52,102 +53,54 @@ public open class LineComponent( context: DrawingContext, left: Float, right: Float, - centerY: Float, - thicknessScale: Float = 1f, - ): Unit = - with(context) { - draw( - context, - left = left, - top = centerY - thickness * thicknessScale / 2, - right = right, - bottom = centerY + thickness * thicknessScale / 2, - ) - } - - /** - * Checks whether the [LineComponent] fits horizontally within the given [boundingBox] with its - * current [thicknessDp]. - */ - public open fun fitsInHorizontal( - context: DrawingContext, - left: Float, - right: Float, - centerY: Float, - boundingBox: RectF, - thicknessScale: Float = 1f, - ): Boolean = - with(context) { - boundingBox.contains( - left, - centerY - thickness * thicknessScale / 2, - right, - centerY + thickness * thicknessScale / 2, - ) - } + y: Float, + thicknessFactor: Float = 1f, + ) { + val halfThickness = (thicknessFactor * context.thickness).half + draw( + context = context, + left = left, + top = y - halfThickness, + right = right, + bottom = y + halfThickness, + ) + } /** A convenience function for [draw] that draws the [LineComponent] vertically. */ public open fun drawVertical( context: DrawingContext, + x: Float, top: Float, bottom: Float, - centerX: Float, - thicknessScale: Float = 1f, - ): Unit = - with(context) { - draw( - context, - left = centerX - thickness * thicknessScale / 2, - top = top, - right = centerX + thickness * thicknessScale / 2, - bottom = bottom, - ) - } - - /** - * Checks whether the [LineComponent] fits vertically within the given [boundingBox] with its - * current [thicknessDp]. - */ - public open fun fitsInVertical( - context: DrawingContext, - top: Float, - bottom: Float, - centerX: Float, - boundingBox: RectF, - thicknessScale: Float = 1f, - ): Boolean = - with(context) { - boundingBox.contains( - centerX - thickness * thicknessScale / 2, - top, - centerX + thickness * thicknessScale / 2, - bottom, - ) - } + thicknessFactor: Float = 1f, + ) { + val halfThickness = (thicknessFactor * context.thickness).half + draw( + context = context, + left = x - halfThickness, + top = top, + right = x + halfThickness, + bottom = bottom, + ) + } - /** - * Checks whether the [LineComponent] vertically intersects the given [boundingBox] with its - * current [thicknessDp]. - */ - public open fun intersectsVertical( + internal fun intersectsVertical( context: DrawingContext, - top: Float, - bottom: Float, - centerX: Float, - boundingBox: RectF, - thicknessScale: Float = 1f, - ): Boolean = - with(context) { - val left = centerX - thickness * thicknessScale / 2 - val right = centerX + thickness * thicknessScale / 2 - boundingBox.left < right && left < boundingBox.right - } + x: Float, + bounds: RectF, + thicknessFactor: Float = 1f, + ): Boolean { + val halfThickness = (thicknessFactor * context.thickness).half + val left = x - halfThickness.half + val right = x + halfThickness.half + return bounds.left < right && left < bounds.right + } /** Creates a new [LineComponent] based on this one. */ override fun copy( fill: Fill, shape: Shape, - margins: Dimensions, + margins: Insets, strokeFill: Fill, strokeThicknessDp: Float, shadow: Shadow?, @@ -159,7 +112,7 @@ public open class LineComponent( fill: Fill = this.fill, thicknessDp: Float = this.thicknessDp, shape: Shape = this.shape, - margins: Dimensions = this.margins, + margins: Insets = this.margins, strokeFill: Fill = this.strokeFill, strokeThicknessDp: Float = this.strokeThicknessDp, shadow: Shadow? = this.shadow, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/Shadow.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/Shadow.kt index 620e7b54c..1a88eb49d 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/Shadow.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/Shadow.kt @@ -25,19 +25,19 @@ import com.patrykandpatrick.vico.core.common.MeasuringContext * Stores shadow properties. * * @param radiusDp the blur radius (in dp). - * @param dxDp the horizontal offset (in dp). - * @param dyDp the vertical offset (in dp). + * @param xDp the horizontal offset (in dp). + * @param yDp the vertical offset (in dp). * @param color the color. */ @Immutable public data class Shadow( private val radiusDp: Float, - private val dxDp: Float = 0f, - private val dyDp: Float = 0f, + private val xDp: Float = 0f, + private val yDp: Float = 0f, private val color: Int = Defaults.SHADOW_COLOR, ) { /** Updates [paint]’s shadow layer. */ public fun updateShadowLayer(context: MeasuringContext, paint: Paint) { - with(context) { paint.setShadowLayer(radiusDp.pixels, dxDp.pixels, dyDp.pixels, color) } + with(context) { paint.setShadowLayer(radiusDp.pixels, xDp.pixels, yDp.pixels, color) } } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/ShapeComponent.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/ShapeComponent.kt index 35eba9d93..552bbebc0 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/ShapeComponent.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/ShapeComponent.kt @@ -18,9 +18,9 @@ package com.patrykandpatrick.vico.core.common.component import android.graphics.Paint import android.graphics.Path -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.DrawingContext import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.alpha import com.patrykandpatrick.vico.core.common.half import com.patrykandpatrick.vico.core.common.shape.Shape @@ -38,7 +38,7 @@ import com.patrykandpatrick.vico.core.common.shape.Shape public open class ShapeComponent( public val fill: Fill = Fill.Black, public val shape: Shape = Shape.Rectangle, - protected val margins: Dimensions = Dimensions.Empty, + protected val margins: Insets = Insets.Zero, public val strokeFill: Fill = Fill.Transparent, protected val strokeThicknessDp: Float = 0f, protected val shadow: Shadow? = null, @@ -66,15 +66,17 @@ public open class ShapeComponent( right: Float, bottom: Float, ) { - fill.shader?.provideShader(context, left, top, right, bottom)?.let(paint::setShader) - strokeFill.shader?.provideShader(context, left, top, right, bottom)?.let(strokePaint::setShader) + fill.shaderProvider?.getShader(context, left, top, right, bottom)?.let(paint::setShader) + strokeFill.shaderProvider + ?.getShader(context, left, top, right, bottom) + ?.let(strokePaint::setShader) } override fun draw(context: DrawingContext, left: Float, top: Float, right: Float, bottom: Float) { with(context) { - var adjustedLeft = left + margins.getLeftDp(isLtr).pixels + var adjustedLeft = left + margins.getLeft(context) var adjustedTop = top + margins.topDp.pixels - var adjustedRight = right - margins.getRightDp(isLtr).pixels + var adjustedRight = right - margins.getRight(context) var adjustedBottom = bottom - margins.bottomDp.pixels if (adjustedLeft >= adjustedRight || adjustedTop >= adjustedBottom) return val strokeThickness = strokeThicknessDp.pixels @@ -100,7 +102,7 @@ public open class ShapeComponent( public open fun copy( fill: Fill = this.fill, shape: Shape = this.shape, - margins: Dimensions = this.margins, + margins: Insets = this.margins, strokeFill: Fill = this.strokeFill, strokeThicknessDp: Float = this.strokeThicknessDp, shadow: Shadow? = this.shadow, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/TextComponent.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/TextComponent.kt index 20faa85bb..64e9efbe8 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/TextComponent.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/component/TextComponent.kt @@ -30,14 +30,13 @@ import android.text.TextUtils import androidx.compose.runtime.Immutable import com.patrykandpatrick.vico.core.common.Defaults import com.patrykandpatrick.vico.core.common.Defaults.TEXT_COMPONENT_LINE_COUNT -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.DrawingContext -import com.patrykandpatrick.vico.core.common.HorizontalPosition +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.MeasuringContext -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position +import com.patrykandpatrick.vico.core.common.bounds import com.patrykandpatrick.vico.core.common.copy import com.patrykandpatrick.vico.core.common.data.CacheStore -import com.patrykandpatrick.vico.core.common.getBounds import com.patrykandpatrick.vico.core.common.half import com.patrykandpatrick.vico.core.common.heightWithSpacingAddition import com.patrykandpatrick.vico.core.common.piRad @@ -82,8 +81,8 @@ public open class TextComponent( protected val lineHeightSp: Float? = null, protected val lineCount: Int = TEXT_COMPONENT_LINE_COUNT, protected val truncateAt: TextUtils.TruncateAt? = TextUtils.TruncateAt.END, - protected val margins: Dimensions = Dimensions.Empty, - protected val padding: Dimensions = Dimensions.Empty, + protected val margins: Insets = Insets.Zero, + protected val padding: Insets = Insets.Zero, public val background: Component? = null, protected val minWidth: MinWidth = MinWidth.fixed(), ) { @@ -95,7 +94,6 @@ public open class TextComponent( } private lateinit var layout: Layout private lateinit var measuringLayout: Layout - private val tempMeasureBounds = RectF() /** * Uses [Canvas] to draw this [TextComponent]. @@ -115,8 +113,8 @@ public open class TextComponent( text: CharSequence, x: Float, y: Float, - horizontalPosition: HorizontalPosition = HorizontalPosition.Center, - verticalPosition: VerticalPosition = VerticalPosition.Center, + horizontalPosition: Position.Horizontal = Position.Horizontal.Center, + verticalPosition: Position.Vertical = Position.Vertical.Center, maxWidth: Int = DEF_LAYOUT_SIZE, maxHeight: Int = DEF_LAYOUT_SIZE, rotationDegrees: Float = 0f, @@ -132,9 +130,9 @@ public open class TextComponent( verticalPosition.getTextTopPosition(context, y, layout.heightWithSpacingAddition) context.withSavedCanvas { - val bounds = layout.getBounds(tempMeasureBounds) - val paddingLeft = padding.getLeftDp(isLtr).pixels - val paddingRight = padding.getRightDp(isLtr).pixels + val bounds = layout.bounds + val paddingLeft = padding.getLeft(context) + val paddingRight = padding.getRight(context) val textAlignmentCorrection: Float with(receiver = bounds) { @@ -162,15 +160,15 @@ public open class TextComponent( xCorrection = when (horizontalPosition) { - HorizontalPosition.Start -> widthDelta.half - HorizontalPosition.End -> -widthDelta.half + Position.Horizontal.Start -> widthDelta.half + Position.Horizontal.End -> -widthDelta.half else -> 0f } * context.layoutDirectionMultiplier yCorrection = when (verticalPosition) { - VerticalPosition.Top -> heightDelta.half - VerticalPosition.Bottom -> -heightDelta.half + Position.Vertical.Top -> heightDelta.half + Position.Vertical.Bottom -> -heightDelta.half else -> 0f } } @@ -198,28 +196,28 @@ public open class TextComponent( } } - private fun HorizontalPosition.getTextStartPosition( + private fun Position.Horizontal.getTextStartPosition( context: MeasuringContext, baseXPosition: Float, width: Float, ): Float = with(context) { when (this@getTextStartPosition) { - HorizontalPosition.Start -> + Position.Horizontal.Start -> if (isLtr) getTextRightPosition(baseXPosition, width) else getTextLeftPosition(baseXPosition) - HorizontalPosition.Center -> baseXPosition - width.half - HorizontalPosition.End -> + Position.Horizontal.Center -> baseXPosition - width.half + Position.Horizontal.End -> if (isLtr) getTextLeftPosition(baseXPosition) else getTextRightPosition(baseXPosition, width) } } private fun MeasuringContext.getTextLeftPosition(baseXPosition: Float): Float = - baseXPosition + padding.getLeftDp(isLtr).pixels + margins.getLeftDp(isLtr).pixels + baseXPosition + padding.getLeft(this) + margins.getLeft(this) private fun MeasuringContext.getTextRightPosition(baseXPosition: Float, width: Float): Float = - baseXPosition - padding.getRightDp(isLtr).pixels - margins.getRightDp(isLtr).pixels - width + baseXPosition - padding.getRight(this) - margins.getRight(this) - width private fun getTextAlignmentCorrection(width: Float): Float { val ltrAlignment = @@ -240,7 +238,7 @@ public open class TextComponent( } @JvmName("getTextTopPositionExt") - private fun VerticalPosition.getTextTopPosition( + private fun Position.Vertical.getTextTopPosition( context: MeasuringContext, textY: Float, layoutHeight: Float, @@ -248,9 +246,9 @@ public open class TextComponent( with(context) { textY + when (this@getTextTopPosition) { - VerticalPosition.Top -> -layoutHeight - padding.bottomDp.pixels - margins.bottomDp.pixels - VerticalPosition.Center -> -layoutHeight.half - VerticalPosition.Bottom -> padding.topDp.pixels + margins.topDp.pixels + Position.Vertical.Top -> -layoutHeight - padding.bottomDp.pixels - margins.bottomDp.pixels + Position.Vertical.Center -> -layoutHeight.half + Position.Vertical.Bottom -> padding.topDp.pixels + margins.topDp.pixels } } @@ -308,8 +306,6 @@ public open class TextComponent( text: CharSequence? = null, maxWidth: Int = DEF_LAYOUT_SIZE, maxHeight: Int = DEF_LAYOUT_SIZE, - outRect: RectF = tempMeasureBounds, - includePaddingAndMargins: Boolean = true, rotationDegrees: Float = 0f, pad: Boolean = text == null, ): RectF = @@ -322,24 +318,19 @@ public open class TextComponent( } } val layout = getLayout(this, measuredText, maxWidth, maxHeight, rotationDegrees) - layout - .getBounds(outRect) + layout.bounds .apply { val minWidth = minWidth.getValue(context, this@TextComponent, maxWidth, maxHeight, rotationDegrees) - padding.horizontalDp.pixels right = right.coerceAtLeast(minWidth).coerceAtMost(layout.width.toFloat()) - if (includePaddingAndMargins) { - right += padding.horizontalDp.pixels - bottom += padding.verticalDp.pixels - } + right += padding.horizontalDp.pixels + bottom += padding.verticalDp.pixels } .rotate(rotationDegrees) .apply { - if (includePaddingAndMargins) { - right += margins.horizontalDp.pixels - bottom += margins.verticalDp.pixels - } + right += margins.horizontalDp.pixels + bottom += margins.verticalDp.pixels } } @@ -352,8 +343,8 @@ public open class TextComponent( lineHeightSp: Float? = this.lineHeightSp, lineCount: Int = this.lineCount, truncateAt: TextUtils.TruncateAt? = this.truncateAt, - margins: Dimensions = this.margins, - padding: Dimensions = this.padding, + margins: Insets = this.margins, + padding: Insets = this.padding, background: Component? = this.background, minWidth: MinWidth = this.minWidth, ): TextComponent = @@ -507,8 +498,6 @@ public open class TextComponent( } internal class Text(private val text: CharSequence) : MinWidth { - private val bounds = RectF() - override fun getValue( context: MeasuringContext, textComponent: TextComponent, @@ -519,7 +508,7 @@ public open class TextComponent( context.run { textComponent .getLayout(context, text, maxWidth, maxHeight, rotationDegrees) - .getBounds(bounds) + .bounds .width() + textComponent.padding.horizontalDp.pixels } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/CartesianLayerDrawingModel.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/CartesianLayerDrawingModel.kt index 6dbae3373..32ecdd42b 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/CartesianLayerDrawingModel.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/CartesianLayerDrawingModel.kt @@ -19,18 +19,18 @@ package com.patrykandpatrick.vico.core.common.data import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayer /** Houses drawing information for a [CartesianLayer]. */ -public abstract class CartesianLayerDrawingModel( - private val drawingInfo: List> -) : List> by drawingInfo { +public abstract class CartesianLayerDrawingModel( + private val entries: List> +) : List> by entries { /** * Returns an intermediate [CartesianLayerDrawingModel] between this one and [from]. The returned - * drawing model includes the provided [DrawingInfo] list. [fraction] is the balance between - * [from] and this [CartesianLayerDrawingModel], with 0 corresponding to [from], and 1 - * corresponding to this [CartesianLayerDrawingModel]. The returned object should be an instance - * of the [CartesianLayerDrawingModel] subclass to which this function belongs. + * drawing model includes the provided [Entry] list. [fraction] is the balance between [from] and + * this [CartesianLayerDrawingModel], with 0 corresponding to [from], and 1 corresponding to this + * [CartesianLayerDrawingModel]. The returned object should be an instance of the + * [CartesianLayerDrawingModel] subclass to which this function belongs. */ public abstract fun transform( - drawingInfo: List>, + entries: List>, from: CartesianLayerDrawingModel?, fraction: Float, ): CartesianLayerDrawingModel @@ -42,14 +42,14 @@ public abstract class CartesianLayerDrawingModel, > { /** Sets the initial and target [CartesianLayerDrawingModel]s. */ @@ -36,10 +36,7 @@ public interface CartesianLayerDrawingModelInterpolator< /** * Creates an instance of the default [CartesianLayerDrawingModelInterpolator] implementation. */ - public fun < - T : CartesianLayerDrawingModel.DrawingInfo, - R : CartesianLayerDrawingModel, - > default(): CartesianLayerDrawingModelInterpolator = - DefaultCartesianLayerDrawingModelInterpolator() + public fun > default(): + CartesianLayerDrawingModelInterpolator = DefaultCartesianLayerDrawingModelInterpolator() } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/DefaultCartesianLayerDrawingModelInterpolator.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/DefaultCartesianLayerDrawingModelInterpolator.kt index eae51a25d..955030c48 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/DefaultCartesianLayerDrawingModelInterpolator.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/DefaultCartesianLayerDrawingModelInterpolator.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.ensureActive @Suppress("UNCHECKED_CAST") internal class DefaultCartesianLayerDrawingModelInterpolator< - T : CartesianLayerDrawingModel.DrawingInfo, + T : CartesianLayerDrawingModel.Entry, R : CartesianLayerDrawingModel, > : CartesianLayerDrawingModelInterpolator { private var transformationMaps = emptyList>>() @@ -40,12 +40,12 @@ internal class DefaultCartesianLayerDrawingModelInterpolator< override suspend fun transform(fraction: Float): R? = newDrawingModel?.transform( - drawingInfo = + entries = transformationMaps.mapNotNull { map -> map .mapNotNull { (x, model) -> currentCoroutineContext().ensureActive() - model.transform(fraction)?.let { drawingInfo -> x to drawingInfo } + model.transform(fraction)?.let { entry -> x to entry } } .takeIf { list -> list.isNotEmpty() } ?.toMap() @@ -58,18 +58,18 @@ internal class DefaultCartesianLayerDrawingModelInterpolator< transformationMaps = buildList { repeat(max(oldDrawingModel?.size.orZero, newDrawingModel?.size.orZero)) { index -> val map = mutableMapOf>() - oldDrawingModel?.getOrNull(index)?.forEach { (x, drawingInfo) -> - map[x] = TransformationModel(drawingInfo) + oldDrawingModel?.getOrNull(index)?.forEach { (x, entry) -> + map[x] = TransformationModel(entry) } - newDrawingModel?.getOrNull(index)?.forEach { (x, drawingInfo) -> - map[x] = TransformationModel(map[x]?.old, drawingInfo) + newDrawingModel?.getOrNull(index)?.forEach { (x, entry) -> + map[x] = TransformationModel(map[x]?.old, entry) } add(map) } } } - private class TransformationModel( + private class TransformationModel( val old: T?, val new: T? = null, ) { diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/ExtraStore.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/ExtraStore.kt index 7a18784fa..e770e8952 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/ExtraStore.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/data/ExtraStore.kt @@ -28,14 +28,11 @@ public abstract class ExtraStore internal constructor() { /** Returns the value associated with the provided key, or `null` if there’s no such value. */ public fun getOrNull(key: Key): T? = mapDelegate[key] as? T - /** Creates a copy of this [ExtraStore]. */ - public abstract fun copy(): ExtraStore + internal abstract fun copy(): ExtraStore - /** Copies this [ExtraStore]’s content to [destination]. */ - public abstract fun copyContentTo(destination: MutableMap, Any>) + internal abstract fun copyContentTo(destination: MutableMap, Any>) - /** Combines this [ExtraStore] and [other]. */ - public abstract operator fun plus(other: ExtraStore): ExtraStore + internal abstract operator fun plus(other: ExtraStore): ExtraStore override fun equals(other: Any?): Boolean = this === other || other is ExtraStore && mapDelegate == other.mapDelegate @@ -45,9 +42,8 @@ public abstract class ExtraStore internal constructor() { /** Used for writing to and reading from [ExtraStore]s. */ @Suppress("UNUSED") public open class Key - public companion object { - /** An empty [ExtraStore]. */ - public val Empty: ExtraStore = MutableExtraStore() + internal companion object { + val Empty = MutableExtraStore() } } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/VicoBitmapShader.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/BitmapShaderProvider.kt similarity index 92% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/VicoBitmapShader.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/BitmapShaderProvider.kt index 21a2465ab..25fd90bdc 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/VicoBitmapShader.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/BitmapShaderProvider.kt @@ -21,12 +21,12 @@ import android.graphics.BitmapShader import android.graphics.Shader import com.patrykandpatrick.vico.core.common.DrawingContext -internal data class VicoBitmapShader( +internal data class BitmapShaderProvider( private val bitmap: Bitmap, private val xTileMode: Shader.TileMode, private val yTileMode: Shader.TileMode, -) : DynamicShader { - override fun provideShader( +) : ShaderProvider { + override fun getShader( context: DrawingContext, left: Float, top: Float, diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/CacheableDynamicShader.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/CachingShaderProvider.kt similarity index 75% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/CacheableDynamicShader.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/CachingShaderProvider.kt index e4769dbfd..db04093f6 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/CacheableDynamicShader.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/CachingShaderProvider.kt @@ -17,16 +17,15 @@ package com.patrykandpatrick.vico.core.common.shader import android.graphics.Shader +import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.common.DrawingContext -/** - * [CacheableDynamicShader] can cache created [Shader] instances for reuse between identical sets of - * bounds. - */ -public abstract class CacheableDynamicShader : DynamicShader { +/** @suppress */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public abstract class CachingShaderProvider : ShaderProvider { private val cache = HashMap(1) - override fun provideShader( + override fun getShader( context: DrawingContext, left: Float, top: Float, @@ -41,10 +40,6 @@ public abstract class CacheableDynamicShader : DynamicShader { } } - /** - * Called when new instance of [Shader] must be created, as the [left], [top], [right], and - * [bottom] bounds have changed or there is no cached [Shader]. - */ public abstract fun createShader( context: DrawingContext, left: Float, @@ -53,7 +48,6 @@ public abstract class CacheableDynamicShader : DynamicShader { bottom: Float, ): Shader - /** Creates a cache key using the provided [left], [top], [right], and [bottom] bounds. */ protected open fun createKey(left: Float, top: Float, right: Float, bottom: Float): String = "$left,$top,$right,$bottom" } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComponentShader.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComponentShaderProvider.kt similarity index 80% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComponentShader.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComponentShaderProvider.kt index c4cc21ed7..30eba5233 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComponentShader.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComponentShaderProvider.kt @@ -24,13 +24,13 @@ import com.patrykandpatrick.vico.core.common.DrawingContext import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.half -internal data class ComponentShader( +internal data class ComponentShaderProvider( private val component: Component, private val componentSizeDp: Float, - private val checkeredArrangement: Boolean = true, - private val tileXMode: Shader.TileMode = Shader.TileMode.REPEAT, - private val tileYMode: Shader.TileMode = tileXMode, -) : CacheableDynamicShader() { + private val checker: Boolean = true, + private val xTileMode: Shader.TileMode = Shader.TileMode.REPEAT, + private val yTileMode: Shader.TileMode = xTileMode, +) : CachingShaderProvider() { override fun createShader( context: DrawingContext, left: Float, @@ -39,12 +39,11 @@ internal data class ComponentShader( bottom: Float, ): Shader = with(context) { - val size = componentSizeDp.pixels.toInt() * if (checkeredArrangement) 2 else 1 + val size = componentSizeDp.pixels.toInt() * if (checker) 2 else 1 val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - context.withOtherCanvas(canvas) { - if (checkeredArrangement) { + withCanvas(Canvas(bitmap)) { + if (checker) { val halfSize = componentSizeDp.pixels.half with(component) { draw(context, -halfSize, -halfSize, componentSizeDp.pixels) @@ -57,7 +56,7 @@ internal data class ComponentShader( component.draw(context, 0f, 0f, componentSizeDp.pixels, componentSizeDp.pixels) } } - return BitmapShader(bitmap, tileXMode, tileYMode) + return BitmapShader(bitmap, xTileMode, yTileMode) } private fun Component.draw(context: DrawingContext, x: Float, y: Float, size: Float) { diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/VicoComposeShader.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComposeShaderProvider.kt similarity index 84% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/VicoComposeShader.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComposeShaderProvider.kt index a55489cee..8f9144971 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/VicoComposeShader.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ComposeShaderProvider.kt @@ -23,12 +23,12 @@ import android.os.Build import androidx.annotation.RequiresApi import com.patrykandpatrick.vico.core.common.DrawingContext -internal data class VicoComposeShader( - private val first: DynamicShader, - private val second: DynamicShader, +internal data class ComposeShaderProvider( + private val first: ShaderProvider, + private val second: ShaderProvider, private val mode: Mode, -) : DynamicShader { - override fun provideShader( +) : ShaderProvider { + override fun getShader( context: DrawingContext, left: Float, top: Float, @@ -36,8 +36,8 @@ internal data class VicoComposeShader( bottom: Float, ) = mode.createShader( - first.provideShader(context, left, top, right, bottom), - second.provideShader(context, left, top, right, bottom), + first.getShader(context, left, top, right, bottom), + second.getShader(context, left, top, right, bottom), ) interface Mode { diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/DynamicShader.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/DynamicShader.kt deleted file mode 100644 index 869c6da5c..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/DynamicShader.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.common.shader - -import android.graphics.Bitmap -import android.graphics.BlendMode -import android.graphics.ComposeShader -import android.graphics.PorterDuff -import android.graphics.RectF -import android.graphics.Shader -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.compose.runtime.Immutable -import com.patrykandpatrick.vico.core.common.DrawingContext -import com.patrykandpatrick.vico.core.common.component.Component - -/** Creates [Shader]s on demand. */ -@Immutable -public fun interface DynamicShader { - /** Creates a [Shader] by using the provided [bounds]. */ - public fun provideShader(context: DrawingContext, bounds: RectF): Shader = - provideShader( - context = context, - left = bounds.left, - top = bounds.top, - right = bounds.right, - bottom = bounds.bottom, - ) - - /** Creates a [Shader] by using the provided [left], [top], [right], and [bottom] bounds. */ - public fun provideShader( - context: DrawingContext, - left: Float, - top: Float, - right: Float, - bottom: Float, - ): Shader - - public companion object { - /** Creates a [DynamicShader] out of the given [bitmap]. */ - public fun bitmap( - bitmap: Bitmap, - xTileMode: Shader.TileMode = Shader.TileMode.REPEAT, - yTileMode: Shader.TileMode = xTileMode, - ): DynamicShader = VicoBitmapShader(bitmap, xTileMode, yTileMode) - - /** - * Creates a [ComposeShader] out of two [DynamicShader]s, combining [first] and [second] via - * [mode]. - */ - @RequiresApi(Build.VERSION_CODES.Q) - public fun compose( - first: DynamicShader, - second: DynamicShader, - mode: BlendMode, - ): DynamicShader = VicoComposeShader(first, second, VicoComposeShader.Mode.Blend(mode)) - - /** - * Creates a [ComposeShader] out of two [DynamicShader]s, combining [first] and [second] via - * [mode]. - */ - public fun compose( - first: DynamicShader, - second: DynamicShader, - mode: PorterDuff.Mode, - ): DynamicShader = VicoComposeShader(first, second, VicoComposeShader.Mode.PorterDuff(mode)) - - /** - * Creates a [DynamicShader] in the form of a horizontal gradient. - * - * @param colors the sRGB colors to be distributed along the gradient line. - */ - public fun horizontalGradient(vararg colors: Int): DynamicShader = horizontalGradient(colors) - - /** - * Creates a [DynamicShader] in the form of a horizontal gradient. - * - * @param colors the sRGB colors to be distributed along the gradient line. - * @param positions controls the position of each color on the gradient line. Each element of - * the array should belong to the interval [[0, 1]], where 0 corresponds to the start of the - * gradient line, and 1 corresponds to the end of the gradient line. If `null` (the default - * value) is passed, the colors will be distributed evenly along the gradient line. - */ - public fun horizontalGradient(colors: IntArray, positions: FloatArray? = null): DynamicShader = - LinearGradientShader(colors, positions, true) - - /** - * Creates a [DynamicShader] in the form of a vertical gradient. - * - * @param colors the sRGB colors to be distributed along the gradient line. - */ - public fun verticalGradient(vararg colors: Int): DynamicShader = verticalGradient(colors) - - /** - * Creates a [DynamicShader] in the form of a vertical gradient. - * - * @param colors the sRGB colors to be distributed along the gradient line. - * @param positions controls the position of each color on the gradient line. Each element of - * the array should belong to the interval [[0, 1]], where 0 corresponds to the start of the - * gradient line, and 1 corresponds to the end of the gradient line. If `null` (the default - * value) is passed, the colors will be distributed evenly along the gradient line. - */ - public fun verticalGradient(colors: IntArray, positions: FloatArray? = null): DynamicShader = - LinearGradientShader(colors, positions, false) - - /** - * Creates a [DynamicShader] that repeatedly draws [component] in a grid or checkered pattern. - */ - public fun component( - component: Component, - componentSizeDp: Float, - checkeredArrangement: Boolean = true, - tileXMode: Shader.TileMode = Shader.TileMode.REPEAT, - tileYMode: Shader.TileMode = tileXMode, - ): DynamicShader = - ComponentShader(component, componentSizeDp, checkeredArrangement, tileXMode, tileYMode) - } -} - -/** Converts this [Shader] to a [DynamicShader]. */ -public fun Shader.toDynamicShader(): DynamicShader = DynamicShader { _, _, _, _, _ -> this } diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/LinearGradientShader.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/LinearGradientShaderProvider.kt similarity index 94% rename from vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/LinearGradientShader.kt rename to vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/LinearGradientShaderProvider.kt index 047a1fd0c..14245fb2a 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/LinearGradientShader.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/LinearGradientShaderProvider.kt @@ -24,11 +24,11 @@ import java.util.Objects /** @suppress */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public class LinearGradientShader( +public class LinearGradientShaderProvider( private val colors: IntArray, private val positions: FloatArray?, private val isHorizontal: Boolean, -) : CacheableDynamicShader() { +) : CachingShaderProvider() { override fun createShader( context: DrawingContext, left: Float, @@ -47,7 +47,7 @@ public class LinearGradientShader( override fun equals(other: Any?): Boolean = this === other || - other is LinearGradientShader && + other is LinearGradientShaderProvider && colors.contentEquals(other.colors) && positions.contentEquals(other.positions) diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ShaderProvider.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ShaderProvider.kt new file mode 100644 index 000000000..73d7cf4ba --- /dev/null +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shader/ShaderProvider.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2024 by Patryk Goworowski and Patrick Michalik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.patrykandpatrick.vico.core.common.shader + +import android.graphics.Bitmap +import android.graphics.BitmapShader +import android.graphics.BlendMode +import android.graphics.ComposeShader +import android.graphics.PorterDuff +import android.graphics.RectF +import android.graphics.Shader +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Immutable +import com.patrykandpatrick.vico.core.common.DrawingContext +import com.patrykandpatrick.vico.core.common.component.Component + +/** Creates [Shader]s on demand. */ +@Immutable +public fun interface ShaderProvider { + /** Returns a [Shader] for the given bounds. */ + public fun getShader( + context: DrawingContext, + left: Float, + top: Float, + right: Float, + bottom: Float, + ): Shader + + public companion object { + /** Creates a [BitmapShader]-producing [ShaderProvider]. */ + public fun bitmap( + bitmap: Bitmap, + xTileMode: Shader.TileMode = Shader.TileMode.REPEAT, + yTileMode: Shader.TileMode = xTileMode, + ): ShaderProvider = BitmapShaderProvider(bitmap, xTileMode, yTileMode) + + /** Creates a [ComposeShader]-producing [ShaderProvider]. */ + @RequiresApi(Build.VERSION_CODES.Q) + public fun compose( + first: ShaderProvider, + second: ShaderProvider, + mode: BlendMode, + ): ShaderProvider = ComposeShaderProvider(first, second, ComposeShaderProvider.Mode.Blend(mode)) + + /** Creates a [ComposeShader]-producing [ShaderProvider]. */ + public fun compose( + first: ShaderProvider, + second: ShaderProvider, + mode: PorterDuff.Mode, + ): ShaderProvider = + ComposeShaderProvider(first, second, ComposeShaderProvider.Mode.PorterDuff(mode)) + + /** Creates a [ShaderProvider] that produces a horizontal gradient. */ + public fun horizontalGradient(vararg colors: Int): ShaderProvider = horizontalGradient(colors) + + /** + * Creates a [ShaderProvider] that produces a horizontal gradient. [positions] specifies the + * color-stop offsets (between 0 and 1), with `null` giving an even distribution. + */ + public fun horizontalGradient(colors: IntArray, positions: FloatArray? = null): ShaderProvider = + LinearGradientShaderProvider(colors, positions, true) + + /** Creates a [ShaderProvider] that produces a vertical gradient. */ + public fun verticalGradient(vararg colors: Int): ShaderProvider = verticalGradient(colors) + + /** + * Creates a [ShaderProvider] that produces a vertical gradient. [positions] specifies the + * color-stop offsets (between 0 and 1), with `null` giving an even distribution. + */ + public fun verticalGradient(colors: IntArray, positions: FloatArray? = null): ShaderProvider = + LinearGradientShaderProvider(colors, positions, false) + + /** + * Creates a [ShaderProvider] that produces a [Shader] wherein [component] is repeatedly drawn + * in a grid or checkered pattern. + */ + public fun component( + component: Component, + componentSizeDp: Float, + checker: Boolean = true, + xTileMode: Shader.TileMode = Shader.TileMode.REPEAT, + yTileMode: Shader.TileMode = xTileMode, + ): ShaderProvider = + ComponentShaderProvider(component, componentSizeDp, checker, xTileMode, yTileMode) + } +} + +/** Converts this [Shader] to a [ShaderProvider]. */ +public fun Shader.toShaderProvider(): ShaderProvider = ShaderProvider { _, _, _, _, _ -> this } + +internal fun ShaderProvider.getShader(context: DrawingContext, bounds: RectF) = + getShader(context, bounds.left, bounds.top, bounds.right, bounds.bottom) diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/Corner.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/Corner.kt deleted file mode 100644 index 80f93d63d..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/Corner.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.common.shape - -/** - * A class used to specify the size and look of a given shape corner. - * - * @param cornerTreatment affects the final appearance of the corner. - */ -public sealed class Corner(public val cornerTreatment: CornerTreatment) { - /** - * Calculates the size of the corner. - * - * @param availableCornerSize the available space that this corner can take. - * @param density the density of the screen (used in pixel size calculation). - * @return the size of the corner (in pixels). - */ - public abstract fun getCornerSize(availableCornerSize: Float, density: Float): Float - - /** - * Defines an absolute size for a corner (in dp). - * - * @param sizeDp the size of the corner (in dp). - */ - public class Absolute(public val sizeDp: Float, cornerTreatment: CornerTreatment) : - Corner(cornerTreatment) { - override fun getCornerSize(availableCornerSize: Float, density: Float): Float = sizeDp * density - } - - /** - * Defines a relative size for a corner (in percent). - * - * @param percentage the percentage of the space available for the corner that will be used as its - * size. - */ - public class Relative(public val percentage: Int, cornerTreatment: CornerTreatment) : - Corner(cornerTreatment) { - init { - if (percentage !in 0..MAX_PERCENTAGE) { - throw IllegalArgumentException("Expected a percentage (0-100), got $percentage.") - } - } - - override fun getCornerSize(availableCornerSize: Float, density: Float): Float = - availableCornerSize / MAX_PERCENTAGE * percentage - } - - public companion object { - private const val MAX_PERCENTAGE = 100 - - /** A [Corner] that is completely rounded. */ - public val FullyRounded: Corner = Relative(MAX_PERCENTAGE, RoundedCornerTreatment) - - /** A sharp [Corner]. */ - public val Sharp: Corner = Relative(0, SharpCornerTreatment) - } -} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CornerLocation.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CornerLocation.kt deleted file mode 100644 index b2e24abee..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CornerLocation.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.common.shape - -/** Defines the location of a shape corner. */ -public enum class CornerLocation { - TopLeft, - TopRight, - BottomRight, - BottomLeft, -} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CornerTreatment.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CornerTreatment.kt deleted file mode 100644 index 8bc24fc86..000000000 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CornerTreatment.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2024 by Patryk Goworowski and Patrick Michalik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.patrykandpatrick.vico.core.common.shape - -import android.graphics.Path -import android.graphics.RectF -import com.patrykandpatrick.vico.core.common.piRad - -/** Defines a shape corner style. */ -public interface CornerTreatment { - /** - * Draws a shape corner of the appropriate style. - * - * @param x1 the horizontal coordinate of the starting point. - * @param y1 the vertical coordinate of the starting point. - * @param x2 the horizontal coordinate of the end point. - * @param y2 the vertical coordinate of the end point. - * @param cornerLocation the location of the corner. - * @path the [Path] to use to draw the corner. - */ - public fun createCorner( - x1: Float, - y1: Float, - x2: Float, - y2: Float, - cornerLocation: CornerLocation, - path: Path, - ) -} - -/** Creates sharp corners. */ -public object SharpCornerTreatment : CornerTreatment { - public override fun createCorner( - x1: Float, - y1: Float, - x2: Float, - y2: Float, - cornerLocation: CornerLocation, - path: Path, - ): Unit = - when (cornerLocation) { - CornerLocation.TopLeft -> { - path.lineTo(x1, y2) - } - CornerLocation.TopRight -> { - path.lineTo(x2, y1) - } - CornerLocation.BottomRight -> { - path.lineTo(x1, y2) - } - CornerLocation.BottomLeft -> { - path.lineTo(x2, y1) - } - } -} - -/** Creates cut corners. */ -public object CutCornerTreatment : CornerTreatment { - override fun createCorner( - x1: Float, - y1: Float, - x2: Float, - y2: Float, - cornerLocation: CornerLocation, - path: Path, - ) { - path.lineTo(x1, y1) - path.lineTo(x2, y2) - } -} - -/** Creates rounded corners. */ -public object RoundedCornerTreatment : CornerTreatment { - private val tempRect = RectF() - - override fun createCorner( - x1: Float, - y1: Float, - x2: Float, - y2: Float, - cornerLocation: CornerLocation, - path: Path, - ) { - val startAngle: Float - when (cornerLocation) { - CornerLocation.TopLeft -> { - startAngle = 1f.piRad - tempRect.set(x1, y2, x2 * 2 - x1, y1 * 2 - y2) - } - CornerLocation.TopRight -> { - startAngle = 1.5f.piRad - tempRect.set(x1 * 2 - x2, y1, x2, y2 * 2 - y1) - } - CornerLocation.BottomRight -> { - startAngle = 0f - tempRect.set(x2 * 2 - x1, y1 * 2 - y2, x1, y2) - } - CornerLocation.BottomLeft -> { - startAngle = 0.5f.piRad - tempRect.set(x2, y2 * 2 - y1, x1 * 2 - x2, y1) - } - } - path.arcTo(tempRect, startAngle, 0.5f.piRad) - } -} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CorneredShape.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CorneredShape.kt index e14eefb5b..65affdfc0 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CorneredShape.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/CorneredShape.kt @@ -17,8 +17,13 @@ package com.patrykandpatrick.vico.core.common.shape import android.graphics.Path +import android.graphics.RectF +import androidx.annotation.IntRange import androidx.annotation.RestrictTo import com.patrykandpatrick.vico.core.common.MeasuringContext +import com.patrykandpatrick.vico.core.common.piRad +import com.patrykandpatrick.vico.core.common.shape.CorneredShape.Corner +import com.patrykandpatrick.vico.core.common.shape.CorneredShape.CornerShape import kotlin.math.absoluteValue /** @@ -40,10 +45,10 @@ public open class CorneredShape( protected fun getCornerScale(width: Float, height: Float, density: Float): Float { val availableSize = minOf(width, height) - val tL = topLeft.getCornerSize(availableSize, density) - val tR = topRight.getCornerSize(availableSize, density) - val bR = bottomRight.getCornerSize(availableSize, density) - val bL = bottomLeft.getCornerSize(availableSize, density) + val tL = topLeft.getSize(availableSize, density) + val tR = topRight.getSize(availableSize, density) + val bR = bottomRight.getSize(availableSize, density) + val bL = bottomLeft.getSize(availableSize, density) return minOf( width / (tL + tR).nonZero, width / (bL + bR).nonZero, @@ -68,49 +73,49 @@ public open class CorneredShape( val size = minOf(width, height).absoluteValue val scale = getCornerScale(width, height, density).coerceAtMost(1f) - val tL = topLeft.getCornerSize(size, density) * scale - val tR = topRight.getCornerSize(size, density) * scale - val bR = bottomRight.getCornerSize(size, density) * scale - val bL = bottomLeft.getCornerSize(size, density) * scale + val tL = topLeft.getSize(size, density) * scale + val tR = topRight.getSize(size, density) * scale + val bR = bottomRight.getSize(size, density) * scale + val bL = bottomLeft.getSize(size, density) * scale path.moveTo(left, top + tL) - topLeft.cornerTreatment.createCorner( + topLeft.shape.create( + path = path, + position = CornerPosition.TopLeft, x1 = left, y1 = top + tL, x2 = left + tL, y2 = top, - cornerLocation = CornerLocation.TopLeft, - path, ) path.lineTo(right - tR, top) - topRight.cornerTreatment.createCorner( + topRight.shape.create( + path = path, + position = CornerPosition.TopRight, x1 = right - tR, y1 = top, x2 = right, y2 = top + tR, - cornerLocation = CornerLocation.TopRight, - path, ) path.lineTo(right, bottom - bR) - bottomRight.cornerTreatment.createCorner( + bottomRight.shape.create( + path = path, + position = CornerPosition.BottomRight, x1 = right, y1 = bottom - bR, x2 = right - bR, y2 = bottom, - cornerLocation = CornerLocation.BottomRight, - path, ) path.lineTo(left + bL, bottom) - bottomLeft.cornerTreatment.createCorner( + bottomLeft.shape.create( + path = path, + position = CornerPosition.BottomLeft, x1 = left + bL, y1 = bottom, x2 = left, y2 = bottom - bL, - cornerLocation = CornerLocation.BottomLeft, - path, ) path.close() } @@ -142,6 +147,81 @@ public open class CorneredShape( return result } + /** Denotes a corner position. */ + public enum class CornerPosition { + TopLeft, + TopRight, + BottomRight, + BottomLeft, + } + + /** Defines a corner shape. */ + public fun interface CornerShape { + /** Connects ([x1], [y1]) and ([x2], [y2]) to create a corner. */ + public fun create( + path: Path, + position: CornerPosition, + x1: Float, + y1: Float, + x2: Float, + y2: Float, + ) + + /** Houses [CornerShape] singletons. */ + public companion object { + /** Produces sharp corners. */ + public val Sharp: CornerShape = CornerShape { path, position, x1, y1, x2, y2 -> + with(path) { + when (position) { + CornerPosition.TopLeft -> lineTo(x1, y2) + CornerPosition.TopRight -> lineTo(x2, y1) + CornerPosition.BottomRight -> lineTo(x1, y2) + CornerPosition.BottomLeft -> lineTo(x2, y1) + } + } + } + + /** Produces rounded corners. */ + public val Rounded: CornerShape = RoundedCornerShape + + /** Produces cut corners. */ + public val Cut: CornerShape = CornerShape { path, _, x1, y1, x2, y2 -> + path.lineTo(x1, y1) + path.lineTo(x2, y2) + } + } + } + + /** Defines a corner style. */ + public sealed class Corner(internal val shape: CornerShape) { + internal abstract fun getSize(availableCornerSize: Float, density: Float): Float + + /** Produces absolutely sized corners. */ + public class Absolute(private val sizeDp: Float, shape: CornerShape) : Corner(shape) { + override fun getSize(availableCornerSize: Float, density: Float) = sizeDp * density + } + + /** Produces relatively sized corners. */ + public class Relative(@IntRange(0, 100) private val percent: Int, shape: CornerShape) : + Corner(shape) { + init { + require(percent in 0..100) { "`percent` must be in [0, 100]." } + } + + override fun getSize(availableCornerSize: Float, density: Float) = + availableCornerSize / 100 * percent + } + + /** Houses [Corner] singletons. */ + public companion object { + /** Produces sharp corners. */ + public val Sharp: Corner = Absolute(sizeDp = 0f, shape = CornerShape.Sharp) + + /** Produces fully rounded corners. */ + public val Rounded: Corner = Relative(percent = 100, shape = CornerShape.Rounded) + } + } + /** Houses [CorneredShape] singletons and factory functions. */ public companion object { /** A [CorneredShape] with fully rounded corners. */ @@ -155,10 +235,10 @@ public open class CorneredShape( bottomLeftDp: Float = 0f, ): CorneredShape = CorneredShape( - Corner.Absolute(topLeftDp, RoundedCornerTreatment), - Corner.Absolute(topRightDp, RoundedCornerTreatment), - Corner.Absolute(bottomRightDp, RoundedCornerTreatment), - Corner.Absolute(bottomLeftDp, RoundedCornerTreatment), + Corner.Absolute(topLeftDp, CornerShape.Rounded), + Corner.Absolute(topRightDp, CornerShape.Rounded), + Corner.Absolute(bottomRightDp, CornerShape.Rounded), + Corner.Absolute(bottomLeftDp, CornerShape.Rounded), ) /** Creates a [CorneredShape] with rounded corners of the provided radius. */ @@ -172,10 +252,10 @@ public open class CorneredShape( bottomLeftPercent: Int = 0, ): CorneredShape = CorneredShape( - Corner.Relative(topLeftPercent, RoundedCornerTreatment), - Corner.Relative(topRightPercent, RoundedCornerTreatment), - Corner.Relative(bottomRightPercent, RoundedCornerTreatment), - Corner.Relative(bottomLeftPercent, RoundedCornerTreatment), + Corner.Relative(topLeftPercent, CornerShape.Rounded), + Corner.Relative(topRightPercent, CornerShape.Rounded), + Corner.Relative(bottomRightPercent, CornerShape.Rounded), + Corner.Relative(bottomLeftPercent, CornerShape.Rounded), ) /** Creates a [CorneredShape] with rounded corners of the provided radius. */ @@ -190,10 +270,10 @@ public open class CorneredShape( bottomLeftDp: Float = 0f, ): CorneredShape = CorneredShape( - Corner.Absolute(topLeftDp, CutCornerTreatment), - Corner.Absolute(topRightDp, CutCornerTreatment), - Corner.Absolute(bottomRightDp, CutCornerTreatment), - Corner.Absolute(bottomLeftDp, CutCornerTreatment), + Corner.Absolute(topLeftDp, CornerShape.Cut), + Corner.Absolute(topRightDp, CornerShape.Cut), + Corner.Absolute(bottomRightDp, CornerShape.Cut), + Corner.Absolute(bottomLeftDp, CornerShape.Cut), ) /** Creates a [CorneredShape] with cut corners of the provided size. */ @@ -207,10 +287,10 @@ public open class CorneredShape( bottomLeftPercent: Int = 0, ): CorneredShape = CorneredShape( - Corner.Relative(topLeftPercent, CutCornerTreatment), - Corner.Relative(topRightPercent, CutCornerTreatment), - Corner.Relative(bottomRightPercent, CutCornerTreatment), - Corner.Relative(bottomLeftPercent, CutCornerTreatment), + Corner.Relative(topLeftPercent, CornerShape.Cut), + Corner.Relative(topRightPercent, CornerShape.Cut), + Corner.Relative(bottomRightPercent, CornerShape.Cut), + Corner.Relative(bottomLeftPercent, CornerShape.Cut), ) /** Creates a [CorneredShape] with cut corners of the provided size. */ @@ -218,3 +298,37 @@ public open class CorneredShape( cut(allPercent, allPercent, allPercent, allPercent) } } + +private object RoundedCornerShape : CornerShape { + private val bounds = RectF() + + override fun create( + path: Path, + position: CorneredShape.CornerPosition, + x1: Float, + y1: Float, + x2: Float, + y2: Float, + ) { + val startAngle: Float + when (position) { + CorneredShape.CornerPosition.TopLeft -> { + startAngle = 1f.piRad + bounds.set(x1, y2, 2 * x2 - x1, 2 * y1 - y2) + } + CorneredShape.CornerPosition.TopRight -> { + startAngle = 1.5f.piRad + bounds.set(2 * x1 - x2, y1, x2, 2 * y2 - y1) + } + CorneredShape.CornerPosition.BottomRight -> { + startAngle = 0f + bounds.set(2 * x2 - x1, 2 * y1 - y2, x1, y2) + } + CorneredShape.CornerPosition.BottomLeft -> { + startAngle = 0.5f.piRad + bounds.set(x2, 2 * y2 - y1, 2 * x1 - x2, y1) + } + } + path.arcTo(bounds, startAngle, 0.5f.piRad) + } +} diff --git a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/MarkerCorneredShape.kt b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/MarkerCorneredShape.kt index 8974a6424..fa72580a2 100644 --- a/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/MarkerCorneredShape.kt +++ b/vico/core/src/main/java/com/patrykandpatrick/vico/core/common/shape/MarkerCorneredShape.kt @@ -23,14 +23,9 @@ import com.patrykandpatrick.vico.core.common.doubled import com.patrykandpatrick.vico.core.common.half /** - * [MarkerCorneredShape] is an extension of [CorneredShape] that supports drawing a triangular tick - * at a given point. + * A [CorneredShape] extension that additionally draws a triangular tick. * - * @param topLeft specifies a [Corner] for the top left of the [Shape]. - * @param topRight specifies a [Corner] for the top right of the [Shape]. - * @param bottomLeft specifies a [Corner] for the bottom left of the [Shape]. - * @param bottomRight specifies a [Corner] for the bottom right of the [Shape]. - * @param tickSizeDp the size of the tick (in dp). + * @property tickSizeDp the size of the tick (in dp). */ public open class MarkerCorneredShape( topLeft: Corner, @@ -51,13 +46,13 @@ public open class MarkerCorneredShape( ) : this(all, all, all, all, tickSizeDp) public constructor( - corneredShape: CorneredShape, + base: CorneredShape, tickSizeDp: Float = MARKER_TICK_SIZE, ) : this( - topLeft = corneredShape.topLeft, - topRight = corneredShape.topRight, - bottomRight = corneredShape.bottomRight, - bottomLeft = corneredShape.bottomLeft, + topLeft = base.topLeft, + topRight = base.topRight, + bottomRight = base.bottomRight, + bottomLeft = base.bottomLeft, tickSizeDp = tickSizeDp, ) @@ -78,8 +73,8 @@ public open class MarkerCorneredShape( val availableCornerSize = minOf(right - left, bottom - top) val cornerScale = getCornerScale(right - left, bottom - top, density) - val minLeft = left + bottomLeft.getCornerSize(availableCornerSize, density) * cornerScale - val maxLeft = right - bottomRight.getCornerSize(availableCornerSize, density) * cornerScale + val minLeft = left + bottomLeft.getSize(availableCornerSize, density) * cornerScale + val maxLeft = right - bottomRight.getSize(availableCornerSize, density) * cornerScale val coercedTickSize = tickSize.coerceAtMost((maxLeft - minLeft).half.coerceAtLeast(0f)) diff --git a/vico/core/src/test/java/com/patrykandpatrick/vico/core/cartesian/CartesianChartTest.kt b/vico/core/src/test/java/com/patrykandpatrick/vico/core/cartesian/CartesianChartTest.kt index 4b8e8466b..4370caf68 100644 --- a/vico/core/src/test/java/com/patrykandpatrick/vico/core/cartesian/CartesianChartTest.kt +++ b/vico/core/src/test/java/com/patrykandpatrick/vico/core/cartesian/CartesianChartTest.kt @@ -21,6 +21,7 @@ import android.graphics.RectF import android.graphics.Xfermode import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/CartesianChartView.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/CartesianChartView.kt index 14feedbee..c9321152f 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/CartesianChartView.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/CartesianChartView.kt @@ -18,6 +18,9 @@ package com.patrykandpatrick.vico.views.cartesian import android.content.Context import android.graphics.Canvas +import android.os.Build +import android.os.Bundle +import android.os.Parcelable import android.util.AttributeSet import android.view.MotionEvent import android.view.ScaleGestureDetector @@ -25,9 +28,8 @@ import android.view.View import android.widget.OverScroller import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianDrawingContext -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerPadding +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.MutableCartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions import com.patrykandpatrick.vico.core.cartesian.Scroll import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer @@ -35,6 +37,7 @@ import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartRanges import com.patrykandpatrick.vico.core.cartesian.data.MutableCartesianChartRanges import com.patrykandpatrick.vico.core.cartesian.data.RandomCartesianModelGenerator import com.patrykandpatrick.vico.core.cartesian.data.toImmutable +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding import com.patrykandpatrick.vico.core.common.Defaults import com.patrykandpatrick.vico.core.common.NEW_PRODUCER_ERROR_MESSAGE import com.patrykandpatrick.vico.core.common.Point @@ -84,6 +87,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 zoomEnabled = false, layerPadding = CartesianLayerPadding(), spToPx = context::spToPx, + pointerPosition = null, ) private val scaleGestureListener: ScaleGestureDetector.OnScaleGestureListener = @@ -91,11 +95,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 private val scaleGestureDetector = ScaleGestureDetector(context, scaleGestureListener) - private var markerTouchPoint: Point? = null - private var scrollDirectionResolved = false - private var horizontalDimensions = MutableHorizontalDimensions() + private val layerDimensions = MutableCartesianLayerDimensions() private var previousLayerPaddingHashCode: Int? = null @@ -306,7 +308,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 } private fun handleTouchEvent(point: Point?) { - markerTouchPoint = point + measuringContext.pointerPosition = point } override fun dispatchDraw(canvas: Canvas) { @@ -314,8 +316,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 withChartAndModel { chart, model -> measuringContext.reset() - horizontalDimensions.clear() - chart.prepare(measuringContext, horizontalDimensions, canvasBounds) + layerDimensions.clear() + chart.prepare(measuringContext, layerDimensions) if (chart.layerBounds.isEmpty) return@withChartAndModel @@ -325,21 +327,20 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 postInvalidateOnAnimation() } - zoomHandler.update(measuringContext, horizontalDimensions, chart.layerBounds) - scrollHandler.update(measuringContext, chart.layerBounds, horizontalDimensions) + zoomHandler.update(measuringContext, layerDimensions, chart.layerBounds) + scrollHandler.update(measuringContext, chart.layerBounds, layerDimensions) val drawingContext = CartesianDrawingContext( - measuringContext = measuringContext, - canvas = canvas, - markerTouchPoint = markerTouchPoint, - horizontalDimensions = horizontalDimensions, - layerBounds = chart.layerBounds, - scroll = scrollHandler.value, - zoom = zoomHandler.value, + measuringContext, + canvas, + layerDimensions, + chart.layerBounds, + scrollHandler.value, + zoomHandler.value, ) - chart.draw(drawingContext, markerTouchPoint) + chart.draw(drawingContext) measuringContext.reset() } } @@ -347,6 +348,28 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 override fun getChartDesiredHeight(widthMeasureSpec: Int, heightMeasureSpec: Int): Int = Defaults.CHART_HEIGHT.dpInt + override fun onSaveInstanceState(): Parcelable = + Bundle().apply { + scrollHandler.saveInstanceState(this) + zoomHandler.saveInstanceState(this) + putParcelable(SUPER_STATE_KEY, super.onSaveInstanceState()) + } + + override fun onRestoreInstanceState(state: Parcelable?) { + var superState = state + if (state is Bundle) { + scrollHandler.restoreInstanceState(state) + zoomHandler.restoreInstanceState(state) + superState = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + state.getParcelable(SUPER_STATE_KEY, BaseSavedState::class.java) + } else { + @Suppress("DEPRECATION") state.getParcelable(SUPER_STATE_KEY) + } + } + super.onRestoreInstanceState(superState) + } + private inline fun withChartAndModel( block: (chart: CartesianChart, model: CartesianChartModel) -> Unit ) { @@ -354,6 +377,10 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 val model = model ?: return block(chart, model) } + + private companion object { + const val SUPER_STATE_KEY = "superState" + } } internal val MotionEvent.movedXDistance: Float diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ScrollHandler.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ScrollHandler.kt index a6306965c..5ce747b9e 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ScrollHandler.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ScrollHandler.kt @@ -24,11 +24,11 @@ import android.view.animation.AccelerateDecelerateInterpolator import com.patrykandpatrick.vico.core.cartesian.AutoScrollCondition import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.HorizontalDimensions import com.patrykandpatrick.vico.core.cartesian.Scroll import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModel import com.patrykandpatrick.vico.core.cartesian.getDelta import com.patrykandpatrick.vico.core.cartesian.getMaxScrollDistance +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerDimensions import com.patrykandpatrick.vico.core.common.Animation import com.patrykandpatrick.vico.core.common.rangeWith @@ -54,7 +54,7 @@ public class ScrollHandler( private val scrollListeners = mutableSetOf() private var initialScrollHandled = false private var context: CartesianMeasuringContext? = null - private var horizontalDimensions: HorizontalDimensions? = null + private var layerDimensions: CartesianLayerDimensions? = null private var bounds: RectF? = null internal var postInvalidate: (() -> Unit)? = null internal var postInvalidateOnAnimation: (() -> Unit)? = null @@ -86,26 +86,26 @@ public class ScrollHandler( internal fun update( context: CartesianMeasuringContext, bounds: RectF, - horizontalDimensions: HorizontalDimensions, + layerDimensions: CartesianLayerDimensions, ) { this.context = context - this.horizontalDimensions = horizontalDimensions + this.layerDimensions = layerDimensions this.bounds = bounds - maxValue = context.getMaxScrollDistance(bounds.width(), horizontalDimensions) + maxValue = context.getMaxScrollDistance(bounds.width(), layerDimensions) if (!initialScrollHandled) { - value = initialScroll.getValue(context, horizontalDimensions, bounds, maxValue) + value = initialScroll.getValue(context, layerDimensions, bounds, maxValue) initialScrollHandled = true } } private inline fun withUpdated( - block: (CartesianMeasuringContext, HorizontalDimensions, RectF) -> Unit + block: (CartesianMeasuringContext, CartesianLayerDimensions, RectF) -> Unit ) { val context = this.context - val horizontalDimensions = this.horizontalDimensions + val layerDimensions = this.layerDimensions val bounds = this.bounds - if (context != null && horizontalDimensions != null && bounds != null) { - block(context, horizontalDimensions, bounds) + if (context != null && layerDimensions != null && bounds != null) { + block(context, layerDimensions, bounds) } } @@ -113,8 +113,8 @@ public class ScrollHandler( delta > 0 && value < maxValue || delta == 0f || delta < 0 && value > 0 internal fun autoScroll(model: CartesianChartModel, oldModel: CartesianChartModel?) { - if (!autoScrollCondition.shouldPerformAutoScroll(model, oldModel)) return - animateScroll(autoScroll, autoScrollInterpolator, autoScrollDuration) + if (!autoScrollCondition.shouldScroll(oldModel, model)) return + animateScroll(autoScroll, autoScrollDuration, autoScrollInterpolator) } internal fun saveInstanceState(bundle: Bundle) { @@ -129,7 +129,7 @@ public class ScrollHandler( internal fun clearUpdated() { context = null - horizontalDimensions = null + layerDimensions = null bounds = null postInvalidate = null postInvalidateOnAnimation = null @@ -142,7 +142,7 @@ public class ScrollHandler( return value - oldValue } - private fun animateScrollBy(delta: Float, interpolator: TimeInterpolator, duration: Long): Float { + private fun animateScrollBy(delta: Float, duration: Long, interpolator: TimeInterpolator): Float { val oldValue = value val limitedDelta = delta.coerceIn((-value).rangeWith(maxValue - value)) with(animator) { @@ -161,22 +161,22 @@ public class ScrollHandler( /** Triggers a scroll. */ public fun scroll(scroll: Scroll) { - withUpdated { context, horizontalDimensions, bounds -> - scrollBy(scroll.getDelta(context, horizontalDimensions, bounds, maxValue, value)) + withUpdated { context, layerDimensions, bounds -> + scrollBy(scroll.getDelta(context, layerDimensions, bounds, maxValue, value)) } } /** Triggers an animated scroll. */ public fun animateScroll( scroll: Scroll, - interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(), duration: Long = Animation.DIFF_DURATION.toLong(), + interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(), ) { - withUpdated { context, horizontalDimensions, bounds -> + withUpdated { context, layerDimensions, bounds -> animateScrollBy( - scroll.getDelta(context, horizontalDimensions, bounds, maxValue, value), - interpolator, + scroll.getDelta(context, layerDimensions, bounds, maxValue, value), duration, + interpolator, ) } } @@ -195,10 +195,10 @@ public class ScrollHandler( /** Facilitates listening for scroll events. */ public interface Listener { /** Called when the scroll value changes. */ - public fun onValueChanged(oldValue: Float, newValue: Float) {} + public fun onValueChanged(old: Float, new: Float) {} /** Called when the maximum scroll value changes. */ - public fun onMaxValueChanged(oldMaxValue: Float, newMaxValue: Float) {} + public fun onMaxValueChanged(old: Float, new: Float) {} } private companion object { diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ZoomHandler.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ZoomHandler.kt index 382a6842e..3bbf28ccf 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ZoomHandler.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/cartesian/ZoomHandler.kt @@ -20,7 +20,7 @@ import android.graphics.RectF import android.os.Bundle import com.patrykandpatrick.vico.core.cartesian.CartesianChart import com.patrykandpatrick.vico.core.cartesian.CartesianMeasuringContext -import com.patrykandpatrick.vico.core.cartesian.MutableHorizontalDimensions +import com.patrykandpatrick.vico.core.cartesian.MutableCartesianLayerDimensions import com.patrykandpatrick.vico.core.cartesian.Scroll import com.patrykandpatrick.vico.core.cartesian.Zoom import com.patrykandpatrick.vico.core.cartesian.scale @@ -36,9 +36,9 @@ import com.patrykandpatrick.vico.core.common.Defaults */ public class ZoomHandler( internal val zoomEnabled: Boolean = true, - private val initialZoom: Zoom = Zoom.max(Zoom.static(), Zoom.Content), + private val initialZoom: Zoom = Zoom.max(Zoom.fixed(), Zoom.Content), private val minZoom: Zoom = Zoom.Content, - private val maxZoom: Zoom = Zoom.max(Zoom.static(Defaults.MAX_ZOOM), Zoom.Content), + private val maxZoom: Zoom = Zoom.max(Zoom.fixed(Defaults.MAX_ZOOM), Zoom.Content), ) { private var overridden = false private val listeners = mutableSetOf() @@ -63,14 +63,14 @@ public class ZoomHandler( internal fun update( context: CartesianMeasuringContext, - horizontalDimensions: MutableHorizontalDimensions, + layerDimensions: MutableCartesianLayerDimensions, bounds: RectF, ) { - val minValue = minZoom.getValue(context, horizontalDimensions, bounds) - val maxValue = maxZoom.getValue(context, horizontalDimensions, bounds) + val minValue = minZoom.getValue(context, layerDimensions, bounds) + val maxValue = maxZoom.getValue(context, layerDimensions, bounds) valueRange = minValue..maxValue - if (!overridden) value = initialZoom.getValue(context, horizontalDimensions, bounds) - horizontalDimensions.scale(value) + if (!overridden) value = initialZoom.getValue(context, layerDimensions, bounds) + layerDimensions.scale(value) } internal fun zoom(factor: Float, centroidX: Float, scroll: Float, bounds: RectF): Scroll { @@ -107,12 +107,12 @@ public class ZoomHandler( /** Facilitates listening for zoom events. */ public interface Listener { /** Called when the zoom factor changes. */ - public fun onValueChanged(oldValue: Float, newValue: Float) {} + public fun onValueChanged(old: Float, new: Float) {} /** Called when the range of zoom factors changes. */ public fun onValueRangeChanged( - oldValueRange: ClosedFloatingPointRange, - newValueRange: ClosedFloatingPointRange, + old: ClosedFloatingPointRange, + new: ClosedFloatingPointRange, ) {} } @@ -123,7 +123,7 @@ public class ZoomHandler( fun default(zoomEnabled: Boolean, scrollEnabled: Boolean) = ZoomHandler( zoomEnabled = zoomEnabled, - initialZoom = if (scrollEnabled) Zoom.max(Zoom.static(), Zoom.Content) else Zoom.Content, + initialZoom = if (scrollEnabled) Zoom.max(Zoom.fixed(), Zoom.Content) else Zoom.Content, ) } } diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/ChartView.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/ChartView.kt index 05ea3c243..978df6ba6 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/ChartView.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/ChartView.kt @@ -43,7 +43,7 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch /** Displays a [CartesianChart]. */ -public abstract class ChartView +public abstract class ChartView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) { @@ -58,7 +58,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) /** Houses the chart data. */ - public abstract var model: Model? + public abstract var model: M? protected val animator: ValueAnimator = ValueAnimator.ofFloat(Animation.range.start, Animation.range.endInclusive).apply { @@ -80,11 +80,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 protected var placeholder: View? = null - /** - * Whether to display an animation when the chart is created. In this animation, the value of each - * chart entry is animated from zero to the actual value. - */ - public var runInitialAnimation: Boolean = true + /** Whether to run an initial animation when the [ChartView] is created. */ + public var animateIn: Boolean = true override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -150,7 +147,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 } protected fun startAnimation(transformModel: suspend (key: Any, fraction: Float) -> Unit) { - if (model != null || runInitialAnimation) { + if (model != null || animateIn) { handler?.post { isAnimationRunning = true animator.start { fraction -> diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/View.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/View.kt index 41c91e65d..1ae63dc4c 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/View.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/View.kt @@ -25,21 +25,12 @@ import com.patrykandpatrick.vico.core.common.Point internal val Int.specSize: Int get() = View.MeasureSpec.getSize(this) -internal val Int.specMode: Int - get() = View.MeasureSpec.getMode(this) - internal var View.verticalPadding: Int get() = paddingTop + paddingBottom set(value) { updatePadding(top = value / 2, bottom = value / 2) } -internal var View.horizontalPadding: Int - get() = paddingLeft + paddingRight - set(value) { - updatePadding(left = value / 2, right = value / 2) - } - internal fun OverScroller.fling( startX: Int = 0, startY: Int = 0, diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ChartStyle.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ChartStyle.kt index c9717f251..b1e656677 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ChartStyle.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ChartStyle.kt @@ -26,7 +26,7 @@ import com.patrykandpatrick.vico.core.cartesian.layer.absolute import com.patrykandpatrick.vico.core.cartesian.layer.absoluteRelative import com.patrykandpatrick.vico.core.cartesian.layer.asWick import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.getRepeating import com.patrykandpatrick.vico.core.common.shape.CorneredShape import com.patrykandpatrick.vico.views.R @@ -110,9 +110,9 @@ internal fun TypedArray.getColumnCartesianLayer( } else { null }, - dataLabelVerticalPosition = - VerticalPosition.entries[ - getInteger(R.styleable.ColumnCartesianLayerStyle_dataLabelVerticalPosition, 0)], + dataLabelPosition = + Position.Vertical.entries[ + getInteger(R.styleable.ColumnCartesianLayerStyle_dataLabelPosition, 0)], dataLabelRotationDegrees = getFloat(R.styleable.ColumnCartesianLayerStyle_dataLabelRotationDegrees, 0f), ) @@ -261,7 +261,7 @@ internal fun TypedArray.getCandlestickCartesianLayer(context: Context): Candlest else -> throw IllegalArgumentException("Unexpected `candleStyle` value.") } CandlestickCartesianLayer( - candles = candleProvider, + candleProvider = candleProvider, minCandleBodyHeightDp = typedArray.getRawDimension( context, diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ComponentStyle.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ComponentStyle.kt index 4c48cac31..de57b96b6 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ComponentStyle.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ComponentStyle.kt @@ -23,15 +23,15 @@ import android.graphics.Paint import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer import com.patrykandpatrick.vico.core.common.DefaultAlpha import com.patrykandpatrick.vico.core.common.Defaults -import com.patrykandpatrick.vico.core.common.Dimensions import com.patrykandpatrick.vico.core.common.Fill +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.LayeredComponent -import com.patrykandpatrick.vico.core.common.VerticalPosition +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.component.Component import com.patrykandpatrick.vico.core.common.component.LineComponent import com.patrykandpatrick.vico.core.common.component.ShapeComponent import com.patrykandpatrick.vico.core.common.copyColor -import com.patrykandpatrick.vico.core.common.shader.DynamicShader +import com.patrykandpatrick.vico.core.common.shader.ShaderProvider import com.patrykandpatrick.vico.core.common.shape.Shape import com.patrykandpatrick.vico.views.R import com.patrykandpatrick.vico.views.common.defaultColors @@ -95,10 +95,10 @@ internal fun TypedArray.getComponent(context: Context): Component? = use { array if (layeredComponent != null) { LayeredComponent( - rear = baseComponent, + back = baseComponent, front = layeredComponent, padding = - Dimensions( + Insets( allDp = getRawDimension( context = context, @@ -176,8 +176,10 @@ internal fun TypedArray.getLine(context: Context, defaultColor: Int): LineCartes }, areaFill = LineCartesianLayer.AreaFill.double( - Fill(DynamicShader.verticalGradient(positiveGradientTopColor, positiveGradientBottomColor)), - Fill(DynamicShader.verticalGradient(negativeGradientTopColor, negativeGradientBottomColor)), + Fill( + ShaderProvider.verticalGradient(positiveGradientTopColor, positiveGradientBottomColor) + ), + Fill(ShaderProvider.verticalGradient(negativeGradientTopColor, negativeGradientBottomColor)), ), pointProvider = getNestedTypedArray(context, R.styleable.LineStyle_pointStyle, R.styleable.ComponentStyle) @@ -205,8 +207,8 @@ internal fun TypedArray.getLine(context: Context, defaultColor: Int): LineCartes } else { null }, - dataLabelVerticalPosition = - VerticalPosition.entries[getInteger(R.styleable.LineStyle_dataLabelVerticalPosition, 0)], + dataLabelPosition = + Position.Vertical.entries[getInteger(R.styleable.LineStyle_dataLabelPosition, 0)], dataLabelRotationDegrees = getFloat(R.styleable.LineStyle_dataLabelRotationDegrees, 0f), stroke = if (dashLength > 0f && dashGap > 0f) { diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ShapeStyle.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ShapeStyle.kt index b28c13473..23e1281c3 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ShapeStyle.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ShapeStyle.kt @@ -19,14 +19,9 @@ package com.patrykandpatrick.vico.views.common.theme import android.content.Context import android.content.res.TypedArray import androidx.annotation.StyleableRes -import com.patrykandpatrick.vico.core.common.shape.Corner -import com.patrykandpatrick.vico.core.common.shape.CornerTreatment import com.patrykandpatrick.vico.core.common.shape.CorneredShape -import com.patrykandpatrick.vico.core.common.shape.CutCornerTreatment import com.patrykandpatrick.vico.core.common.shape.DashedShape -import com.patrykandpatrick.vico.core.common.shape.RoundedCornerTreatment import com.patrykandpatrick.vico.core.common.shape.Shape -import com.patrykandpatrick.vico.core.common.shape.SharpCornerTreatment import com.patrykandpatrick.vico.views.R private const val ONE_HUNDRED_PERCENT = 100 @@ -75,7 +70,7 @@ private fun TypedArray.getCorner( @StyleableRes sizeIndex: Int, @StyleableRes treatmentIndex: Int, handleNullSizeIndex: Boolean = true, -): Corner = +): CorneredShape.Corner = when { !hasValue(sizeIndex) && handleNullSizeIndex -> { getCorner( @@ -87,11 +82,11 @@ private fun TypedArray.getCorner( } isFraction(sizeIndex) -> { val percentage = (getFraction(sizeIndex, defaultValue = 0f) * ONE_HUNDRED_PERCENT).toInt() - Corner.Relative( - percentage = percentage, - cornerTreatment = + CorneredShape.Corner.Relative( + percent = percentage, + shape = if (percentage == 0) { - SharpCornerTreatment + CorneredShape.CornerShape.Sharp } else { getCornerTreatment(treatmentIndex) }, @@ -99,11 +94,11 @@ private fun TypedArray.getCorner( } else -> { val sizeDp = getRawDimension(context, sizeIndex, defaultValue = 0f) - Corner.Absolute( + CorneredShape.Corner.Absolute( sizeDp = sizeDp, - cornerTreatment = + shape = if (sizeDp == 0f) { - SharpCornerTreatment + CorneredShape.CornerShape.Sharp } else { getCornerTreatment(treatmentIndex) }, @@ -114,9 +109,9 @@ private fun TypedArray.getCorner( private fun TypedArray.getCornerTreatment( @StyleableRes index: Int, defaultValue: Int = -1, -): CornerTreatment = +): CorneredShape.CornerShape = when (getInt(index, defaultValue)) { -1 -> getCornerTreatment(R.styleable.ShapeStyle_cornerTreatment, defaultValue = 0) - 0 -> RoundedCornerTreatment - else -> CutCornerTreatment + 0 -> CorneredShape.CornerShape.Rounded + else -> CorneredShape.CornerShape.Cut } diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/TextComponentStyle.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/TextComponentStyle.kt index 8bf2f2821..2bbdc7d50 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/TextComponentStyle.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/TextComponentStyle.kt @@ -28,7 +28,7 @@ import com.patrykandpatrick.vico.core.common.Defaults.AXIS_LABEL_HORIZONTAL_PADD import com.patrykandpatrick.vico.core.common.Defaults.AXIS_LABEL_VERTICAL_PADDING import com.patrykandpatrick.vico.core.common.Defaults.TEXT_COMPONENT_LINE_COUNT import com.patrykandpatrick.vico.core.common.Defaults.TEXT_COMPONENT_TEXT_SIZE -import com.patrykandpatrick.vico.core.common.Dimensions +import com.patrykandpatrick.vico.core.common.Insets import com.patrykandpatrick.vico.core.common.component.TextComponent import com.patrykandpatrick.vico.core.common.firstNonNegativeOf import com.patrykandpatrick.vico.core.common.orZero @@ -119,7 +119,7 @@ private fun TypedArray.getTypeface(context: Context): Typeface? { } } -private fun TypedArray.getPadding(context: Context): Dimensions { +private fun TypedArray.getPadding(context: Context): Insets { fun getDpDimension(@StyleableRes index: Int): Float = getRawDimension(context, index, -1f) val padding = getDpDimension(R.styleable.TextComponentStyle_android_padding) @@ -129,7 +129,7 @@ private fun TypedArray.getPadding(context: Context): Dimensions { val paddingTop = getDpDimension(R.styleable.TextComponentStyle_android_paddingTop) val paddingEnd = getDpDimension(R.styleable.TextComponentStyle_android_paddingEnd) val paddingBottom = getDpDimension(R.styleable.TextComponentStyle_android_paddingBottom) - return Dimensions( + return Insets( startDp = firstNonNegativeOf(paddingStart, paddingHorizontal, padding) ?: AXIS_LABEL_HORIZONTAL_PADDING.toFloat(), @@ -145,7 +145,7 @@ private fun TypedArray.getPadding(context: Context): Dimensions { ) } -private fun TypedArray.getMargins(context: Context): Dimensions { +private fun TypedArray.getMargins(context: Context): Insets { fun getDpDimension(@StyleableRes index: Int): Float = getRawDimension(context, index, -1f) val padding = getDpDimension(R.styleable.TextComponentStyle_margin) @@ -155,7 +155,7 @@ private fun TypedArray.getMargins(context: Context): Dimensions { val paddingTop = getDpDimension(R.styleable.TextComponentStyle_marginTop) val paddingEnd = getDpDimension(R.styleable.TextComponentStyle_marginEnd) val paddingBottom = getDpDimension(R.styleable.TextComponentStyle_marginBottom) - return Dimensions( + return Insets( startDp = firstNonNegativeOf(paddingStart, paddingHorizontal, padding).orZero, topDp = firstNonNegativeOf(paddingTop, paddingVertical, padding).orZero, endDp = firstNonNegativeOf(paddingEnd, paddingHorizontal, padding).orZero, diff --git a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ThemeHandler.kt b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ThemeHandler.kt index e7ecbf236..23e8f841f 100644 --- a/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ThemeHandler.kt +++ b/vico/views/src/main/java/com/patrykandpatrick/vico/views/common/theme/ThemeHandler.kt @@ -23,13 +23,14 @@ import android.util.AttributeSet import android.util.Log import android.view.animation.AccelerateInterpolator import com.patrykandpatrick.vico.core.cartesian.CartesianChart -import com.patrykandpatrick.vico.core.cartesian.CartesianLayerPadding import com.patrykandpatrick.vico.core.cartesian.FadingEdges import com.patrykandpatrick.vico.core.cartesian.axis.Axis import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis +import com.patrykandpatrick.vico.core.cartesian.layer.CartesianLayerPadding import com.patrykandpatrick.vico.core.common.Defaults import com.patrykandpatrick.vico.core.common.Defaults.FADING_EDGE_VISIBILITY_THRESHOLD_DP +import com.patrykandpatrick.vico.core.common.Position import com.patrykandpatrick.vico.core.common.hasFlag import com.patrykandpatrick.vico.core.common.shape.DashedShape import com.patrykandpatrick.vico.views.R @@ -178,7 +179,7 @@ internal class ThemeHandler(private val context: Context, attrs: AttributeSet?) 0, )], verticalLabelPosition = - VerticalAxis.VerticalLabelPosition.entries[ + Position.Vertical.entries[ axisStyle.getInteger(R.styleable.AxisStyle_verticalAxisVerticalLabelPosition, 0)], tick = tick, tickLengthDp = tickLengthDp, @@ -267,8 +268,8 @@ internal class ThemeHandler(private val context: Context, attrs: AttributeSet?) } FadingEdges( - startEdgeWidthDp = startLength, - endEdgeWidthDp = endLength, + startWidthDp = startLength, + endWidthDp = endLength, visibilityThresholdDp = threshold, visibilityInterpolator = interpolator ?: AccelerateInterpolator(), ) diff --git a/vico/views/src/main/res/values/attrs.xml b/vico/views/src/main/res/values/attrs.xml index 95f1775cb..1c4ee0a3c 100644 --- a/vico/views/src/main/res/values/attrs.xml +++ b/vico/views/src/main/res/values/attrs.xml @@ -17,7 +17,7 @@ - + @@ -310,7 +310,7 @@ - + @@ -486,7 +486,7 @@ - +