Skip to content

Commit d703513

Browse files
authored
Merge pull request #24 from Pfuster12/v1.3.0-milestone
1.3.0 milestone
2 parents 092ef21 + 3d5588f commit d703513

File tree

10 files changed

+633
-77
lines changed

10 files changed

+633
-77
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,9 @@ possible.
295295

296296
If you place the LiveChart in a scrollview the touch event gets consumed early.
297297

298-
Unfortunately this is a feature of the Android Touch system. Without ruining the ScrollView functionality from the library side, a working solution is to call the LiveChart view's parent requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) :
298+
Unfortunately this is a feature of the Android Touch system. Without ruining the ScrollView
299+
functionality from the library side, a working solution is to call the
300+
LiveChart view's parent `requestDisallowInterceptTouchEvent(disallowIntercept: Boolean)` :
299301

300302
```
301303
liveChart.setDataset(dataset)

app/src/main/java/com/yabu/livechartdemoapp/MainActivity.kt

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class MainActivity : AppCompatActivity() {
3232
pathStrokeWidth = 8f
3333
secondPathStrokeWidth = 4f
3434
textHeight = 40f
35+
textColor = Color.WHITE
3536
overlayLineColor = Color.BLUE
3637
overlayCircleDiameter = 32f
3738
overlayCircleColor = Color.GREEN
@@ -49,6 +50,12 @@ class MainActivity : AppCompatActivity() {
4950

5051
livechartSimple.setDataset(dataset)
5152
.setLiveChartStyle(chartStyle)
53+
.drawYBounds()
54+
.drawSmoothPath()
55+
.drawVerticalGuidelines()
56+
.setVerticalGuidelineSteps(2)
57+
.setHorizontalGuidelineSteps(2)
58+
.drawHorizontalGuidelines()
5259
.setOnTouchCallbackListener(object : LiveChart.OnTouchCallback {
5360
@SuppressLint("SetTextI18n")
5461
override fun onTouchCallback(point: DataPoint) {
@@ -82,17 +89,39 @@ class MainActivity : AppCompatActivity() {
8289
}
8390

8491
livechart.setDataset(firstDataset)
85-
.setSecondDataset(secondDataset)
8692
.setLiveChartStyle(style)
87-
.disableTouchOverlay()
8893
.drawYBounds()
94+
.setOnTouchCallbackListener(object : LiveChart.OnTouchCallback {
95+
@SuppressLint("SetTextI18n")
96+
override fun onTouchCallback(point: DataPoint) {
97+
livechart.parent
98+
.requestDisallowInterceptTouchEvent(true)
99+
}
100+
101+
override fun onTouchFinished() {
102+
livechart.parent
103+
.requestDisallowInterceptTouchEvent(false)
104+
}
105+
})
89106
.drawDataset()
90107

91108
livechartNegative.setDataset(negativeDataset)
92109
.drawYBounds()
93110
.drawBaseline()
94111
.drawLastPointLabel()
95112
.drawFill(false)
113+
.setOnTouchCallbackListener(object : LiveChart.OnTouchCallback {
114+
@SuppressLint("SetTextI18n")
115+
override fun onTouchCallback(point: DataPoint) {
116+
livechartNegative.parent
117+
.requestDisallowInterceptTouchEvent(true)
118+
}
119+
120+
override fun onTouchFinished() {
121+
livechartNegative.parent
122+
.requestDisallowInterceptTouchEvent(false)
123+
}
124+
})
96125
.drawDataset()
97126
}
98127
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.yabu.livechart.utils
2+
3+
/**
4+
* Code adapted for LiveChart from Stuart Kent - July 3, 2015
5+
* https://www.stkent.com/2015/07/03/building-smooth-paths-using-bezier-curves.html
6+
*/
7+
class EPointF(val x: Float, val y: Float) {
8+
9+
fun plus(factor: Float, ePointF: EPointF): EPointF {
10+
return EPointF(x + factor * ePointF.x, y + factor * ePointF.y)
11+
}
12+
13+
operator fun plus(ePointF: EPointF): EPointF {
14+
return plus(1.0f, ePointF)
15+
}
16+
17+
fun minus(factor: Float, ePointF: EPointF): EPointF {
18+
return EPointF(x - factor * ePointF.x, y - factor * ePointF.y)
19+
}
20+
21+
operator fun minus(ePointF: EPointF): EPointF {
22+
return minus(1.0f, ePointF)
23+
}
24+
25+
fun scaleBy(factor: Float): EPointF {
26+
return EPointF(factor * x, factor * y)
27+
}
28+
29+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.yabu.livechart.utils
2+
3+
4+
import android.graphics.Path
5+
import com.yabu.livechart.model.DataPoint
6+
7+
/**
8+
* Code adapted for LiveChart from Stuart Kent - July 3, 2015
9+
* https://www.stkent.com/2015/07/03/building-smooth-paths-using-bezier-curves.html
10+
*/
11+
object PolyBezierPathUtil {
12+
13+
/**
14+
* Computes a Poly-Bezier curve passing through a given list of knots.
15+
* The curve will be twice-differentiable everywhere and satisfy natural
16+
* boundary conditions at both ends.
17+
*
18+
* @param points [DataPoint] list
19+
* @return a Path representing the twice-differentiable curve
20+
* passing through all the given knots
21+
*/
22+
fun computePathThroughDataPoints(knots: List<EPointF>): Path {
23+
throwExceptionIfInputIsInvalid(knots)
24+
val polyBezierPath = Path()
25+
val firstKnot: EPointF = knots[0]
26+
polyBezierPath.moveTo(firstKnot.x, firstKnot.y)
27+
28+
val n = knots.size - 1
29+
if (n == 1) {
30+
val lastKnot: EPointF = knots[1]
31+
polyBezierPath.lineTo(lastKnot.x, lastKnot.y)
32+
} else {
33+
val controlPoints: List<EPointF> = computeControlPoints(n, knots)
34+
for (i in 0 until n) {
35+
val targetKnot: EPointF = knots[i + 1]
36+
appendCurveToPath(
37+
polyBezierPath,
38+
controlPoints[i],
39+
controlPoints[n + i],
40+
targetKnot
41+
)
42+
}
43+
}
44+
return polyBezierPath
45+
}
46+
47+
private fun computeControlPoints(
48+
n: Int,
49+
knots: List<EPointF>
50+
): List<EPointF> {
51+
val result: MutableList<EPointF> = MutableList(2*n) { EPointF(0f, 0f)}
52+
val target: List<EPointF> = constructTargetVector(n, knots)
53+
val lowerDiag = constructLowerDiagonalVector(n - 1)
54+
val mainDiag = constructMainDiagonalVector(n)
55+
val upperDiag = constructUpperDiagonalVector(n - 1)
56+
val newTarget: MutableList<EPointF> = MutableList(n) { EPointF(0f, 0f) }
57+
val newUpperDiag = MutableList(n-1) { it.toFloat()}
58+
59+
// forward sweep for control points c_i,0:
60+
newUpperDiag[0] = upperDiag[0] / mainDiag[0]
61+
newTarget[0] = target[0].scaleBy(1 / mainDiag[0])
62+
for (i in 1 until n - 1) {
63+
newUpperDiag[i] = upperDiag[i] /
64+
(mainDiag[i] - lowerDiag[i - 1] * newUpperDiag[i - 1])
65+
}
66+
for (i in 1 until n) {
67+
val targetScale = 1 /
68+
(mainDiag[i] - lowerDiag[i - 1] * newUpperDiag[i - 1])
69+
newTarget[i] =
70+
target[i].minus(newTarget[i - 1].scaleBy(lowerDiag[i - 1])).scaleBy(
71+
targetScale
72+
)
73+
}
74+
75+
// backward sweep for control points c_i,0:
76+
result[n - 1] = newTarget[n - 1]
77+
for (i in n - 2 downTo 0) {
78+
result[i] = newTarget[i].minus(newUpperDiag[i], result[i + 1])
79+
}
80+
81+
// calculate remaining control points c_i,1 directly:
82+
for (i in 0 until n - 1) {
83+
result[n + i] = knots[i + 1].scaleBy(2f).minus(result[i + 1])
84+
}
85+
result[2 * n - 1] = knots[n].plus(result[n - 1]).scaleBy(0.5f)
86+
return result
87+
}
88+
89+
private fun constructTargetVector(
90+
n: Int,
91+
knots: List<EPointF>
92+
): List<EPointF> {
93+
val result: MutableList<EPointF> = MutableList<EPointF>(n) { EPointF(0f, 0f) }
94+
result[0] = knots[0].plus(2f, knots[1])
95+
for (i in 1 until n - 1) {
96+
result[i] = knots[i].scaleBy(2f).plus(knots[i + 1]).scaleBy(2f)
97+
}
98+
result[result.size - 1] = knots[n - 1].scaleBy(8f).plus(knots[n])
99+
return result
100+
}
101+
102+
private fun constructLowerDiagonalVector(length: Int): List<Float> {
103+
val result = MutableList<Float>(length) { it.toFloat() }
104+
for (i in 0 until length - 1) {
105+
result[i] = 1f
106+
}
107+
result[result.size - 1] = 2f
108+
return result
109+
}
110+
111+
private fun constructMainDiagonalVector(n: Int): List<Float> {
112+
val result = MutableList<Float>(n) { it.toFloat() }
113+
result[0] = 2f
114+
for (i in 1 until n - 1) {
115+
result[i] = 4f
116+
}
117+
result[n - 1] = 7f
118+
return result
119+
}
120+
121+
private fun constructUpperDiagonalVector(length: Int): List<Float> {
122+
val result = MutableList<Float>(length) { it.toFloat() }
123+
for (i in 0 until length) {
124+
result[i] = 1f
125+
}
126+
return result
127+
}
128+
129+
private fun appendCurveToPath(
130+
path: Path,
131+
control1: EPointF,
132+
control2: EPointF,
133+
targetKnot: EPointF
134+
) {
135+
path.cubicTo(
136+
control1.x,
137+
control1.y,
138+
control2.x,
139+
control2.y,
140+
targetKnot.x,
141+
targetKnot.y
142+
)
143+
}
144+
145+
private fun throwExceptionIfInputIsInvalid(knots: List<EPointF>) {
146+
require(knots.size >= 2) { "Collection must contain at least two knots" }
147+
}
148+
}

livechart/src/main/java/com/yabu/livechart/view/LiveChart.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,23 @@ class LiveChart(context: Context, attrs: AttributeSet?) : FrameLayout(context, a
134134
return this
135135
}
136136

137+
/**
138+
* Draw smooth path flag.
139+
*/
140+
@PublicApi
141+
fun drawSmoothPath(): LiveChart {
142+
livechart.drawSmoothPath()
143+
overlay.drawSmoothPath()
144+
return this
145+
}
146+
147+
@PublicApi
148+
fun drawStraightPath(): LiveChart {
149+
livechart.drawStraightPath()
150+
overlay.drawStraightPath()
151+
return this
152+
}
153+
137154
/**
138155
* Disable Fill flag.
139156
*/
@@ -197,6 +214,51 @@ class LiveChart(context: Context, attrs: AttributeSet?) : FrameLayout(context, a
197214
return this
198215
}
199216

217+
/**
218+
* Draw vertical guidelines
219+
*/
220+
@Suppress("UNUSED")
221+
@PublicApi
222+
fun drawVerticalGuidelines(): LiveChart {
223+
livechart.drawVerticalGuidelines()
224+
225+
return this
226+
}
227+
228+
/**
229+
* Draw horizontal guidelines
230+
*/
231+
@Suppress("UNUSED")
232+
@PublicApi
233+
fun drawHorizontalGuidelines(): LiveChart {
234+
livechart.drawHorizontalGuidelines()
235+
236+
return this
237+
}
238+
239+
/**
240+
* Set vertical guidelines steps
241+
*/
242+
@Suppress("UNUSED")
243+
@PublicApi
244+
fun setVerticalGuidelineSteps(steps: Int): LiveChart {
245+
livechart.setVerticalGuidelineSteps(steps)
246+
247+
return this
248+
}
249+
250+
/**
251+
* Set horizontal guidelines steps
252+
*/
253+
@Suppress("UNUSED")
254+
@PublicApi
255+
fun setHorizontalGuidelineSteps(steps: Int): LiveChart {
256+
livechart.setHorizontalGuidelineSteps(steps)
257+
258+
return this
259+
}
260+
261+
200262
/**
201263
* Draw on chart and bind overlay to dataset.
202264
*/

livechart/src/main/java/com/yabu/livechart/view/LiveChartAttributes.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ internal object LiveChartAttributes {
1515

1616
const val MAIN_COLOR = Color.BLACK
1717

18+
const val SECOND_COLOR = Color.GRAY
19+
20+
const val TRANSPARENT_COLOR = "#00000000"
21+
22+
const val CORNER_RADIUS = 0f
23+
1824
const val POSITIVE_COLOR = "#01C194"
1925

2026
const val FILL_COLOR = "#11303030"
@@ -25,6 +31,12 @@ internal object LiveChartAttributes {
2531

2632
const val NEGATIVE_FILL_COLOR = "#11d70a53"
2733

34+
const val BOUNDS_LINE_COLOR = Color.GRAY
35+
36+
const val BASELINE_LINE_COLOR = Color.GRAY
37+
38+
const val GUIDELINE_COLOR = Color.LTGRAY
39+
2840
const val TAG_WIDTH = 120f
2941

3042
const val CHART_END_PADDING = 140f

livechart/src/main/java/com/yabu/livechart/view/LiveChartStyle.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class LiveChartStyle {
2020
/**
2121
* Main color
2222
*/
23-
var secondColor: Int = Color.GRAY
23+
var secondColor: Int = LiveChartAttributes.SECOND_COLOR
2424

2525
/**
2626
* Positive from baseline fill color.
@@ -47,15 +47,30 @@ class LiveChartStyle {
4747
*/
4848
var negativeFillColor: Int = Color.parseColor(LiveChartAttributes.NEGATIVE_FILL_COLOR)
4949

50+
/**
51+
* Main Path corner radius pixel amount.
52+
*/
53+
var mainCornerRadius: Float = LiveChartAttributes.CORNER_RADIUS
54+
55+
/**
56+
* Second Path corner radius pixel amount.
57+
*/
58+
var secondCornerRadius: Float = LiveChartAttributes.CORNER_RADIUS
59+
5060
/**
5161
* Baseline color.
5262
*/
53-
var baselineColor: Int = Color.GRAY
63+
var baselineColor: Int = LiveChartAttributes.BASELINE_LINE_COLOR
64+
65+
/**
66+
* Bounds color.
67+
*/
68+
var boundsLineColor: Int = LiveChartAttributes.BOUNDS_LINE_COLOR
5469

5570
/**
5671
* Baseline color.
5772
*/
58-
var boundsLineColor: Int = Color.GRAY
73+
var guideLineColor: Int = LiveChartAttributes.GUIDELINE_COLOR
5974

6075
/**
6176
* Path stroke width

0 commit comments

Comments
 (0)