Adding filter capabilities to Vega Sankey visualizations in Kibana

Interested in adding interactive filters to your Vega visualizations in Kibana? Discover how to leverage the `kibanaAddFilter` function to create a dynamic and responsive Sankey visualization with ease.

In this blog we are going to discover how to enable Vega Sankey visualizations to create Kibana Filters.

Vega is a standout tool in the world of data visualization, allowing you to create detailed and interactive visual displays of your data. Using a simple JSON syntax, Vega allows you to define how your visualizations look and behave. It's a key part of the Kibana ecosystem, meaning you can use it alongside other Kibana visualizations within your dashboards. An in-depth exploration of how Vega works can be found in this blog.

Vega shines when you need more than what Kibana's standard visualization tools offer. It lets you build custom visualizations tailored to your specific needs, whether you're dealing with complex data relationships or want a unique visual style.

A few years back, we shared a blog on how to create a Sankey chart in Kibana. Sankey charts are great for illustrating the relationship between two data fields. While the chart worked well, as Kibana grew with new features, a limitation became clear: the inability to create a Kibana filter directly from the chart.

In this blog we are going to discover how we enabled the Sankey visualization from the previous blog to create Kibana Filters.

NOTE: when the code is copied to Kibana, it may present you a warning message stating:The URL is big and Kibana might stop working. To resolve this error go to Kibana Advanced settings and enable Store in Session.

Adjust Vega code

Kibana Filter

When you use Vega within Kibana, you gain access to special functions that allow you to adjust Kibana's settings directly from your Vega visualizations. These functions let you modify the Kibana context settings, enhancing the interactivity of your dashboards.

Here, we'll focus on using the kibanaAddFilter function. This function allows you to add a filter to the Kibana dashboard where your visualization is displayed. Keep in mind that these functions are exclusive to Vega and are not available in Vega-Lite.

Filter when clicking

We’re using the kibanaAddFilter function to add a Filter to Kibana when a stack is clicked in the visualization. Since the two stacks are both based on different fields, we need to make sure we are actually filtering on the correct field. This requires a conditional expression to determine which stack was clicked.

In Vega, you can use conditional expressions with the pattern: { condition ? if value : else value }. For our condition, we check if the clicked stack is stk1. This stk1 has been defined in the data section of our Vega code, and is used for the left stack in the chart. If the condition is true, it means the left stack was clicked, and we should filter on the geo.src field. If false, we filter on the geo.dest field, which corresponds to the right stack. With this logic, we can update the groupSelector in the signals section to reflect the correct filtering behavior.

    {
      name: groupSelector
      value: false
      on: [
        {
          // Clicking groupMark sets this signal to the filter values
          events: @groupMark:click!
          update: '''{stack:datum.stack == "stk1" ? kibanaAddFilter({"match_phrase": {"machine.os.keyword":datum.grpId}}) : kibanaAddFilter({"match_phrase": {"geo.dest":datum.grpId}}) }'''
        }
      ]
    }

Remove unused code

Now that the filtering is handled at the Kibana level, we can simplify our Vega visualization by removing some unnecessary logic. First, we can eliminate the filter logic from the data section of the Vega specification.

        // when a country is selected, filter out unrelated data
        {
          type: filter
          expr: !groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2
        }

Secondly, we can remove the logic to show the “show all”-button from the signals.

        {
          // Clicking "show all" button, or double-clicking anywhere resets it
          events: [
            {type: "click", markname: "groupReset"}
            {type: "dblclick"}
          ]
          update: "false"
        }

Finally, the “show all” button itself can also be removed.

    {
      // Create a "show all" button. Shown only when a country is selected.
      type: group
      data: [
        // We need to make the button show only when groupSelector signal is true.
        // Each mark is drawn as many times as there are elements in the backing data.
        // Which means that if values list is empty, it will not be drawn.
        // Here I create a data source with one empty object, and filter that list
        // based on the signal value. This can only be done in a group.
        {
          name: dataForShowAll
          values: [{}]
          transform: [{type: "filter", expr: "groupSelector"}]
        }
      ]
      // Set button size and positioning
      encode: {
        enter: {
          xc: {signal: "width/2"}
          y: {value: 30}
          width: {value: 80}
          height: {value: 30}
        }
      }
      marks: [
        {
          // This group is shown as a button with rounded corners.
          type: group
          // mark name allows signal capturing
          name: groupReset
          // Only shows button if dataForShowAll has values.
          from: {data: "dataForShowAll"}
          encode: {
            enter: {
              cornerRadius: {value: 6}
              fill: {value: "#f5f5f5"}
              stroke: {value: "#c1c1c1"}
              strokeWidth: {value: 2}
              // use parent group's size
              height: {
                field: {group: "height"}
              }
              width: {
                field: {group: "width"}
              }
            }
            update: {
              // groups are transparent by default
              opacity: {value: 1}
            }
            hover: {
              opacity: {value: 0.7}
            }
          }
          marks: [
            {
              type: text
              // if true, it will prevent clicking on the button when over text.
              interactive: false
              encode: {
                enter: {
                  // center text in the parent group
                  xc: {
                    field: {group: "width"}
                    mult: 0.5
                  }
                  yc: {
                    field: {group: "height"}
                    mult: 0.5
                    offset: 2
                  }
                  align: {value: "center"}
                  baseline: {value: "middle"}
                  fontWeight: {value: "bold"}
                  text: {value: "Show All"}
                }
              }
            }
          ]
        }
      ]
    }

Change dataset

To fully utilize the Sankey visualization, you need to apply it to your own dataset. In this guide, we'll adjust the visualization to work with the Logs Kibana sample dataset. Start by ensuring the Sample web logs are loaded. Then, modify the Vega code to fit the new dataset by changing the index to kibana_sample_data_logs.

If @timestamp isn't used as the timestamp field for your dataset, you'll need to update the %timefield% setting. Setting the %timefield% settings ensures we are able to use the Kibana time range. Therefore, the Kibana time range should correspond to the time range of your dataset. To change the field used for one of the stacks, make adjustments in two areas: data retrieval and filtering logic. Since the Logs Kibana sample dataset has only one geo.src value, we'll change the field to machine.os.keyword.

Conclusion

Lens is the go-to visualization editor in Kibana because it's both flexible and user-friendly. However, for certain visualization types, Vega offers the flexibility to create visuals that Lens doesn't support, like the Sankey chart we discussed in this blog. Initially, the Sankey visualization couldn't create Kibana filters, a feature available in Lens. This blog shows how you can create Kibana filters with Vega using the kibanaAddFilter function.

Vega Code

The resulting Vega configuration can be found here in the Github Repository.

Note: we have updated the vega version to version 5, since that is the latest version at time of writing.

Want to get Elastic certified? Find out when the next Elasticsearch Engineer training is running!

Elasticsearch is packed with new features to help you build the best search solutions for your use case. Dive into our sample notebooks to learn more, start a free cloud trial, or try Elastic on your local machine now.

Ready to build state of the art search experiences?

Sufficiently advanced search isn’t achieved with the efforts of one. Elasticsearch is powered by data scientists, ML ops, engineers, and many more who are just as passionate about search as your are. Let’s connect and work together to build the magical search experience that will get you the results you want.

Try it yourself