Skip to content

Commit 1679af7

Browse files
committed
github chart added.
1 parent ae941af commit 1679af7

File tree

11 files changed

+251
-7
lines changed

11 files changed

+251
-7
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ dependencies {
8383
implementation(libs.androidx.compose.runtime)
8484
implementation(libs.androidx.compose.material)
8585
implementation(libs.androidx.compose.material3)
86+
implementation(libs.kotlinx.datetime)
8687
implementation(project(":drafter"))
8788
}
8889
task("testClasses") {}

app/src/main/kotlin/io/androidpoet/drafterdemo/MainActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import io.androidpoet.drafterdemo.bars.StackedBarChartExample
3232
import io.androidpoet.drafterdemo.bars.WaterfallChartExample
3333
import io.androidpoet.drafterdemo.buble.BubbleChartExample
3434
import io.androidpoet.drafterdemo.gantt.GanttChartExample
35+
import io.androidpoet.drafterdemo.githubgraph.GithubGraph
3536
import io.androidpoet.drafterdemo.line.GroupedLineChartExample
3637
import io.androidpoet.drafterdemo.line.ScatterPlotChartExample
3738
import io.androidpoet.drafterdemo.line.SimpleLineChartExample
@@ -67,6 +68,7 @@ class MainActivity : ComponentActivity() {
6768
item { RadarChartExample() }
6869
item { GanttChartExample() }
6970
item { BubbleChartExample() }
71+
item { GithubGraph() }
7072
}
7173
}
7274
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.androidpoet.drafterdemo.githubgraph
2+
3+
import androidx.compose.foundation.layout.fillMaxWidth
4+
import androidx.compose.foundation.layout.height
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.graphics.Color
8+
import androidx.compose.ui.unit.dp
9+
import io.androidpoet.drafter.heatmap.ContributionData
10+
import io.androidpoet.drafter.heatmap.ContributionHeatmap
11+
import io.androidpoet.drafter.heatmap.ContributionHeatmapData
12+
import kotlinx.datetime.Clock
13+
import kotlin.time.Duration.Companion.days
14+
15+
val now = Clock.System.now()
16+
// Create sample data with varying contribution levels
17+
val contributions = listOf(
18+
ContributionData(now, 12), // Level 4 (bright green)
19+
ContributionData(now.minus(1.days), 8), // Level 3
20+
ContributionData(now.minus(2.days), 5), // Level 2
21+
ContributionData(now.minus(3.days), 2), // Level 1
22+
ContributionData(now.minus(4.days), 0) // Empty
23+
)
24+
25+
val data = ContributionHeatmapData(contributions)
26+
27+
@Composable
28+
fun GithubGraph() {
29+
ContributionHeatmap(
30+
data = data,
31+
modifier = Modifier
32+
.fillMaxWidth()
33+
.height(200.dp)
34+
)
35+
}

baselineprofile-app/src/main/kotlin/io/androidpoet/drafter/baselineprofile/app/bars/StackedBarChartExample.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import androidx.compose.ui.graphics.Color
2121
import io.androidpoet.drafter.bars.BarChart
2222
import io.androidpoet.drafter.bars.StackedBarChartData
2323
import io.androidpoet.drafter.bars.StackedBarChartRenderer
24-
import io.androidpoet.drafterdemo.ChartContainer
25-
import io.androidpoet.drafterdemo.ChartTitle
24+
import io.androidpoet.drafter.baselineprofile.app.ChartContainer
25+
import io.androidpoet.drafter.baselineprofile.app.ChartTitle
2626

