Skip to content

Interaction Manager

The Interaction Manager (IM) is a special visualization that adds a higher level of interaction to an app or dashboard. If we have a set of visuals and we add an IM visualization, it will automatically register and listen for any click or range operation (Time Range Filter, Time Range Picker, Range Filter) that occurs to immediately build and trigger the corresponding filter that will affect all visuals in the dashboard.

The IM is not attached to any provider or source.

1
2
3
4
5
// Define the Interaction Manager
cf.create().graph('Interaction Manager').element('chart').execute()

// Or use the shortcut for it
cf.create().imanager().element('chart').execute()

The IM won’t show anything until one or many of the previously mentioned events is triggered.

Displaying Filters

As soon as we fire an event by clicking or applying a range of values the IM will start showing the respective filters. For example if we click in a Bars visualization that is grouped by “venuecity”, we’ll have the following filter reflected in the IM:

IM1

The filter displayed by itself also have a high level of interaction. The green circle allows the filter to be disabled/enabled:

IM2

The name of the field is used to remove the filter permanently:

IM3

The red square represents the operation. By default the operation is “IN” when clicking on visuals. Filters with this operation can be switched to “NOT IN” and backwards by clicking on the red square:

IM4

Range visualizations will trigger filters with operation of type “BETWEEN”:

IM5 A time range filter IM6 A metric range filter

Multiple Filters

The more visualizations interact with, the more filters we’ll have. Multigroups visualizations will trigger two or more filters at a time (Heat Map, Trend, Pivot….). They all will be joined by the operation “AND”.

IM7

Repeated filters won’t re-query the visual.

Exposed API

Sometime we may want a custom way to handle filters. Or even have a different UI to interact with filters. In any of these cases instead of handling events, multi-source and visuals validations, applying and removing filters and some other taks, we can also let the Interaction Manager to take of all that and we just need to create our custom UI.

To avoid the IM from rendering its own UI , we use the noUI option when is created:

1
cf.create().imanager().set('noUI', true).element('IM').execute();
The IM API exposes several methods that allows the interaction with it:

1
2
3
// First obtain the IM component and access the API
let IM = cf.getIM();
let api = IM.get('api');

Api Methods

getFilters()

Returns the list of Filters objects that are currently applied. No parameters required.

setFilters()

Sets new filters to the IM. Receives two parameters, the first one is a list of Filters objects, the second one is a boolean that indicates the IM if it should clean the stored filters (if any) within the IM before adding the new ones. False by default.

These list of filters won't be applied and won't affect any visual until the applyFilters method is called.

1
2
3
4
5
6
let filter = cf.Filter('myfield')
                .label('My Field')
                .operation('IN')
                .value(['Value-1','Value-2']);

api.setFilters([filter])

Note

Filter objects to be used with the IM, must have the property label(), and it must match with the label of the field that we want to filter. This ensures the cross-source filtering capability.

applyFilters()

It triggers the filters that are stored by the IM. No parameters required.

removeFilter()

Removes one filter. May take two parameters: The filter object to be removed (required) and the option if the filter is permanently removed or just disabled.

1
2
3
4
5
6
7
let toRemove = api.getFilters().find(f => f._path === 'myfield');

// Remove filter and delete it from IM
api.removeFilter(toRemove) 

// Remove but don't delete so it can be re-applied
api.removeFilter(toRemove, { disable: true}) 

deleteAllFilters()

Completly removes all filters from the visualizations and from the IM. No parameters required.

updateContent()

This should be only used if we use the built-in IM UI. For example if we have created an app that when it loads y should also load any set of filters stored somewhere. In that case we need to set all the filters to the IM, apply them and then update the UI.

1
2
3
api.setFilters(storedFilters)
api.applyFilters();
api.updateContent();

addRule()

Add a new rule for a visual, it takes two parameters: the visual id (the id of the DOM element) and the rule. Rules are explained in the Filter Rules section.

removeRule()

Deletes a rule for visual. Takes the visual id as a parameter.

stopExecution()

Complete stops the execution of the IM before a filter event is processed. See the Filter Event section. Takes no parameters.

Many callbacks can be executed after the visualization loads the data after the filter is applied.

Filter Events

