Skip to content

Commit

Permalink
github chart added.
Browse files Browse the repository at this point in the history
  • Loading branch information
AndroidPoet committed Jan 1, 2025
1 parent ae941af commit 1679af7
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 7 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ dependencies {
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
implementation(libs.kotlinx.datetime)
implementation(project(":drafter"))
}
task("testClasses") {}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import io.androidpoet.drafterdemo.bars.StackedBarChartExample
import io.androidpoet.drafterdemo.bars.WaterfallChartExample
import io.androidpoet.drafterdemo.buble.BubbleChartExample
import io.androidpoet.drafterdemo.gantt.GanttChartExample
import io.androidpoet.drafterdemo.githubgraph.GithubGraph
import io.androidpoet.drafterdemo.line.GroupedLineChartExample
import io.androidpoet.drafterdemo.line.ScatterPlotChartExample
import io.androidpoet.drafterdemo.line.SimpleLineChartExample
Expand Down Expand Up @@ -67,6 +68,7 @@ class MainActivity : ComponentActivity() {
item { RadarChartExample() }
item { GanttChartExample() }
item { BubbleChartExample() }
item { GithubGraph() }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.androidpoet.drafterdemo.githubgraph

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.androidpoet.drafter.heatmap.ContributionData
import io.androidpoet.drafter.heatmap.ContributionHeatmap
import io.androidpoet.drafter.heatmap.ContributionHeatmapData
import kotlinx.datetime.Clock
import kotlin.time.Duration.Companion.days

val now = Clock.System.now()
// Create sample data with varying contribution levels
val contributions = listOf(
ContributionData(now, 12), // Level 4 (bright green)
ContributionData(now.minus(1.days), 8), // Level 3
ContributionData(now.minus(2.days), 5), // Level 2
ContributionData(now.minus(3.days), 2), // Level 1
ContributionData(now.minus(4.days), 0) // Empty
)

val data = ContributionHeatmapData(contributions)

@Composable
fun GithubGraph() {
ContributionHeatmap(
data = data,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import androidx.compose.ui.graphics.Color
import io.androidpoet.drafter.bars.BarChart
import io.androidpoet.drafter.bars.StackedBarChartData
import io.androidpoet.drafter.bars.StackedBarChartRenderer
import io.androidpoet.drafterdemo.ChartContainer
import io.androidpoet.drafterdemo.ChartTitle
import io.androidpoet.drafter.baselineprofile.app.ChartContainer
import io.androidpoet.drafter.baselineprofile.app.ChartTitle

@Composable
fun StackedBarChartExample() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package io.androidpoet.drafter.baselineprofile.app.line

import ScatterPlotData
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
Expand All @@ -25,6 +24,7 @@ import androidx.compose.ui.unit.dp
import io.androidpoet.drafter.scatterplot.ScatterPlot
import io.androidpoet.drafter.baselineprofile.app.ChartContainer
import io.androidpoet.drafter.baselineprofile.app.ChartTitle
import io.androidpoet.drafter.scatterplot.ScatterPlotData
import io.androidpoet.drafter.scatterplot.SimpleScatterPlotRenderer
import kotlin.random.Random

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@
*/
package io.androidpoet.drafter.baselineprofile.app.radar

import RadarChartData
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import io.androidpoet.drafter.radar.RadarChart
import io.androidpoet.drafter.baselineprofile.app.ChartContainer
import io.androidpoet.drafter.baselineprofile.app.ChartTitle
import io.androidpoet.drafter.radar.RadarChart
import io.androidpoet.drafter.radar.RadarChartData

@Composable
fun RadarChartExample() {
Expand Down
2 changes: 2 additions & 0 deletions drafter/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ kotlin {
implementation(compose.material3)
implementation(compose.runtime)
implementation(compose.animation)
implementation(libs.kotlinx.datetime)

}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.androidpoet.drafter.heatmap

import androidx.compose.ui.graphics.Color
import kotlinx.datetime.Instant

public data class ContributionData(
val timestamp: Instant,
val count: Int
)

public data class ContributionHeatmapData(
val contributions: List<ContributionData>,
val baseColor: Color = Color(0xFF40C463), // GitHub's green color
val backgroundSquareColor: Color = Color(0xFF2D333B) // Dark background for squares
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Designed and developed by 2024 androidpoet (Ranbir Singh)
*
* 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 io.androidpoet.drafter.heatmap

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlinx.datetime.*

@Composable
public fun ContributionHeatmap(
data: ContributionHeatmapData,
modifier: Modifier = Modifier,
renderer: HeatmapRenderer = DefaultHeatmapRenderer(),
startDate: LocalDate = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault())
.date
.minus(1, DateTimeUnit.YEAR),
endDate: LocalDate = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault())
.date
) {
val density = LocalDensity.current
val cellSize = with(density) { 11.dp.toPx() } // Slightly larger cells
val cellPadding = with(density) { 3.dp.toPx() } // More padding between cells

val startInstant = startDate.atStartOfDayIn(TimeZone.currentSystemDefault())
val endInstant = endDate.atStartOfDayIn(TimeZone.currentSystemDefault())

val animationProgress = remember { Animatable(0f) }

LaunchedEffect(Unit) {
animationProgress.animateTo(
targetValue = 1f,
animationSpec = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
)
)
}

Box(
modifier = modifier
.background(Color(0xFF0D1117)) // GitHub dark theme background
.padding(16.dp)
) {
Canvas(
modifier = Modifier
.fillMaxSize()
.padding(start = 16.dp, top = 16.dp) // Add padding for labels
) {
renderer.drawHeatmap(
drawScope = this,
data = data,
cellSize = cellSize,
cellPadding = cellPadding,
startInstant = startInstant,
endInstant = endInstant,
animationProgress = animationProgress.value
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Designed and developed by 2024 androidpoet (Ranbir Singh)
*
* 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 io.androidpoet.drafter.heatmap

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import kotlinx.datetime.*

public interface HeatmapRenderer {
public fun drawHeatmap(
drawScope: DrawScope,
data: ContributionHeatmapData,
cellSize: Float,
cellPadding: Float,
startInstant: Instant,
endInstant: Instant,
animationProgress: Float
)
}

public class DefaultHeatmapRenderer : HeatmapRenderer {

// GitHub's actual contribution level colors
private val emptyColor = Color(0xFF2D333B) // Dark background for empty squares
private val level1Color = Color(0xFF0E4429) // Least contributions
private val level2Color = Color(0xFF006D32)
private val level3Color = Color(0xFF26A641)
private val level4Color = Color(0xFF39D353) // Most contributions

private fun getContributionColor(count: Int): Color {
return when {
count == 0 -> emptyColor
count <= 3 -> level1Color
count <= 6 -> level2Color
count <= 9 -> level3Color
else -> level4Color
}
}

override fun drawHeatmap(
drawScope: DrawScope,
data: ContributionHeatmapData,
cellSize: Float,
cellPadding: Float,
startInstant: Instant,
endInstant: Instant,
animationProgress: Float
) {
with(drawScope) {
val startDate = startInstant.toLocalDateTime(TimeZone.currentSystemDefault()).date
val endDate = endInstant.toLocalDateTime(TimeZone.currentSystemDefault()).date
val weeks = startDate.daysUntil(endDate) / 7

val contributionsMap: Map<Long, Int> = data.contributions.associate {
it.timestamp.toEpochMilliseconds() to it.count
}

var currentDate = startDate
for (week in 0..weeks) {
val weekProgress = (week.toFloat() / weeks).coerceIn(0f, 1f)
if (weekProgress <= animationProgress) {
for (dayOfWeek in 0..6) {
if (currentDate <= endDate) {
val currentInstant = currentDate
.atStartOfDayIn(TimeZone.currentSystemDefault())

val contributions = contributionsMap[currentInstant.toEpochMilliseconds()] ?: 0

val x = week * (cellSize + cellPadding)
val y = dayOfWeek * (cellSize + cellPadding)

// Draw cell with appropriate color based on contributions
drawRect(
color = getContributionColor(contributions),
topLeft = Offset(x, y),
size = Size(cellSize, cellSize),
alpha = 1f // Full opacity always
)

currentDate = currentDate.plus(1, DateTimeUnit.DAY)
}
}
}
}
}
}
}
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[versions]
agp = "8.5.2"
dokka = "1.9.10"
kotlinxDatetime = "0.6.1"
nexusPlugin = "0.29.0"
kotlin = "2.0.20"
kotlinBinaryCompatibility = "0.16.3"
Expand Down Expand Up @@ -41,4 +42,5 @@ androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTest" }
androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "baselineProfiles" }
androidx-benchmark-macro = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" }
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiAutomator" }
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiAutomator" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }

0 comments on commit 1679af7

Please sign in to comment.