Adding Tooltips To Your Power BI Custom Visual
Learn how to add tooltips to your Power BI Custom Visual. This is video #17 of the Power BI Custom Visual Development Fundamentals series, please check the playlist below for the set.
Resources
Transcript
Hello again and welcome back to this series on developing Power BI Custom Visuals.
Today, you will learn how to add tooltips to your visual.
These tooltips can help the user dig deeper into the data by showing extra details about each data point.
In this video, you will implement a tooltip that, in addition to the value of each bar, will also show the percent difference from the average of the series.
So, let’s get to it, right now.
Let’s start by updating the view model and the data point with the additional bit of data we want to show.
average: number;
First, we’ll add a property called average to the view model interface.
As expected, this will break the code so let’s go the view model function and fix that.
average: 0
Let’s first add a default value for the average field of zero.
if (viewModel.dataPoints.length > 0) {
viewModel.average = d3.sum(viewModel.dataPoints, d => d.value) / viewModel.dataPoints.length;
}
Then let’s update this average at the end, after having loaded all the data points.
This will make it easier for us to calculate the deviation of each data point, which is what we’re going to do right now.
So let’s go up to the data point code…
tooltips: VisualTooltipDataItem[]
And add a new property called tooltips of type VisualTooltipDataItem array.
This is a an API type that Power BI requires you to use in order to show tooltips.
If you press F12 on this type, you can see what bits of information you can supply for the tooltip.
At the very least we must supply a display name and a value for the tooltip, though we can display other things as well.
We can also display several tooltips at the same time for the same data point, which is why we are using an array here.
Anyway, this made the code break again, so let’s go and fix it.
let metadata = dv[0].metadata;
First, let’s surface the metadata view, as this will help us get the names of the data role columns we’re currently using.
|| !dv[0].metadata
I’ll add a safety check too, just to make sure it’s there.
let categoryColumnName = metadata.columns.filter(c => c.roles["category"])[0].displayName;
That includes the name of whatever column is being used as category…
let valueColumnName = metadata.columns.filter(c => c.roles["measure"])[0].displayName;
And the name of the column being used as measure.
With this information, we can set up basic tooltips…
tooltips: [{
displayName: categoryColumnName,
value: categories.values[i]
}, {
displayName: valueColumnName,
value: (values.values[i]).toFixed(2)
}]
So we’re now defining the base tooltip for each particular data point.
This doesn’t take into account any averages or anything like that, it just sets up a basic tooltip with two lines, one for the category and one for the value.
We’ll take care of the deviations in a bit, before that, let’s render this on-screen so we have something to show for.
.on("mouseover", (d) => {
let mouse = d3.mouse(this.svg.node());
let x = mouse[0];
let y = mouse[1];
this.host.tooltipService.show({
dataItems: d.tooltips,
identities: [d.identity],
coordinates: [x, y],
isTouchEvent: false
});
})
What we need to do here is to add some code that pops up the tooltip whenever the mouse enters each bar.
So there’s a couple of things happening here.
First, we’re attaching an event to each svg rectangle that will run every single time the user moves the mouse over a bar.
When this event runs, by the grace of d3 we get the associated data item to play with automatically.
What we don’t get are the mouse coordinates where we want to display the tooltip so we need to go and get them.
With that in hand, we can ask the host to display a tooltip in the right place and with the right data.
Let’s see what this did…
Well, this is nice, we have tooltips, but the tooltips are not tracking the mouse yet.
Let’s go back to the code.
.on("mousemove", (d) => {
let mouse = d3.mouse(this.svg.node());
let x = mouse[0];
let y = mouse[1];
this.host.tooltipService.move({
dataItems: d.tooltips,
identities: [d.identity],
coordinates: [x, y],
isTouchEvent: false
});
})
So to fix this properly, we need to handle the on mouse move event as well.
This trigger whenever the user keeps hovering over the bar.
We want this event to be snappy so we don’t want to create a new tooltip from thin air here.
Instead, we want to move the tooltip that we already have so it tracks the mouse position.
That’s why we’re calling the move function on the tooltip service instead of calling show again.
Now since we’re here, we may well clean up after ourselves.
.on("mouseout", (d) => {
this.host.tooltipService.hide({
immediately: true,
isTouchEvent: false
});
});
On mouse out triggers whenever the mouse moves out of each bar, so we can use this event to get the tooltip out of sight.
Well, so far so good, we have a working tooltip that tracks the mouse.
What we don’t have yet though is that percent deviation value that I talked about.
So, let’s add that in then.
for (let dp of viewModel.dataPoints) {
dp.tooltips.push({
displayName: "Deviation (abs)",
value: (dp.value - viewModel.average).toFixed(2)
});
}
First, let’s add a tooltip to each data point with the absolute deviation from the average.
We can just iterate though the lot and calculate that as we go.
if (viewModel.average != 0) {
for (let dp of viewModel.dataPoints) {
dp.tooltips.push({
displayName: "Deviation (%)",
value: (100 * (dp.value - viewModel.average) / viewModel.average).toFixed(2) + "%"
})
}
}
Then we can add a tooltip for the percent deviation as well.
Of course, we only add this tooltip if the average is different from zero, otherwise, we’ll have a problem calculating this.
Let’s see what this did…
And there you have it, full working tooltips with derived data as a bonus.
That’s it for today.
If you have any questions, let me know and I’ll get back to you.
In the next video in this series, you will learn how to add localization functionality to your visual so you can display any static labels you have in different languages.
Until then, take care and see you next time.