UNPKG

d3-beeswarm

Version:

D3 plugin which computes a 'beeswarm' arrangement

182 lines (154 loc) 6.08 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.vanilla = factory()); }(this, function () { 'use strict'; // each data MUST have a uniq id for direct-access, which defaults to its 'id' attribute // each data MUST have a value to sort on, which default to its 'value' attribute // data in SortedDirectAccessDoublyLinkedList are sorted by 'value', from min to max, in a doubly-linked list // each node in the doubly-linked list is of the form {datum: , value: , prev: , next: } // 'datum' refers to the original datum; 'value' is retrieved from data, 'prev'/'next' refer to previous/next value-based nodes function SortedDirectAccessDoublyLinkedList () { this._valueAccessor = // accessor to the value to sort on function (obj) { return obj.value; }; this._idAccessor = // accessor to the uniq id function (obj) { return obj.id; }; this._min = null; // reference to a doubly-linked node with the min value this._max = null; // reference to a doubly-linked node with the max value this._closestTo0 = null; // reference to the doubly-linked node with the value closest or egal to 0 this.size = 0; // number of data in the doubly-linked list this._idToNode = {}; // direct access to a node of the doubly-linked list } SortedDirectAccessDoublyLinkedList.prototype.valueAccessor = function (_) { if (!arguments.length) { return this._valueAccessor; } this._valueAccessor = _; //for chaining purpose return this; }; SortedDirectAccessDoublyLinkedList.prototype.idAccessor = function (_) { if (!arguments.length) { return this._idAccessor; } this._idAccessor = _; //for chaining purpose return this; }; SortedDirectAccessDoublyLinkedList.prototype.min = function () { return this._min; }; SortedDirectAccessDoublyLinkedList.prototype.max = function () { return this._max; }; SortedDirectAccessDoublyLinkedList.prototype.closestTo0 = function () { return this._closestTo0; }; SortedDirectAccessDoublyLinkedList.prototype.dln = function (datum){ // dln = doubly-linked node return this._idToNode[this._idAccessor(datum)]; }; SortedDirectAccessDoublyLinkedList.prototype.empty = function () { this._min = null; this._max = null; this._closestTo0 = null; this.size = 0; this._idToNode = {}; //for chaining purpose return this; }; SortedDirectAccessDoublyLinkedList.prototype.add = function (datum){ //create a new doubly-linked node var dln = { datum: datum, // original datum value: this._valueAccessor(datum), // store datum's value, intensively used when sorting prev: null, // previous value-based node next: null // next value-based node }; //insert node in the adequate position in the doubly-linked list if (this.size === 0) { //special case: no node in the list yet this._min = this._max = this._closestTo0 = dln; } else { if (dln.value <= this._min.value) {//special case: new datum is the min dln.next = this._min; dln.next.prev = dln; this._min = dln; } else if (dln.value >= this._max.value) { //special case: new datum is the max dln.prev = this._max; dln.prev.next = dln; this._max = dln; } else { //insert the node at the adequate position var current = this._max; //loop to find immediate prev while (current.value > dln.value) { current = current.prev; } dln.prev = current; dln.next = current.next; dln.next.prev = dln; dln.prev.next = dln; } if (Math.abs(dln.value) < Math.abs(this._closestTo0.value)) { this._closestTo0 = dln; } } //direct access to the node this._idToNode[this._idAccessor(datum)] = dln; //update size this.size++; //for chaining purpose return this; }; SortedDirectAccessDoublyLinkedList.prototype.addMany = function (data) { data.forEach( function (datum, item) { this.add(datum); }, this); //for chaining purpose return this; }; SortedDirectAccessDoublyLinkedList.prototype.remove = function (datum) { var dln = this.dln(datum); //remove node from the doubly-linked list if (this.size === 1) { //special case: last item to remove this._min = this._max = this._closestTo0 = null; } else { if (dln === this._closestTo0) { if (this._closestTo0.next === null) { //closestTo0 is also the max, consider merge code with below this._closestTo0 = dln.prev; } else if (this._closestTo0.prev === null) { //closestTo0 is also the min, consider merge code with below this._closestTo0 = dln.prev; } else { //consider merge code with below var prevAbsValue = Math.abs(this._closestTo0.prev.value); var nextAbsValue = Math.abs(this._closestTo0.next.value); if (prevAbsValue < nextAbsValue) { this._closestTo0 = this._closestTo0.prev; } else { this._closestTo0 = this._closestTo0.next; } } } if (dln === this._min) { //special case: datum is the min this._min = this._min.next; this._min.prev = null; } else if (dln === this._max) { //special case: datum is the max this._max = this._max.prev; this._max.next = null; } else { //remove pointers to the node dln.next.prev = dln.prev; dln.prev.next = dln.next; } } delete this._idToNode[this._idAccessor(datum)]; //remove direct access to the node dln = null; // carbage collector //update size this.size--; //for chaining purpose return this; }; return SortedDirectAccessDoublyLinkedList; }));