plotly / react-cytoscapejs

React component for Cytoscape.js network visualisations
MIT License
483 stars 68 forks source link

Device Event that is fired after elements are selected and box selection has ended #6

Closed xhluca closed 6 years ago

xhluca commented 6 years ago

Description

In the cytoscape.js documentation, a list of user input device events is given. Particularly, those are the events relating to box selection:

Is there an event that is fired after box selection has ended and all the elements are selected?

boxend in this case is fired after the box selection has ended, but before the elements are selected. Please refer to example #1 and example #2.

boxselect is fired only after boxend is fired, as shown in example #2. In fact, it is fired once for every element that is selected, whereas we are interested in an event that is fired once after all the elements have been selected by the box.

Example 1

In the following example we run a simple cytoscape app, and set a breakpoint at cy.on('boxend', ...). It stops right after boxend event is fired. Notice in the gif that the nodes 4 and 5 are not selected, or else their color would have been changed.

bug

Example 2

In the following example we set the event handlers to be:

let boxNodeData;

cy.on('boxstart', event => {
  console.log('boxstart');
  boxNodeData = [];
})

cy.on('box', 'node', event => {
  console.log('pushed');
  boxNodeData.push(event.target._private.data);
})

cy.on('boxend', event => {
  console.log('boxend');
  console.log(boxNodeData);
})

When we run that example, the following is shown in the console: capture

"pushed" is printed after "boxend", which means that the box events are fired after the box selection ends. Therefore, boxend can't be used for this particular purpose.

maxkfranz commented 6 years ago

The events are somewhat analogous to mousedown-mouseup-click: boxstart-boxend-box.

You can track all of the elements that are in a box gesture by keeping a list similar to your example:

const GROUP_THRESHOLD = 100; // time delta that separates one box gesture from another

let boxed = cy.collection();

const sendData = eles => {
  let elsData = eles.map(el => el.data());

  // send the array of data json objs somewhere; just log for now...
  console.log(elsData);
};

const checkGroup = _.debounce(() => {
  sendData(boxed);

  boxed = cy.collection();
}, GROUP_THRESHOLD); // events must be at least this duration apart to make a separate group

const addToBoxed = el => {
  boxed.merge(el);

  checkGroup();
};

cy.on('box', e => { // or 'boxselect'
  const el = e.target;

  addToBoxed(el);
});

I've considered having native support in the core lib for getting a group of enclosed elements at once from a box gesture. The only solution I've seen that could work well for that is to allow for e.target to have more than one element. This would also work for other gestures, and this would generally reduce the number of event objects created for group events. However, this approach is inconsistent with how listeners work in most systems --- e.g. the DOM. It would also be a breaking API change.

So, the pattern posted above works for now.

xhluca commented 6 years ago

Thank you Max! This works perfectly, I'll keep an eye out for changes on the API in case native support is added.