@@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.PathMeasure
3535import androidx.compose.ui.graphics.SolidColor
3636import androidx.compose.ui.graphics.drawscope.DrawScope
3737import androidx.compose.ui.graphics.drawscope.Stroke
38+ import androidx.compose.ui.graphics.drawscope.clipRect
3839import androidx.compose.ui.graphics.drawscope.inset
3940import androidx.compose.ui.graphics.isSpecified
4041import androidx.compose.ui.graphics.takeOrElse
@@ -72,12 +73,12 @@ import ir.ehsannarmani.compose_charts.models.Line
7273import ir.ehsannarmani.compose_charts.models.PopupProperties
7374import ir.ehsannarmani.compose_charts.models.ZeroLineProperties
7475import ir.ehsannarmani.compose_charts.utils.calculateOffset
75- import ir.ehsannarmani.compose_charts.utils.rememberComputedChartMaxValue
7676import kotlinx.coroutines.CoroutineScope
7777import kotlinx.coroutines.Job
7878import kotlinx.coroutines.delay
7979import kotlinx.coroutines.launch
8080import kotlin.math.max
81+ import kotlin.math.min
8182
8283private data class Popup (
8384 val properties : PopupProperties ,
@@ -116,16 +117,6 @@ fun LineChart(
116117 line.values.minOfOrNull { it } ? : 0.0
117118 } ? : 0.0 else 0.0
118119) {
119- if (data.isNotEmpty()) {
120- require(minValue <= (data.minOfOrNull { line -> line.values.minOfOrNull { it } ? : 0.0 }
121- ? : 0.0 )) {
122- " Chart data must be at least $minValue (Specified Min Value)"
123- }
124- require(maxValue >= (data.maxOfOrNull { line -> line.values.maxOfOrNull { it } ? : 0.0 }
125- ? : 0.0 )) {
126- " Chart data must be at most $maxValue (Specified Max Value)"
127- }
128- }
129120
130121 val density = LocalDensity .current
131122 val scope = rememberCoroutineScope()
@@ -158,13 +149,20 @@ fun LineChart(
158149 mutableStateListOf<PathData >()
159150 }
160151
161- val computedMaxValue =
162- rememberComputedChartMaxValue(minValue, maxValue, indicatorProperties.count)
163- val indicators = remember(indicatorProperties.indicators, minValue, maxValue) {
152+ val computedMaxValue = remember(maxValue, indicatorProperties.indicators) {
153+ val indicatorMax = indicatorProperties.indicators.maxOrNull() ? : return @remember maxValue
154+ max(maxValue, indicatorMax)
155+ }
156+ val computedMinValue = remember(minValue, indicatorProperties.indicators) {
157+ val indicatorMin = indicatorProperties.indicators.minOrNull() ? : return @remember minValue
158+ min(minValue, indicatorMin)
159+ }
160+
161+ val indicators = remember(indicatorProperties.indicators, computedMinValue, maxValue) {
164162 indicatorProperties.indicators.ifEmpty {
165163 split(
166164 count = indicatorProperties.count,
167- minValue = minValue ,
165+ minValue = computedMinValue ,
168166 maxValue = computedMaxValue
169167 )
170168 }
@@ -228,7 +226,7 @@ fun LineChart(
228226 }
229227 }
230228
231- LaunchedEffect (data, minValue , computedMaxValue) {
229+ LaunchedEffect (data, computedMinValue , computedMaxValue) {
232230 linesPathData.clear()
233231 }
234232
@@ -303,7 +301,7 @@ fun LineChart(
303301 fraction = fraction.toDouble(),
304302 rounded = line.curvedEdges ? : curvedEdges,
305303 size = Size (insetPad.width(size), insetPad.height(size)),
306- minValue = minValue ,
304+ minValue = computedMinValue ,
307305 maxValue = computedMaxValue
308306 )
309307 }
@@ -359,7 +357,7 @@ fun LineChart(
359357 LineChartCanvas (
360358 data = data,
361359 maxValue = computedMaxValue,
362- minValue = minValue ,
360+ minValue = computedMinValue ,
363361 indicators = indicators,
364362 indicatorProperties = indicatorProperties,
365363 labelProperties = labelProperties,
@@ -380,7 +378,7 @@ fun LineChart(
380378 ) { xTicks, yTicks ->
381379 val drawZeroLine = {
382380 val zeroY = size.height - calculateOffset(
383- minValue = minValue ,
381+ minValue = computedMinValue ,
384382 maxValue = computedMaxValue,
385383 total = size.height,
386384 value = 0f
@@ -398,7 +396,7 @@ fun LineChart(
398396 getLinePath(
399397 dataPoints = it.values.mapIndexed { index, v -> index.toFloat() to v.toFloat() },
400398 maxValue = computedMaxValue.toFloat(),
401- minValue = minValue .toFloat(),
399+ minValue = computedMinValue .toFloat(),
402400 rounded = it.curvedEdges ? : curvedEdges,
403401 size = size
404402 )
@@ -500,7 +498,7 @@ fun LineChart(
500498 properties = line.dotProperties ? : dotsProperties,
501499 linePath = segmentedPath,
502500 maxValue = computedMaxValue.toFloat(),
503- minValue = minValue .toFloat(),
501+ minValue = computedMinValue .toFloat(),
504502 pathMeasure = pathMeasure,
505503 scope = scope,
506504 startIndex = pathData.startIndex,
@@ -598,18 +596,26 @@ private fun DrawScope.drawPopup(
598596
599597 }
600598 if (offsetAnimator != null ) {
601- val animatedOffset = if (popup.properties.mode is PopupProperties .Mode .PointMode ) {
599+ var animatedOffset = if (popup.properties.mode is PopupProperties .Mode .PointMode ) {
602600 rectOffset
603601 } else {
604602 Offset (
605603 x = offsetAnimator.first.value,
606604 y = offsetAnimator.second.value
607605 )
608606 }
609- val rect = Rect (
607+ var rect = Rect (
610608 offset = animatedOffset,
611609 size = rectSize
612610 )
611+ if (rect.top < 0 ) rect = rect.copy(top = 0f , bottom = rect.height)
612+ if (rect.bottom > size.height) rect =
613+ rect.copy(top = size.height - rect.height, bottom = size.height)
614+ if (rect.left < 0 ) rect = rect.copy(left = 0f , right = rect.width)
615+ if (rect.right > size.width) rect =
616+ rect.copy(left = size.width - rect.width, right = size.width)
617+
618+ animatedOffset = Offset (rect.left, rect.top)
613619 drawPath(
614620 path = Path ().apply {
615621 addRoundRect(
@@ -804,15 +810,18 @@ data class InsetPad(
804810 )
805811}
806812
807- fun DrawScope.inset (insetPad : InsetPad , block : DrawScope .() -> Unit ) =
813+ fun DrawScope.inset (insetPad : InsetPad , block : DrawScope .() -> Unit ) {
814+ val mostLeft = size.width / 2f
815+ val mostTop = size.height / 2f
808816 inset(
809- left = insetPad.left,
810- right = insetPad.right,
811- top = insetPad.top,
812- bottom = insetPad.bottom
817+ left = insetPad.left.coerceAtMost(mostLeft) ,
818+ right = insetPad.right.coerceAtMost(size.width - mostLeft) ,
819+ top = insetPad.top.coerceAtMost(mostTop) ,
820+ bottom = insetPad.bottom.coerceAtMost(size.height - mostTop),
813821 ) {
814822 block()
815823 }
824+ }
816825
817826fun getInsetPad (
818827 textMeasurer : TextMeasurer ,
@@ -1008,7 +1017,12 @@ private fun RowScope.LineChartCanvas(
10081017 } else emptyList()
10091018 val xTicks = if (labelProperties.labels.isNotEmpty() && labelProperties.enabled) {
10101019 val measureLabel =
1011- { text: String -> textMeasurer.measure(text, style = labelProperties.textStyle) }
1020+ { text: String ->
1021+ textMeasurer.measure(
1022+ text,
1023+ style = labelProperties.textStyle
1024+ )
1025+ }
10121026 val labels = labelProperties.labels
10131027 val maxLabelHeight = labels.maxOf {
10141028 measureLabel(it).size.height
@@ -1032,7 +1046,9 @@ private fun RowScope.LineChartCanvas(
10321046 }
10331047 } else emptyList()
10341048 inset(insetPad) {
1035- insetDrawScope(xTicks, yTicks)
1049+ clipRect {
1050+ insetDrawScope(xTicks, yTicks)
1051+ }
10361052 }
10371053 }
10381054}
0 commit comments