Skip to content
This repository has been archived by the owner on Jul 29, 2019. It is now read-only.

Network: use separate refresh indicator in NodeBase, instead of width… #2885

Merged
merged 17 commits into from Apr 14, 2017

Conversation

wimrijnders
Copy link
Contributor

@wimrijnders wimrijnders commented Mar 21, 2017

This PR is a resolution of #1847 and #2436. Please examine these for the problem description.

Discussion

It should be noted that it is not necessary to call getNodeAt() within an event listener to determine the handled node; the node id is passed to the event listener in the supplied parameter:

network.on('click', function(params){ 
  var nodeId = params.nodes[0];
  ...
}); 

However, there are situations imaginable where getNodeAt() is called outside of an event listener,
so it still makes sense to ensure that this method is working correctly.

Analysis

The basic problem is that within Node, the dimension this.shape.width has a double function
as an indicator that a shape should be recalculated. This happens in method refresh(), which
is called by select() (among others):

  /**
   * Reset the calculated size of the node, forces it to recalculate its size
   * @private
   */
  _reset() {
    this.shape.width = undefined;
    this.shape.height = undefined;
  }

The undefined value of the width gets picked up during redraw in the shape instances, where it is
used in a lazy fashion to recalculate the shape. Almost all shapes do it in the following way:

  _resizeShape(...) {
    if ((this.width === undefined) || (this.labelModule.differentState(selected, hover))) {
      // Recalculate here
    }
  }

The exceptions are shapes CircularImage and Image, which defer the calculation until the contained image is loaded.

In practice, when a Node is clicked on, the click event will fire before Network.redraw() has been called. So, during the click event, the shape's width member is undefined.
The width is also used within getNodeAt() to determine the selected node, ultimately within method Node.isOverlappingWith(). Because the width is undefined, isOverlappingWith() returns undefined.

Solution

Member Shape.width should not be used as an indicator for resize, it should have a single task as a dimension. It is better that a new, different member variable is used for refresh indication.

  • Added member variable doRefresh in NodeBase, which is set in Node._reset()
  • Refactored test on resize required to NodeBase.needRefresh() for all relevant shapes
  • Note that the exceptional shapes Image and CircularImage do not have this adjustment

This can be tested in example examples/network/events/interactionEvents.html, where a line of code
is added to show the output of getNodeAt() during a click event.
All shape types have been tested using this page; however, this test code has been removed.

@wimrijnders
Copy link
Contributor Author

After staring long and hard at the code of the image shapes, I figured out how to best use doRefresh for these.

So now the refresh of shapes image and circularImage works similar to the rest of the shapes.

@@ -53,6 +54,7 @@
network.on("click", function (params) {
params.event = "[original event]";
document.getElementById('eventSpan').innerHTML = '<h2>Click event:</h2>' + JSON.stringify(params, null, 4);
console.log('click event, getNodeAt returns: ' + this.getNodeAt(params.pointer.DOM));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating the example. Perfect!

@@ -380,8 +380,7 @@ class Node {
* @private
*/
_reset() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get rid of the _reset function and replace it with needRefresh?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, can do.

*
* @protected
*/
needRefresh(selected, hover) {
Copy link
Member

@mojoaxel mojoaxel Mar 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a "private" function. it should start with a underscore: _needRefresh.
Also i would rename it to _needsRefresh but that is just personal taste.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my initial thought too. However, it's not strictly private but protected, in the sense that it is called from derived classes. So I wasn't 100% sure to make it look like a private method.

Are there any guidelines for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no guidelines. I don't know the code good enough to know if there is a schema for this. Have a look at the surrounding code - you decide.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think you should add the "s": needsRefresh :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no guidelines

Actually, I think there should be. In due time, I will make a proposal; there are a couple of style issues that bother me.

needsRefresh

Yes, that's better. Silly me.

@@ -380,8 +380,7 @@ class Node {
* @private
*/
_reset() {
this.shape.width = undefined;
this.shape.height = undefined;
this.shape.doRefresh = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me doRefresh sounds like a function. What about this.shape._refreshNeeded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but without the '_' prefix, because it is not private.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I guess

* @protected
*/
needRefresh(selected, hover) {
if (this.doRefresh) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a little dangerous to do a if (this.doRefresh) because this is valid e.g. also if doRefresh is a valid String or any object. That's why I would recommend to invert the logic:

if (!this.doRefresh) { 
  return (this.width === undefined) || (this.labelModule.differentState(selected, hover));
}
this.doRefresh = false;
return true;

*
* @protected
*/
needRefresh(selected, hover) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think you should add the "s": needsRefresh :-)

@@ -84,6 +89,7 @@ class CircleImageBase extends NodeBase {
this.width = width;
this.height = height;
this.radius = 0.5 * this.width;
this.refreshNeeded = false;
Copy link
Member

@mojoaxel mojoaxel Mar 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wimrijnders Are you sure we don't need the refresh here because of the hover/selected state? I'm not sure I understand it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of this line is to turn off the indicator variable after a triggered resize calculation.
As far as I can tell, all the required resizing has been done in this block before this line. The value is set to false to prevent the resize calculation from triggering continuously.

In summary, when disregarding the rest of the logic, what happens here is:

if (refreshNeeded === true) {
  // Do the resize calculation
  refreshNeeded = false;
}

I hope this explains the intention. I believe this code is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you mean: why is not hover and selected checked for triggering the resize calculation, just like with the rest of the shapes.

The direct answer is: I don't know, this is how the code worked before I added the changes and I did not want to change the logic too much.
I'll look at the code to see if I can understand this myself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I understand what is happening; needsRefresh() is/should be called in the child classes. So the changes I made to CircleImageBase are misplaced.

needsRefresh() is already called in Circle.resize(). For completeness, the call should also be used in CircularImage.resize().

I will make an update to the PR which I hope will clarify the point. Well spotted, @mojoaxel.

@wimrijnders
Copy link
Contributor Author

wimrijnders commented Mar 24, 2017

OK, figured it out. I moved the test for refresh from CircleImageBase to its child classes.

I also did some cleanup and refactoring in the process, to better understand the code. But this was becoming a never-ending story: the more I look, the more I see things that need looking at. So, I left some TODO's in the code and quickly closed off before I found more.

So, please glance at the TODO's to see if they're OK.

@britonk1
Copy link

britonk1 commented Apr 13, 2018

@wimrijnders

Regarding getNodeAt() not being required within event listeners - I don't believe this to be the case for "oncontext" events? The on context event does not explicitly select the node that has been right clicked and therefore the node ID does not necessarily get passed in params.

I am still having the problem with getNodeAt using the latest version of VIS.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants