09-28-2022 11:10 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
Actually, the App is designed to be responsive and not a fixed size. It will dynamically size to the browser window to take advantage of higher resolution displays. If you want it to be fixed size, you will need to change some relative position calculations.
I see you're answering a lot of other questions and mine may have been lost in the shuffle. It could also be you're still working on a solution, or there is no solution at all. I just need to know if there's a way to only see Weekdays in the timeline (underlay gallery) as that's the environment I work in? I've tried for hours to get it to work myself.. Whether it's messing with the date label properties, the gallery item or template fill properties, or the Weekend Fill Rectangle Properties... I'm having no luck at all. Anything will help, even if it's telling me I'm hosed. Much appreciated.
Hello @forbudt4u ,
I have been thinking about your question. It would not be simple to do since most of the layout calculations are based on a continuous sequence of days.
We still have to account for the weekend dates even if we don't show them, The Gantt bar start and end dates will need to account for the date gaps where the displayed weekend days were.
One possible approach could be to change the Items in the galUnderlayGrid control.
The Items are currently just a list of numbers Sequence(150,0). This could be changed to a collection of dates that does not contain weekend days.
You could create that collection like this:
// Create a collection of dates that does not contain weekends starting with gblGanttRangeStart
ForAll(Sequence(150,0),
If(Weekday(DateAdd(gblGanttRangeStart,Value,Days)) in [2,3,4,5,6],
Collect(colWeekDays,DateAdd(gblGanttRangeStart,Value,Days))
)
)
Change the galUnderlayGrid.Items property to the new collection colWeekDays
Change the labels to use the date value in the collection rather than calculating the date based on the number.
lblUGGridSectionStartDate.Text: ThisItem.Value
lblUGDayName: Text(ThisItem.Value, "[$-en-US]ddd")
lblUGDayNum: If(!(lblGridDensity.Text= "DAILY"), Text(ThisItem.Value, "[$-en-US]mmm") & " ") & Day(ThisItem.Value)
This will give you a weekday grid with correct dates and no weekend days. This example has a start date of Oct 10, 2021:
Note that there are several areas with calculations that expect the week to be 7 days. gblGanttIncrement, navigation buttons, grid density, etc.
Also, the colWeekDays collection will need to be recreated when value of gblGanttRangeStart (date range "From") changes.
That should give you a start anyway. Hopefully that helps.
-Ron
Outstanding, that's what I was missing... a collection to handle M-F (days 2-6). I knew there would be many other controls I would need to update the properties on once I got over this hump and this is what I needed to get started. I'm also going to address the duration column as it will have to account for weekdays only as well. Much appreciated and I'll follow up in here once I get it figured out so it benefits others that are looking to accomplish the same goal. Good day!!
Thank you for the solution , but some projects are having many tasks beyond 50 and some 100 then this will fail. I have taken a sub gallery to filter all the tasks for project but the icon is setting in the correct due date inside the sub gallery. Can you guide on the same.
I like the responsive design, but just not for the apps I build. I want to utilize more screen real estate and match the other apps that pertain to project work we have. I’ve really been able to take your design you shared and done some amazing things. I just haven’t figured out the calculations yet 🤔
I really appreciate the work you’ve put into this. Tinkering with something is one of the ways we learn more so, it’s helpful even in that fashion
Great app! Thanks for sharing.
Would it be possible to edit this to display multiple bars per line? (a bit different to a standard Gantt I know).
Use case - I need it as a rostering / project planning solution. I can't find anything that quite meets my company's needs, which is to visually display staff availability vs planned project dates (even COTS solutions). Attached is a screenshot example (blurred as unfortunately there's sensitive info) to hopefully better put across what I'm explaining. Would be hugely grateful for any steer!
sounds a bit like I’m working on at the moment. I have coloured days based on their day type and I have created 7 vertical galleries, 1 for each day. I have a primary gallery listing Office365Users (which I’m filtering by department) and then 7 vertical galleries with various logic to show a label for each person based on the start and end date of the task.
yes this what i am looking at can you please share that with me.