Commit 69965a62 authored by Marc Duez's avatar Marc Duez
Browse files

scatterplot.js : jsdoc

parent dd31bee9
......@@ -20,6 +20,18 @@
* You should have received a copy of the GNU General Public License
* along with "Vidjil". If not, see <http://www.gnu.org/licenses/>
*/
/**
* scatterplot is a View object who display node as bubble or bar according to 2 selected axis
*
* @constructor
* @augments View
* @this View
*
* @param {string} id - dom id of the html div who will contain the scatterplot
* @param {Model} model
* */
function ScatterPlot(id, model) {
var self = this;
......@@ -151,13 +163,14 @@ function ScatterPlot(id, model) {
this.use_system_grid = false
}
/*
Déclaration des fonctions attribuées à l'objet
*/
ScatterPlot.prototype = {
/* Fonction permettant l'initialisation complète du ScatterPlot
*/
/**
* init the view before use <br>
* build grid / pre-select axis
* */
init: function() {
console.log("ScatterPlot " + this.id + ": init()");
......@@ -175,8 +188,9 @@ ScatterPlot.prototype = {
this.resize();
},
/* Fonction permettant l'initialisation d'un élément SVG contenu dans le Scatterplot, pour D3JS
*/
/**
* build all the svg container who will receive axis / labels / clones
* */
initSVG: function() {
var self = this;
......@@ -332,8 +346,10 @@ ScatterPlot.prototype = {
},
/* Function which allows to return the number of active clones
*/
/**
* Function which allows to return the number of active clones
* @return {integer}
* */
returnActiveclones: function() {
var activeclones = 0;
for (var i = 0; i < this.m.clones.length; i++) {
......@@ -343,7 +359,8 @@ ScatterPlot.prototype = {
return activeclones;
},
/* Function which allows to initialize the D3JS engine - usefull with reinitMotor()
/**
* initialize the D3JS physic engine with default values - usefull with reinitMotor()
* */
initMotor: function() {
this.force
......@@ -358,248 +375,10 @@ ScatterPlot.prototype = {
this.fixedAllClones(false);
},
/* Function which permits to modify the D3JS physic engine, compared to the graph distribution
* */
graphMotor: function() {
var self = this;
this.force
//Added greater 'alpha' to obtains a larger nodes animation
.alpha(100)
//Gravity only for Edit Distance - no gravity to DBSCAN for fixed nodes in the SVG frame (without charge between nodes)
.gravity(function(d) {
if (self.dbscanActive) return 0;
else return -0.001
})
//Added edges array (different according to the graphe visualization)
.links(self.edge)
/*
LinkDistance:
-Edit Distance = Distance power 1.3, with zoom and coeff parameters
-DBSCAN = 2 choices:
-> CORE node = default length (see DBSCANLength) and zoom + coeff parameters
-> NEAR node = attribution of a superior length
*/
.linkDistance(function(d) {
if (self.dbscanActive && (self.m.clone(d.source)
.tagCluster == "CORE" || self.m.clone(d.target)
.tagCluster == "CORE"))
return (self.DBSCANLength * self.mouseZoom * (self.resizeCoef / ((20 * self.resizeCoef) / 100)));
else {
//Added length between nodes to let the CORE node at the middle of the cluster
if (self.dbscanActive) return (8 * self.mouseZoom * (self.resizeCoef / ((20 * self.resizeCoef) / 100)));
}
if (!self.dbscanActive) return (Math.pow(d.len, 1.4) * self.mouseZoom * (self.resizeCoef / ((20 * self.resizeCoef) / 100)));
})
.charge(function(d) {
if (self.reloadCharge) {
return -1;
}
}) //Allows to act to the DBSCAN visualization -> clusters with length not equals to 1 are rejected -> independant clusters = independant graphs
.linkStrength(1) //Added strong strength
.size([self.resizeW - 50, self.resizeH - 50]);
this.setGraphLinks(); //Edges initialization in the graph
},
/* Function which allows to initialize the edges creation, for the D3JS engine
* */
setGraphLinks: function() {
var self = this;
//Add links selected, added data
var allLinks = this.grpLinks
.selectAll("line")
//Attributing to each 'line' object a data contains in the edges array, give with the engine initialization
.data(self.edge);
//Added real edges (see setEdgeContainer developer documentation)
this.setEdgeContainer(allLinks);
},
/* Function which allow to initialize/re-initialize the edges array of the graph, and unset lines
* */
unsetGraphLinks: function() {
this.force.links([]); //The engine not contains links
this.grpLinks.selectAll("line")
.remove(); //Useless links are delete
},
/* Function which allows to re-initialize the D3JS engine, according to the visualization
* -> If visualization 'DBSCAN' -> New configuration fo the physic engine (see 'graphMotor')
* -> Else, configuration by default of the engine (see 'init_motor')
* */
reinit_motor: function() {
this.m.resetClusters();
if (!this.reinit) {
//The configuration of the engine is active
this.reinit = true;
//All the nodes positions are saved
if (this.canSavePositions)
this.saveInitialPositions();
//Permission to save all positions is now delete - in order to not save initial positions of nodes when switching: DBSCAN -> Edit Distance OR Edit Distance -> DBSCAN
this.canSavePositions = false;
//Re-init of X/Y axis
this.switchInitMenu();
/* Relaunch of the physic engine ->
* If DBSCAN mode is activated, we compute the DBSCAN object
* Else, relaunch of the engine for Edit Distance visualization
*/
if (this.dbscanActive) {
this.activeSliderDistanceMax(false);
this.runGraphVisualization("dbscan");
} else this.runGraphVisualization("edit_distance");
//Charge disabled
this.reloadCharge = false;
//Activation of the slider to move the distanceMax parameter, IF AND ONLY IF the Edit Distance visualization is activated
if (!this.dbscanActive) {
this.activeSliderDistanceMax(true);
}
} else {
//Graph mode disabled
this.reinit = false;
//Reloading of the charge
this.reloadCharge = true;
//Allow to save all nodes
this.canSavePositions = true;
//Re-init grid (for 'normal' distribution)
this.initGrid();
//Re-init of X/Y axis menu (see switchInitMenu doc)
this.switchInitMenu();
//Motor by default
this.initMotor();
//Reloading of the initial positions of nodes
this.reinitInitialPositions();
//Reloading of physic engine
this.force.start();
//Reloading of graph distribution axis
this.m.update();
//Edit Distance slider disabled
this.activeSliderDistanceMax(false);
}
},
/* Function which allows to run a graph visualization
* @param viewing: the visualization given
*/
runGraphVisualization: function(viewing) {
//All the links are now erased
this.unsetGraphLinks();
//Compute edges, according to the visualization given
switch (viewing) {
case "dbscan":
this.computeDBSCANEdges();
break;
case "edit_distance":
this.computeEditDistanceEdges();
break;
}
//Re-init the physic engine
this.graphMotor();
//Added color to edges, according to the visualization given
this.addColorEdge();
//We allow to separate clusters
this.reloadCharge = true;
//Reload the physic engine
this.force.start();
//Charge disabled -> no separation of clusters by charge
this.reloadCharge = false;
},
/* Function which allows to create physically edges (see D3JS developer documentation on enter() and remove())
* @allEdges: array of all edges
*/
setEdgeContainer: function(allEdges) {
//Added SVG 'line' element -> "line_active" 'cause no apparition of links - links will be active when manual selection of one or more clone(s)
this.edgeContainer = allEdges.enter()
.append("svg:line")
.attr("class", "line_inactive")
.on("mouseover", function(d) {
self.m.focusEdge(d);
});
},
/* Function which permits to add a color for each object which is contains in the initial edges array
*/
addColorEdge: function() {
//DBSCAN visualization case
if (this.dbscanActive) {
for (var i = 0; i < this.edge.length; i++) {
//Yellow color attributed to edges which have source or target, with tag 'CORE'
if (this.m.clone(this.edge[i].source)
.tagCluster == "CORE" || this.m.clone(this.edge[i].target)
.tagCluster == "CORE")
this.edge[i].color = "yellow";
else
//Red color attributed to edges which have source or target, with tag 'NEAR' (neighbors)
if (this.m.clone(this.edge[i].source)
.tagCluster == "NEAR" || this.m.clone(this.edge[i].target)
.tagCluster == "NEAR")
this.edge[i].color = "red";
}
}
//Edit Distance visualization case
else {
for (var i = 0; i < this.edge.length; i++) {
//If the link's length is lower than distanceMax, color the link
if (this.edge[i].len <= this.distanceMax)
//colorGenerator usage for color red (0) to blue (270), by gap: 270/distanceMax
this.edge[i].color = colorGenerator((270 / this.distanceMax) * this.edge[i].len);
//If the link is not interesting, we attribute the white color (usefull for the activeAllLine() function)
else this.edge[i].color = "white";
}
}
},
/* Function which allows to compute usefull edges in dataset, for the DBSCAN graph visualization
*/
computeDBSCANEdges: function() {
this.edge = self.keepUsefullEdgesForDBSCANGraph(self.allEdges);
},
/* Function which allows to compute usefull edges in dataset, for the Edit Distance graph visualization
*/
computeEditDistanceEdges: function() {
this.edge = self.keepUsefullEdgesForEditDistanceGraph(self.allEdges);
},
/* Function which allows to keep only edges which concerns nodes in specific cluster -> verification done to see if source and target is in the same cluster
@param tmp: array of all edges
*/
keepUsefullEdgesForDBSCANGraph: function(tmp) {
var returnedTab = [];
if (typeof tmp != 'undefined') {
for (var i = 0; i < tmp.length; i++) {
if (this.m.clone(tmp[i].source)
.cluster == this.m.clone(tmp[i].target)
.cluster) {
returnedTab.push(tmp[i]);
}
}
}
return returnedTab;
},
/* Function which allows to keep only edges which length is lower than distanceMax
@param tmp: array of all edges
*/
keepUsefullEdgesForEditDistanceGraph: function(tmp) {
var returnedTab = [];
if (typeof tmp != 'undefined') {
//Sorted array, according to growing edges length
tmp.sort(function(a, b) {
if (a.len < b.len) return -1;
if (a.len > b.len) return 1;
return 0
});
for (var i = 0; i < tmp.length; i++) {
//Keep only edges lower or equals than distanceMax
if (tmp[i].len <= this.distanceMax)
returnedTab.push(tmp[i]);
else
break;
}
}
return returnedTab
},
/* Fonction permettant d'initialiser le menu contenant les axes
/**
* build scatterplot menu <br>
* preset/axisX/axisY selector<br>
* scatterplot/bargraph selector<br>
* */
initMenu: function() {
var self = this;
......@@ -744,26 +523,8 @@ ScatterPlot.prototype = {
if (this.mode=="plot") $("#"+this.id+"_plot").addClass("sp_selected_mode")
},
/*Function which allows to switch between initial menu and graph menu*/
switchInitMenu: function() {
var axis_select_x = document.getElementsByClassName("axis_select_x");
var axis_select_y = document.getElementsByClassName("axis_select_y");
var graph_selector = document.getElementsByClassName("axis_select_graph");
for (var i = 0; i < axis_select_x.length; i++)
axis_select_x[i].style.display = this.reinit ? "none" : "block";
for (var i = 0; i < axis_select_y.length; i++)
axis_select_y[i].style.display = this.reinit ? "none" : "block";
for (var i = 0; i < graph_selector.length; i++)
graph_selector[i].style.display = this.reinit ? "block" : "none";
},
/* Function which allows to update edges of graph
* */
updateGraph: function() {
this.setGraphLinks();
},
/* Function which allows to initialize SVG element for the Diagram distribution
/**
* init bar positions using current bubbles positions
* */
initBar: function() {
self = this;
......@@ -797,7 +558,8 @@ ScatterPlot.prototype = {
})
},
/* Fonction permettant la mise à jour de l'élément SVG concernant le graphe 'diagramme'
/**
* compute and update the position of all rectangles representing a clone using selected axis
* */
updateBar: function() {
self = this
......@@ -847,6 +609,13 @@ ScatterPlot.prototype = {
this.computeBarTab();
},
/**
* sort clones in different bar using a distribution function and a list of potential values <br>
* the values list length is the minimum number of bar we will get<br>
* if the distribution function return a value outside the list of given potential values, a new bar will be created for this value
* param {function} fct - distribution function
* param {string[]} values - a list of potential values
* */
makeBarTab: function (fct, values) {
var min, max;
this.barTab = [];
......@@ -892,7 +661,10 @@ ScatterPlot.prototype = {
return this;
},
//sp.sortBarTab(tab, function(a){var va = m.clone(a).getJ()})
/**
* sort each bar of barplot using a distribution function <br>
* param {function} fct - distribution function
* */
sortBarTab: function (fct) {
for (var i in this.barTab) {
......@@ -921,6 +693,10 @@ ScatterPlot.prototype = {
return this;
},
/**
* return the size of the biggest bar
* @return {float} - size
* */
computeBarMax : function () {
var bar_max = 0;
for (var i in this.barTab) {
......@@ -935,6 +711,9 @@ ScatterPlot.prototype = {
return bar_max;
},
/**
* compute the position of each rectangles / labels and start the display
* */
computeBarTab : function () {
var bar_max = this.computeBarMax();
var tab_length = Object.keys(this.barTab).length;
......@@ -978,6 +757,10 @@ ScatterPlot.prototype = {
return this
},
/**
* draw/move rectangles to their computed position
* @param {integer} speed
* */
drawBarTab : function (speed) {
var self = this;
//redraw
......@@ -1009,7 +792,8 @@ ScatterPlot.prototype = {
})
},
/* Fonction permettant de désactiver la distribution "Bar"
/**
* transition between bar graph and scatterplot mode
* */
endBar: function() {
var self = this;
......@@ -1041,8 +825,10 @@ ScatterPlot.prototype = {
},
/* Fonction permettant de calculer la hauteur d'un 'bar' pour un clone donné (ID du clone)
* @param cloneID: l'ID du clone
/**
* compute the height of a bar representing a given clone in the bar graph mode
* @param {integer} cloneID - clone index
* @return {float} height
* */
getBarHeight: function(cloneID) {
var size = this.m.clone(cloneID)
......@@ -1050,8 +836,10 @@ ScatterPlot.prototype = {
return size / this.bar_max;
},
/* Fonction permettant de calculer la position d'un 'bar' pour un clone donné (ID du clone)
* @param cloneID: l'ID du clone
/**
* compute the x pos of a bar representing a given clone in the bar graph mode
* @param {integer} cloneID - clone index
* @return {float} pos
* */
getBarPosition: function(cloneID) {
for (var i = 0; i < this.vKey.length; i++) {
......@@ -1062,13 +850,16 @@ ScatterPlot.prototype = {
}
},
/* switch mode between bar and plot
*/
/**
* switch mode between bar and plot
* */
changeMode : function (mode) {
this.changeSplitMethod(this.splitX, this.splitY, mode);
},
/**
* build a system labels descriptor
* */
buildSystemGrid: function() {
this.systemGrid = {
"label": []
......@@ -1118,7 +909,10 @@ ScatterPlot.prototype = {
}
},
/* Recalcule les coefficients d'agrandissement/réduction, en fonction de la taille de la div
/**
* resize the scatterplot to the size of the div parent element or to a given size
* @param {float} [div_width]
* @param {float} [div_height]
* */
resize: function(div_width, div_height) {
var print = true
......@@ -1143,7 +937,12 @@ ScatterPlot.prototype = {
.updateMenu()
.initGrid();
},
/**
* @param {float} [div_width]
* @param {float} [div_height]
* @param {float} [print]
* */
compute_size: function(div_width, div_height, print) {
if (typeof div_height == 'undefined') {
var div = document.getElementById(this.id)
......@@ -1178,44 +977,19 @@ ScatterPlot.prototype = {
return this;
},
/* Function which allows to add a stroke attribute to all edges
*/
addColor: function() {
this.edgeContainer
.style("stroke", function(d) {
return d.color;
});
},
/*Function which permits to compute and add edges positions, according to source and target nodes
*/
addPosition: function() {
var self = this;
//self.marge = margin top and left given in the scatterPlot
this.edgeContainer
//Awarding of the source position
.attr("x1", function(d) {
return (d.source.px + self.marge_left);
})
.attr("y1", function(d) {
return (d.source.py + self.marge_top);
})
//Awarding of the target position
.attr("x2", function(d) {
return (d.target.px + self.marge_left);
})
.attr("y2", function(d) {
return (d.target.py + self.marge_top);
});
},
/**
* compute the position of the 500 next frame but don't display them <br>
* (used for export a scatterplot screenshot without waiting for the clones have found a balance)
* */
fastForward: function() {
this.force.stop()
for (var i = 0; i < 500; i++) this.computeFrame()
this.drawFrame()
},
/* Fonction permettant le calcul d'une étape d'animation
/**
* one animation step<br>
* compute next frame and draws
* */
tick: function() {
var self = this
......@@ -1236,6 +1010,10 @@ ScatterPlot.prototype = {
},
/**
* compute next position of each clones <br>
* resolve collision
* */
computeFrame: function() {
var self = this;
......@@ -1262,6 +1040,9 @@ ScatterPlot.prototype = {
return this
},
/**
* draw each clones
* */
drawFrame: function() {
if (this.mode != "bar"){
this.active_node
......@@ -1282,7 +1063,8 @@ ScatterPlot.prototype = {
}
},
/* Fonction permettant le déplacement des nodes en fonction de la méthode de répartition utilisée à l'instant T
/**
* move current clone's positions closer to their expected positions
* */
move: function() {
self = this;
......@@ -1305,8 +1087,8 @@ ScatterPlot.prototype = {
},
/* Fonction permettant de mettre à jour les rayons de chaque cercle
* Précision: r2 = rayon affiché // r1 = rayon a atteindre
/**
* update current clone's radius closer to their expected radius
* */
updateRadius: function() {
return function(d) {
......@@ -1318,21 +1100,24 @@ ScatterPlot.prototype = {
}
},
/* Fonction permettant de repositionner les nodes, ayant des positions impossibles a afficher
/**
* repair empty clone's position <br>
* */
debugNaN: function() {
return function(d) {
//Repositionnement dans l'axe des X si position non-déterminée/infinie
if (!isFinite(d.x)) {
d.x = Math.random() * 500;
}
//Repositionnement dans l'axe des Y si position non-déterminée/infinie
if (!isFinite(d.y)) {
d.y = Math.random() * 500;
}
}
},
/**
* resolve collision function
* @param {node} node - d3js node for a clone
* */
collide: function(node) {
var r = node.r2 + 30,
nx1 = node.x - r,
......@@ -1353,20 +1138,13 @@ ScatterPlot.prototype = {
node.x -= x *= l;
node.y -= y *= l;
}
/*
if (l < r) {
l = (l - r) / l * .5;
node.x -= x *= l;
node.y -= y *= l;
quad.point.x += x;
quad.point.y += y;