Add bookmark support to visuals in Power BI reports

With Power BI report bookmarks, you can capture and save a configured view of a report page. Then you can go back to the saved view quickly and easily whenever you want. The bookmark saves the entire configuration, including selections and filters.

For more information about bookmarks, see Use bookmarks to share insights and build stories in Power BI.

Visuals that support bookmarks

A Power BI visual that supports bookmarks has to be able to save and provide the correct information when needed. If your visual interacts with other visuals, selects data points, or filters other visuals, you need to save the bookmarked state in the visual's filterState properties.

Note

Creating a visual that supports bookmarks requires:

  • Powerbi-visuals-utils-interactivityutils version 3.0.0 or later for filter visuals and any visual that uses InteractivityService.
  • Visual API version 1.11.0 or later for non-filter visuals that use SelectionManager instead of InteractivityService.
  • To find out which version you’re using, check the apiVersion in the pbiviz.json file.

How Power BI visuals interact with Power BI in report bookmarks

Let's say you want to create several bookmarks on a report page with each bookmark having different data points selected.

First, select one or more data points in your visual. The visual passes your selections to the host. Then select Add in the Bookmark pane. Power BI saves the current selections for the new bookmark.

Do this several times to create new bookmarks. After you create the bookmarks, you can switch between them.

Each time you select a bookmark, Power BI restores the saved filter or selection state and passes it to the visuals. The visuals in the report are highlighted or filtered according to the state that's stored in the bookmark. To do this, your visual must pass the correct selection state to the host (for example, the colors of rendered data points).

The new selection state (or filter) is communicated through the options.jsonFilters property in the update method. The jsonFilters can be either Advanced Filter or Tuple Filter.

  • If your visual contains selected data points, reset the selection to that of the selected bookmark by using the callback function, registerOnSelectCallback, in ISelectionManager.
  • If your visual uses filters to select data, reset the filter values to the corresponding values of the selected bookmark.

Visuals with selection

If your visual interacts with other visuals using Selection, you can add bookmark support in one of two ways:

  • Through InteractivityService to manage selections, use the applySelectionFromFilter. This is the easier and preferred method.
  • Through SelectionManager, if your visual doesn't use InteractivityService.

Use InteractivityService to restore bookmark selections

If your visual uses InteractivityService, you don't need any other actions to support the bookmarks in your visual.

When you select a bookmark, the utility automatically handles the visual's selection state.

Use SelectionManager to restore bookmark selections

If you're not using InteractivityService, you can save and recall bookmark selections using the ISelectionManager.registerOnSelectCallback method as follows:

When you select a bookmark, Power BI calls the callback method of the visual with the corresponding selections.

this.selectionManager.registerOnSelectCallback(
    (ids: ISelectionId[]) => {
        //called when a selection was set by Power BI
    });
);

Let's assume you created a data point in the visualTransform method of your visual.

The datapoints looks like this:

visualDataPoints.push({
    category: categorical.categories[0].values[i],
    color: getCategoricalObjectValue<Fill>(categorical.categories[0], i, 'colorSelector', 'fill', defaultColor).solid.color,
    selectionId: host.createSelectionIdBuilder()
        .withCategory(categorical.categories[0], i)
        .createSelectionId(),
    selected: false
});

You now have visualDataPoints as your data points and the ids array passed to the callback function.

At this point, the visual should compare the ISelectionId[] array with the selections in your visualDataPoints array, and then mark the corresponding data points as selected.

this.selectionManager.registerOnSelectCallback(
    (ids: ISelectionId[]) => {
        visualDataPoints.forEach(dataPoint => {
            ids.forEach(bookmarkSelection => {
                if (bookmarkSelection.equals(dataPoint.selectionId)) {
                    dataPoint.selected = true;
                }
            });
        });
    });
);

After you update the data points, they'll reflect the current selection state that's stored in the filter object. Then, when the data points are rendered, the custom visual's selection state will match the state of the bookmark.

Visuals with a filter

To support bookmarks in visuals that have a filter, use InteractivityService.

Let's assume that the visual creates a filter of data by date range. You have startDate and endDate as the start and end dates of the range.

The visual creates an advanced filter and calls the host method applyJsonFilter to filter data by the relevant conditions.

The target is the table that's used for filtering.

import { AdvancedFilter } from "powerbi-models";

const filter: IAdvancedFilter = new AdvancedFilter(
    target,
    "And",
    {
        operator: "GreaterThanOrEqual",
        value: startDate
            ? startDate.toJSON()
            : null
    },
    {
        operator: "LessThanOrEqual",
        value: endDate
            ? endDate.toJSON()
            : null
    });

this.host.applyJsonFilter(
    filter,
    "general",
    "filter",
    (startDate && endDate)
        ? FilterAction.merge
        : FilterAction.remove
);

Each time you select a bookmark, the custom visual gets an update call.

In the update method, the visual checks the filter in the object:

const filter: IAdvancedFilter = FilterManager.restoreFilter(
    && options.jsonFilters
    && options.jsonFilters[0] as any
) as IAdvancedFilter;

If the filter object isn't null, the visual restores the filter conditions from the object:

const jsonFilters: AdvancedFilter = this.options.jsonFilters as AdvancedFilter[];

if (jsonFilters
    && jsonFilters[0]
    && jsonFilters[0].conditions
    && jsonFilters[0].conditions[0]
    && jsonFilters[0].conditions[1]
) {
    const startDate: Date = new Date(`${jsonFilters[0].conditions[0].value}`);
    const endDate: Date = new Date(`${jsonFilters[0].conditions[1].value}`);

    // apply restored conditions
} else {
    // apply default settings
}

After that, the visual changes its internal state to match the current conditions. The internal state includes the data points and visualization objects (lines, rectangles, and so on).

Important

In the above report bookmarks scenario, the visual shouldn't call applyJsonFilter to filter the other visuals. They will already be filtered by Power BI.

The Timeline Slicer visual changes the range selector to the corresponding data ranges.

Save the filter state of the visual

In addition to saving the conditions of the filter for the bookmark, you also can save other filter aspects.

For example, the Timeline Slicer stores the Granularity property values as a filter state. It allows the granularity of the timeline (days, months, years, etc.) to change as you change bookmarks.

The filterState property saves a filter aspect as a property. The visual can store various filterState values in bookmarks.

To save a property value as a filter state, set the object property as "filterState": true in the capabilities.json file.