top of page
Writer's pictureArill André Aam

Part 1: How to create a custom bar chart visualisation in Power Apps

A step-by-step guide for creating a custom bar chart using only built-in controls in Power Apps.



Custom visualisations convey data accurately and precisely, bringing key insights and real value to clients. Effective data visualisations enhance your business app portfolio and add value to existing Dynamics 365 and Power Platform applications. However, it's important to be aware of the benefits and limitations of these applications. I will explain this further later in the blog.

Step 1 - Set up your page in a desired layout using containers


In this example I have used the following container layout:


I have split the screen vertically into two responsive containers. In the top container, I will create a simple bar chart to visualise the total number of units sold (phones, tablets, and PCs) per country. In the bottom container, I will create a related clustered bar chart to break down the number of units sold by size (Small or Large). The ScreenContainer's X and Y properties are set to 0, while Width and Height are set to Parent.Width and Parent.Height, respectively. This setup creates a full-screen container that helps structure the content consistently and reliably.

In this example I have used a Cilate color palette in the "Fill" properties:

ScreenContainer.Fill: ColorFade(RGBA(13, 50, 61, 1),10%)
Container_bars.Fill: RGBA(13, 50, 61, 0.7)

The two containers' properties are set to:

AlignInContainer: AlignInContainer.Center 
Width: Min(Parent.Width*0.9, 1000)

The Min function selects the smaller of two values separated by a comma. In this case, it sets a maximum width of 1000px or 90% of the app's screen width, whichever is smaller. While this post won't cover responsive design in Power Apps in depth, you can learn more in the official documentation.


Step 2 - Add the mock data


In order for us to create a bar chart visualisation we need data. Although this is not best practice, put the following data set in the OnStart property of the App:


ClearCollect(
    CountrySales,
    Table(
        { Country: "USA", TotalSales: 2240 },
        { Country: "Canada", TotalSales: 1980 },
        { Country: "UK", TotalSales: 2950 },
        { Country: "Germany", TotalSales: 1850 },
        { Country: "France", TotalSales: 2900 }
    )
);

This will allow us to easily re-run the OnStart operations and update the data set if we do changes to the data while building or for testing purposes. Simply select the three dots on the App property and select Run OnStart:

Below "Variables" in the tree view you will now find a new collection named CountrySales:


Step 3 - Add a title label and configure a vertical gallery for the chart's Y-axis


  1. First, add a self-explanatory title label such as e.g. "Total number of units sold per country" with properties X=15 and Y=8.

  2. Add an empty vertical gallery.

  3. Rename the gallery to gal_ValueLabels and set X=0, Y=60, Width=80 and Height=225.

  4. In this bar chart I want to have five value labels on my Y-axis. Hence, In the items property add the following custom "data set":

Items: [5,4,3,2,1]

5. Next, add the PowerFx below to the TemplateSize property of gal_ValueLabels. This will split the height of the gallery into five equal parts.

TemplateSize: gal_ValueLabels.Height/5

6. Add a label and rename it lbl_Values with properties Y=0, Width=50, Height=20 and Color=Color.White. In addition, on the X property, add the PowerFx below. This will ensure the label is always centered in the gallery.

X: (Parent.TemplateWidth-Self.Width)/2

7. Lastly, in the Text property on lbl_Values, add the PowerFx below.

Text: Round(Max(CountrySales, TotalSales)*1.1/5 *ThisItem.Value,-2)

Explanation:

  • Max(CountrySales, TotalSales): Retrieves the highest TotalSales value from the dataset.

  • * 1.1: Adds a 10% buffer to the maximum sales value.

  • / 5: Divides the result by 5 to distribute it evenly across the five value labels.

  • * ThisItem.Value: Multiplies the result by the current item's value to determine the respective level value on the Y-axis.

  • Round(..., -2): Rounds the final value to the nearest hundred for a cleaner, more readable display.


The result should look something like this:

Step 4 - Configure a horisontal gallery for the X-axis and bars


  1. Add an empty horisontal gallery, rename it gal_bars with the following property configuration:

