Registering Physical Actions

logo

Note

Please be well versed with the Physical Behavioural Model and the Dispatching of Behavioural Actions

In this section, we will learn how to register physical actions which act as a source of triggers for interactions.

A physical action is any of the browser events(like click, hover, etc) or any other source of trigger that can cause an interaction in the canvas causing it to change its behaviour. However, every time we register a physical action, we need to map that action with an existing behaviour or register a new behaviour for it.

Let us understand how this works with the cars.json data.

Understanding how to register a physical action

Registering a physical action is the same as adding an event listener to a component in the chart. However, you don't need to specify the target element(s). It automatically registers on the chart and based on the behaviour, it can perform the interaction desired.

However, in order to register this action, you need to generate the right payload with criteria to attach with the behaviour when it is dispatched. (We have not yet mapped a behaviour to the action yet though).

We will understand registering physical actions with an example. Let us consider whenever we click the mouse button after pressing the Ctrl button in the canvas, an interaction is to be triggered. We will use the ActionModel provided by Muze to register it

ActionModel
     .for(canvas)
     .registerPhysicalActions({})

The registerPhysicalActions method helps in registering a physical action. The code to add Ctrl + Click interaction on the browser can be found below

ActionModel
.for(canvas)
.registerPhysicalActions({  /* to register the action */
    ctrlClick: firebolt => (targetEl, behaviours) => {
        targetEl.on('click', function (data) {
            if (event.metaKey) {
                const event = utils.getEvent();
                const mousePos = utils.getClientPoint(this, event);
                const interactionConfig = {
                    data,
                    getAllPoints: true
                };
                const nearestPoint = firebolt.context.getNearestPoint(mousePos.x, mousePos.y, 
                          interactionConfig);
                behaviours.forEach(behaviour => firebolt.dispatchBehaviour(behaviour, {
                    criteria: nearestPoint.id
                }));
            }
        });
    }
});

Seems like a lot of code, but the objective is fairly simple:

  • Register the browser event on the element
  • Get the actual value of the point in the chart we need the interaction at, from the mouse position
  • Dispatch the associated behaviours with that value
  • This value is actually a set of rows in the DataModel based on which the behaviour will create the selection and rejection sets

So, let's break down the action:

Naming the new physical action

We register the event by naming a new property. For instance, the new property we have used for our action is ctrlClick. It gets two arguments:

  • targetEl: This is the target element on which the physical action is to be bound to
  • behaviours: Set of behaviours mapped to the physical action
 .registerPhysicalActions({  /* to register the action */ 
     ctrlClick:  firebolt => (targetEl, behaviours) => {})

Register the browser event on the element:

Next we register the actual browser event as desired. For instance, the new event is applicable only if the mouse button is clicked once the Ctrl button is pressed:

ctrlClick: firebolt => (targetEl, behaviours) => {
     targetEl.on('click', function (args) { /* Binding browser event
            if (event.metaKey) { /* metaKey for Ctrl, whether it is pressed or not*/

Get the mouse position:

You can use the utilities provided by Muze to get the details about the event (in this case, specifically the mouse position)

const event = utils.getEvent(); /* Gets the event details */
const mousePos = utils.getClientPoint(this, event); /* Gets the mouse point */

Get the values from the mouse position:

Once you have the mouse position, you can get the nearest point in the plot and consequently the data value attached to it. To do so, you need the context from firebolt. This context defines which canvas(or which unit(s) in the canvas) the interaction has to take place.

const nearestPoint = firebolt
                    .context /* context is the canvas container where interaction is to occur"
                    .getNearestPoint(mousePos.x, mousePos.y, interactionConfig); 

Muze uses firebolt to control all the interactions and you can get the context where you want the interaction to take effect(i.e., the canvas container) using firebolt.context.

This context can extract the nearest point using the mouse positions and any other configurations attached to it.

For example, the config for this example:

const interactionConfig = {
     data,
     getAllPoints: true /* Interaction for all points associated to the current mouse position */
}

Since some interactions may have a group of points, you may choose to set the interaction on that group or on the specific point where the interaction has occurred.

main
run-button
run-button
reset-button
loadData('/static/cars.json').then((res) => {    
  let node = document.getElementById('chart-container');    
  const env = muze();    
  const canvas = env.canvas();    
  const DataModel = muze.DataModel;  	
  loadData('/static/cars-schema.json').then((schema) => {
    const dataModelInstance = new DataModel(res, schema);
          canvas
          	.width(600)
          	.height(400)
			.rows(['Miles_per_Gallon'])
			.columns(['Cylinders'])
    		.color('Origin')
			.data(dataModelInstance)
    		.mount(node)
    });
})

Here, you could associate a full bar with the interaction setting the getAllPoints API. If it is set to false, interaction happens on the specific portion of the bar where the mouse is clicked on.

Dispatch the behaviour with the Nearest Point information

Once we get the nearest point, we can now dispatch the behaviours attached to this action (read about mapping behaviours to physical actions here with the necessary ids and all the additional information(if required) for your behaviour:

behaviours.forEach(behaviour => 
          firebolt.dispatchBehaviour(behaviour, {
          criteria: nearestPoint.id, 
}));

This dispatching of behaviours is what triggeres the change in the behaviour. The attached behaviours then cause a side effect and a DataModel propagation to complete the interaction.

You can find the code for the interaction below. Note that we have also mapped the physical action with behaviours and dissociated the previous interaction in the code below:

main
run-button
run-button
reset-button
        canvas
          .width(600)
          .height(400)
          .rows(['Horsepower'])
          .columns(['Origin'])
          .data(dataModelInstance)
          .mount(node);
        ActionModel
          .for(canvas)
          .registerPhysicalActions({  /* to register the action */
          ctrlClick: firebolt => (targetEl, behaviours) => {
            targetEl.on('click', function (data) {
              if (event.metaKey) {
                const event = utils.getEvent();
                const mousePos = utils.getClientPoint(this, event);
                const interactionConfig = {
                  data,
                  getAllPoints: true
                };
                const nearestPoint = firebolt.context.getNearestPoint(mousePos.x, mousePos.y,
                                                                      interactionConfig);
                behaviours.forEach(behaviour => firebolt.dispatchBehaviour(behaviour, {
                  criteria: nearestPoint.id
                }));
              }
            });
          }
        });

Wrapping Up

In this section we get a fair understanding of what registering a physical actions entail. To summarize:

  • Physical actions are like browser events(clicks, hover, etc)
  • To register a physical action, we use the registerPhyiscalActions method from the ActionModel
  • First, the event has to be bound to the target element
  • Secondly, the nearest point for the interaction is retrieved from the context of the interaction
  • This point along with the position are dispatched in the payload to all the behaviours