Skip to content

Commit fd62c27

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents cb87e10 + dc23211 commit fd62c27

File tree

6 files changed

+161
-92
lines changed

6 files changed

+161
-92
lines changed

.github/FUNDING.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# These are supported funding model platforms
2+
3+
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4+
patreon: # Replace with a single Patreon username
5+
open_collective: # Replace with a single Open Collective username
6+
ko_fi: # Replace with a single Ko-fi username
7+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9+
liberapay: # Replace with a single Liberapay username
10+
issuehunt: # Replace with a single IssueHunt username
11+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12+
polar: # Replace with a single Polar username
13+
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14+
thanks_dev: # Replace with a single thanks.dev username
15+
custom: [https://plisio.net/donate/0gHfO7oL]

compose-charts/src/commonMain/kotlin/ir/ehsannarmani/compose_charts/LineChart.kt

Lines changed: 107 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -286,44 +286,46 @@ fun LineChart(
286286
)
287287
val pathData = linesPathData[index]
288288

289-
290-
val showOnPointsThreshold =
291-
((properties.mode as? PopupProperties.Mode.PointMode)?.threshold
292-
?: 0.dp).toPx()
293-
val pointX =
294-
pathData.xPositions.find { it in positionX - showOnPointsThreshold..positionX + showOnPointsThreshold }
295-
if (properties.mode !is PopupProperties.Mode.PointMode || pointX != null) {
296-
val fraction =
297-
((if (properties.mode is PopupProperties.Mode.PointMode) (pointX?.toFloat()
298-
?: 0f) else positionX) / size.width)
299-
val popupValue = getPopupValue(
300-
points = line.values,
301-
fraction = fraction.toDouble(),
302-
rounded = line.curvedEdges ?: curvedEdges,
303-
size = _size,
304-
minValue = minValue,
305-
maxValue = maxValue
306-
)
307-
popups.add(
308-
Popup(
309-
position = popupValue.offset,
310-
value = popupValue.calculatedValue,
311-
properties = properties
289+
if(positionX >= pathData.xPositions[pathData.startIndex] && positionX <= pathData.xPositions[pathData.endIndex]) {
290+
val showOnPointsThreshold =
291+
((properties.mode as? PopupProperties.Mode.PointMode)?.threshold
292+
?: 0.dp).toPx()
293+
val pointX =
294+
pathData.xPositions.find { it in positionX - showOnPointsThreshold..positionX + showOnPointsThreshold }
295+
296+
if (properties.mode !is PopupProperties.Mode.PointMode || pointX != null) {
297+
val fraction =
298+
((if (properties.mode is PopupProperties.Mode.PointMode) (pointX?.toFloat()
299+
?: 0f) else positionX) / size.width)
300+
val popupValue = getPopupValue(
301+
points = line.values,
302+
fraction = fraction.toDouble(),
303+
rounded = line.curvedEdges ?: curvedEdges,
304+
size = _size,
305+
minValue = minValue,
306+
maxValue = maxValue
312307
)
313-
)
314-
315-
if (popupsOffsetAnimators.count() < popups.count()) {
316-
repeat(popups.count() - popupsOffsetAnimators.count()) {
317-
popupsOffsetAnimators.add(
318-
// add fixed position for popup when mode is point mode
319-
if (properties.mode is PopupProperties.Mode.PointMode) {
320-
Animatable(popupValue.offset.x) to Animatable(
321-
popupValue.offset.y
322-
)
323-
} else {
324-
Animatable(0f) to Animatable(0f)
325-
}
308+
popups.add(
309+
Popup(
310+
position = popupValue.offset,
311+
value = popupValue.calculatedValue,
312+
properties = properties
326313
)
314+
)
315+
316+
if (popupsOffsetAnimators.count() < popups.count()) {
317+
repeat(popups.count() - popupsOffsetAnimators.count()) {
318+
popupsOffsetAnimators.add(
319+
// add fixed position for popup when mode is point mode
320+
if (properties.mode is PopupProperties.Mode.PointMode) {
321+
Animatable(popupValue.offset.x) to Animatable(
322+
popupValue.offset.y
323+
)
324+
} else {
325+
Animatable(0f) to Animatable(0f)
326+
}
327+
)
328+
}
327329
}
328330
}
329331
}
@@ -357,12 +359,18 @@ fun LineChart(
357359
}
358360
if (linesPathData.isEmpty() || linesPathData.count() != data.count()) {
359361
data.map {
362+
val startIndex = if(it.viewRange.startIndex < 0 || it.viewRange.startIndex >= it.values.size - 1) 0 else it.viewRange.startIndex
363+
val endIndex = if(it.viewRange.endIndex < 0 || it.viewRange.endIndex <= it.viewRange.startIndex
364+
|| it.viewRange.endIndex > it.values.size - 1) it.values.size - 1 else it.viewRange.endIndex
365+
360366
getLinePath(
361367
dataPoints = it.values.map { it.toFloat() },
362368
maxValue = maxValue.toFloat(),
363369
minValue = minValue.toFloat(),
364370
rounded = it.curvedEdges ?: curvedEdges,
365-
size = size.copy(height = chartAreaHeight)
371+
size = size.copy(height = chartAreaHeight),
372+
startIndex,
373+
endIndex
366374
)
367375
}.also {
368376
linesPathData.addAll(it)
@@ -405,13 +413,26 @@ fun LineChart(
405413
brush = line.color,
406414
style = Stroke(width = stroke, pathEffect = pathEffect)
407415
)
416+
417+
var startOffset = 0f
418+
var endOffset = size.width
419+
if(pathData.startIndex > 0) {
420+
startOffset = pathData.xPositions[pathData.startIndex] .toFloat()
421+
}
422+
423+
if(pathData.endIndex < line.values.size - 1) {
424+
endOffset = pathData.xPositions[pathData.endIndex].toFloat()
425+
}
426+
408427
if (line.firstGradientFillColor != null && line.secondGradientFillColor != null) {
409428
drawLineGradient(
410429
path = pathData.path,
411430
color1 = line.firstGradientFillColor,
412431
color2 = line.secondGradientFillColor,
413432
progress = line.gradientProgress.value,
414-
size = size.copy(height = chartAreaHeight)
433+
size = size.copy(height = chartAreaHeight),
434+
startOffset,
435+
endOffset
415436
)
416437
} else if (line.drawStyle is DrawStyle.Fill) {
417438
var fillColor = Color.Unspecified
@@ -423,7 +444,9 @@ fun LineChart(
423444
color1 = fillColor,
424445
color2 = fillColor,
425446
progress = 1f,
426-
size = size.copy(height = chartAreaHeight)
447+
size = size.copy(height = chartAreaHeight),
448+
startOffset,
449+
endOffset
427450
)
428451
}
429452

@@ -440,7 +463,9 @@ fun LineChart(
440463
minValue = minValue.toFloat(),
441464
pathMeasure = pathMeasure,
442465
scope = scope,
443-
size = size.copy(height = chartAreaHeight)
466+
size = size.copy(height = chartAreaHeight),
467+
startIndex = pathData.startIndex,
468+
endIndex = pathData.endIndex
444469
)
445470
}
446471
}
@@ -509,7 +534,7 @@ private fun DrawScope.drawPopup(
509534
textMeasurer: TextMeasurer,
510535
scope: CoroutineScope,
511536
progress: Float,
512-
offsetAnimator: Pair<Animatable<Float, AnimationVector1D>, Animatable<Float, AnimationVector1D>>? = null,
537+
offsetAnimator: Pair<Animatable<Float, AnimationVector1D>, Animatable<Float, AnimationVector1D>>? = null
513538
) {
514539
val offset = popup.position
515540
val popupProperties = popup.properties
@@ -615,6 +640,8 @@ fun DrawScope.drawDots(
615640
pathMeasure: PathMeasure,
616641
scope: CoroutineScope,
617642
size: Size? = null,
643+
startIndex: Int,
644+
endIndex: Int,
618645
) {
619646
val _size = size ?: this.size
620647

@@ -623,47 +650,49 @@ fun DrawScope.drawDots(
623650
pathMeasure.setPath(linePath, false)
624651
val lastPosition = pathMeasure.getPosition(pathMeasure.length)
625652
dataPoints.forEachIndexed { valueIndex, value ->
626-
val dotOffset = Offset(
627-
x = _size.width.spaceBetween(
628-
itemCount = dataPoints.count(),
629-
index = valueIndex
630-
),
631-
y = (_size.height - calculateOffset(
632-
maxValue = maxValue.toDouble(),
633-
minValue = minValue.toDouble(),
634-
total = _size.height,
635-
value = value.second
636-
)).toFloat()
653+
if(valueIndex in startIndex..endIndex) {
654+
val dotOffset = Offset(
655+
x = _size.width.spaceBetween(
656+
itemCount = dataPoints.count(),
657+
index = valueIndex
658+
),
659+
y = (_size.height - calculateOffset(
660+
maxValue = maxValue.toDouble(),
661+
minValue = minValue.toDouble(),
662+
total = _size.height,
663+
value = value.second
664+
)).toFloat()
637665

638-
)
639-
if (lastPosition != Offset.Unspecified && lastPosition.x >= dotOffset.x - 20 || !properties.animationEnabled) {
640-
if (!value.first.isRunning && properties.animationEnabled && value.first.value != 1f) {
641-
scope.launch {
642-
value.first.animateTo(1f, animationSpec = properties.animationSpec)
666+
)
667+
if (lastPosition != Offset.Unspecified && lastPosition.x >= dotOffset.x - 20 || !properties.animationEnabled) {
668+
if (!value.first.isRunning && properties.animationEnabled && value.first.value != 1f) {
669+
scope.launch {
670+
value.first.animateTo(1f, animationSpec = properties.animationSpec)
671+
}
643672
}
644-
}
645673

646-
val radius: Float
647-
val strokeRadius: Float
648-
if (properties.animationEnabled) {
649-
radius =
650-
(properties.radius.toPx() + properties.strokeWidth.toPx() / 2) * value.first.value
651-
strokeRadius = properties.radius.toPx() * value.first.value
652-
} else {
653-
radius = properties.radius.toPx() + properties.strokeWidth.toPx() / 2
654-
strokeRadius = properties.radius.toPx()
674+
val radius: Float
675+
val strokeRadius: Float
676+
if (properties.animationEnabled) {
677+
radius =
678+
(properties.radius.toPx() + properties.strokeWidth.toPx() / 2) * value.first.value
679+
strokeRadius = properties.radius.toPx() * value.first.value
680+
} else {
681+
radius = properties.radius.toPx() + properties.strokeWidth.toPx() / 2
682+
strokeRadius = properties.radius.toPx()
683+
}
684+
drawCircle(
685+
brush = properties.strokeColor,
686+
radius = radius,
687+
center = dotOffset,
688+
style = Stroke(width = properties.strokeWidth.toPx(), pathEffect = pathEffect),
689+
)
690+
drawCircle(
691+
brush = properties.color,
692+
radius = strokeRadius,
693+
center = dotOffset,
694+
)
655695
}
656-
drawCircle(
657-
brush = properties.strokeColor,
658-
radius = radius,
659-
center = dotOffset,
660-
style = Stroke(width = properties.strokeWidth.toPx(), pathEffect = pathEffect),
661-
)
662-
drawCircle(
663-
brush = properties.color,
664-
radius = strokeRadius,
665-
center = dotOffset,
666-
)
667696
}
668697
}
669698
}

compose-charts/src/commonMain/kotlin/ir/ehsannarmani/compose_charts/extensions/line_chart/Gradient.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ internal fun DrawScope.drawLineGradient(
1111
color1: Color,
1212
color2: Color,
1313
progress: Float,
14-
size: Size? = null
14+
size: Size? = null,
15+
startOffset: Float,
16+
endOffset: Float
1517
) {
1618
val _size = size ?: this.size
1719
drawIntoCanvas {
1820
val p = Path()
1921
p.addPath(path)
20-
p.lineTo(_size.width, _size.height)
21-
p.lineTo(0f, _size.height)
22+
p.lineTo(endOffset, _size.height)
23+
p.lineTo(startOffset, _size.height)
2224
p.close()
2325
val paint = Paint()
2426
paint.shader = LinearGradientShader(

compose-charts/src/commonMain/kotlin/ir/ehsannarmani/compose_charts/extensions/line_chart/Line.kt

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,28 @@ package ir.ehsannarmani.compose_charts.extensions.line_chart
33
import androidx.compose.ui.geometry.Size
44
import androidx.compose.ui.graphics.Path
55
import androidx.compose.ui.graphics.drawscope.DrawScope
6+
import ir.ehsannarmani.compose_charts.models.ViewRange
67
import ir.ehsannarmani.compose_charts.utils.calculateOffset
78

89
internal data class PathData(
910
val path: Path,
10-
val xPositions: List<Double>
11+
val xPositions: List<Double>,
12+
val startIndex: Int,
13+
val endIndex: Int
1114
)
1215
internal fun DrawScope.getLinePath(
1316
dataPoints: List<Float>,
1417
maxValue: Float,
1518
minValue: Float,
1619
rounded: Boolean = true,
17-
size: Size? = null
20+
size: Size? = null,
21+
startIndex : Int,
22+
endIndex: Int
1823
): PathData {
1924

2025
val _size = size ?: this.size
2126
val path = Path()
22-
if (dataPoints.isEmpty()) return PathData(path = path, xPositions = emptyList())
27+
if (dataPoints.isEmpty()) return PathData(path = path, xPositions = emptyList(),0, Int.MAX_VALUE)
2328
val calculateHeight = { value: Float ->
2429
calculateOffset(
2530
maxValue = maxValue.toDouble(),
@@ -29,23 +34,33 @@ internal fun DrawScope.getLinePath(
2934
)
3035
}
3136

32-
path.moveTo(0f, (_size.height - calculateHeight(dataPoints[0])).toFloat())
37+
if(startIndex == 0) {
38+
path.moveTo(0f, (_size.height - calculateHeight(dataPoints[0])).toFloat())
39+
}else {
40+
val x = (startIndex * (_size.width / (dataPoints.size - 1)))
41+
val y = _size.height - calculateHeight(dataPoints[startIndex]).toFloat()
42+
path.moveTo(x, y)
43+
}
44+
3345
val xPositions = mutableListOf<Double>()
3446
for (i in 0 until dataPoints.size - 1) {
3547
val x1 = (i * (_size.width / (dataPoints.size - 1)))
3648
val y1 = _size.height - calculateHeight(dataPoints[i]).toFloat()
3749
val x2 = ((i + 1) * (_size.width / (dataPoints.size - 1)))
3850
val y2 = _size.height - calculateHeight(dataPoints[i + 1]).toFloat()
3951

40-
if (rounded) {
41-
val cx = (x1 + x2) / 2f
42-
path.cubicTo(x1 = cx, y1 = y1, x2 = cx, y2 = y2, x3 = x2, y3 = y2)
43-
} else {
44-
path.cubicTo(x1, y1, x1, y1, (x1 + x2) / 2, (y1 + y2) / 2)
45-
path.cubicTo((x1 + x2) / 2, (y1 + y2) / 2, x2, y2, x2, y2)
52+
if (i in startIndex..<endIndex) {
53+
if (rounded) {
54+
val cx = (x1 + x2) / 2f
55+
path.cubicTo(x1 = cx, y1 = y1, x2 = cx, y2 = y2, x3 = x2, y3 = y2)
56+
} else {
57+
path.cubicTo(x1, y1, x1, y1, (x1 + x2) / 2, (y1 + y2) / 2)
58+
path.cubicTo((x1 + x2) / 2, (y1 + y2) / 2, x2, y2, x2, y2)
59+
}
4660
}
61+
4762
xPositions.add(x1.toDouble())
4863
}
4964
xPositions.add(_size.width.toDouble())
50-
return PathData(path = path, xPositions = xPositions)
65+
return PathData(path = path, xPositions = xPositions,startIndex,endIndex)
5166
}

compose-charts/src/commonMain/kotlin/ir/ehsannarmani/compose_charts/models/Line.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ data class Line(
2424
val curvedEdges:Boolean? = null,
2525
val strokeProgress: Animatable<Float, AnimationVector1D> = Animatable(0f),
2626
val gradientProgress: Animatable<Float, AnimationVector1D> = Animatable(0f),
27+
val viewRange: ViewRange = ViewRange()
2728
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package ir.ehsannarmani.compose_charts.models
2+
3+
data class ViewRange(
4+
val startIndex: Int = 0,
5+
val endIndex: Int = Int.MAX_VALUE
6+
)
7+

0 commit comments

Comments
 (0)