From 184d6090f9c41353cb92f2cbb831f7f4c02b271b Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sat, 28 Mar 2026 08:24:02 +0100 Subject: [PATCH 1/3] Grantt chart Inspired by https://github.com/PhilJay/MPAndroidChart/pull/5519 --- app/src/main/AndroidManifest.xml | 1 + .../chartexample/TimeIntervalChartActivity.kt | 51 ++++++ .../chartexample/notimportant/MainActivity.kt | 2 + .../layout/activity_time_interval_chart.xml | 59 +++++++ .../info/appdev/charting/charts/GanttChart.kt | 165 ++++++++++++++++++ .../appdev/charting/data/GanttChartData.kt | 90 ++++++++++ .../info/appdev/charting/data/GanttTask.kt | 15 ++ 7 files changed, 383 insertions(+) create mode 100644 app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt create mode 100644 app/src/main/res/layout/activity_time_interval_chart.xml create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/data/GanttChartData.kt create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt 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..5a3e5013e9 --- /dev/null +++ b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt @@ -0,0 +1,51 @@ +package info.appdev.chartexample + +import android.graphics.Color +import android.os.Bundle +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +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(), OnSeekBarChangeListener, 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 onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) = Unit + + override fun onStartTrackingTouch(p0: SeekBar?) = Unit + + override fun onStopTrackingTouch(p0: SeekBar?) = 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..e415f8a1ab --- /dev/null +++ b/app/src/main/res/layout/activity_time_interval_chart.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + 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..50e9831437 --- /dev/null +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt @@ -0,0 +1,165 @@ +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 + + 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() { + chartLeft = padding + 70 + 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 gridLines = 10 + val timeLabelPaint = Paint().apply { + color = -0x99999a + textSize = 22f + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + for (i in 0..gridLines) { + val x = chartLeft + (i / gridLines.toFloat()) * (chartRight - chartLeft) + canvas.drawLine(x, chartTop, x, chartBottom, gridPaint!!) + + val time = minTime + (i / gridLines.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 = 24f + 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 +} From 209cbbc95d7c231d739385889b72360c57d6f981 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Fri, 3 Apr 2026 07:35:55 +0200 Subject: [PATCH 2/3] Adapt screenshots --- ...Start-46-TimeIntervalChartActivity-Error.png | Bin 0 -> 17440 bytes ...lChartActivity-Timeinterval-1SampleClick.png | Bin 0 -> 17440 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Error.png create mode 100644 screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Error.png b/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Error.png new file mode 100644 index 0000000000000000000000000000000000000000..65b04763bc5110b4a523dab9b47a5484ef27eff1 GIT binary patch literal 17440 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 Qi0>%MDa*#6HoX7e0JOHnO8@`> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..65b04763bc5110b4a523dab9b47a5484ef27eff1 GIT binary patch literal 17440 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 Qi0>%MDa*#6HoX7e0JOHnO8@`> literal 0 HcmV?d00001 From dbd1edfe5f22adaded99c6ff6b23eaff6cf4a761 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 06:06:50 +0000 Subject: [PATCH 3/3] Fix Gantt chart label clipping, x-axis label overlap, and seekbar overlay - GanttChart.kt: dynamically compute chartLeft from max label width so long names like 'Development' are never clipped by the view edge - GanttChart.kt: compute grid-line count from available width / label width so x-axis time labels never overlap each other - GanttChart.kt: extract labelTextSize, gridLinesMin/Max as constants so measurement paint and drawing paint are always in sync - activity_time_interval_chart.xml: remove unused SeekBar widgets and TextViews that were rendering on top of the chart - TimeIntervalChartActivity.kt: remove OnSeekBarChangeListener interface and its unused override methods Agent-Logs-Url: https://github.com/AppDevNext/AndroidChart/sessions/a790c59a-5a33-46f0-9ff9-81024453c605 Co-authored-by: hannesa2 <3314607+hannesa2@users.noreply.github.com> --- .../chartexample/TimeIntervalChartActivity.kt | 10 +--- .../layout/activity_time_interval_chart.xml | 52 +------------------ .../info/appdev/charting/charts/GanttChart.kt | 34 +++++++++--- 3 files changed, 30 insertions(+), 66 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt index 5a3e5013e9..cb0c749f15 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt @@ -2,8 +2,6 @@ package info.appdev.chartexample import android.graphics.Color import android.os.Bundle -import android.widget.SeekBar -import android.widget.SeekBar.OnSeekBarChangeListener import info.appdev.chartexample.notimportant.DemoBase import info.appdev.charting.charts.GanttChart import info.appdev.charting.data.EntryFloat @@ -16,7 +14,7 @@ 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(), OnSeekBarChangeListener, OnChartValueSelectedListener { +class TimeIntervalChartActivity : DemoBase(), OnChartValueSelectedListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_time_interval_chart) @@ -38,12 +36,6 @@ class TimeIntervalChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartVa override fun saveToGallery() = Unit - override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) = Unit - - override fun onStartTrackingTouch(p0: SeekBar?) = Unit - - override fun onStopTrackingTouch(p0: SeekBar?) = Unit - override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = Unit override fun onNothingSelected() = Unit diff --git a/app/src/main/res/layout/activity_time_interval_chart.xml b/app/src/main/res/layout/activity_time_interval_chart.xml index e415f8a1ab..9f5a3e1c7c 100644 --- a/app/src/main/res/layout/activity_time_interval_chart.xml +++ b/app/src/main/res/layout/activity_time_interval_chart.xml @@ -1,5 +1,5 @@ - @@ -8,52 +8,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - - - - - - - - - + diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt index 50e9831437..1afe35bcf1 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt @@ -20,6 +20,9 @@ class GanttChart : View { 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() @@ -66,7 +69,18 @@ class GanttChart : View { } private fun calculateDimensions() { - chartLeft = padding + 70 + 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 @@ -102,18 +116,24 @@ class GanttChart : View { timeRange = 100f } - val gridLines = 10 val timeLabelPaint = Paint().apply { color = -0x99999a textSize = 22f isAntiAlias = true textAlign = Paint.Align.CENTER } - for (i in 0..gridLines) { - val x = chartLeft + (i / gridLines.toFloat()) * (chartRight - chartLeft) + + // 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 / gridLines.toFloat()) * timeRange + val time = minTime + (i / maxGridLines.toFloat()) * timeRange canvas.drawText(String.format(Locale.getDefault(), "%.0f", time), x, chartBottom + 30, timeLabelPaint) } } @@ -132,7 +152,7 @@ class GanttChart : View { val labelPaint = Paint() labelPaint.color = -0xcccccd - labelPaint.textSize = 24f + labelPaint.textSize = labelTextSize labelPaint.isAntiAlias = true labelPaint.textAlign = Paint.Align.RIGHT @@ -154,7 +174,7 @@ class GanttChart : View { // Center label vertically in the slot val labelY = taskY + (taskHeight / 2) + 8 - canvas.drawText(task.name!!, chartLeft - 20, labelY, labelPaint) + canvas.drawText(task.name!!, chartLeft - padding, labelY, labelPaint) val rect = RectF(startX, taskY, endX, taskY + taskHeight) taskPaint!!.color = task.color