diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 648410d1e4..bcecfefdb9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -60,6 +60,7 @@
+
diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt
new file mode 100644
index 0000000000..cb0c749f15
--- /dev/null
+++ b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt
@@ -0,0 +1,43 @@
+package info.appdev.chartexample
+
+import android.graphics.Color
+import android.os.Bundle
+import info.appdev.chartexample.notimportant.DemoBase
+import info.appdev.charting.charts.GanttChart
+import info.appdev.charting.data.EntryFloat
+import info.appdev.charting.data.GanttChartData
+import info.appdev.charting.data.GanttTask
+import info.appdev.charting.highlight.Highlight
+import info.appdev.charting.listener.OnChartValueSelectedListener
+
+/**
+ * Demo activity showing Gantt-style timeline visualization.
+ * Each horizontal bar represents a task with start time and duration.
+ */
+class TimeIntervalChartActivity : DemoBase(), OnChartValueSelectedListener {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_time_interval_chart)
+
+ val chart = findViewById(R.id.chart)
+
+ // Create Gantt chart data
+ val ganttData = GanttChartData()
+
+ // Add sample project tasks
+ ganttData.addTask(GanttTask("Design", 0f, 50f, Color.rgb(255, 107, 107))) // Red: 0-50
+ ganttData.addTask(GanttTask("Development", 40f, 100f, Color.rgb(66, 165, 245))) // Blue: 40-140
+ ganttData.addTask(GanttTask("Testing", 120f, 40f, Color.rgb(76, 175, 80))) // Green: 120-160
+ ganttData.addTask(GanttTask("Launch", 150f, 20f, Color.rgb(255, 193, 7))) // Yellow: 150-170
+
+ // Set data and render
+ chart.setData(ganttData)
+ }
+
+ override fun saveToGallery() = Unit
+
+ override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = Unit
+
+ override fun onNothingSelected() = Unit
+
+}
diff --git a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt
index 35572d42af..5f0ff4f1c7 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt
@@ -75,6 +75,7 @@ import info.appdev.chartexample.ScrollViewActivity
import info.appdev.chartexample.SpecificPositionsLineChartActivity
import info.appdev.chartexample.StackedBarActivity
import info.appdev.chartexample.StackedBarActivityNegative
+import info.appdev.chartexample.TimeIntervalChartActivity
import info.appdev.chartexample.TimeLineActivity
import info.appdev.chartexample.compose.HorizontalBarComposeActivity
import info.appdev.chartexample.compose.HorizontalBarFullComposeActivity
@@ -219,6 +220,7 @@ class MainActivity : ComponentActivity() {
add(ContentItem("Demonstrate and fix issues"))
add(ContentItem("Gradient", "Show a gradient edge case", GradientActivity::class.java))
add(ContentItem("Timeline", "Show a time line with Unix timestamp", TimeLineActivity::class.java))
+ add(ContentItem("Timeinterval", "Grantt chart", TimeIntervalChartActivity::class.java))
}
}
}
diff --git a/app/src/main/res/layout/activity_time_interval_chart.xml b/app/src/main/res/layout/activity_time_interval_chart.xml
new file mode 100644
index 0000000000..9f5a3e1c7c
--- /dev/null
+++ b/app/src/main/res/layout/activity_time_interval_chart.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt
new file mode 100644
index 0000000000..1afe35bcf1
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt
@@ -0,0 +1,185 @@
+package info.appdev.charting.charts
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.AttributeSet
+import android.view.View
+import info.appdev.charting.data.GanttChartData
+import java.util.Locale
+
+class GanttChart : View {
+ private var data: GanttChartData? = null
+ private var taskPaint: Paint? = null
+ private var gridPaint: Paint? = null
+ private var textPaint: Paint? = null
+
+ private var chartLeft = 0f
+ private var chartTop = 0f
+ private var chartRight = 0f
+ private var chartBottom = 0f
+ private val padding = 16f
+ private val labelTextSize = 24f
+ private val gridLinesMin = 2
+ private val gridLinesMax = 10
+
+ constructor(context: Context?) : super(context) {
+ init()
+ }
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
+ init()
+ }
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ init()
+ }
+
+ private fun init() {
+ taskPaint = Paint().apply {
+ isAntiAlias = true
+ }
+ gridPaint = Paint().apply {
+ color = -0x333334
+ strokeWidth = 1f
+ }
+ textPaint = Paint().apply {
+ color = -0x99999a
+ textSize = 28f
+ isAntiAlias = true
+ }
+ }
+
+ fun setData(data: GanttChartData?) {
+ this.data = data
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ if (data == null || data!!.taskCount == 0) {
+ return
+ }
+
+ calculateDimensions()
+ drawGrid(canvas)
+ drawTasks(canvas)
+ }
+
+ private fun calculateDimensions() {
+ val labelMeasurePaint = Paint().apply {
+ textSize = labelTextSize
+ isAntiAlias = true
+ }
+ var maxLabelWidth = 0f
+ if (data != null) {
+ for (i in 0.. maxLabelWidth) maxLabelWidth = w
+ }
+ }
+ chartLeft = maxLabelWidth + padding * 3
+ chartTop = padding + 30
+ chartRight = width - padding
+ chartBottom = height - padding - 30
+ }
+
+ private val taskHeight: Float
+ // Dynamically calculate task height based on available space
+ get() {
+ if (data == null || data!!.taskCount == 0) {
+ return 40f
+ }
+ val availableHeight = chartBottom - chartTop
+ val taskCount = data!!.taskCount
+ // 50% of slot for bar, 50% for gap
+ return (availableHeight / taskCount) * 0.5f
+ }
+
+ private val taskSpacing: Float
+ get() {
+ if (data == null || data!!.taskCount == 0) {
+ return 12f
+ }
+ val availableHeight = chartBottom - chartTop
+ val taskCount = data!!.taskCount
+ return (availableHeight / taskCount) * 0.5f
+ }
+
+ private fun drawGrid(canvas: Canvas) {
+ val minTime = data!!.minTime
+ val maxTime = data!!.maxTime
+ var timeRange = maxTime - minTime
+ if (timeRange == 0f) {
+ timeRange = 100f
+ }
+
+ val timeLabelPaint = Paint().apply {
+ color = -0x99999a
+ textSize = 22f
+ isAntiAlias = true
+ textAlign = Paint.Align.CENTER
+ }
+
+ // Calculate how many grid lines fit without overlapping labels
+ val sampleLabel = String.format(Locale.getDefault(), "%.0f", maxTime)
+ val labelWidth = timeLabelPaint.measureText(sampleLabel) + 8f
+ val chartWidth = chartRight - chartLeft
+ val maxGridLines = (chartWidth / labelWidth).toInt().coerceIn(gridLinesMin, gridLinesMax)
+
+ for (i in 0..maxGridLines) {
+ val x = chartLeft + (i / maxGridLines.toFloat()) * chartWidth
+ canvas.drawLine(x, chartTop, x, chartBottom, gridPaint!!)
+
+ val time = minTime + (i / maxGridLines.toFloat()) * timeRange
+ canvas.drawText(String.format(Locale.getDefault(), "%.0f", time), x, chartBottom + 30, timeLabelPaint)
+ }
+ }
+
+ private fun drawTasks(canvas: Canvas) {
+ val minTime = data!!.minTime
+ val maxTime = data!!.maxTime
+ var timeRange = maxTime - minTime
+ if (timeRange == 0f) {
+ timeRange = 100f
+ }
+
+ val taskHeight = this.taskHeight
+ val taskSpacing = this.taskSpacing
+ val slotHeight = taskHeight + taskSpacing
+
+ val labelPaint = Paint()
+ labelPaint.color = -0xcccccd
+ labelPaint.textSize = labelTextSize
+ labelPaint.isAntiAlias = true
+ labelPaint.textAlign = Paint.Align.RIGHT
+
+ val borderPaint = Paint()
+ borderPaint.color = -0x666667
+ borderPaint.strokeWidth = 2f
+ borderPaint.style = Paint.Style.STROKE
+
+ for (i in 0.. = ArrayList()
+
+ /**
+ * Add a task to the Gantt chart.
+ *
+ * @param task The task to add
+ */
+ fun addTask(task: GanttTask?) {
+ tasks.add(task!!)
+ }
+
+ /**
+ * Add multiple tasks to the Gantt chart.
+ *
+ * @param taskList List of tasks to add
+ */
+ fun addTasks(taskList: MutableList) {
+ tasks.addAll(taskList)
+ }
+
+ /**
+ * Get a specific task by index.
+ *
+ * @param index Task index
+ * @return The task at the given index
+ */
+ fun getTask(index: Int): GanttTask {
+ return tasks[index]
+ }
+
+ /**
+ * Get the number of tasks.
+ *
+ * @return Number of tasks in the chart
+ */
+ val taskCount: Int
+ get() = tasks.size
+
+ /**
+ * Get the earliest start time across all tasks.
+ *
+ * @return Minimum start time
+ */
+ val minTime: Float
+ get() {
+ if (tasks.isEmpty()) return 0f
+ var min = Float.MAX_VALUE
+ for (task in tasks) {
+ min = min(min, task.startTime)
+ }
+ return min
+ }
+
+ /**
+ * Get the latest end time across all tasks.
+ *
+ * @return Maximum end time
+ */
+ val maxTime: Float
+ get() {
+ if (tasks.isEmpty()) return 100f
+ var max = 0f
+ for (task in tasks) {
+ max = max(max, task.endTime)
+ }
+ return max
+ }
+
+ /**
+ * Clear all tasks.
+ */
+ fun clearTasks() {
+ tasks.clear()
+ }
+}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt
new file mode 100644
index 0000000000..9290551b7d
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt
@@ -0,0 +1,15 @@
+package info.appdev.charting.data
+
+/**
+ * Represents a single task in a Gantt chart.
+ * Each task has a name, start time, duration, and display color.
+ *
+ * @param name Task name/label
+ * @param startTime When the task starts
+ * @param duration How long the task lasts
+ * @param color Display color (Android color int)
+ */
+class GanttTask(val name: String?, val startTime: Float, val duration: Float, val color: Int) {
+ val endTime: Float
+ get() = startTime + duration
+}
diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Error.png b/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Error.png
new file mode 100644
index 0000000000..65b04763bc
Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Error.png differ
diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png
new file mode 100644
index 0000000000..65b04763bc
Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png differ