UpSet.js —JavaScript Tutorial

an interactive JavaScript re-implementation of UpSet(R)

Samuel Gratzl
7 min readMay 3, 2020

This article is part of three part article series

  1. UpSet.js — The UpSet.js Ecosystem
  2. UpSet.js — JavaScript Tutorial
  3. UpSet.js — Behind the (technical) Scenes

Introduction

This part of the article series gives a basic introduction into the concepts, data structures, and features of UpSet.js. The first part contains a high level introduction of the UpSet.js ecosystem. The last part focuses on the software architecture of the library and encountered challenges during its development.

An adapted version of this tutorial for the UpSet.js for Jupyter integration is available at:

Similarly, an adapted version for the UpSet.js for R integration is available at:

Hello World Example

Installation

UpSet.js is just a single dependency to install:

<script src="https://unpkg.com/@upsetjs/bundle"></script>

or via npm

npm install @upsetjs/bundle

when embedded as a script, it will be available under the name UpSetJS.

Hello World

const data = [
{ name: 'S1', elems: [0,1,2] },
{ name: 'S2', elems: [1,2,3] },
{ name: 'S3', elems: [0,2,4] },
];
const sets = UpSetJS.asSets(data);
UpSetJS.render(document.body, {
sets: sets,
width: 600,
height: 400,
});

Let’s dissect it the code in the CodePen line by line. The simplest data format is a list of sets, where a set is defined by a name and an array of contained elements. Note that by default elements are compared by === thus, numbers or strings work the best as elements. asSets(sets) expands the set definition by some additional attributes. For example, a cardinality attribute which is defined as cardinality = elems.length. In addition, a type attribute is added, which is fixed to set to better differentiate it from set combinations.

The last method render(element, props) renders the UpSet plot. The first argument has to be a valid DOM element, the second the object of properties. There are only three properties required: sets, width, and height . The first one are the computed sets, while the other define the dimensions of the plot. The result is a static UpSet plot showing three sets vertically and all set combinations horizontally.

UpSet.js supports three options to export the plot as shown in the bottom right corner:

  1. as a static PNG file
  2. as a SVG file
    in the background the SVG file which represents the UpSet plot is cloned and downloaded. Thus, it supports all styling effects from UpSet.js.
  3. as a Vega Lite specification
    Vega Lite is a specification language for plots. It is a JSON format, that can be viewed with the Vega Lite library or edited in their online Vega Lite editor.

The plot is static, since it is a stateless component and we haven’t specified event listeners yet. Before we go into details how to specify interactions, let’s take a closer look at the data structures and how to customize the data.

UpSet.js Data Structures

Elements and Sets

UpSet.js has three basic data structures: elements, sets, and set combinations. UpSet.js is designed to be independent of the element structure and just enforces the structure on sets and set combinations.

There are two ways to generate sets. We have seen the first one in the previous example. We start with a list of sets which each consist of a name and a list of elements contained in this set. The other option reverses the structure as in the following example:

const elems = [
{ sets: ['S1', 'S3'] },
{ sets: ['S1', 'S2'] },
{ sets: ['S1', 'S2', 'S3'] },
{ sets: ['S2'] },
{ sets: ['S3'] },
];
const sets = UpSetJS.extractSets(elems);

The resulting sets are equal to the previous ones except that the former has numbers as elements whereas the later the complex element objects.

Set Intersections and Set Combinations

When no explicit combinations are specified, UpSet.js will generate all possible set intersections and display them. One can customize this behavior by providing explicit combinations. The easiest way to generate set combinations is using the generateCombinations method as in the following example

const combinations = UpSetJS.generateCombinations(sets, {
type: 'intersection',
min: 1,
limit: 100,
order: 'cardinality:desc',
});

This will generate all possible set intersections, which contain at least one set. Moreover, it sorts the combinations by their cardinality decreasing and then limits the number of returned elements to at most 100. The resulting combinations can then be passed as the combinations property. A detailed list of all options is available at https://upset.js.org/api/model/

As a shortcut, one can specify the parameters for the generation of the combinations directly:

Interaction

Events

UpSet.js support three kind of events: onHover, onClick, and onContextMenu. The onHover event is internally implemented by using the corresponding onMouseEnter and onMouseLeave events. Each listener has to have the same signature (set: ISetLike | null, evt: MouseEvent) => null handlers. Each event either has the corresponding set (which is either a horizontal set or a vertical set intersection) or null for resetting purposes. In addition, the underlying MouseEvent is provided in case the one needs the precise mouse coordinates.

Selection

As discussed in the first part of this series, UpSet.js is a stateless component, which just uses the events to trigger any interaction that has to be implemented by the user of the library. As simple example is setting the selection property to the currently hovered set as in the following example:

...
const props = {
sets: sets,
width: 600,
height: 400,
selection: null
}
props.onHover = (set) => {
props.selection = set;
UpSetJS.render(document.body, props);
};
UpSetJS.render(document.body, props);