The IM dispatches several events during its workflow that we can subscribe to. They are useful to perform actions in different stages. For example to show/hide a loading indicator between a filter is triggered and the visual is ready with the new data.

The way we subscribe (and unsubscribe) is in the same way that we do with other events, by using the aktive instance of the IM:

1
2
3
4
5
6
7
let IM = cf.getIM();
IM.on('filter:before-add', myCallback)
IM.on('filter:added', myCallback)
IM.on('filter:before-remove', myCallback)
IM.on('filter:removed', myCallback)
IM.on('filter:viz-finish', myCallback)
IM.on('filter:text', myCallback)

Let's describe the events.

filter:before-add

Is fired right before a filter is going to be added to the IM. At this point the filter has not being saved or applied to any visual. If we interrupt the IM workflow at this point (by using the api.stopExecution()) the app will stay the same as before.

The data obtained by the callback is an object with these properties: name which is the event name and filters, an array of the filters to be processed

filter:added

At this point the filter was stored by the IM and it was triggered over the visuals, so many of them will be in the process of querying the server to obtain the new data.

The data for this event is also an object with three properties: name as the event name, affectedVisuals which is an array of the visuals that are going to be affected by the filters, and filters which is an array with the current filters stored by the IM.

filter:before-remove

Same as filter:before-add, but when a filter is going to be removed. The filters property of the data received by the callback is an array with the filter being removed. If the IM workflow is stopped at this point, the filter will never be removed from the IM or from any visual.

filter:removed

Same as filter:added but when the filter was removed from the IM and the queries are triggered for each visual. The filter property of the data will represent the remaining filters (if any) in the IM. The filter that was removed will be the removed property.

filter:viz-finish

This event is fired by each visual. So if we have ten visuals it will be fired ten times (as long as the visual can be affected in one way or another by the filter).

So for example if a visual is affected by a filter and it has to query the server, then the event is going to be fired just right after the new data is obtained by the visual. That's why this is the event by excellence to dispatch visual related actions like the loading indicator mentioned before, since different visuals have different times to get back the data from the server.

On the other hand, if the filter affects the visual just as a client filter, then the event will be triggered inmediatly.

Filter Rules

Whenever a filter is triggered, the IM will try to apply it to all registered visuals. However, sometimes we may want to override some behaviours such as avoiding a given chart from triggering a filter, or have a another one not to be affected by any filter even if it should. These and more can be done by using rules

Rules are checked everytime between a filter is triggered and before being applied so it can decide what to do with the visual and the filter.

They are defined by visual as shown below where bars-element-id is the HTML element id of the chart:

1
2
3
4
5
let rules = {
    "bars-element-id": {
        // ... rules
    }
}

Rules can be set to the IM before the IM is executed by using the .set method:

1
cf.create().imanager().set('rules',rules).element('chart').execute()

Rules can also be added after the IM is executed, by using the .addRule method explained before. The difference is that this method takes only one rule at a time since rules are meant to be declared only at the beginning.

1
api.addRule("bars-element-id", { /*rule*/ })

There are different rule properties:

receive

A boolean. If false, no filter will affect the visual. True by default. This visual will never trigger the filter:viz-finish event.

trigger

A boolean. If false, the visual will never trigger a filter when clicked. True by default

clientFilters

