Power BI (PBI) is a suite of business analytics tools that deliver insights throughout your organization. The ease of use and powerful features enable PBI to gain a great reception in the market since it was introduced in July 2015. You can find the latest releases, learning guide, detailed documentation as well as in-depth discussion on diverse topics, starting from here.
Data Analysis Expressions (DAX), is a functional and query language by Microsoft. It is the formula language used throughout Power BI. DAX for PBI provides capabilities allowing you to flexibly transfer, manipulate and carry out dynamic aggregation and other operations with your data, and mastering it will help you get the most out of your data. DAX is not a programming language though; its unique abstract concepts do not exist in other programming languages. A traditional study approach for a programming language is not effective for DAX learning. A good start point can be found here.
Recently, when working with PBI for visualization, we had an opportunity to use DAX to implement a feature required by a customer as there was nothing we could use directly to fulfill it in PBI. We solved it by utilizing DAX’s capability and the way PBI incorporate with DAX. Here are the two key difficulties - the need to:
- Calculate some measures based on user’s input (PBI does not have an out-of-box feature to take a user’s input)
- Toggle the visibility for a chart (based on user’s input) when the control criteria did not exist in, or could not be easily mapped to, any column in our existing tables.
The purpose of this blog posting is to discuss an approach that to some extent solves these problems.
Calculating measures based on a user’s input
Each issue involves taking a user’s input, so let’s look at that first. Within the PBI UI, the only component to use to solve the problem is the built-in Slicer visual. Because a Slicer can slice a table into a single row, this feature can be used to make it possible to capture user input. The flow diagram and the bulletin below illustrate the process.
- Create a standalone table containing all input values (in a column) in the data model. The first step is to create a table that contains all input values for the data model. Whether to create a new table or to duplicate an existing one depends on which option will work best for you. The minimum requirement is that one column in the table contains all possible values for selection. You might also need to include a column of description text to make it user friendly, and a column of numerical identities for each value to make the filtering calculation easier. Note that this table must be stand-alone, with no relationship with other tables.
- Properly design and run a Slicer instance on the report page to slice the new table. In the PBI desktop, create a Slicer instance on report page by using the table that was created in step 1. Be sure to design the slicer properly so that you can determine the value a specific user has selected. While it is best to slice to a single value, multiple identical values (likely in case you copy an existing table) are also workable with the help of the DISTINCTCOUNT function.
- Create a measure in data model to pick up the sliced value. Add a calculated measure, for example "myinput", in the model and assign it the value from a suitable column (depending on how you use it in the calculation) of the standalone table. At this point, "myinput" can be used in the desired calculation. As a result, when user selects a different value, "myinput" will be re-evaluated. All measure(s) referencing “myinput” will also be re-evaluated, and propagate to the whole dependency chain. The corresponding visual is then re-rendered with new value(s).
It is important to note that after a table/column is loaded into Power BI, the data becomes "static." Even if there is calculation logic in your model to dynamically change the value(s) in a column, the values will not be shown in your Power BI report until you reload the data. As a result, to view the change in the report on a user's input, implement logic with "calculated measure," and not with “calculated column”. The value of a calculated measure is evaluated at query time. This guarantees that every time a user chooses a value, the measure based on it will be re-calculated, and the corresponding visual will be rendered again.
Toggling the visibility for a chart
The second problem we encountered relates to the visibility of a chart. After input is picked up from a user, controlling the visibility of a chart can be tackled in various ways. Basically, the logic behind the visual must be implemented by “calculated measure.” That is, to perform the calculation for the measure upon user’s positive selection, or to assign a “BLANK” (the return value of BLANK() function) to the measure otherwise.
Below is a simple example that outlines the entire process - the content in the example is only for purposes illustration.
A farmer’s shop sells produce. The shop uses PBI to show volume of sales and the average unit price over the first week of 2017. Below is the data model and the visual built in PBI. Constructing this visual is straightforward, and it uses the built-in summarize function over the “Sales” column to derive total sales.
Now consider that the owner wants to toggle the bar charts for odd and even days’ sales in the same graph without affecting the line chart for average unit price. This would make it easier to determine if the local “odd-even-days” events have any impact on the sales. That is, when choosing odd days, the graph displays bars only for January 1st, 3rd, 5th and 7th, while no bars are shown for the 2nd, 4th, and 6th. When choosing even days, the bars display the other way around, but in each case, the line-chart is not impacted, as shown in the following images:
Let’s look at the process for implementing this functionality.
Step 1. Create a standalone table containing all input values (in a column) in the data model. The owner needs to choose between odd and even days, so the table should have a column of day type, and two values, one representing odd days and the other even days. Providing a numerical ID for each value will make the DAX calculation easier. The result is a 2-by-2 standalone table, called OddEven.
After adding this table, the data model will appear as shown in the following image:
Step 2. Properly design and run a Slicer instance on the report page to slice the table above. In PBI, create an instance of the built-in Slicer visual, choosing the “DateType” column in the field of the Slicer.
Step 3. Create a measure in the data model to pick up the sliced value.
DaySignature = IF(COUNT(OddEven[Signature])=1, MAX(OddEven[Signature]), 0)
This way, DaySignature holds 1 when and odd day is selected, and it holds 2 when an even day is selected. If neither or both are chosen – should you accidently set the Slicer to the wrong mode for this purpose – DaySignature holds value 0.
Finally, we can use the measure “DaySignature” in the calculation for the visibility control of the charts in our report.
Below is the calculation for measures for odd and even days’ sales. As discussed earlier, remember to implement visual logic with calculated measure to be refreshable upon data change, i.e. a change in a user’s selection. Because we need individual visibility control over odd and even days’ sales chart, it would be easier if we calculate them separately.
TotalSales = SUM(Fact_Sales[Sales]) TotalSalesEvenDays = IF([DaySignature]=1, BLANK(), CALCULATE([TotalSales], FILTER(Fact_Sales, NOT(ISODD(Fact_Sales[DateId]))))) TotalSalesOddDays = IF([DaySignature]=2, BLANK(), CALCULATE([TotalSales], FILTER(Fact_Sales, ISODD(Fact_Sales[DateId]))))
A better way to implement this would be with a single measure - it would be tidier and have more options when choosing visual types.
TotalSalsOddEven = SWITCH([DaySignature], 1, CALCULATE([TotalSales], FILTER(Fact_Sales, ISODD(Fact_Sales[DateId]))), 2, CALCULATE([TotalSales], FILTER(Fact_Sales, NOT(ISODD(Fact_Sales[DateId])))), [TotalSales])
Hopefully, this simple example has provided you with a solid understanding of the topic. For your reference, the data and all related code is included in a pbix file you can access here (Example).
A final point to note here is that using this approach has a limitation. The input offered to the user for selection must be pre-defined and must be discrete/scattered values. In other words, be sure to put all of them into a table. If your scenario involves arbitrary input, for example 5.25 or 1.23456, this approach will not be feasible.
The Data Migration Team would like to thank primary contributors Bin Zhao, Andy Isley, Kasper de Jong, and Mukesh Kumar for their efforts in preparing this blog posting. The detail provided has been harvested as part of a customer engagement sponsored through the DM Jumpstart Program.