Reviewing this verion
Why are ids strings (instead of numbers)? More importantly, why are ids needed at all? In JavaScript, objects can be unambiguously identified with their reference.
var o = {};
var o2 = {};
var o3 = o;
o !== o2
o === o3
o2 !== o3
By spec, the object reference is guaranteed to be unique (so you wouldn't need to check for uniqueness or generate unique ids as you often do) and unforgeable (so people aren't tempted to be smart about an id generation scheme)
Searching things by id always incurs a cost because of the lookup and this lookup is rather unnecessary. ids are useful only when interacting with contexts where the memory isn't shared (different machine, different process, database, force in a WebWorker ;-) ). And even then, they could be an implementation detail that can be hidden.
WeakMaps (or a Map or a Set if you need to enumerate the values) can be useful to associate data to an object without putting it as a property. They'll help with memory leak without having to do too much about it. Polyfills exist. They have all sorts of downsides, but they're polyfills, so the native version will be used if available which is going to be more and more common.
I admit that removing the use of ids will break APIs... Maybe for the best?
In the graph API, nodes
and edges
ought to be getters and not methods.
if one middleware throws, everything is cancelled. Is it intended? Add a comment if so.
(cosmetic : use forEach to traverse an array; second argument is index)
- graph.addMethod
Why not create graphs from a constructor and just let people access to Graph.prototype? oh... that's related to having access to per-instance private? Damned JavaScript!
- graph.attach
Looks a lot like an event system
Set configurable: false
, otherwise, if the node isn't cloned and already provided an id, it will remain to true
... or get rid of ids altogether ;-)
Same bug for edges.
Use Array.isArray (probably repeated in a lot of places)
If conf.renderer === null
, 'container' in o
throws an error. Beware of typeof x === 'object' in general.
Maybe schedule it via rAF
No need to convert to string, the JS engine already does it. See step 6 of http://es5.github.io/#x11.2.1
Purely philosophical question : why a quadtree and not a binary tree?
for-in
will be probably faster and more elegant
Maybe you'll be interested in the to-be-standard Object.assign and add it as polyfill https://github.com/paulmillr/es6-shim/blob/1942309cb40da9e87ab8565ec60b19b9c5591c64/es6-shim.js#L651-L657
If useful, take a look at performance.now() for sub-millisecond precision.
- https://github.com/jacomyal/sigma.js/blob/9cfc11bc505030ea43c25a7f2de8ad223a9ad544/src/misc/sigma.misc.bindEvents.js#L64-L67
- https://github.com/jacomyal/sigma.js/blob/9cfc11bc505030ea43c25a7f2de8ad223a9ad544/src/classes/sigma.classes.camera.js#L232-L235
- https://github.com/jacomyal/sigma.js/blob/9cfc11bc505030ea43c25a7f2de8ad223a9ad544/src/classes/sigma.classes.quad.js#L98-L101
Maybe add a polyfill for Math.hypot
=> this.captors.forEach(bindCaptor)
use forEach
- https://github.com/jacomyal/sigma.js/blob/9cfc11bc505030ea43c25a7f2de8ad223a9ad544/src/classes/sigma.classes.dispatcher.js#L44-L59
- https://github.com/jacomyal/sigma.js/blob/9cfc11bc505030ea43c25a7f2de8ad223a9ad544/src/classes/sigma.classes.dispatcher.js#L91-L107
- https://github.com/jacomyal/sigma.js/blob/9cfc11bc505030ea43c25a7f2de8ad223a9ad544/src/classes/sigma.classes.dispatcher.js#L131-L145
=> forEach
Maybe belongs in util.
Will throw if one property is non-configurable. Maybe belongs in util.
Initialize only if this.settings('clone')
First, I've been rushing to release in time, and since it's a complex code, having someone making the effort to read and find some trouble spots is really helpful. Thanks for that :)
Overall comments
ids
I kept ids for two main reasons:
I admit the edge ids especially are kind of annoying, but it is easy to spawn them from parsers, like it happens in the GEXF parser.
But I keep this in mind for a later version. I prefer not to break the API so soon, but if there is later a nice graph model with references-based indexes that is well tested and faster, I will integrate it.
API
I wanted a simple and intuitive way to retrieve data from the graph, so the
nodes
andedges
methods return a copy of the related arrays if called without arguments, the related object if an ID is specified, and the array of the related objects if an array of ids is given. But since they are almost always called without arguments, this is indeed open to criticism...Important
Middlewares
Oops indeed... Middlewares do require some more work, especially on errors management.
sigma.classes.graph
It looks indeed like an event system. But since the target is also the dispatcher, I prefered to design it in a more ad-hoc way. Also, since methods can be added, I did not see a good way to use the prototype while keeping these methods catchable... There are a bit more explanation on the Graph wiki page.
Indeed, my bad.
Cosmetic
Your notes that are not commented here are those I just have to implement in the sources.
You are right. The
renderCamera
methods usesrequestAnimationFrame
, but resizing slowly still makes te process steps happen way too much.Also, the
if (_self.settings)
just before is a nice useless piece of code, have to fix that.No idea, actually. I just needed to add a spatial index to avoid lagging when moving the mouse as it was in the previous version (sigh), and I knew the quadtree was good enough.
@Yomguithereal : What do you think about it?
After your investigations on force directed layout, it looks like I have optimized the wrong loops...
performances.now
,Math.hypot
andNumber.isFinite
Didn't know about those, it looks interesting, thanks.