A boolean. If true, the visual will be affected only by filters matching its group-by attributes and they will be applied in the form of client filters. Other filters will be ignored (so the filter:viz-finish event won't be triggered). Client filters don't trigger new queries on the affected visualization.

serverFilters

An array of strings representing the field names that can affect the visual if a filter is applied. These filter trigger new queries.

This configuration will take precedence over client filters. So if a field is found in the serverFilters array, then the filter will be always taken as a server filter even if the clientFilters property is true.

filterConditions

A function. This is only recommended for very specific cases that can't be solved with the above configurations. If specified, clientFilters and serverFilters will be ignored.

1
2
3
4
5
6
let condition = function(f, viz) {
    let result = { execute: true, client: false }

    // Any logic here
    return result;
}

This function takes as paramateres, a filter and the visual (an aktive instance) and returns an object with two properties: execute, that tells the IM if that filter should re-execute the visual or not, and client that specifies if that filter should be used as a client or not.

So, what if we have several filters to be applied? This function is called by each filter because usually the validation is done based on which filter can be applied and which can. If for a given validation the execute parameter is returned as true at least once, then the visual will be executed.

This is an example of a definition of different filter rules:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
        var rules = {
            'slicer-country': { clientFilters: true },
            'slicer-period': { clientFilters: true },
            'slicer-metric-type': { clientFilters: true },
            'slicer-metric-selection': { clientFilters: true },
            'slicer-region': {
                clientFilters: true,
                serverFilters: ['country'],
            },
            'trend': { 
                filterConditions: condition, 
                trigger: false 
            },
            'rdt': { filterConditions: condition }
        }

        var IM = cf.create()
                    .imanager()
                    .set('rules', rules)
                    .element('imanager')
                    .execute();

Drill Hierarchy

Attribute Fields

The drill hierarchy feature allows to define a path (a list) of fields that a visual will follow according to the filter being applied on it, as long as the filter matches a field defined in that path.

So if we have a Bars that is grouped by “venuecity”, we can tell the IM that when the Bars is clicked, right after being affected by the “venuecity” filter, it should switch the group by to the next field in the path (let’s say “venuename”). This is what is called drill-in. Then, whenever the filter “venuecity” is removed, it should switch or drill-out back to “venuecity”.

Drill paths can have 2 or more field names. The main rule here is that the first field name in the path must be the current “group by” of the visual, so the IM knows what it should use as the base field for that group.

If we follow the example of the Bars explained before, we can configure a drill hierarchy like this:

1
2
3
4
5
6
7
8
9
// Define the path for the group by
let drill = {
    'barsElementId': { 
        'group1': ['venuecity','venuename', 'eventname'],
        // If the Bars is stacked or clustered, we could also configure the 2nd group:
        //'group2': ['catdesc', 'catname']
    }
}
cf.create().imanager().set('drill',drill).element('chart').execute()

Let’s go into what we have:

The drill hierarchy configuration is a JSON. Each visual that we want to assign a configuration is represented by the HTML element that hosts that visual.

Single group visuals must only have the key group1 configured, as they only have one group by. Multigroups visuals on the other hand may have 1 or many groups (depending on the visual) configured by using group1, group2…. groupN, depending on how many groups we want to configure. It is NOT required to set a path to all groups, since we may only want to specify a path for the 2nd group for example.

Special drill hierarchy configuration: Pivot Table

The Pivot Table doesn't use groups, but rows and columns. This is how the drill hierarchy is defined for it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let drill = {
    'pivotElementId': { 

        'rows': {
            // Conf for the first row
            '1': ['venuecity','venuename', 'eventname'],

            // Conf for the third row
            '3': ['state','city']
        },

        'columns': {
            // Conf for the first column
            '1': ['catgroup','catname'],
        }
    }
}

The configuration above shows how to specify a drill path for the specific rows or columns. The number represents the row or column as it was specified in the Pivot Table aql.

Note

The first row or colum is identified with 1 and not with 0 for clarity purpouses.

Time Fields

Another type of drill hierarchy can be used: Time units or granularities hierarchy.

This type of hierarchy is used with fields of type time. As we explain here, ChartFactor supports a set of granularities for time fields that we can also use as a drill path.

For time fields, we support drilling into different granularities. So let’s say we have a Trend visualization that is grouped by “saletime”, and the data is in months. So if we apply a filter from the saletime we’d like to see the filtered data in the Trend in days, and if we click a day, then the data should be represented in minutes. How cool is that?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Define the path for the group by
let drill = {
    'barsElementId': { 
        'group1': ['venuecity','venuename', 'eventname'],
        // If the Bars is stacked or clustered, we could also configure the 2nd group:
        //'group2': ['catdesc', 'catname']
    },
    'trendElementId': {
        'group1': ['MONTH','DAY', 'MINUTE'],
   }
}
cf.create().imanager().set('drill',drill).element('chart').execute()

Of course, for drill paths with time, we can specify any supported time unit as long as they are in order. This means that after MONTH, we can not ask the Trend to drill into YEAR.

The other thing is that this type of visuals will only drill-in because when the respective time filter is removed they will go back to the base granularity no matter on which position in the drill path it is.

Mixed Fields

Multigroups visualizations can group by an attribute and a time field at the same time. In these cases the IM will ignore any configuration specified for the time field and will only use the drill path for the attribute field if any.