2727
@Composable
2828
fun StackedBarChartExample() {

baselineprofile-app/src/main/kotlin/io/androidpoet/drafter/baselineprofile/app/line/ScatterPlotChartExample.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package io.androidpoet.drafter.baselineprofile.app.line
1717

18-
import ScatterPlotData
1918
import androidx.compose.foundation.layout.fillMaxSize
2019
import androidx.compose.foundation.layout.height
2120
import androidx.compose.runtime.Composable
@@ -25,6 +24,7 @@ import androidx.compose.ui.unit.dp
2524
import io.androidpoet.drafter.scatterplot.ScatterPlot
2625
import io.androidpoet.drafter.baselineprofile.app.ChartContainer
2726
import io.androidpoet.drafter.baselineprofile.app.ChartTitle
27+
import io.androidpoet.drafter.scatterplot.ScatterPlotData
2828
import io.androidpoet.drafter.scatterplot.SimpleScatterPlotRenderer
2929
import kotlin.random.Random
3030

baselineprofile-app/src/main/kotlin/io/androidpoet/drafter/baselineprofile/app/radar/RadarChart.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@
1515
*/
1616
package io.androidpoet.drafter.baselineprofile.app.radar
1717

18-
import RadarChartData
1918
import androidx.compose.runtime.Composable
2019
import androidx.compose.ui.graphics.Color
21-
import io.androidpoet.drafter.radar.RadarChart
22-
import io.androidpoet.drafter.baselineprofile.app.ChartContainer
2320
import io.androidpoet.drafter.baselineprofile.app.ChartTitle
21+
import io.androidpoet.drafter.radar.RadarChart
22+
import io.androidpoet.drafter.radar.RadarChartData
2423

2524
@Composable
2625
fun RadarChartExample() {

drafter/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ kotlin {
8383
implementation(compose.material3)
8484
implementation(compose.runtime)
8585
implementation(compose.animation)
86+
implementation(libs.kotlinx.datetime)
87+
8688
}
8789
}
8890
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.androidpoet.drafter.heatmap
2+
3+
import androidx.compose.ui.graphics.Color
4+
import kotlinx.datetime.Instant
5+
6+
public data class ContributionData(
7+
val timestamp: Instant,
8+
val count: Int
9+
)
10+
11+
public data class ContributionHeatmapData(
12+
val contributions: List<ContributionData>,
13+
val baseColor: Color = Color(0xFF40C463), // GitHub's green color
14+
val backgroundSquareColor: Color = Color(0xFF2D333B) // Dark background for squares
15+
)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Designed and developed by 2024 androidpoet (Ranbir Singh)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.androidpoet.drafter.heatmap
17+
18+
import androidx.compose.animation.core.Animatable
19+
import androidx.compose.animation.core.FastOutSlowInEasing
20+
import androidx.compose.animation.core.tween
21+
import androidx.compose.foundation.Canvas
22+
import androidx.compose.foundation.background
23+
import androidx.compose.foundation.layout.*
24+
import androidx.compose.runtime.Composable
25+
import androidx.compose.runtime.LaunchedEffect
26+
import androidx.compose.runtime.remember
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.graphics.Color
29+
import androidx.compose.ui.platform.LocalDensity
30+
import androidx.compose.ui.unit.dp
31+
import kotlinx.datetime.*
32+
33+
@Composable
34+
public fun ContributionHeatmap(
35+
data: ContributionHeatmapData,
36+
modifier: Modifier = Modifier,
37+
renderer: HeatmapRenderer = DefaultHeatmapRenderer(),
38+
startDate: LocalDate = Clock.System.now()
39+
.toLocalDateTime(TimeZone.currentSystemDefault())
40+
.date
41+
.minus(1, DateTimeUnit.YEAR),
42+
endDate: LocalDate = Clock.System.now()
43+
.toLocalDateTime(TimeZone.currentSystemDefault())
44+
.date
45+
) {
46+
val density = LocalDensity.current
47+
val cellSize = with(density) { 11.dp.toPx() } // Slightly larger cells
48+
val cellPadding = with(density) { 3.dp.toPx() } // More padding between cells
49+
50+
val startInstant = startDate.atStartOfDayIn(TimeZone.currentSystemDefault())
51+
val endInstant = endDate.atStartOfDayIn(TimeZone.currentSystemDefault())
52+
53+
val animationProgress = remember { Animatable(0f) }
54+
55+
LaunchedEffect(Unit) {
56+
animationProgress.animateTo(
57+
targetValue = 1f,
58+
animationSpec = tween(
59+
durationMillis = 1000,
60+
easing = FastOutSlowInEasing
61+
)
62+
)
63+
}
64+
65+
Box(
66+
modifier = modifier
67+
.background(Color(0xFF0D1117)) // GitHub dark theme background
68+
.padding(16.dp)
69+
) {
70+
Canvas(
71+
modifier = Modifier
72+
.fillMaxSize()
73+
.padding(start = 16.dp, top = 16.dp) // Add padding for labels
74+
) {
75+
renderer.drawHeatmap(
76+
drawScope = this,
77+
data = data,
78+
cellSize = cellSize,
79+
cellPadding = cellPadding,
80+
startInstant = startInstant,
81+
endInstant = endInstant,
82+
animationProgress = animationProgress.value
83+
)
84+
}
85+
}
86+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Designed and developed by 2024 androidpoet (Ranbir Singh)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.androidpoet.drafter.heatmap
17+
18+
import androidx.compose.ui.geometry.Offset
19+
import androidx.compose.ui.geometry.Size
20+
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.graphics.drawscope.DrawScope
22+
import kotlinx.datetime.*
23+
24+
public interface HeatmapRenderer {
25+
public fun drawHeatmap(
26+
drawScope: DrawScope,
27+
data: ContributionHeatmapData,
28+
cellSize: Float,
29+
cellPadding: Float,
30+
startInstant: Instant,
31+
endInstant: Instant,
32+
animationProgress: Float
33+
)
34+
}
35+
36+
public class DefaultHeatmapRenderer : HeatmapRenderer {
37+
38+
// GitHub's actual contribution level colors
39+
private val emptyColor = Color(0xFF2D333B) // Dark background for empty squares
40+
private val level1Color = Color(0xFF0E4429) // Least contributions
41+
private val level2Color = Color(0xFF006D32)
42+
private val level3Color = Color(0xFF26A641)
43+
private val level4Color = Color(0xFF39D353) // Most contributions
44+
45+
private fun getContributionColor(count: Int): Color {
46+
return when {
47+
count == 0 -> emptyColor
48+
count <= 3 -> level1Color
49+
count <= 6 -> level2Color
50+
count <= 9 -> level3Color
51+
else -> level4Color
52+
}
53+
}
54+
55+
override fun drawHeatmap(
56+
drawScope: DrawScope,
57+
data: ContributionHeatmapData,
58+
cellSize: Float,
59+
cellPadding: Float,
60+
startInstant: Instant,
61+
endInstant: Instant,
62+
animationProgress: Float
63+
) {
64+
with(drawScope) {
65+
val startDate = startInstant.toLocalDateTime(TimeZone.currentSystemDefault()).date
66+
val endDate = endInstant.toLocalDateTime(TimeZone.currentSystemDefault()).date
67+
val weeks = startDate.daysUntil(endDate) / 7
68+
69+
val contributionsMap: Map<Long, Int> = data.contributions.associate {
70+
it.timestamp.toEpochMilliseconds() to it.count
71+
}
72+
73+
var currentDate = startDate
74+
for (week in 0..weeks) {
75+
val weekProgress = (week.toFloat() / weeks).coerceIn(0f, 1f)
76+
if (weekProgress <= animationProgress) {
77+
for (dayOfWeek in 0..6) {
78+
if (currentDate <= endDate) {
79+
val currentInstant = currentDate
80+
.atStartOfDayIn(TimeZone.currentSystemDefault())
81+
82+
val contributions = contributionsMap[currentInstant.toEpochMilliseconds()] ?: 0
83+
84+
val x = week * (cellSize + cellPadding)
85+
val y = dayOfWeek * (cellSize + cellPadding)
86+
87+
// Draw cell with appropriate color based on contributions
88+
drawRect(
89+
color = getContributionColor(contributions),
90+
topLeft = Offset(x, y),
91+
size = Size(cellSize, cellSize),
92+
alpha = 1f // Full opacity always
93+
)
94+
95+
currentDate = currentDate.plus(1, DateTimeUnit.DAY)
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)