Skip to main content
Version: 1.0.0

Update layer data on interaction

In this tutorial, we will learn how you can update a layer's data on a brush interaction. Brushing is a form of dragging interaction over the chart.

What are side effects?

Side effects are any visual alterations made to the chart such as adding/removing elements or modifying their visual properties. Each side effect has a behaviour attached to it. Behaviours are how muze reacts to a particular interaction. For example, if you hover on the chart, a highlight behaviour is dispatched which shows a tooltip on the chart. Here, tooltip is the final visual that a user sees on the chart which we call as side effect.

Now, let's take an example and create a side effect to dynamically update a reference line when a user drags over the chart.

Create the chart

First, we create a calculatedVariable called Month and create a canvas. Then we pass the datamodel instance and the rows and columns to make the bar chart.

const data = await loadData("seattle-weather.csv");
const schema = await loadData("seattle-weather-schema.json");

const formattedData = await DataModel.loadData(data, schema);
let dm = new DataModel(formattedData);

dm = rootData.calculateVariable(
{
name: "Month",
type: "dimension",
},
["Date"],
(date) => {
return monthNames[new Date(date).getMonth()];
},
);

const canvas = muze
.canvas()
.rows(["Precipitation"])
.columns(["Month"])
.layers([
{
mark: "bar",
},
])
.data(dm)
.mount("#chart");

Add a reference line layer

Now, we will add a reference line layer using mark type tick. As this layer will show the average of all the months initially, we need to apply a groupBy operation on the DataModel and set it in the layer. We can do that using the source attribute of the layer configuration. The source attribute takes a function and a DataModel instance is being passed to this function. Here, we can apply any DataModel operation like groupBy, select or sort and return a new DataModel instance from here. The new DataModel instance will be used by the layer to draw the plots.

canvas.layers([
{
mark: "bar",
},
{
mark: "tick",
name: "averageLine",
className: "averageLine",
encoding: {
x: {
field: null,
},
y: "Precipitation",
color: {
value: () => "#f71616",
},
},
source: (dm) => dm.groupBy([]),
interactive: false,
},
]);

Creating a side effect

Now, we will create and register a side effect which will dynamically update the data of layer on interaction.

muze.ActionModel.for(canvas).registerSideEffects(
class AverageLine extends SurrogateSideEffect {
static formalName() {
return "averageLine";
}

apply(selectionSet) {}

static target() {
return "visual-unit";
}
},
);

Updating the layer data from the side effect

The apply function is responsible for adding new elements to the visualization or updating them. It receives a selectionSet which contains the entry and exit set of the data interacted with.

For example, here we will update the data of averageLine layer that we had created from this function. setLayerData takes a DataModel instance and the name of the layer as the second argument and updates the data of that layer.

apply(selectionSet) {
const model = selectionSet.mergedEnter.model;
if (model) {
const groupedModel = model.groupBy([]);
this.setLayerData(groupedModel, 'averageLine');
} else {
this.resetLayerData('averageLine');
}
}

Mapping the side effect with an interaction behaviour

Finally, we will map the side effect with brush behaviour so that it gets triggered whenever we brush on the chart.

muze.ActionModel.for(canvas).mapSideEffects({
select: ["averageLine"],
brush: ["averageLine"],
});

This tells Muze to dispatch the averageLine side effect whenever we brush on the chart.