08-23-2021 16:45 PM - last edited 08-24-2021 07:38 AM
I was working on an internal project a couple of years ago and one of the desired features was to display the date in a Gantt-type chart. I had already been doing quite a bit with Power Apps at the time so I thought maybe I could do this somehow with a canvas app. After trying several things, including embedding Power BI, it seemed like a futile effort.
Then one day after playing around with making some bar graphs with some dynamically sized label controls, I had an idea. It seemed that it would be possible to use some similar techniques to create a data-driven Gantt-like display. The idea has evolved a bit since the original.
Okay, enough of the boring story and onto the App!
App Features
Expand or Collapse to Parent Level
Click on the chevron to expand or collapse main tasks including the top-level project.
Automatic Grid Scaling
The app will automatically scale the date grid density based on the window size and date range.
Daily View
Weekly View
4-Week View
Navigate and Zoom
Select start and end dates to display tasks in that range on Gannt. Use Navigate buttons to move forward or backward in time. Use Zoom buttons to increase/decrease end date to effectively “Zoom” the time window in or out.
Automatically Adjust Gantt Density Based on Browser Window Size
App will dynamically change timeline grid lines based to display more data on larger/hi-res screens.
Smaller window scales to weekly grid to display data
Maximize window and see that it changes the scale from weekly to daily grid
If a task label is too long to put inside the Gantt bar based on the scaling, it is automatically placed outside the end of the bar.
Project Selection
At the bottom of the screen, there is a project list gallery where you can select the projects to include in the display.
How It All Works
The Data
For this demo app, I created some data that is loosely based on construction and remodeling projects. It is simplified for the purpose of demoing the UI. I imported the data into the app so that it would be easy to distribute the demo. Note that I wanted to make the data flat and simple for the purpose of showing the UI. In a real app, this data would be distilled from multiple tables or views. I load this table into a collection (colTasks) at startup to make things easier.
About the Tasks data:
Column Name | Description |
Id | Unique Id for the row |
ProjectId | Project number |
ProjectName | Project name |
TaskNo | Top-level task number (0=parent project) |
SubTaskNo | Sub task number |
TaskName | Task name |
StartDate | Start date for this task |
EndDate | End date for this task |
TaskType | Task type for this task |
TaskLvl | Task indent level for this task |
Duration | Duration in days for this task (parents contain sum of subtasks) if the duration is 0, then the task is considered a milestone and displayed with a pentagon icon (there is no diamond icon and I was a little too lazy to do something else). |
Show | Show this row in the Gantt |
Expanded | Are the sub tasks showing or not? |
Task Types
The Task Types are in a separate table. Each type has a color specified by a hex RGB color code which is used to color code the Gantt bars.
The Gantt Bars
The core of this app is how the Gantt bars are displayed. It comes down to two basic things: Length and Position.
To find the position, we first need to see how many horizontal pixels there are in a day on the grid.
We can get that by using the following calculation:
pixels per day = (end pixel - start pixel) / (end date - start date)
Then calculate the position X:
X = start pixel + (end date - start date) * pixels per day
For the bar length (width):
width = duration days * pixels per day
I used label controls to track the data to support the above calculations. Label controls are great for this since they automatically calculate the values as the data changes. Although these would normally be hidden, I am displaying them in this app for educational purposes.
The actual formulas in the app are as follows:
Pixels per day (label):
Value(lblPixInRange.Text)/Value(lblDaysInRange.Text)
Gantt bar position X:
Value(lblStartPix.Text) + DateDiff(dteStartDate.SelectedDate,ThisItem.StartDate,Days) * Value(lblPixPerDay.Text)
Gantt bar Width:
ThisItem.Duration * Value(lblPixPerDay.Text)
Date Overlay Grid (Underlay?)
The date grid is just a horizontal gallery control that actually sits behind the Gantt gallery.
The Items/Data source for the gallery is sequence starting at 0. I figured that 150 should be a safe maximum for most applications.
I have a hidden label (lblUGGridSectionStartDate) that I used to simplify calculating the timeline headers.
lblUGGridSectionStartDate.Text will dynamically change its value based on the density of the grid and start date.
Switch(
lblGridDensity.Text,
"4WEEKS",
dteStartDate.SelectedDate + ThisItem.Value * 28,
"WEEKLY",
dteStartDate.SelectedDate + ThisItem.Value * 7,
dteStartDate.SelectedDate + ThisItem.Value
)
Expand/Collapse Subtask Display
The key to determining what data to show in the Gantt view is literally the Boolean ‘Show’ column in the collection (as well as the selected project, of course).
The Items/Data source for the main gallery (galGanttView):
Filter(
colTasks,
ProjectId in Filter(
galProjectList.AllItems,
chkPLIsSelected.Value = true
).Result,
Show
)
When clicking on the chevron (icoGVShowHideSubs), the app will toggle the expanded state of the control and data column: ‘Expanded’ as well as updating the subtasks ‘Show’ column.
Select(Parent);
If(
ThisItem.Expanded,
UpdateIf(
colTasks,
// hide subtasks of current task
And(
ProjectId = ThisItem.ProjectId,
TaskNo = ThisItem.TaskNo,
SubTaskNo > 0
),
{Show: false},
// change expanded field for this task
And(
ProjectId = ThisItem.ProjectId,
TaskNo = ThisItem.TaskNo,
SubTaskNo = 0
),
{Expanded: false},
// hide all tasks for this project task (0)
If(
ThisItem.TaskNo = 0,
And(
ProjectId = ThisItem.ProjectId,
TaskNo > 0,
Show
)
),
{
Show: false,
Expanded: false
}
),
UpdateIf(
colTasks,
// show subtasks of current task
And(
ProjectId = ThisItem.ProjectId,
TaskNo = ThisItem.TaskNo,
SubTaskNo > 0
),
{Show: true},
// change expanded field for this task
And(
ProjectId = ThisItem.ProjectId,
TaskNo = ThisItem.TaskNo,
SubTaskNo = 0
),
{Expanded: true},
// show all tasks for this project task (0)
If(
ThisItem.TaskNo = 0,
And(
ProjectId = ThisItem.ProjectId,
TaskNo > 0
)
),
{
Show: true,
Expanded: true
}
)
);
The appearance of the chevron icon itself is controlled by the ‘Expanded’ column.
If(
ThisItem.Expanded,
Icon.ChevronDown,
Icon.ChevronRight
)
Summary
You will find that there are other features there that I have not called out. But the general idea was to show what could be done with standard controls. I didn’t want to over complicate things by adding too much for this demo.
This is my first post to the Community Samples. Hopefully others will find this useful for their projects.
Cheers!
-Ron Larsen
watch?v=l60u2IpveTA
Hi thank you for your reply but what exactly i want is to display task duedates like icons on the project timeline in a single row rather than multiple rows. Its my requirement i tried to take sub gallery and filtered all the tasks of the project but the duedate is not sitting correctly on the correct date. Please check my image.
Seeing that the way Ron's current layout is designed to use buttons as his method to display the tasks/subtasks, the subtasks rely on the main task, as well as the auto sizing expressions being set based on the actual task dates... you would likely have to redesign the entire thing to accommodate for the subtask buttons to overlay the task button itself. I'm also not sure why you would want all of the Prepare Site subtasks to all show up within the Design/Drafting Main task?
One way to do this would be to add multiple circle icons to the current gallery (galGanttView). You could conditionally hide or show them based on the number of due dates/subtasks that you have.
I used 4 in this example, but you could put 50 in here if you wanted.
Use the X position to set the relative date. These would maintain their position relative to the main task during scrolling or zoom.
Value(lblStartPix.Text) + DateDiff( **SUBTASK n DUE DATE GOES HERE** ,Days) * Value(lblPixPerDay.Text)
Hope this helps
-Ron
Here is an example using the start date of the subtasks (since I do not have due date in this data). I expanded subtasks on a couple to show it works. 🙂
For the X value of Circle1, I needed to get the StartDate of subtask 1 using the index.
Then I only want to show it on the main task - and ONLY if it exists:
Here is the example code for Circle1-Circle3. Repeat and update the index for additional icons.
//Circle1.X
Value(lblStartPix.Text) + DateDiff(dteStartDate.SelectedDate,Index(Filter(colTasks, ProjectId=ThisItem.ProjectId, TaskLvl=1),1).StartDate ,Days) * Value(lblPixPerDay.Text)
//Circle1.Visible
And(ThisItem.TaskLvl=0, Not(IsEmpty(Index(Filter(colTasks, ProjectId=ThisItem.ProjectId, TaskLvl=1),1))))
//Circle2.X
Value(lblStartPix.Text) + DateDiff(dteStartDate.SelectedDate,Index(Filter(colTasks, ProjectId=ThisItem.ProjectId, TaskLvl=1),2).StartDate ,Days) * Value(lblPixPerDay.Text)
//Circle2.Visible
And(ThisItem.TaskLvl=0, Not(IsEmpty(Index(Filter(colTasks, ProjectId=ThisItem.ProjectId, TaskLvl=1),2))))
//Circle3.X
Value(lblStartPix.Text) + DateDiff(dteStartDate.SelectedDate,Index(Filter(colTasks, ProjectId=ThisItem.ProjectId, TaskLvl=1),3).StartDate ,Days) * Value(lblPixPerDay.Text)
//Circle3.Visible
And(ThisItem.TaskLvl=0, Not(IsEmpty(Index(Filter(colTasks, ProjectId=ThisItem.ProjectId, TaskLvl=1),3))))
This works with the sample data as it is. You can change the data design to suit your needs.
Good Luck. Remember to Like this if it is useful to you.
-Ron
I really like your creativity here. I'm trying to pull 2 tables together. I have a main TASKS table. I also have an ACTIONS table where RefID = ID (The TASK ID).
I created a subgallery and filter the ACTIONS table where RefID = ThisItem.ID, but no records are found in the sub gallery. If I take out the filter in the sub gallery, it will show all of the ACTION records. Do you have an idea of how I could fix this? I made a flexible gallery so that it could expand, but right now I'm just trying to get the records.
Thanks for any help - Once again, thanks for your great work!
Nevermind...I think I was running into delegation issues. I think I figured it out now
You’ve done a great job here. I was hoping to have it display like my other main apps which are 1700 x 900 and not the 16:9 setting. When I update the display setting, the bars don’t line up with the date. For example, the start date might be like September 10, but the bar shows it start on maybe on September 15, something like that. I checked all of the coding to make sure it had the same calculations as previously. Any ideas??
thanks