Items: CountrySales
X: gal_ValueLabels.Width+gal_ValueLabels.X
Y: 60
Width: Parent.Width-gal_ValueLabels.Width
Height: Parent.Height-Self.Y
TemplateSize: (Parent.Width-gal_ValueLabels.Width)/(CountRows(CountrySales))
ShowScrollbar: false

Explanation:

  • Items: CountrySales - Binds the gallery to display data from the CountrySales dataset.

  • X: gal_ValueLabels.Width + gal_ValueLabels.X - Positions the element horizontally right next to the gal_ValueLabels gallery.

  • Y: 60 - Places the element 60 pixels from the top of its parent container.

  • Width: Parent.Width - gal_ValueLabels.Width - Sets the element's width to fill the remaining space beside the gal_ValueLabels gallery.

  • Height: Parent.Height - Self.Y - Adjusts the element's height to extend from its Y-position to the bottom of the parent container.

  • TemplateSize: (Parent.Width - gal_ValueLabels.Width) / CountRows(CountrySales) - Sizes each template within the gallery to evenly distribute across the available width based on the number of rows in the CountrySales data set.

Result should look something like this (White dashed borders added for assistance):

Step 5 - Add graph line and connect the two galleries


  1. Add a rectangle shape, rename it shp_rectangle_graphline with the following property configuration:

X: 0
Y: Parent.Height*0.75
Width: Parent.TemplateWidth
Height: 1
Color: Color.White

2. Set the vertical gallery gal_ValueLabels from Step #3 to dynamically adjust according to gallery gal_bars.

Y: gal_bars.Y
Height:  gal_bars.TemplateHeight*0.75

Now, the two galleries will adjust according to each other if the app layout changes:

Step 6 - Add a button to display the bar graph's total value


  1. Add a button control, rename it btn_barTotal with the following property configuration:

X: (Parent.TemplateWidth-Self.Width)/2
Y: shp_rectangle_graphline.Y-Self.Height
Width: Min(Parent.TemplateWidth*0.15,50)
Height: Parent.TemplateHeight-(Parent.TemplateHeight-shp_rectangle_graphline.Y)-1
Border radius: 5
DisplayMode: DisplayMode.Disabled
DisabledFill: RGBA(0, 0, 0, 0)
DisabledBorderColor: ColorValue("#FAFAFA")
Text: ""