The selection property accepts three format:

  • an instance of a set (combination) within the plot
  • an array of elements
  • a function which computes the overlap to the given set. Its signature has to be of the form (set: ISetLike) => number

Queries

Queries are another way to specify selections. The differences are that multiple queries are supported and that each query has an associated name and color. In addition, a query legend is shown on the top of the UpSet plot. Like selection a query can use the same three different variants to describe it, by a set (combination), by an array of elements, or an overlap function. This example shows three queries, each using a different variant to specify the query.

UpSetJS.render(document.body, { 
sets: sets,
width: 600,
height: 400,
queries: [
{ name: 'S2', color: 'steelblue', set: sets.find((d) => d.name === 'S2') },
{ name: 'Elements', color: 'darkred', elems: elems.slice(0, 2) },
{ name: 'Function', color: 'darkgreen', overlap: (s) => Math.max(s.cardinality - 1, 0) },
]
});

In order to avoid visual overlap only the first query will be shown similar to a selection. Every other query will be shown in a reduced version using a simple mark indicating its position. A selection given via the selection property has precedence over a query thus forcing all queries to be rendered in a reduced form only. In the next example both a selection and queries are given:

Even there is no explicit selection set, UpSet.js renders every query using the reduced variant because of the provided onHover listener. It is a heuristic implemented in the library assuming that a selection will be set as a reaction to an onHover event. This heuristic avoids that the representation of query changes when the user hovers over a set in the plot. For example, the same heuristic is not used for an onClick listener and one can notice how the plot changes after clicking a bar in the following example:

Numerical Attributes

Another feature of UpSet.js is to render box plots as add-ons to the set (combination) bars. For example, they can be used to show the distribution of a numerical attribute within the elements in a set. In the following example, 1000 random elements are generated and then the _.sampleSize method from Lodash is used to subset them to generate sets.

const elems = Array(1000).fill(0).map((_, i) => ({ name: i.toString(), value: Math.random() }));const data = [
{ name: 'S1', elems: _.sampleSize(elems, 100), },
{ name: 'S2', elems: _.sampleSize(elems, 80), },
{ name: 'S3', elems: _.sampleSize(elems, 50), },
{ name: 'S4', elems: _.sampleSize(elems, 75), }
];
const sets = UpSetJS.asSets(data);
const props = {
sets: sets,
width: 600,
height: 400,
selection: null,
setAddons: [
UpSetJS.boxplotAddon((v) => v.value, elems, { name: 'Value' })
],
combinationAddons: [
UpSetJS.boxplotAddon((v) => v.value, elems, { name: 'Value', orient: 'vertical' })
]
}
props.onHover = (set) => {
props.selection = set;
UpSetJS.render(document.body, props);
};
UpSetJS.render(document.body, props);

UpSet.js supports add-ons to be added to both combination and set representations. The boxplotAddon is just one example of an add-on. The signature of the boxplotAddonfactory method is boxplotAddon(accessor: (v: T) => number, domain: T[] | {min: number, max: number}, options: {name?: string, orient?: 'horizontal' | 'vertical') => UpSetAddon. The first argument is the accessor to extract the value from a given element. The second one are either all elements or the computed minimum and maximum value. They are used to proper scale the box plots. The last argument are options among other the name to be shown as a label and its orientation.

UpSet Addons can optionally support the rendering of selections and queries. In the example above, when hovering over a set, a box plot of the highlighted elements is shown to indicate any distribution changes.

Styling and Customization

Theme

UpSet.js supports two themes out of the box which can be further customized: light (default) and dark. The theme property controls it and triggers a change in default values for coloring bars, selection, labels, …

Note that the background of the example was changed manually, since UpSet.js doesn’t enforce a specific background color.

Labels

A common operation is to customize the labels of the plot. The corresponding properties are setName and combinationName.

UpSet.js supports a variety of different customization including its layout ( widthRatios, heightRatios), fontSizes, fontFamily, padding, and colors ( color, textColor, selectionColor, …). All options can be found at https://upset.js.org/api/bundle/ or as part of the React Storybook story at https://upset.js.org/api/react/?path=/docs/upset--default

Log Scale

When dealing with a large number of elements, UpSet.js supports switching to logarithmic scale. The corresponding attribute is numericScale: "log"

However, with increasing number of elements, it is getting computationally more expensive to compute the overlaps for sets.

Summary

In this tutorial the basic principles, data structures, and concepts are explained. The last part of this article series discusses the software architecture of UpSet.js along with implementation details and challenges during its development.

See Also

  1. UpSet.js — The UpSet.js Ecosystem
  2. UpSet.js — JavaScript Tutorial
  3. UpSet.js — Behind the (technical) Scenes

Links

--

--

Samuel Gratzl

Research Software Engineer with a focus on Data Visualization. Author of LineUp.js (https://lineup.js.org) and UpSet.js (https://upset.js.org).