Power BI report containing a column chart with small multiples that show custom labels on each column.
Notice the label on each column that shows the year and average game length in h:mm format.
These custom labels are available for any bar or column chart – small multiples are not required.
How to add custom labels
The setting for these labels is a little bit hidden. On my column chart, I went to Format Pane, located the Data labels section, and found the Values area. Under Values, there is a toggle for Custom label that must first be enabled before populating the Field below it.
You can drag any field in the fields list into the custom label field. It only accepts one field, so if you want to display multiple values, you’ll need to create a custom column to concatenate values. That is exactly what I did in my example report.
You can control the orientation and position of the labels (mine are set to horizontal and inside base). You can also format the label values and background.
The April 2023 release of Power BI desktop introduced a new preview feature called dynamic format strings for measures. This allows us to return values with different formats from the same measure. Previously, we needed to create calculation groups (usually by using Tabular Editor) to accomplish this. But now it is built in to Power BI Desktop.
This is great when you are converting to different currencies or switching measures in a report page. Let’s say you have a measure that returns different values based upon a selection in another visual on the page. For instance, I created a report that allows a consumer to choose a metric to be shown across multiple visuals. Depending on the selection, I may be returning an integer, a percentage, or a currency amount. If you can use parameters to switch measures, Power BI takes care of the measure formats for you. But if you had to write your own measure to handle more complex logic, you must handle the measure formatting on your own. I did this in the below report for Workout Wednesday 2023 Week 16 because I wanted a measure that consistently filtered to only the top N products that could be used in multiple visuals on the page and didn’t unnecessarily repeat DAX logic.
The measure used in my visuals was called Value.
In PBI Desktop version 2.116.622.0 or later, there is an option set the measure format string.
As of April 2023, you need to enable the preview feature. Go to File -> Options and Settings – > Options -> Global -> Preview Features. Enable the dynamic format strings for measures option. You will need to restart PBI Desktop for this to take effect.
Next, open your report, select your measure, and change the format to Dynamic.
A new drop-down box will appear next to the formula bar.
By default, Measure will be selected and the measure definition will be shown. But you can now change the drop-down to Format to see the format expression. The format expression is written in DAX.
In the format expression below, if the selected metric is “# of Customers”, I formatted it as an integer with no thousands separator. If the selected metric is “Gross Margin %”, I formatted it as a percentage with two digits after the decimal. Otherwise, I formatted the metric as an integer with a thousands separator.
The end result is that any axes, values, data labels, and tooltips are formatted according to my expression, while maintaining the numeric data type.
A few things to note
This dynamic measure format string is scoped to the particular measure. It is a common point of frustration when people learn calculation groups to accomplish a similar goal that calculation groups are scoped to the model and require logic that limits which measures they are applied to.
The SELECTEDMEASURE() function, which is commonly used in calculation items, can be used in the dynamic measure format string to reference the measure, but you can also just use the measure name in brackets.
There are some limitations at the moment that limit use with report measures and DirectQuery for Analysis Services.
I had to do this for a client the other day, and I realized I hadn’t blogged about it. Let’s say you need to include data in a Power BI model, but the only source of the data is a matrix that is output from another system. And that matrix has multiple fields populating the columns. An example of this is below. The matrix has fiscal year and product category on columns, vertical on rows, and the profit metric populating the values.
You may know about the wonderful unpivot functionality in Power Query, but to handle this matrix, you first need to Transpose.
The steps to turn this matrix into a table are:
Transpose the query.
Remove the last column that contains the vertical totals.
Filter out the “Totals” value in the second column, which contains the product categories.
Use the first row as headers.
Select the Fiscal Year, Product Category, and Metric columns. Select Unpivot Other Columns.
Rename the Attribute column to Verticals.
Transposing a table changes categories into rows.
Transposing a query in Power Query
We don’t need the totals columns or rows because Power BI can recalculate those, and we don’t want to double-count profits.
In order to unpivot, we need to promote the first row to column headers, so the first column is labeled Fiscal Year and the fourth column is labeled Vertical Z.
The first three columns are as they should be, but the verticals need to change from columns to rows. This is accomplished by unpivoting. But you only want to unpivot the verticals and leave the fiscal year, product category, and metric columns as they are.
Then make sure column names are user-friendly, and it’s finished. You may also opt to remove the Metric column and rename the value column, if there is only one metric.
The finished product after transposing and unpivoting
In the Power BI report for Workout Wednesday 2022 Week 48, the holiday cards are populated using SVG measures
When you click on one of the holiday cards, it navigates to a specified url. This was made possible by using the HTML Content custom visual.
The navigation to the URL is achieved by modifying the SVG code to include an href attribute. Depending on the placement of the href attribute, you can make one part of the SVG image or the entire image navigate to a URL when clicked.
Step by Step
To make a clickable SVG image for Power BI, there are 7 steps:
Open the url in a text editor or html editor
Replace all double quotes with single quotes
Add href attribute around the content you want to be clickable
Create a measure in Power BI and paste the contents of the SVG
Add the HTML Content visual to a report page
Populate the values of the visual with the measure
In the format pane for the visual, set Allow Opening URLS to On.
For example, I have an SVG of a coffee cup.
If I open it in Notepad++ (you can also use Visual Studio code or another editor), it looks like this.
Because we are putting the contents in a DAX measure, we need to replace the double quotes with single quotes.
Then I add the href attribute. I want my entire image to navigate to my website (DataSavvy.me) when it is clicked. So I add <a href='https://datasavvy.me'> just after the opening <svg> tag, and I add a closing </a> at the end. Remember that the URL should be surrounded by single quotes rather than double quotes.
Then I create a measure called SVG. I enter double quotes, paste the content from Notepad++, and add closing quotes as the end. Because I’m using the HTML content visual, I don’t have to add "data:image/svg+xml;utf8," at the beginning of my measure as I would if I were using this in a table visual.
Now I add the HTML Content visual and put my SVG measure in the Values field well.
With the visual selected, I go to the formatting pane, expand the Content Formatting section, and turn Allow Opening URLs to On.
When I hover over the image, the cursor changes, indicating the image is clickable.
When I click the image, I get a prompt to allow navigation to the url I put in the SVG.
New possibilities unlocked
While static clickable SVGs are pretty cool, the potential is really in the fact that we can dynamically populate the SVG based upon data in our dataset. You can change the entire image or an attribute of the image (color, size, URL, etc.) based upon a slicer selection.
Now that you can make dynamic clickable images in Power BI, how do you plan to use them?
For more formal enterprise Power BI development, many people have a checklist to ensure data acquisition and data modeling quality and performance. Fewer people have a checklist for their data visualization. I’d like to offer some ideas for quality checks on the visual design of your Power BI report. I’ll update this list as I get feedback or new ideas.
Checklist illustration by Manypixels Gallery on IconScout
The goal of my data visualization quality checklist is to ensure my report matches my intended message, audience, and navigation.
There are currently 4 sections to my PBI data viz quality check:
Message check
Squint test
Visual components check
Accessibility check
Message check
Can you explain the purpose/message of your report in a single sentence?
Can you explain how each visual on the page supports that purpose/message?
I use the term purpose more often when I have a report that supports more exploratory data viz, allowing users to filter and navigate to find their own meaning in their own decision contexts. Message is much easier to define in explanatory data viz, where I intend to communicate a (set of) conclusion(s). My purpose or message statement often involves defining my intended audience.
If you cannot define the purpose/message of your report page, your report may be unclear or unfocused. If you can’t identify how a visual supports the purpose/message, you may consider removing or changing the visual to improve clarity and usefulness.
Squint test
You can perform a squint test by taking a step back and squinting while viewing your report page. Alternatively, you can use a desktop application or browser add-in that blurs the page, simulating far sightedness. Looking at the blurry report page helps you evaluate the visual hierarchy.
What elements on the page stand out? Should they? Areas of high contrast in color or size stand out. People read larger things first.
Does the page seem balanced? Is there significantly more white space or more bright color on one side of the page?
Does the page background stand out more than the foreground?
When visually scanning an image-heavy page (which follows a Z-pattern in Western cultures), does the order of items on the page make sense? Did you position explanatory text before the chart that needs the explanation? If there are slicers, buttons, or other items that require interaction before reading the rest of the page, are they placed near the top left?
Is there enough space between the items on the page to keep the page from feeling overly busy? Is it easy to tell where one visual ends and another begins?
Visual components check
I have two levels of visual components checks: reviewing individual visuals and reviewing the visuals across a report page.
Individual visuals components check:
Do charts have descriptive, purposeful titles? If possible, state a conclusion in the title. Otherwise, make it very clear what people should expect to find in your charts so they can decide if it’s worth the effort to further analyze them.
Are chart backgrounds transparent or using low-saturation colors? We don’t want a background color standing out more than the data points in the chart.
Are bright colors reserved for highlighting items that need attention?
Are visual borders too dark or intense? If every chart has a border that contrasts highly from the background, it can take the focus away from the chart and impede the visual flow. We often don’t need to use borders at all because we can use whitespace for visual separation.
Does the chart use jargon or acronyms that are unfamiliar to your intended audience? Try to spell out words, add explanatory text, and/or include navigation links/buttons to a glossary to reduce the amount of effort it takes to understand the report.
Are colors used within and across the page easily distinguishable?
Are fonts used consistently, with only purposeful deviations?
If charts should be compared, are the axis scales set to facilitate a reasonable comparison?
Does the interactivity between elements on the page provide useful information?
Are visuals appropriately aligned? Misalignment can be distracting.
Accessibility Check
I have a more comprehensive accessibility checklist on my blog that I keep updated as new accessibility features and tools are released. Below are some important things you can check to ensure your report can be read by those with different visual, motor, and cognitive conditions.
Do text and visual components have sufficient color contrast (generally, 4.5:1 for text and 3:1 for graphical components)?
Is color used as the only means of conveying information?
Is tab order set on all non-decorative visuals in each page? Decorative items should be hidden in tab order.
Has alt text been added to all non-decorative items on the page?
Is key information only accessible through an interaction? If so, you may consider rearranging your visuals so they are pre-filtered to make the important conclusion more obvious.
If you try navigating your report with a keyboard, is the experience acceptable for keyboard-only users? Does accessing important information require too many key presses? Are there interactive actions that can’t be performed using a keyboard?
Do you have any video, audio, or animation that auto-plays or cannot be controlled by the report user?
You may have used the UNICHAR() function in DAX to return Unicode characters in DAX measures. If you haven’t yet read Chris Webb’s blog post on the topic, I recommend you do. But did you know there is a Power Query function that can return Unicode characters? This can be useful in cases when you want to assign a Unicode character to a categorical value.
In Power Query, you can use Character.FromNumber to return a Unicode character based upon the specified number.
I recently used this function in a Workout Wednesday for Power BI exercise to visualize music notes. I made a scatterplot that used eighth notes and eighth rests as the markers.
To create an eighth note (𝅘𝅥𝅮), I added Character.FromNumber(9834) to my Power Query expression. The eighth rest is Character.FromNumber(119102). There are many music-related Unicode characters available.
With the Character.FromNumber function, we can make a custom column that uses an If statement to determine the correct character to use for each row in a table.
One thing to note is that the query preview in the Power Query Editor doesn’t always render the Unicode values correctly. This doesn’t mean it won’t work in your report, though.
For instance, Unicode character 119102 doesn’t look like the eighth rest when viewed in the Power Query preview.
The question mark in a box represents a unicode character that could not be properly rendered
But after applying changes and sending the data into the dataset, you’ll see your expected output in the Data view in Power BI Desktop.
Output of the custom column containing Unicode characters in the Data view in Power BI Desktop
The Unicode character does show correctly in Power Query Online when creating a dataflow.
While DAX is the answer for generating music notes or emojis based upon aggregated values, Power Query can help you generate a Unicode value per row in a table by using the Character.FromNumber function.
I noticed Adam Saxton post a tip on the Guy in a Cube YouTube channel about publishing reports from Power BI Desktop for external users. According to Microsoft Docs (as of June 21, 2022), you can’t publish directly from Power BI Desktop to an external tenant. But Adam shows how that is now possible thanks to an update in Azure Active Directory.
To sign into another tenant in Power BI Desktop:
Click the Sign in button.
Enter your email address (i.e., username@domain) and click Continue.
When the sign-in dialog appears, select Use another account.
Select sign-in options.
Select Sign in to an organization
Enter the organization’s domain (e.g., mycompany.org) and select Next.
You will then be shown a list of users with your user signed in to the external tenant. Select that user.
Then you will be signed in with your B2B user to the external tenant. This allows you to build “thin” reports off of shared datasets as well as publish to PowerBI.com from Power BI Desktop!
It would still be better for usability if Power BI had a tenant switcher similar Azure Data Factory or the Azure Portal, but this works perfectly fine in the meantime.
A couple of minor gaps
As Adam notes in his video, sensitivity labels don’t get applied if you publish from Power BI Desktop using an external user.
Also, once you publish, the link in the success dialog won’t work because it doesn’t contain the CTID in the URL. If I click on the link labeled “Open ‘WoW2022 Week 24.pbix’ in Power BI” in the screenshot below, it will try to open the link as if the report were in my tenant.
Success dialog seen after successfully publishing from Power BI Desktop
That won’t work since the report is in another tenant, so next I’ll get the error “Sorry, we couldn’t find that report”. You can go to the external tenant (by including the CTID in your url) and see your report is published, so don’t worry too much about this.
This tip makes life a bit easier for me when I’m publishing multiple times in a row from Power BI desktop. It’s fewer clicks than using the Upload a file dialog in PowerBI.com. I hope Microsoft will round out the B2B features so all links generated by Power BI have the CTID in the url, and maybe add the tenant switcher, too.
I am a fan of the viridis color palettes available in python and R, so I decided to make Power BI theme files for each of the 4 color maps (viridis, inferno, magma, plasma). These color palettes are not only lovely to look at, they are colorblind/CVD friendly and perceptually uniform (or close to it).
The screenshots below show the colors you’ll get when you use my theme files.
Viridis
Viridis theme colors in Power BI
Plasma
Plasma theme colors in Power BI
Magma
Magma theme colors in Power BI
Inferno
Inferno theme colors in Power BI
I generated a palette of 10 colors and then dropped the darkest and lightest colors in an effort to try to help you get good color contrast without inadvertently highlighting a data point. I chose to use the second darkest color of the 8 as the first/main color, which should work well on light backgrounds.
You’ll also notice that I have set in the theme the minimum, center, and maximum colors for use in a diverging color palette. This diverging palette includes the darkest and lightest color in an effort to give you a wider scale.
Give the themes a try
If you don’t enjoy choosing colors and just want something that looks good, feel free to hop over to the Github project and download the JSON files. You can learn more about the method I used to choose the colors and my suggestions for usage in the project documentation.
If you do use the themes, feel free to let me know how they worked and if you have suggestions for improvements.
I needed to pull some user data for an app that uses Intercom. While I will probably import the data using Data Factory or a function in the long term, I needed to pull some quick data in a refreshable manner to combine with other data already available in Power BI.
I faced two challenges in getting this code to work:
Intercom’s API uses cursor-based pagination when retrieving contacts
I needed this query to be refreshable in PowerBI.com so I could schedule a daily refresh.
If you have worked with the Web.Contents function in Power Query, you may be familiar with all the various ways you can use it that aren’t supported in a refresh on PowerBI.com. Chris Webb’s blog is a great source for this info, if you find yourself stuck with a query that works in Power BI Desktop but not in the service.
The Intercom API
In version 2.4 of the Intercom API, users are a type of contact. You must make an HTTP GET call to https://api.intercom.io/contacts and pass an access token and Accept:application/json in the headers.
In the query string, you can specify the number of contacts per page by specifying per_page=x where x is a number less than or equal to 150. If you have more than 150 contacts, you will need to handle the cursor-based pagination.
When you make the initial call, the API will return a JSON object that contains a total count of contacts and a record for pages. Expanding the pages record shows the current page, the number of contacts per page, and the total number of pages.
Data returned in the Power Query Editor when retrieving contacts from the Intercom API
Expanding the next record gives you page 2 with a starting_after ID.
The cursor used for pagination in the Intercom API as retrieved using Power Query
To get the next page of contacts, the API call would be https://api.intercom.io/contacts?per_page=150&starting_after=[the starting_after ID listed above].
The Power Query Queries
This blog post from Gil Raviv gave me some ideas where to start, but the code in that blog will not refresh in PowerBI.com. You cannot put the iterations and the Web.Contents call and the generated list all in one query if you want to use scheduled refresh.
I ended up creating one query and two functions to accomplish my goal. The second function is optional, but you may find it useful as the time values in the API response are listed as Unix timestamps and you probably want to convert them to datetime values.
The first function contains the API call with an input parameter for the starting_after ID.
//Web API call function
(startafter) as record =>
let
Source = Json.Document(Web.Contents("https://api.intercom.io/contacts",[Query=[per_page="150",starting_after=startafter],Headers=[Accept="application/json", Authorization="Bearer <your access token goes here>"]])),
data = try Source[data] otherwise null,
pages = Source[pages],
ttlpages = pages[total_pages],
nextkey = pages[next][starting_after],
next = try nextkey otherwise null,
res = [Data=data, Next=next,TotalPages = total_pages]
in
res
It’s important to make the url in the Web.Contents function be a static string. If it is concatenated or dynamic in any way, the query will not be able to refresh in the Power BI service. All the query string parameters can go in the Query arguments of the Web.Contents function. If you have multiple query string arguments, you can put them in brackets with a comma separating them. You can do the same with multiple headers.
This function attempts the API call and returns null if it encounters an error. Once the data is returned, it retrieves the specific JSON objects needed to return the data. The next two lines are used to retrieve the starting_after value. The function returns the contact data, starting_after value, and total_pages value.
The List.Generate call generates the rows I need to call my first function. It sets an iterator variable to 0 and then calls the function, which returns the first page of data along with the total pages and the starting_after ID. As long as data is returned, it makes the API call again with the previously returned starting_after ID. This creates a list of lists that can be converted into a table of records. Then the records can be expanded to fields.
I expanded several columns out into multiple columns. Then I adjusted the data types of my columns to the correct types (they came back as Any data type).
There were several columns that contained Unix timestamps. All of the custom function calls are returning the datetime version of those values. I needed to handle null values in the timestamp conversion, so I replaced all the null timestamps with 0, converted them, and then converted the datetime value of 1-Jan-1970 back to null. I did the replace for all 7 columns in one step. Then I removed the original columns that contained the Unix timestamp as they were not analytically relevant for me.
Below is my Unix timestamp to datetime conversion function.
(UnixTime as number) as datetime=>
let
DT = #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, UnixTime)
in DT
Advice and warnings
When using an API key that must be passed in the headers, it is safest to use a custom connector. Otherwise, you have to embed your API key in the M code, as shown above. When the query is sent to Intercom, it is encrypted using HTTPS. But anyone that opens your PBIX file would have access to it. Your key will be captured in the Power BI logs. And anyone that can manage to intercept your web request and decrypt your traffic would have access to it. This is not ideal. But creating a custom connector requires more advanced code and a gateway to make it usable in the Power BI service. With either option, you will choose Anonymous authentication for the data source.
Be sure to use the RelativePath and Query options in the Web.Contents call. This is necessary to make the query refreshable in the service. The value passed to the first parameter of Web.Contents must be a static string and must be valid in itself (no errors returned).
After publishing your report, you’ll need to set the credentials for the dataset before you can refresh it. Be sure to check the Skip test connection box. Otherwise, your credentials update will fail.
The Skip test connection option for the web data source in the dataset settings in PowerBI.com
Even though we are using anonymous authentication, you can still choose an Organizational privacy level.
In my contacts list, if I want only app users from your Intercom contacts list, I needed to filter the results on role = “user” since contacts also includes leads. The Role attribute was in the custom attributes returned by the API.
It took a bit of experimentation to get this working, so I thought I would share what I found to work. As always, Power BI is always changing and a better way to do this may be available in the future.
If you would like this development experience to improve, here are some Power BI Ideas to vote for:
If you’ve visited the Tab order area of the Selection Pane in Power BI in the last couple of months, you might have noticed some new buttons.
Three new buttons for managing tab order in Power BI
The hover text on the first button says “Expand All”. This button is useful if you have grouped visuals. Groups are indicated by a carat to the left of the item in the tab order list.
Tab order for a report page containing one group
Selecting the Expand all button shows the individual objects within a group.
Tab order with the group expanded
The second button is the Collapse All button. It will collapse the groups so only the name of the group is shown and not the individual objects within the group.
The third button is a great new addition: Have tab order match visual order.
The option to have tab order match visual order is the third button under Tab order.
This button will set the tab order for the visuals on the page to sort ascending by Y and then X coordinates. Let’s look at an example.
I have a report page containing 7 textboxes.
The X,Y coordinates of each box are shown in the box. The original tab order is indicated by the numbers in the circle.
After clicking the button to have tab order match visual order, the tab order is changed shown below.
Tab order set with the top left visual being first and the bottom right visual being last
This is often the correct tab order that matches how we read the report visually. This little button can increase keyboard/screen reader accessibility in one second instead of taking a couple of minutes per page.
There will be times that this tab order will not be what you want. Some exceptions might be when you use visuals that have a different amount of space inside the visual container, so the containers are intentionally misaligned (according to the X,Y coordinates) in order for the content to appear visually aligned. Then you might need to customize your tab order a bit. Another exception might be if you have some buttons or links at the top right of the report page that you want a user to visit last (after the content of the report). In that case, you would customize your tab order to make the button last.
But the majority of the time, this option to make tab order match visual order is exactly what you need. I applaud the Power BI team for taking this step to make creating accessible reports a little easier.