Explanation:

  • X: (Parent.TemplateWidth - Self.Width) / 2 - Centers the element horizontally within its parent template by subtracting its width from the parent template's width and dividing by 2.

  • Y: shp_rectangle_graphline.Y - Self.Height - Positions the element vertically so that it is placed just above shp_rectangle_graphline by subtracting its height from shp_rectangle_graphline's Y position.

  • Width: Min(Parent.TemplateWidth * 0.15, 50) - Sets the width to 15% of the parent template's width, but no more than 50 pixels.

  • Height: Parent.TemplateHeight - (Parent.TemplateHeight - shp_rectangle_graphline.Y) - 1 - Sets the height to the difference between the parent template's height and the Y position of shp_rectangle_graphline, minus 1 pixel.

  • Border radius: 5 - Rounds the corners of the element with a border radius of 5 pixels.

  • DisplayMode: DisplayMode.Disabled - Sets the display mode to disabled, making the element non-interactive.

  • DisabledFill: RGBA(0, 0, 0, 0) - Makes the fill color of the disabled element fully transparent.

  • DisabledBorderColor: ColorValue("#FAFAFA") - Sets the border color of the disabled element to a very light gray (#FAFAFA).

  • Text: "" - Ensures that no text is displayed within the element.

Result:


Step 7 - Add another button to display the bar graph's actual values

  1. Add or copy the button control, rename it btn_barActuals with the following property configuration:

X: (Parent.TemplateWidth-Self.Width)/2
Y: shp_rectangle_graphline.Y-Self.Height
Width: Min(Parent.TemplateWidth*0.15,50)
Height: (ThisItem.TotalSales/(Max(CountrySales, TotalSales)*1.2))*gal_ValueLabels.Height
Border radius: 5
DisplayMode: DisplayMode.Edit
Fill: Color of choice (I am using Cilate colors "#1fa6a6" as theme)
BorderThickness: 0
Text: ""

Explanation:

  • X: (Parent.TemplateWidth - Self.Width) / 2 - Centers the element horizontally within its parent template by calculating the difference between the parent template's width and the element's width, then dividing by 2.

  • Y: shp_rectangle_graphline.Y - Self.Height - Positions the element vertically so that it is placed just above shp_rectangle_graphline by subtracting the element's height from the Y position of shp_rectangle_graphline.

  • Width: Min(Parent.TemplateWidth * 0.15, 50) - Sets the element's width to 15% of the parent template's width, but caps it at a maximum of 50 pixels.

  • #NB Height: (ThisItem.TotalSales / (Max(CountrySales, TotalSales) 1.2)) gal_ValueLabels.Height - Calculates the height based on the proportion of ThisItem.TotalSales to the maximum total sales, then scales it relative to the height of gal_ValueLabels (comment: with a 20% adjustment toward the Y-axis. This is set manually, as I have not found a better way yet).

  • Border radius: 5 - Rounds the corners of the element with a border radius of 5 pixels for a smoother appearance.

  • DisplayMode: DisplayMode.Edit - Sets the display mode to edit, making the element interactive and allowing user input or interaction.

  • Fill: Color of choice (I am using Cilate colors "#1fa6a6" as theme) - Sets the background color of the element to a specified color, in this case, the Cilate theme color "#1fa6a6".

  • BorderThickness: 0 - Removes the border by setting the border thickness to 0 pixels.

  • Text: "" - Ensures that no text is displayed within the element.

Result:

Step 8 - Add labels on the X-axis

To help the users read your data we need to add labels on the X-axis.

  1. Add a label inside your gallery gal_bars, rename the label lbl_Country with the following configurations:

Align: Align.Center
X: (Parent.TemplateWidth-Self.Width)/2
Y: shp_rectangle_graphline.Y
Width: Parent.TemplateWidth
Color: Color.White
Text: ThisItem.Country

Result:

Step 9 - Add data labels for actuals

Since our graph does not have a perfectly accurate relationship between the two galleries, it is even more important to add labels for the actual values to be able to easily read the data.

  1. Add a label inside your gallery gal_bars, rename the label lbl_bar_actualValues with the following configurations:

Align: Align.Center
X: btn_barActuals.X+(btn_barActuals.Width-Self.Width)/2
Y: shp_rectangle_graphline.Y-btn_barActuals.Height/2
Width: 50
Height: 20
Color: Color.White
Fill: RGBA(13, 50, 61, 0.7) (or another darker contrast)
Text: ThisItem.TotalSales
Size: 11

Explanation:

The X-coordinate positions the element centered horizontally relative to btn_barActuals, while the Y-coordinate centers it vertically relative to shp_rectangle_graphline.


Result (after removing dashed assistance borders):

Voilà! Your bar chart is done :)


Be vary of limitations

When building custom data visualisations in Power Apps, it's crucial to be aware of several limitations:

  1. Performance: Complex visualisations or large datasets can impact app performance. Moreover, custom pages are not built to perform complex data operations and transformation. Thus, the preparing of data is best done in the data platform layer.

  2. Interactivity: Limited options for complex interactions compared to dedicated visualisation tools.

  3. Customisation: Design constraints and limited flexibility in customising visual elements.

  4. Data Refresh: Challenges with real-time data updates and refresh rates.

  5. Scalability: Potential issues with scaling visualisations as app complexity grows. Understanding these limitations helps in making informed decisions and optimising the user experience within Power Apps.


In the next blog post, I will delve deeper into dynamically updating another clustered bar chart based on the selected item in the gallery.


If you enjoyed this blog or disagree with how I solved this - please leave a comment below. I look forward to hearing your thoughts!

Comments


bottom of page