Comparing version 2.0.0-alpha1 to 2.0.0-alpha10-rc1
188
camera.js
@@ -39,2 +39,4 @@ 'use strict'; | ||
var DEFAULT_ZOOMING_RATIO = 1.5; | ||
// TODO: animate options = number polymorphism? | ||
@@ -63,4 +65,4 @@ // TODO: pan, zoom, unzoom, reset, rotate, zoomTo | ||
_this.x = 0; | ||
_this.y = 0; | ||
_this.x = 0.5; | ||
_this.y = 0.5; | ||
_this.angle = 0; | ||
@@ -73,2 +75,3 @@ _this.ratio = 1; | ||
_this.nextFrame = null; | ||
_this.previousState = _this.getState(); | ||
_this.enabled = true; | ||
@@ -123,2 +126,21 @@ return _this; | ||
/** | ||
* Method used to retrieve the camera's previous state. | ||
* | ||
* @return {object} | ||
*/ | ||
}, { | ||
key: 'getPreviousState', | ||
value: function getPreviousState() { | ||
var state = this.previousState; | ||
return { | ||
x: state.x, | ||
y: state.y, | ||
angle: state.angle, | ||
ratio: state.ratio | ||
}; | ||
} | ||
/** | ||
* Method used to retrieve the camera's dimensions. | ||
@@ -151,19 +173,27 @@ * | ||
/** | ||
* Method returning the coordinates of a point from the display frame to the | ||
* graph one but ignores the offset and dimensions of the container. | ||
* Method returning the coordinates of a point from the graph frame to the | ||
* viewport. | ||
* | ||
* @param {number} x The X coordinate. | ||
* @param {number} y The Y coordinate. | ||
* @return {object} The point coordinates in the frame of the graph. | ||
* @param {object} dimensions - Dimensions of the viewport. | ||
* @param {number} x - The X coordinate. | ||
* @param {number} y - The Y coordinate. | ||
* @return {object} - The point coordinates in the viewport. | ||
*/ | ||
// TODO: assign to gain one object | ||
// TODO: angles | ||
}, { | ||
key: 'abstractDisplayToGraph', | ||
value: function abstractDisplayToGraph(x, y) { | ||
var cos = Math.cos(this.angle), | ||
sin = Math.sin(this.angle); | ||
key: 'graphToViewport', | ||
value: function graphToViewport(dimensions, x, y) { | ||
var smallestDimension = Math.min(dimensions.width, dimensions.height); | ||
var dx = smallestDimension / dimensions.width, | ||
dy = smallestDimension / dimensions.height; | ||
// TODO: we keep on the upper left corner! | ||
// TODO: how to normalize sizes? | ||
return { | ||
x: (x * cos - y * sin) * this.ratio, | ||
y: (y * cos + x * sin) * this.ratio | ||
x: (x - this.x + this.ratio / 2 / dx) * (smallestDimension / this.ratio), | ||
y: (this.y - y + this.ratio / 2 / dy) * (smallestDimension / this.ratio) | ||
}; | ||
@@ -173,25 +203,24 @@ } | ||
/** | ||
* Method returning the coordinates of a point from the display frame to the | ||
* graph one. | ||
* Method returning the coordinates of a point from the viewport frame to the | ||
* graph frame. | ||
* | ||
* @param {number} x The X coordinate. | ||
* @param {number} y The Y coordinate. | ||
* @return {object} The point coordinates in the frame of the graph. | ||
* @param {object} dimensions - Dimensions of the viewport. | ||
* @param {number} x - The X coordinate. | ||
* @param {number} y - The Y coordinate. | ||
* @return {object} - The point coordinates in the graph frame. | ||
*/ | ||
// TODO: angles | ||
}, { | ||
key: 'displayToGraph', | ||
value: function displayToGraph(x, y) { | ||
var cos = Math.cos(this.angle), | ||
sin = Math.sin(this.angle); | ||
key: 'viewportToGraph', | ||
value: function viewportToGraph(dimensions, x, y) { | ||
var smallestDimension = Math.min(dimensions.width, dimensions.height); | ||
var xOffset = this.width / 2 - (this.x * cos + this.y * sin) / this.ratio, | ||
yOffset = this.height / 2 - (this.y * cos - this.x * sin) / this.ratio; | ||
var dx = smallestDimension / dimensions.width, | ||
dy = smallestDimension / dimensions.height; | ||
var X = x - xOffset, | ||
Y = y - yOffset; | ||
return { | ||
x: (X * cos - Y * sin) * this.ratio, | ||
y: (Y * cos + X * sin) * this.ratio | ||
x: this.ratio / smallestDimension * x + this.x - this.ratio / 2 / dx, | ||
y: -(this.ratio / smallestDimension * y - this.y - this.ratio / 2 / dy) | ||
}; | ||
@@ -201,21 +230,28 @@ } | ||
/** | ||
* Method returning the coordinates of a point from the graph frame to the | ||
* display one. | ||
* Method returning the abstract rectangle containing the graph according | ||
* to the camera's state. | ||
* | ||
* @param {number} x The X coordinate. | ||
* @param {number} y The Y coordinate. | ||
* @return {object} The point coordinates in the frame of the display. | ||
* @return {object} - The view's rectangle. | ||
*/ | ||
// TODO: angle | ||
}, { | ||
key: 'graphToDisplay', | ||
value: function graphToDisplay(x, y) { | ||
var relCos = Math.cos(this.angle) / this.ratio, | ||
relSin = Math.sin(this.angle) / this.ratio, | ||
xOffset = this.width / 2 - this.x * relCos - this.y * relSin, | ||
yOffset = this.height / 2 - this.y * relCos + this.x * relSin; | ||
key: 'viewRectangle', | ||
value: function viewRectangle(dimensions) { | ||
// TODO: reduce relative margin? | ||
var marginX = 0 * this.width / 8, | ||
marginY = 0 * this.height / 8; | ||
var p1 = this.viewportToGraph(dimensions, 0 - marginX, 0 - marginY), | ||
p2 = this.viewportToGraph(dimensions, this.width + marginX, 0 - marginY), | ||
h = this.viewportToGraph(dimensions, 0, this.height + marginY); | ||
return { | ||
x: x * relCos + y * relSin + xOffset, | ||
y: y * relCos - x * relSin + yOffset | ||
x1: p1.x, | ||
y1: p1.y, | ||
x2: p2.x, | ||
y2: p2.y, | ||
height: p2.y - h.y | ||
}; | ||
@@ -240,2 +276,5 @@ } | ||
// Keeping track of last state | ||
this.previousState = this.getState(); | ||
if ('x' in state) this.x = state.x; | ||
@@ -290,3 +329,3 @@ | ||
key: 'animate', | ||
value: function animate(state, options /*, callback */) { | ||
value: function animate(state, options, callback) { | ||
var _this2 = this; | ||
@@ -318,2 +357,4 @@ | ||
if (typeof callback === 'function') callback(); | ||
return; | ||
@@ -336,4 +377,63 @@ } | ||
this.nextFrame = requestAnimationFrame(fn); | ||
if (this.nextFrame) { | ||
cancelAnimationFrame(this.nextFrame); | ||
this.nextFrame = requestAnimationFrame(fn); | ||
} else { | ||
fn(); | ||
} | ||
} | ||
/** | ||
* Method used to zoom the camera. | ||
* | ||
* @param {number|object} factorOrOptions - Factor or options. | ||
* @return {function} | ||
*/ | ||
}, { | ||
key: 'animatedZoom', | ||
value: function animatedZoom(factorOrOptions) { | ||
if (!factorOrOptions) { | ||
return this.animate({ ratio: this.ratio / DEFAULT_ZOOMING_RATIO }); | ||
} else { | ||
if (typeof factorOrOptions === 'number') return this.animate({ ratio: this.ratio / factorOrOptions });else return this.animate({ ratio: this.ratio / (factorOrOptions.factor || DEFAULT_ZOOMING_RATIO) }, factorOrOptions); | ||
} | ||
} | ||
/** | ||
* Method used to unzoom the camera. | ||
* | ||
* @param {number|object} factorOrOptions - Factor or options. | ||
* @return {function} | ||
*/ | ||
}, { | ||
key: 'animatedUnzoom', | ||
value: function animatedUnzoom(factorOrOptions) { | ||
if (!factorOrOptions) { | ||
return this.animate({ ratio: this.ratio * DEFAULT_ZOOMING_RATIO }); | ||
} else { | ||
if (typeof factorOrOptions === 'number') return this.animate({ ratio: this.ratio * factorOrOptions });else return this.animate({ ratio: this.ratio * (factorOrOptions.factor || DEFAULT_ZOOMING_RATIO) }, factorOrOptions); | ||
} | ||
} | ||
/** | ||
* Method used to reset the camera. | ||
* | ||
* @param {object} options - Options. | ||
* @return {function} | ||
*/ | ||
}, { | ||
key: 'animatedReset', | ||
value: function animatedReset(options) { | ||
return this.animate({ | ||
x: 0, | ||
y: 0, | ||
ratio: 1, | ||
angle: 0 | ||
}, options); | ||
} | ||
}]); | ||
@@ -340,0 +440,0 @@ |
@@ -9,2 +9,6 @@ 'use strict'; | ||
var _camera = require('../camera'); | ||
var _camera2 = _interopRequireDefault(_camera); | ||
var _captor = require('../captor'); | ||
@@ -64,6 +68,6 @@ | ||
_this.downStartTime = null; | ||
_this.startMouseX = null; | ||
_this.startMouseY = null; | ||
_this.lastMouseX = null; | ||
_this.lastMouseY = null; | ||
_this.isMouseDown = false; | ||
_this.isMoving = true; | ||
_this.isMoving = false; | ||
_this.movingTimeout = null; | ||
@@ -74,2 +78,3 @@ _this.startCameraState = null; | ||
_this.doubleClickTimeout = null; | ||
_this.wheelLock = false; | ||
@@ -126,4 +131,2 @@ // Binding methods | ||
var ratio = 1 / DOUBLE_CLICK_ZOOMING_RATIO; | ||
var center = (0, _utils.getCenter)(e); | ||
@@ -133,8 +136,33 @@ | ||
var position = this.camera.abstractDisplayToGraph((0, _utils.getX)(e) - center.x, (0, _utils.getY)(e) - center.y); | ||
var newRatio = cameraState.ratio / DOUBLE_CLICK_ZOOMING_RATIO; | ||
// TODO: factorize | ||
var dimensions = { | ||
width: this.container.offsetWidth, | ||
height: this.container.offsetHeight | ||
}; | ||
var clickX = (0, _utils.getX)(e), | ||
clickY = (0, _utils.getY)(e); | ||
// TODO: baaaad we mustn't mutate the camera, create a Camera.from or #.copy | ||
// TODO: factorize pan & zoomTo | ||
var cameraWithNewRatio = new _camera2.default(); | ||
cameraWithNewRatio.ratio = newRatio; | ||
cameraWithNewRatio.x = cameraState.x; | ||
cameraWithNewRatio.y = cameraState.y; | ||
var clickGraph = this.camera.viewportToGraph(dimensions, clickX, clickY), | ||
centerGraph = this.camera.viewportToGraph(dimensions, center.x, center.y); | ||
var clickGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, clickX, clickY), | ||
centerGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, center.x, center.y); | ||
var deltaX = clickGraphNew.x - centerGraphNew.x - clickGraph.x + centerGraph.x, | ||
deltaY = clickGraphNew.y - centerGraphNew.y - clickGraph.y + centerGraph.y; | ||
this.camera.animate({ | ||
x: position.x * (1 - ratio) + cameraState.x, | ||
y: position.y * (1 - ratio) + cameraState.y, | ||
ratio: ratio * cameraState.ratio | ||
x: cameraState.x - deltaX, | ||
y: cameraState.y - deltaY, | ||
ratio: newRatio | ||
}, { | ||
@@ -159,4 +187,4 @@ easing: 'quadraticInOut', | ||
this.startMouseX = (0, _utils.getX)(e); | ||
this.startMouseY = (0, _utils.getY)(e); | ||
this.lastMouseX = (0, _utils.getX)(e); | ||
this.lastMouseY = (0, _utils.getY)(e); | ||
@@ -191,8 +219,9 @@ this.hasDragged = false; | ||
var cameraState = this.camera.getState(); | ||
var cameraState = this.camera.getState(), | ||
previousCameraState = this.camera.getPreviousState(); | ||
if (this.isMoving) { | ||
this.camera.animate({ | ||
x: cameraState.x + MOUSE_INERTIA_RATIO * (cameraState.x - this.lastCameraState.x), | ||
y: cameraState.y + MOUSE_INERTIA_RATIO * (cameraState.y - this.lastCameraState.y) | ||
x: cameraState.x + MOUSE_INERTIA_RATIO * (cameraState.x - previousCameraState.x), | ||
y: cameraState.y + MOUSE_INERTIA_RATIO * (cameraState.y - previousCameraState.y) | ||
}, { | ||
@@ -202,3 +231,3 @@ duration: MOUSE_INERTIA_DURATION, | ||
}); | ||
} else if (this.startMouseX !== x || this.startMouseY !== y) { | ||
} else if (this.lastMouseX !== x || this.lastMouseY !== y) { | ||
this.camera.setState({ | ||
@@ -210,4 +239,5 @@ x: cameraState.x, | ||
this.isMoving = false; | ||
this.hasDragged = false; | ||
this.emit('mouseup', (0, _utils.getMouseCoords)(e)); | ||
this.isMoving = false; | ||
} | ||
@@ -236,18 +266,26 @@ }, { | ||
var position = this.camera.abstractDisplayToGraph((0, _utils.getX)(e) - this.startMouseX, (0, _utils.getY)(e) - this.startMouseY); | ||
var dimensions = { | ||
width: this.container.offsetWidth, | ||
height: this.container.offsetHeight | ||
}; | ||
var x = this.startCameraState.x - position.x, | ||
y = this.startCameraState.y - position.y; | ||
var eX = (0, _utils.getX)(e), | ||
eY = (0, _utils.getY)(e); | ||
var lastMouse = this.camera.viewportToGraph(dimensions, this.lastMouseX, this.lastMouseY); | ||
var mouse = this.camera.viewportToGraph(dimensions, eX, eY); | ||
var offsetX = lastMouse.x - mouse.x, | ||
offsetY = lastMouse.y - mouse.y; | ||
var cameraState = this.camera.getState(); | ||
if (cameraState.x !== x || cameraState.y !== y) { | ||
var x = cameraState.x + offsetX, | ||
y = cameraState.y + offsetY; | ||
this.lastCameraState = cameraState; | ||
this.camera.setState({ x: x, y: y }); | ||
this.camera.setState({ | ||
x: x, | ||
y: y | ||
}); | ||
} | ||
this.lastMouseX = eX; | ||
this.lastMouseY = eY; | ||
} | ||
@@ -264,29 +302,62 @@ | ||
value: function handleWheel(e) { | ||
if (!this.enabled) return; | ||
var _this4 = this; | ||
if (e.preventDefault) e.preventDefault();else e.returnValue = false; | ||
e.stopPropagation(); | ||
if (!this.enabled) return false; | ||
var delta = (0, _utils.getWheelDelta)(e); | ||
if (!delta) return; | ||
if (!delta) return false; | ||
if (this.wheelLock) return false; | ||
this.wheelLock = true; | ||
// TODO: handle max zoom | ||
var ratio = delta > 0 ? 1 / ZOOMING_RATIO : ZOOMING_RATIO; | ||
var cameraState = this.camera.getState(); | ||
var newRatio = ratio * cameraState.ratio; | ||
var center = (0, _utils.getCenter)(e); | ||
var cameraState = this.camera.getState(); | ||
var dimensions = { | ||
width: this.container.offsetWidth, | ||
height: this.container.offsetHeight | ||
}; | ||
var position = this.camera.abstractDisplayToGraph((0, _utils.getX)(e) - center.x, (0, _utils.getY)(e) - center.y); | ||
var clickX = (0, _utils.getX)(e), | ||
clickY = (0, _utils.getY)(e); | ||
// TODO: baaaad we mustn't mutate the camera, create a Camera.from or #.copy | ||
// TODO: factorize pan & zoomTo | ||
var cameraWithNewRatio = new _camera2.default(); | ||
cameraWithNewRatio.ratio = newRatio; | ||
cameraWithNewRatio.x = cameraState.x; | ||
cameraWithNewRatio.y = cameraState.y; | ||
var clickGraph = this.camera.viewportToGraph(dimensions, clickX, clickY), | ||
centerGraph = this.camera.viewportToGraph(dimensions, center.x, center.y); | ||
var clickGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, clickX, clickY), | ||
centerGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, center.x, center.y); | ||
var deltaX = clickGraphNew.x - centerGraphNew.x - clickGraph.x + centerGraph.x, | ||
deltaY = clickGraphNew.y - centerGraphNew.y - clickGraph.y + centerGraph.y; | ||
this.camera.animate({ | ||
x: position.x * (1 - ratio) + cameraState.x, | ||
y: position.y * (1 - ratio) + cameraState.y, | ||
ratio: ratio * cameraState.ratio | ||
x: cameraState.x - deltaX, | ||
y: cameraState.y - deltaY, | ||
ratio: newRatio | ||
}, { | ||
easing: 'quadraticOut', | ||
easing: 'linear', | ||
duration: MOUSE_ZOOM_DURATION | ||
}, function () { | ||
return _this4.wheelLock = false; | ||
}); | ||
if (e.preventDefault) e.preventDefault();else e.returnValue = false; | ||
e.stopPropagation(); | ||
return false; | ||
@@ -293,0 +364,0 @@ } |
@@ -6,11 +6,2 @@ 'use strict'; | ||
}); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** | ||
* Sigma.js Captor Utils | ||
* ====================== | ||
* | ||
* Miscellenous helper functions related to the captors. | ||
*/ | ||
exports.getX = getX; | ||
@@ -48,2 +39,8 @@ exports.getY = getY; | ||
*/ | ||
/** | ||
* Sigma.js Captor Utils | ||
* ====================== | ||
* | ||
* Miscellenous helper functions related to the captors. | ||
*/ | ||
function getY(e) { | ||
@@ -115,14 +112,6 @@ if (typeof e.offsetY !== 'undefined') return e.offsetY; | ||
*/ | ||
function getMouseCoords(e, x, y) { | ||
if (arguments.length < 2) { | ||
x = getX(e); | ||
y = getY(e); | ||
} | ||
var center = getCenter(e); | ||
// TODO: is this really needed to have this strange {x,y} with now? | ||
function getMouseCoords(e) { | ||
return { | ||
x: x - center.x, | ||
y: y - center.y, | ||
x: getX(e), | ||
y: getY(e), | ||
clientX: e.clientX, | ||
@@ -144,7 +133,7 @@ clientY: e.clientY, | ||
function getWheelDelta(e) { | ||
if (_typeof(e.wheelDelta) !== undefined) return e.wheelDelta; | ||
if (typeof e.wheelDelta !== 'undefined') return e.wheelDelta / 360; | ||
if (_typeof(e.detail) !== undefined) return -e.detail; | ||
if (typeof e.detail !== 'undefined') return e.detail / -9; | ||
throw new Error('sigma/captors/utils.getDelta: could not extract delta from event.'); | ||
} |
{ | ||
"name": "sigma", | ||
"version": "2.0.0-alpha1", | ||
"version": "2.0.0-alpha10-rc1", | ||
"description": "A JavaScript library dedicated to graph drawing.", | ||
@@ -12,10 +12,10 @@ "homepage": "http://sigmajs.org", | ||
"dist": "npm run clean && babel src --out-dir . && webpack --config ./webpack.programs.config.js", | ||
"examples": "webpack-dev-server --config ./examples/webpack.config.js --port 8000 --progress", | ||
"examples": "webpack-dev-server --config ./examples/webpack.config.js --progress", | ||
"lint": "eslint examples src test", | ||
"postpublish": "npm run clean", | ||
"prepublish": "npm run test && npm run dist && npm run build", | ||
"test": "mocha --compilers js:babel-core/register ./test/endpoint.js" | ||
"test": "mocha --require babel-core/register ./test/endpoint.js" | ||
}, | ||
"sigma": { | ||
"clean": "camera.js captor.js easings.js endpoint.js quadtree.js renderer.js sigma.js utils.js captors renderers" | ||
"clean": "camera.js captor.js easings.js endpoint.js quadtree.js renderer.js sigma.js utils.js captors heuristics renderers" | ||
}, | ||
@@ -41,19 +41,20 @@ "repository": { | ||
"babel-core": "^6.14.0", | ||
"babel-loader": "^7.0.0", | ||
"babel-loader": "^7.1.4", | ||
"babel-preset-es2015": "^6.14.0", | ||
"chroma-js": "^1.3.3", | ||
"eslint": "^4.0.0", | ||
"chroma-js": "^1.3.6", | ||
"eslint": "^4.19.1", | ||
"faker": "^4.1.0", | ||
"graphology": "^0.10.2", | ||
"graphology-generators": "^0.6.1", | ||
"glob": "^7.1.2", | ||
"graphology": "^0.11.3", | ||
"graphology-generators": "^0.9.0", | ||
"graphology-gexf": "^0.3.1", | ||
"graphology-layout": "0.0.1", | ||
"graphology-layout-forceatlas2": "0.0.1", | ||
"graphology-layout-forceatlas2": "^0.1.1", | ||
"html-webpack-plugin": "^2.22.0", | ||
"mocha": "^3.0.2", | ||
"mocha": "^5.0.5", | ||
"raw-loader": "^0.5.1", | ||
"rimraf": "^2.6.1", | ||
"webpack": "^2.5.1", | ||
"webpack-dev-server": "^2.4.5", | ||
"worker-loader": "^0.8.0" | ||
"webpack": "^3.10.0", | ||
"webpack-dev-server": "^2.11.2", | ||
"worker-loader": "^1.1.1" | ||
}, | ||
@@ -77,6 +78,6 @@ "keywords": [ | ||
"dependencies": { | ||
"events": "^1.1.1", | ||
"gl-matrix": "^2.3.2", | ||
"graphology-utils": "^1.1.1" | ||
"events": "^2.0.0", | ||
"graphology-metrics": "1.5.0", | ||
"graphology-utils": "^1.3.0" | ||
} | ||
} |
178
quadtree.js
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -12,2 +12,3 @@ Object.defineProperty(exports, "__esModule", { | ||
/* eslint no-nested-ternary: 0 */ | ||
/* eslint no-constant-condition: 0 */ | ||
/** | ||
@@ -25,10 +26,14 @@ * Sigma.js Quad Tree Class | ||
// TODO: a square can be represented as topleft + width | ||
// TODO: a square can be represented as topleft + width, saying for the quad blocks (reduce mem) | ||
// TODO: jsdoc | ||
// TODO: be sure we can handle cases overcoming boundaries (because of size) or use a max | ||
// TODO: be sure we can handle cases overcoming boundaries (because of size) or use a maxed size | ||
// TODO: decide whether to store at leaf level or at medium levels (frustum vs. hover) | ||
// TODO: filtering unwanted labels beforehand through the filter function | ||
// NOTE: this is basically a MX-CIF Quadtree at this point | ||
// NOTE: need to explore R-Trees for edges | ||
// NOTE: need to explore 2d segment tree for edges | ||
/** | ||
@@ -74,6 +79,10 @@ * Constants. | ||
function rectangleCollidesWithQuad(x1, y1, w, qx, qy, qw, qh) { | ||
function squareCollidesWithQuad(x1, y1, w, qx, qy, qw, qh) { | ||
return x1 < qx + qw && x1 + w > qx && y1 < qy + qh && y1 + w > qy; | ||
} | ||
function rectangleCollidesWithQuad(x1, y1, w, h, qx, qy, qw, qh) { | ||
return x1 < qx + qw && x1 + w > qx && y1 < qy + qh && y1 + h > qy; | ||
} | ||
function pointIsInQuad(x, y, qx, qy, qw, qh) { | ||
@@ -147,12 +156,9 @@ var xmp = qx + qw / 2, | ||
// [block, level] | ||
// TODO: does not require a stack if sticking with mid-level containers | ||
var stack = [0, 0]; | ||
var level = 0, | ||
block = 0; | ||
while (stack.length) { | ||
var level = stack.pop(), | ||
block = stack.pop(); | ||
while (true) { | ||
// If we reached max level | ||
if (level === maxLevel) { | ||
if (level >= maxLevel) { | ||
containers[block] = containers[block] || []; | ||
@@ -168,9 +174,9 @@ containers[block].push(key); | ||
var collidingWithTopLeft = rectangleCollidesWithQuad(x1, y1, w, data[topLeftBlock + X_OFFSET], data[topLeftBlock + Y_OFFSET], data[topLeftBlock + WIDTH_OFFSET], data[topLeftBlock + HEIGHT_OFFSET]); | ||
var collidingWithTopLeft = squareCollidesWithQuad(x1, y1, w, data[topLeftBlock + X_OFFSET], data[topLeftBlock + Y_OFFSET], data[topLeftBlock + WIDTH_OFFSET], data[topLeftBlock + HEIGHT_OFFSET]); | ||
var collidingWithTopRight = rectangleCollidesWithQuad(x1, y1, w, data[topRightBlock + X_OFFSET], data[topRightBlock + Y_OFFSET], data[topRightBlock + WIDTH_OFFSET], data[topRightBlock + HEIGHT_OFFSET]); | ||
var collidingWithTopRight = squareCollidesWithQuad(x1, y1, w, data[topRightBlock + X_OFFSET], data[topRightBlock + Y_OFFSET], data[topRightBlock + WIDTH_OFFSET], data[topRightBlock + HEIGHT_OFFSET]); | ||
var collidingWithBottomLeft = rectangleCollidesWithQuad(x1, y1, w, data[bottomLeftBlock + X_OFFSET], data[bottomLeftBlock + Y_OFFSET], data[bottomLeftBlock + WIDTH_OFFSET], data[bottomLeftBlock + HEIGHT_OFFSET]); | ||
var collidingWithBottomLeft = squareCollidesWithQuad(x1, y1, w, data[bottomLeftBlock + X_OFFSET], data[bottomLeftBlock + Y_OFFSET], data[bottomLeftBlock + WIDTH_OFFSET], data[bottomLeftBlock + HEIGHT_OFFSET]); | ||
var collidingWithBottomRight = rectangleCollidesWithQuad(x1, y1, w, data[bottomRightBlock + X_OFFSET], data[bottomRightBlock + Y_OFFSET], data[bottomRightBlock + WIDTH_OFFSET], data[bottomRightBlock + HEIGHT_OFFSET]); | ||
var collidingWithBottomRight = squareCollidesWithQuad(x1, y1, w, data[bottomRightBlock + X_OFFSET], data[bottomRightBlock + Y_OFFSET], data[bottomRightBlock + WIDTH_OFFSET], data[bottomRightBlock + HEIGHT_OFFSET]); | ||
@@ -180,13 +186,93 @@ var collisions = collidingWithTopLeft + collidingWithTopRight + collidingWithBottomLeft + collidingWithBottomRight; | ||
// If we don't have at least a collision, there is an issue | ||
if (collisions === 0) throw new Error("sigma/quadtree.insertNode: no collision (level: " + level + ", key: " + key + ", x: " + x + ", y: " + y + ", size: " + size + ")."); | ||
if (collisions === 0) throw new Error('sigma/quadtree.insertNode: no collision (level: ' + level + ', key: ' + key + ', x: ' + x + ', y: ' + y + ', size: ' + size + ').'); | ||
// If we have 3 collisions, we have a geometry problem obviously | ||
if (collisions === 3) throw new Error('sigma/quadtree.insertNode: 3 impossible collisions (level: ' + level + ', key: ' + key + ', x: ' + x + ', y: ' + y + ', size: ' + size + ').'); | ||
// If we have more that one collision, we stop here and store the node | ||
// in the relevant container | ||
// in the relevant containers | ||
if (collisions > 1) { | ||
// NOTE: this is a nice way to optimize for hover, but not for frustum | ||
// since it requires to uniq the collected nodes | ||
// if (collisions < 4) { | ||
// // If we intersect two quads, we place the node in those two | ||
// if (collidingWithTopLeft) { | ||
// containers[topLeftBlock] = containers[topLeftBlock] || []; | ||
// containers[topLeftBlock].push(key); | ||
// } | ||
// if (collidingWithTopRight) { | ||
// containers[topRightBlock] = containers[topRightBlock] || []; | ||
// containers[topRightBlock].push(key); | ||
// } | ||
// if (collidingWithBottomLeft) { | ||
// containers[bottomLeftBlock] = containers[bottomLeftBlock] || []; | ||
// containers[bottomLeftBlock].push(key); | ||
// } | ||
// if (collidingWithBottomRight) { | ||
// containers[bottomRightBlock] = containers[bottomRightBlock] || []; | ||
// containers[bottomRightBlock].push(key); | ||
// } | ||
// } | ||
// else { | ||
// // Else we keep the node where it is to avoid more pointless computations | ||
// containers[block] = containers[block] || []; | ||
// containers[block].push(key); | ||
// } | ||
containers[block] = containers[block] || []; | ||
containers[block].push(key); | ||
return; | ||
} else { | ||
level++; | ||
} | ||
// Else we recurse into the correct quad | ||
// Else we recurse into the correct quads | ||
if (collidingWithTopLeft) block = topLeftBlock; | ||
if (collidingWithTopRight) block = topRightBlock; | ||
if (collidingWithBottomLeft) block = bottomLeftBlock; | ||
if (collidingWithBottomRight) block = bottomRightBlock; | ||
} | ||
} | ||
function getNodesInAxisAlignedRectangleArea(maxLevel, data, containers, x1, y1, w, h) { | ||
// [block, level] | ||
var stack = [0, 0]; | ||
var collectedNodes = []; | ||
var container = void 0; | ||
while (stack.length) { | ||
var level = stack.pop(), | ||
block = stack.pop(); | ||
// Collecting nodes | ||
container = containers[block]; | ||
if (container) collectedNodes.push.apply(collectedNodes, container); | ||
// If we reached max level | ||
if (level >= maxLevel) continue; | ||
var topLeftBlock = 4 * block + BLOCKS, | ||
topRightBlock = 4 * block + 2 * BLOCKS, | ||
bottomLeftBlock = 4 * block + 3 * BLOCKS, | ||
bottomRightBlock = 4 * block + 4 * BLOCKS; | ||
var collidingWithTopLeft = rectangleCollidesWithQuad(x1, y1, w, h, data[topLeftBlock + X_OFFSET], data[topLeftBlock + Y_OFFSET], data[topLeftBlock + WIDTH_OFFSET], data[topLeftBlock + HEIGHT_OFFSET]); | ||
var collidingWithTopRight = rectangleCollidesWithQuad(x1, y1, w, h, data[topRightBlock + X_OFFSET], data[topRightBlock + Y_OFFSET], data[topRightBlock + WIDTH_OFFSET], data[topRightBlock + HEIGHT_OFFSET]); | ||
var collidingWithBottomLeft = rectangleCollidesWithQuad(x1, y1, w, h, data[bottomLeftBlock + X_OFFSET], data[bottomLeftBlock + Y_OFFSET], data[bottomLeftBlock + WIDTH_OFFSET], data[bottomLeftBlock + HEIGHT_OFFSET]); | ||
var collidingWithBottomRight = rectangleCollidesWithQuad(x1, y1, w, h, data[bottomRightBlock + X_OFFSET], data[bottomRightBlock + Y_OFFSET], data[bottomRightBlock + WIDTH_OFFSET], data[bottomRightBlock + HEIGHT_OFFSET]); | ||
if (collidingWithTopLeft) stack.push(topLeftBlock, level + 1); | ||
@@ -200,2 +286,4 @@ | ||
} | ||
return collectedNodes; | ||
} | ||
@@ -207,9 +295,11 @@ | ||
* @constructor | ||
* @param {Graph} graph - A graph instance. | ||
* @param {object} boundaries - The graph boundaries. | ||
*/ | ||
var QuadTree = function () { | ||
function QuadTree(boundaries) { | ||
function QuadTree(params) { | ||
_classCallCheck(this, QuadTree); | ||
params = params || {}; | ||
// Allocating the underlying byte array | ||
@@ -220,8 +310,12 @@ var L = Math.pow(4, MAX_LEVEL); | ||
this.containers = {}; | ||
this.cache = null; | ||
this.lastRectangle = null; | ||
if (boundaries) this.resize(boundaries); | ||
if (params.boundaries) this.resize(params.boundaries); | ||
if (typeof params.filter === 'function') this.nodeFilter = params.filter; | ||
} | ||
_createClass(QuadTree, [{ | ||
key: "add", | ||
key: 'add', | ||
value: function add(key, x, y, size) { | ||
@@ -233,3 +327,3 @@ insertNode(MAX_LEVEL, this.data, this.containers, key, x, y, size); | ||
}, { | ||
key: "resize", | ||
key: 'resize', | ||
value: function resize(boundaries) { | ||
@@ -247,3 +341,3 @@ this.clear(); | ||
}, { | ||
key: "clear", | ||
key: 'clear', | ||
value: function clear() { | ||
@@ -255,3 +349,3 @@ this.containers = {}; | ||
}, { | ||
key: "point", | ||
key: 'point', | ||
value: function point(x, y) { | ||
@@ -263,7 +357,5 @@ var nodes = []; | ||
while (level <= MAX_LEVEL) { | ||
do { | ||
if (this.containers[block]) nodes.push.apply(nodes, this.containers[block]); | ||
// TODO: should probably use a do...while to avoid useless last op | ||
var quad = pointIsInQuad(x, y, this.data[block + X_OFFSET], this.data[block + Y_OFFSET], this.data[block + WIDTH_OFFSET], this.data[block + HEIGHT_OFFSET]); | ||
@@ -273,6 +365,32 @@ | ||
level++; | ||
} | ||
} while (level <= MAX_LEVEL); | ||
return nodes; | ||
} | ||
}, { | ||
key: 'rectangle', | ||
value: function rectangle(x1, y1, x2, y2, height) { | ||
var lr = this.lastRectangle; | ||
if (lr && x1 === lr.x1 && x2 === lr.x2 && y1 === lr.y1 && y2 === lr.y2 && height === lr.height) { | ||
return this.cache; | ||
} | ||
this.lastRectangle = { | ||
x1: x1, | ||
y1: y1, | ||
x2: x2, | ||
y2: y2, | ||
height: height | ||
}; | ||
// Is the rectangle axis aligned? | ||
if (!isAxisAligned(x1, y1, x2, y2)) throw new Error('sigma/quadtree.rectangle: shifted view is not yet implemented.'); | ||
var collectedNodes = getNodesInAxisAlignedRectangleArea(MAX_LEVEL, this.data, this.containers, x1, y1, Math.abs(x1 - x2) || Math.abs(y1 - y2), height); | ||
this.cache = collectedNodes; | ||
return this.cache; | ||
} | ||
}]); | ||
@@ -279,0 +397,0 @@ |
@@ -6,5 +6,8 @@ 'use strict'; | ||
}); | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
exports.createElement = createElement; | ||
exports.getPixelRatio = getPixelRatio; | ||
exports.createNodeRescalingFunction = createNodeRescalingFunction; | ||
exports.createNormalizationFunction = createNormalizationFunction; | ||
/** | ||
@@ -56,59 +59,38 @@ * Sigma.js Rendering Utils | ||
/** | ||
* Default rescale options. | ||
*/ | ||
var DEFAULT_NODE_RESCALE_OPTIONS = { | ||
mode: 'inside', | ||
margin: 0, | ||
minSize: 1, | ||
maxSize: 8 | ||
}; | ||
var DEFAULT_EDGE_RESCALE_OPTIONS = { | ||
minSize: 0.5, | ||
maxSize: 1 | ||
}; | ||
// TODO: should we let the user handle size through, for instance, d3 scales? | ||
// TODO: should we put the rescaling in the camera itself? | ||
/** | ||
* Factory returning a function rescaling the given node's position and/or size. | ||
* Factory returning a function normalizing the given node's position & size. | ||
* | ||
* @param {object} options - Options. | ||
* @param {object} extent - Extent of the graph. | ||
* @return {function} | ||
*/ | ||
function createNodeRescalingFunction(options, extent) { | ||
options = options || {}; | ||
function createNormalizationFunction(extent) { | ||
var _extent$x = _slicedToArray(extent.x, 2), | ||
minX = _extent$x[0], | ||
maxX = _extent$x[1], | ||
_extent$y = _slicedToArray(extent.y, 2), | ||
minY = _extent$y[0], | ||
maxY = _extent$y[1]; | ||
var mode = options.mode || DEFAULT_NODE_RESCALE_OPTIONS.mode, | ||
height = options.height || 1, | ||
width = options.width || 1; | ||
var ratio = Math.max(maxX - minX, maxY - minY); | ||
var maxX = extent.maxX, | ||
maxY = extent.maxY, | ||
minX = extent.minX, | ||
minY = extent.minY; | ||
var dX = (maxX + minX) / 2, | ||
dY = (maxY + minY) / 2; | ||
var hx = (maxX + minX) / 2, | ||
hy = (maxY + minY) / 2; | ||
var scale = mode === 'outside' ? Math.max(width / Math.max(maxX - minX, 1), height / Math.max(maxY - minY, 1)) : Math.min(width / Math.max(maxX - minX, 1), height / Math.max(maxY - minY, 1)); | ||
var fn = function fn(data) { | ||
return { | ||
x: (data.x - hx) * scale, | ||
y: (data.y - hy) * scale | ||
x: 0.5 + (data.x - dX) / ratio, | ||
y: 0.5 + (data.y - dY) / ratio | ||
}; | ||
}; | ||
// TODO: unit test this | ||
fn.inverse = function (data) { | ||
return { | ||
x: data.x / scale + hx, | ||
y: data.y / scale + hy | ||
x: dX + ratio * (data.x - 0.5), | ||
y: dY + ratio * (data.y - 0.5) | ||
}; | ||
}; | ||
fn.ratio = ratio; | ||
return fn; | ||
} |
@@ -9,4 +9,6 @@ 'use strict'; | ||
var _glMatrix = require('gl-matrix'); | ||
var _extent = require('graphology-metrics/extent'); | ||
var _extent2 = _interopRequireDefault(_extent); | ||
var _renderer = require('../../renderer'); | ||
@@ -28,3 +30,3 @@ | ||
var _node = require('./programs/node'); | ||
var _node = require('./programs/node.fast'); | ||
@@ -51,2 +53,4 @@ var _node2 = _interopRequireDefault(_node); | ||
var _labels = require('../../heuristics/labels'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -69,6 +73,14 @@ | ||
*/ | ||
var WEBGL_OVERSAMPLING_RATIO = 2; | ||
var PIXEL_RATIO = (0, _utils2.getPixelRatio)(); | ||
var WEBGL_OVERSAMPLING_RATIO = (0, _utils2.getPixelRatio)(); | ||
/** | ||
* Defaults. | ||
*/ | ||
var DEFAULT_SETTINGS = { | ||
hideEdgesOnMove: false, | ||
hideLabelsOnMove: false | ||
}; | ||
/** | ||
* Main class. | ||
@@ -83,8 +95,12 @@ * | ||
function WebGLRenderer(container) { | ||
function WebGLRenderer(container, settings) { | ||
_classCallCheck(this, WebGLRenderer); | ||
// Validating | ||
var _this = _possibleConstructorReturn(this, (WebGLRenderer.__proto__ || Object.getPrototypeOf(WebGLRenderer)).call(this)); | ||
settings = settings || {}; | ||
_this.settings = (0, _utils.assign)({}, DEFAULT_SETTINGS, settings); | ||
// Validating | ||
if (!(container instanceof HTMLElement)) throw new Error('sigma/renderers/webgl: container should be an html element.'); | ||
@@ -100,22 +116,13 @@ | ||
// Indices | ||
// TODO: this could be improved by key => index => floatArray | ||
// TODO: the cache should erase keys on node delete | ||
_this.quadtree = new _quadtree2.default(); | ||
_this.nodeArray = null; | ||
_this.nodeIndicesArray = null; | ||
_this.nodeOrder = {}; | ||
_this.nodeDataCache = {}; | ||
_this.edgeArray = null; | ||
_this.edgeIndicesArray = null; | ||
_this.edgeOrder = {}; | ||
_this.nodePrograms = { | ||
def: new _node2.default() | ||
}; | ||
_this.edgePrograms = { | ||
def: new _edge2.default() | ||
}; | ||
// Normalization function | ||
_this.normalizationFunction = null; | ||
// TODO: if we drop size scaling => this should become "rescalingFunction" | ||
_this.nodeRescalingFunction = null; | ||
// Starting dimensions | ||
@@ -127,3 +134,6 @@ _this.width = 0; | ||
_this.highlightedNodes = new Set(); | ||
_this.previousVisibleNodes = new Set(); | ||
_this.displayedLabels = new Set(); | ||
_this.hoveredNode = null; | ||
_this.wasRenderedInThisFrame = false; | ||
_this.renderFrame = null; | ||
@@ -133,3 +143,2 @@ _this.renderHighlightedNodesFrame = null; | ||
_this.needToSoftProcess = false; | ||
_this.pixel = new Uint8Array(4); | ||
@@ -143,2 +152,21 @@ // Initializing contexts | ||
// Blending | ||
var gl = _this.contexts.nodes; | ||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); | ||
gl.enable(gl.BLEND); | ||
gl = _this.contexts.edges; | ||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); | ||
gl.enable(gl.BLEND); | ||
// Loading programs | ||
_this.nodePrograms = { | ||
def: new _node2.default(_this.contexts.nodes) | ||
}; | ||
_this.edgePrograms = { | ||
def: new _edge2.default(_this.contexts.edges) | ||
}; | ||
// Initial resize | ||
@@ -163,9 +191,3 @@ _this.resize(); | ||
_this.bindEventHandlers(); | ||
// Loading programs | ||
for (var k in _this.nodePrograms) { | ||
_this.nodePrograms[k].load(_this.contexts.nodes); | ||
}for (var _k in _this.edgePrograms) { | ||
_this.edgePrograms[_k].load(_this.contexts.edges); | ||
}return _this; | ||
return _this; | ||
} | ||
@@ -179,8 +201,8 @@ | ||
/** | ||
* Method used to test a pixel of the given context. | ||
* Internal function used to create a canvas context and add the relevant | ||
* DOM elements. | ||
* | ||
* @param {WebGLContext} gl - Context. | ||
* @param {number} x - Client x. | ||
* @param {number} y - Client y. | ||
* @return {boolean} | ||
* @param {string} id - Context's id. | ||
* @param {boolean} webgl - Whether the context is a webgl or canvas one. | ||
* @return {WebGLRenderer} | ||
*/ | ||
@@ -190,19 +212,2 @@ | ||
_createClass(WebGLRenderer, [{ | ||
key: 'testPixel', | ||
value: function testPixel(gl, x, y) { | ||
(0, _utils3.extractPixel)(gl, x * WEBGL_OVERSAMPLING_RATIO, (this.height - y) * WEBGL_OVERSAMPLING_RATIO, this.pixel); | ||
return this.pixel[3] !== 0; | ||
} | ||
/** | ||
* Internal function used to create a canvas context and add the relevant | ||
* DOM elements. | ||
* | ||
* @param {string} id - Context's id. | ||
* @param {boolean} webgl - Whether the context is a webgl or canvas one. | ||
* @return {WebGLRenderer} | ||
*/ | ||
}, { | ||
key: 'createContext', | ||
@@ -223,7 +228,17 @@ value: function createContext(id) { | ||
var contextOptions = { | ||
preserveDrawingBuffer: true | ||
preserveDrawingBuffer: true, | ||
antialias: false | ||
}; | ||
var context = element.getContext(webgl ? 'webgl' : '2d', contextOptions); | ||
var context = void 0; | ||
if (webgl) { | ||
context = element.getContext('webgl', contextOptions); | ||
// Edge, I am looking right at you... | ||
if (!context) context = element.getContext('experimental-webgl', contextOptions); | ||
} else { | ||
context = element.getContext('2d', contextOptions); | ||
} | ||
this.contexts[id] = context; | ||
@@ -265,2 +280,10 @@ | ||
// Handling window resize | ||
this.listeners.handleResize = function () { | ||
_this3.needToSoftProcess = true; | ||
_this3.scheduleRender(); | ||
}; | ||
window.addEventListener('resize', this.listeners.handleResize); | ||
// Function checking if the mouse is on the given node | ||
@@ -274,5 +297,6 @@ var mouseIsOnNode = function mouseIsOnNode(mouseX, mouseY, nodeX, nodeY, size) { | ||
var mouseGraphPosition = _this3.camera.displayToGraph(mouseX, mouseY); | ||
var mouseGraphPosition = _this3.camera.viewportToGraph(_this3, mouseX, mouseY); | ||
return _this3.quadtree.point(mouseGraphPosition.x, mouseGraphPosition.y); | ||
// TODO: minus 1? lol | ||
return _this3.quadtree.point(mouseGraphPosition.x, 1 - mouseGraphPosition.y); | ||
}; | ||
@@ -285,5 +309,7 @@ | ||
// give some boost but this slows things down for WebGL empirically. | ||
// TODO: this should be a method from the camera (or can be passed to graph to display somehow) | ||
var sizeRatio = Math.pow(_this3.camera.getState().ratio, 0.5); | ||
var quadNodes = getQuadNodes(e.clientX, e.clientY); | ||
var quadNodes = getQuadNodes(e.x, e.y); | ||
@@ -295,7 +321,8 @@ for (var i = 0, l = quadNodes.length; i < l; i++) { | ||
var pos = _this3.camera.graphToDisplay(data.x, data.y); | ||
var pos = _this3.camera.graphToViewport(_this3, data.x, data.y); | ||
var size = data.size / sizeRatio; | ||
if (mouseIsOnNode(e.clientX, e.clientY, pos.x, pos.y, size)) { | ||
// TODO: we should get the nearest node instead | ||
if (mouseIsOnNode(e.x, e.y, pos.x, pos.y, size)) { | ||
_this3.hoveredNode = node; | ||
@@ -312,7 +339,7 @@ | ||
var _pos = _this3.camera.graphToDisplay(_data.x, _data.y); | ||
var _pos = _this3.camera.graphToViewport(_this3, _data.x, _data.y); | ||
var _size = _data.size / sizeRatio; | ||
if (!mouseIsOnNode(e.clientX, e.clientY, _pos.x, _pos.y, _size)) { | ||
if (!mouseIsOnNode(e.x, e.y, _pos.x, _pos.y, _size)) { | ||
_this3.hoveredNode = null; | ||
@@ -330,3 +357,3 @@ | ||
var quadNodes = getQuadNodes(e.clientX, e.clientY); | ||
var quadNodes = getQuadNodes(e.x, e.y); | ||
@@ -338,7 +365,7 @@ for (var i = 0, l = quadNodes.length; i < l; i++) { | ||
var pos = _this3.camera.graphToDisplay(data.x, data.y); | ||
var pos = _this3.camera.graphToViewport(_this3, data.x, data.y); | ||
var size = data.size / sizeRatio; | ||
if (mouseIsOnNode(e.clientX, e.clientY, pos.x, pos.y, size)) return _this3.emit('downNode', { node: node }); | ||
if (mouseIsOnNode(e.x, e.y, pos.x, pos.y, size)) return _this3.emit('downNode', { node: node }); | ||
} | ||
@@ -351,3 +378,3 @@ }; | ||
var quadNodes = getQuadNodes(e.clientX, e.clientY); | ||
var quadNodes = getQuadNodes(e.x, e.y); | ||
@@ -359,7 +386,7 @@ for (var i = 0, l = quadNodes.length; i < l; i++) { | ||
var pos = _this3.camera.graphToDisplay(data.x, data.y); | ||
var pos = _this3.camera.graphToViewport(_this3, data.x, data.y); | ||
var size = data.size / sizeRatio; | ||
if (mouseIsOnNode(e.clientX, e.clientY, pos.x, pos.y, size)) return _this3.emit('clickNode', { node: node }); | ||
if (mouseIsOnNode(e.x, e.y, pos.x, pos.y, size)) return _this3.emit('clickNode', { node: node }); | ||
} | ||
@@ -429,31 +456,19 @@ | ||
var extent = this.sigma.getGraphExtent(); | ||
// TODO: possible to index this somehow using two byte arrays or so | ||
var extent = (0, _extent2.default)(graph, ['x', 'y']); | ||
// Rescaling function | ||
this.nodeRescalingFunction = (0, _utils2.createNodeRescalingFunction)({ width: this.width, height: this.height }, extent); | ||
this.normalizationFunction = (0, _utils2.createNormalizationFunction)(extent); | ||
var minRescaled = this.nodeRescalingFunction({ | ||
x: extent.minX, | ||
y: extent.minY | ||
}); | ||
var maxRescaled = this.nodeRescalingFunction({ | ||
x: extent.maxX, | ||
y: extent.maxY | ||
}); | ||
this.quadtree.resize({ | ||
x: minRescaled.x, | ||
y: minRescaled.y, | ||
width: maxRescaled.x - minRescaled.x, | ||
height: maxRescaled.y - minRescaled.y | ||
x: 0, | ||
y: 0, | ||
width: 1, | ||
height: 1 | ||
}); | ||
this.nodeRescaleCache = {}; | ||
var nodeProgram = this.nodePrograms.def; | ||
if (!keepArrays) { | ||
this.nodeArray = new Float32Array(_node2.default.POINTS * _node2.default.ATTRIBUTES * graph.order); | ||
nodeProgram.allocate(graph.order); | ||
this.nodeOrder = {}; | ||
@@ -471,19 +486,21 @@ } | ||
var rescaledData = this.nodeRescalingFunction(data); | ||
var rescaledData = this.normalizationFunction(data); | ||
// TODO: Optimize this to be save a loop and one object | ||
// TODO: Optimize this to save a loop and one object, by using a reversed assign | ||
var displayData = (0, _utils.assign)({}, data, rescaledData); | ||
this.quadtree.add(node, displayData.x, displayData.y, displayData.size); | ||
// TODO: this size normalization does not work | ||
this.quadtree.add(node, displayData.x, 1 - displayData.y, displayData.size / this.width); | ||
this.nodeDataCache[node] = displayData; | ||
nodeProgram.process(this.nodeArray, displayData, i * _node2.default.POINTS * _node2.default.ATTRIBUTES); | ||
nodeProgram.process(displayData, i); | ||
} | ||
nodeProgram.bufferData(); | ||
var edgeProgram = this.edgePrograms.def; | ||
if (!keepArrays) { | ||
this.edgeArray = new Float32Array(_edge2.default.POINTS * _edge2.default.ATTRIBUTES * graph.size); | ||
edgeProgram.allocate(graph.size); | ||
this.edgeOrder = {}; | ||
@@ -504,8 +521,10 @@ } | ||
edgeProgram.process(this.edgeArray, sourceData, targetData, _data2, _i * _edge2.default.POINTS * _edge2.default.ATTRIBUTES); | ||
edgeProgram.process(sourceData, targetData, _data2, _i); | ||
} | ||
// Computing edge indices if necessary | ||
if (!keepArrays && typeof edgeProgram.computeIndices === 'function') this.edgeIndicesArray = edgeProgram.computeIndices(this.edgeArray); | ||
if (!keepArrays && typeof edgeProgram.computeIndices === 'function') this.edgeIndicesArray = edgeProgram.computeIndices(); | ||
edgeProgram.bufferData(); | ||
return this; | ||
@@ -528,3 +547,3 @@ } | ||
nodeProgram.process(this.nodeArray, data, this.nodeOrder[key] * _node2.default.POINTS * _node2.default.ATTRIBUTES); | ||
nodeProgram.process(data, this.nodeOrder[key]); | ||
@@ -553,3 +572,3 @@ return this; | ||
edgeProgram.process(this.edgeArray, sourceData, targetData, data, this.edgeOrder[key] * _edge2.default.POINTS * _edge2.default.ATTRIBUTES); | ||
edgeProgram.process(sourceData, targetData, data, this.edgeOrder[key]); | ||
@@ -635,2 +654,6 @@ return this; | ||
// Resizing camera | ||
// TODO: maybe move this elsewhere | ||
if (this.camera) this.camera.resize({ width: this.width, height: this.height }); | ||
// Sizing dom elements | ||
@@ -712,2 +735,5 @@ for (var id in this.elements) { | ||
// TODO: improve this heuristic | ||
var moving = this.camera.isAnimated() || this.captors.mouse.isMoving || this.captors.mouse.hasDragged || this.captors.mouse.wheelLock; | ||
// First we need to resize | ||
@@ -721,22 +747,11 @@ this.resize(); | ||
var cameraState = this.camera.getState(), | ||
cameraMatrix = (0, _utils3.matrixFromCamera)(cameraState); | ||
cameraMatrix = (0, _utils3.matrixFromCamera)(cameraState, { width: this.width, height: this.height }); | ||
var translation = _glMatrix.mat3.fromTranslation(_glMatrix.mat3.create(), [this.width / 2, this.height / 2]); | ||
var program = void 0; | ||
_glMatrix.mat3.multiply(cameraMatrix, translation, cameraMatrix); | ||
var program = void 0, | ||
gl = void 0; | ||
// Drawing nodes | ||
gl = this.contexts.nodes; | ||
program = this.nodePrograms.def; | ||
// Blending | ||
// TODO: check the purpose of this | ||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); | ||
gl.enable(gl.BLEND); | ||
// TODO: should probably use another name for the `program` abstraction | ||
program.render(gl, this.nodeArray, { | ||
program.render({ | ||
matrix: cameraMatrix, | ||
@@ -751,13 +766,41 @@ width: this.width, | ||
// Drawing edges | ||
gl = this.contexts.edges; | ||
program = this.edgePrograms.def; | ||
if (!this.settings.hideEdgesOnMove || !moving) { | ||
program = this.edgePrograms.def; | ||
program.render(gl, this.edgeArray, { | ||
matrix: cameraMatrix, | ||
width: this.width, | ||
height: this.height, | ||
ratio: cameraState.ratio, | ||
edgesPowRatio: 0.5, | ||
scalingRatio: WEBGL_OVERSAMPLING_RATIO, | ||
indices: this.edgeIndicesArray | ||
program.render({ | ||
matrix: cameraMatrix, | ||
width: this.width, | ||
height: this.height, | ||
ratio: cameraState.ratio, | ||
nodesPowRatio: 0.5, | ||
edgesPowRatio: 0.5, | ||
scalingRatio: WEBGL_OVERSAMPLING_RATIO | ||
}); | ||
} | ||
// Do not display labels on move per setting | ||
if (this.settings.hideLabelsOnMove && moving) return this; | ||
// Finding visible nodes to display their labels | ||
var visibleNodes = void 0; | ||
if (cameraState.ratio >= 1) { | ||
// Camera is unzoomed so no need to ask the quadtree for visible nodes | ||
visibleNodes = this.sigma.getGraph().nodes(); | ||
} else { | ||
// Let's ask the quadtree | ||
var viewRectangle = this.camera.viewRectangle(this); | ||
visibleNodes = this.quadtree.rectangle(viewRectangle.x1, 1 - viewRectangle.y1, viewRectangle.x2, 1 - viewRectangle.y2, viewRectangle.height); | ||
} | ||
// Selecting labels to draw | ||
var labelsToDisplay = (0, _labels.labelsToDisplayFromGrid)({ | ||
cache: this.nodeDataCache, | ||
camera: this.camera, | ||
displayedLabels: this.displayedLabels, | ||
previousVisibleNodes: this.previousVisibleNodes, | ||
visibleNodes: visibleNodes | ||
}); | ||
@@ -767,13 +810,12 @@ | ||
// TODO: POW RATIO is currently default 0.5 and harcoded | ||
var nodes = this.sigma.getGraph().nodes(), | ||
context = this.contexts.labels; | ||
var context = this.contexts.labels; | ||
var sizeRatio = Math.pow(cameraState.ratio, 0.5); | ||
for (var i = 0, l = nodes.length; i < l; i++) { | ||
var data = this.nodeDataCache[nodes[i]]; | ||
for (var i = 0, l = labelsToDisplay.length; i < l; i++) { | ||
var data = this.nodeDataCache[labelsToDisplay[i]]; | ||
var _camera$graphToDispla = this.camera.graphToDisplay(data.x, data.y), | ||
x = _camera$graphToDispla.x, | ||
y = _camera$graphToDispla.y; | ||
var _camera$graphToViewpo = this.camera.graphToViewport(this, data.x, data.y), | ||
x = _camera$graphToViewpo.x, | ||
y = _camera$graphToViewpo.y; | ||
@@ -786,3 +828,4 @@ // TODO: we can cache the labels we need to render until the camera's ratio changes | ||
// TODO: this is the label threshold hardcoded | ||
if (size < 8) continue; | ||
// if (size < 8) | ||
// continue; | ||
@@ -797,2 +840,6 @@ (0, _label2.default)(context, { | ||
// Caching visible nodes and displayed labels | ||
this.previousVisibleNodes = new Set(visibleNodes); | ||
this.displayedLabels = new Set(labelsToDisplay); | ||
// Rendering highlighted nodes | ||
@@ -828,5 +875,5 @@ this.renderHighlightedNodes(); | ||
var _camera$graphToDispla2 = camera.graphToDisplay(data.x, data.y), | ||
x = _camera$graphToDispla2.x, | ||
y = _camera$graphToDispla2.y; | ||
var _camera$graphToViewpo2 = camera.graphToViewport(_this5, data.x, data.y), | ||
x = _camera$graphToViewpo2.x, | ||
y = _camera$graphToViewpo2.y; | ||
@@ -860,4 +907,6 @@ var size = data.size / sizeRatio; | ||
// A frame is already scheduled | ||
if (this.renderFrame) return this; | ||
// Let's schedule a frame | ||
this.renderFrame = requestAnimationFrame(function () { | ||
@@ -889,3 +938,3 @@ | ||
if (this.renderHighlightedNodesFrame) return this; | ||
if (this.renderHighlightedNodesFrame || this.renderFrame) return this; | ||
@@ -892,0 +941,0 @@ this.renderHighlightedNodesFrame = requestAnimationFrame(function () { |
@@ -30,3 +30,3 @@ 'use strict'; | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** | ||
* Sigma.js WebGL Renderer Node Program | ||
* Sigma.js WebGL Renderer Edge Program | ||
* ===================================== | ||
@@ -48,12 +48,45 @@ * | ||
var POINTS = 4, | ||
ATTRIBUTES = 6; | ||
var EdgeProgram = function (_Program) { | ||
_inherits(EdgeProgram, _Program); | ||
function EdgeProgram() { | ||
function EdgeProgram(gl) { | ||
_classCallCheck(this, EdgeProgram); | ||
var _this = _possibleConstructorReturn(this, (EdgeProgram.__proto__ || Object.getPrototypeOf(EdgeProgram)).call(this)); | ||
// Binding context | ||
var _this = _possibleConstructorReturn(this, (EdgeProgram.__proto__ || Object.getPrototypeOf(EdgeProgram)).call(this, gl, _edgeVert2.default, _edgeFrag2.default)); | ||
_this.vertexShaderSource = _edgeVert2.default; | ||
_this.fragmentShaderSource = _edgeFrag2.default; | ||
_this.gl = gl; | ||
// Array data | ||
_this.array = null; | ||
_this.indicesArray = null; | ||
// Initializing buffers | ||
_this.buffer = gl.createBuffer(); | ||
_this.indicesBuffer = gl.createBuffer(); | ||
// Locations | ||
_this.positionLocation = gl.getAttribLocation(_this.program, 'a_position'); | ||
_this.normalLocation = gl.getAttribLocation(_this.program, 'a_normal'); | ||
_this.thicknessLocation = gl.getAttribLocation(_this.program, 'a_thickness'); | ||
_this.colorLocation = gl.getAttribLocation(_this.program, 'a_color'); | ||
_this.resolutionLocation = gl.getUniformLocation(_this.program, 'u_resolution'); | ||
_this.ratioLocation = gl.getUniformLocation(_this.program, 'u_ratio'); | ||
_this.matrixLocation = gl.getUniformLocation(_this.program, 'u_matrix'); | ||
_this.scaleLocation = gl.getUniformLocation(_this.program, 'u_scale'); | ||
_this.bind(); | ||
// Enabling the OES_element_index_uint extension | ||
// NOTE: on older GPUs, this means that really large graphs won't | ||
// have all their edges rendered. But it seems that the | ||
// `OES_element_index_uint` is quite everywhere so we'll handle | ||
// the potential issue if it really arises. | ||
var extension = gl.getExtension('OES_element_index_uint'); | ||
_this.canUse32BitsIndices = !!extension; | ||
_this.IndicesArray = _this.canUse32BitsIndices ? Uint32Array : Uint16Array; | ||
_this.indicesType = _this.canUse32BitsIndices ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT; | ||
return _this; | ||
@@ -63,8 +96,32 @@ } | ||
_createClass(EdgeProgram, [{ | ||
key: 'bind', | ||
value: function bind() { | ||
var gl = this.gl; | ||
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); | ||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); | ||
// Bindings | ||
gl.enableVertexAttribArray(this.positionLocation); | ||
gl.enableVertexAttribArray(this.normalLocation); | ||
gl.enableVertexAttribArray(this.thicknessLocation); | ||
gl.enableVertexAttribArray(this.colorLocation); | ||
gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 0); | ||
gl.vertexAttribPointer(this.normalLocation, 2, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 8); | ||
gl.vertexAttribPointer(this.thicknessLocation, 1, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 16); | ||
gl.vertexAttribPointer(this.colorLocation, 1, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 20); | ||
} | ||
}, { | ||
key: 'allocate', | ||
value: function allocate(capacity) { | ||
this.array = new Float32Array(POINTS * ATTRIBUTES * capacity); | ||
} | ||
}, { | ||
key: 'process', | ||
value: function process(array, sourceData, targetData, data, i) { | ||
value: function process(sourceData, targetData, data, offset) { | ||
if (sourceData.hidden || targetData.hidden || data.hidden) { | ||
for (var l = i + EdgeProgram.POINTS * EdgeProgram.ATTRIBUTES; i < l; i++) { | ||
array[i] = 0; | ||
for (var l = i + POINTS * ATTRIBUTES; i < l; i++) { | ||
this.array[i] = 0; | ||
} | ||
@@ -95,2 +152,6 @@ } | ||
var i = POINTS * ATTRIBUTES * offset; | ||
var array = this.array; | ||
// First point | ||
@@ -126,68 +187,56 @@ array[i++] = x1; | ||
array[i++] = thickness; | ||
array[i++] = color; | ||
array[i] = color; | ||
} | ||
}, { | ||
key: 'computeIndices', | ||
value: function computeIndices(array) { | ||
var l = array.length / EdgeProgram.ATTRIBUTES; | ||
value: function computeIndices() { | ||
var l = this.array.length / ATTRIBUTES; | ||
var size = l + l / 2; | ||
var indices = new Uint16Array(size); | ||
var indices = new this.IndicesArray(size); | ||
for (var i = 0, c = 0; i < size; i += 4) { | ||
indices[c++] = i; | ||
indices[c++] = i + 1; | ||
indices[c++] = i + 2; | ||
indices[c++] = i + 2; | ||
indices[c++] = i + 1; | ||
indices[c++] = i + 3; | ||
for (var _i = 0, c = 0; _i < size; _i += 4) { | ||
indices[c++] = _i; | ||
indices[c++] = _i + 1; | ||
indices[c++] = _i + 2; | ||
indices[c++] = _i + 2; | ||
indices[c++] = _i + 1; | ||
indices[c++] = _i + 3; | ||
} | ||
return indices; | ||
this.indicesArray = indices; | ||
} | ||
}, { | ||
key: 'bufferData', | ||
value: function bufferData() { | ||
var gl = this.gl; | ||
// Vertices data | ||
gl.bufferData(gl.ARRAY_BUFFER, this.array, gl.DYNAMIC_DRAW); | ||
// Indices data | ||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indicesArray, gl.STATIC_DRAW); | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render(gl, array, params) { | ||
value: function render(params) { | ||
var gl = this.gl; | ||
var program = this.program; | ||
gl.useProgram(program); | ||
// Attribute locations | ||
var positionLocation = gl.getAttribLocation(program, 'a_position'), | ||
normalLocation = gl.getAttribLocation(program, 'a_normal'), | ||
thicknessLocation = gl.getAttribLocation(program, 'a_thickness'), | ||
colorLocation = gl.getAttribLocation(program, 'a_color'), | ||
resolutionLocation = gl.getUniformLocation(program, 'u_resolution'), | ||
ratioLocation = gl.getUniformLocation(program, 'u_ratio'), | ||
matrixLocation = gl.getUniformLocation(program, 'u_matrix'); | ||
// Creating buffer: | ||
var buffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW); | ||
// Binding uniforms | ||
gl.uniform2f(resolutionLocation, params.width, params.height); | ||
gl.uniform1f(ratioLocation, params.ratio / Math.pow(params.ratio, params.edgesPowRatio)); | ||
// TODO: precise the uniform names | ||
gl.uniform2f(this.resolutionLocation, params.width, params.height); | ||
gl.uniform1f(this.ratioLocation, | ||
// 1 / Math.pow(params.ratio, params.edgesPowRatio) | ||
params.ratio); | ||
gl.uniformMatrix3fv(matrixLocation, false, params.matrix); | ||
gl.uniformMatrix3fv(this.matrixLocation, false, params.matrix); | ||
// Binding attributes: | ||
gl.enableVertexAttribArray(positionLocation); | ||
gl.enableVertexAttribArray(normalLocation); | ||
gl.enableVertexAttribArray(thicknessLocation); | ||
gl.enableVertexAttribArray(colorLocation); | ||
gl.uniform1f(this.scaleLocation, params.scalingRatio); | ||
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, EdgeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 0); | ||
gl.vertexAttribPointer(normalLocation, 2, gl.FLOAT, false, EdgeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 8); | ||
gl.vertexAttribPointer(thicknessLocation, 1, gl.FLOAT, false, EdgeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 16); | ||
gl.vertexAttribPointer(colorLocation, 1, gl.FLOAT, false, EdgeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 20); | ||
// Creating indices buffer: | ||
var indicesBuffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer); | ||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, params.indices, gl.STATIC_DRAW); | ||
// Drawing: | ||
gl.drawElements(gl.TRIANGLES, params.indices.length, gl.UNSIGNED_SHORT, 0); | ||
gl.drawElements(gl.TRIANGLES, this.indicesArray.length, this.indicesType, 0); | ||
} | ||
@@ -199,6 +248,2 @@ }]); | ||
exports.default = EdgeProgram; | ||
EdgeProgram.POINTS = 4; | ||
EdgeProgram.ATTRIBUTES = 6; | ||
exports.default = EdgeProgram; |
@@ -44,12 +44,46 @@ 'use strict'; | ||
var POINTS = 3, | ||
ATTRIBUTES = 5; | ||
var NodeProgram = function (_Program) { | ||
_inherits(NodeProgram, _Program); | ||
function NodeProgram() { | ||
function NodeProgram(gl) { | ||
_classCallCheck(this, NodeProgram); | ||
var _this = _possibleConstructorReturn(this, (NodeProgram.__proto__ || Object.getPrototypeOf(NodeProgram)).call(this)); | ||
// Binding context | ||
var _this = _possibleConstructorReturn(this, (NodeProgram.__proto__ || Object.getPrototypeOf(NodeProgram)).call(this, gl, _nodeVert2.default, _nodeFrag2.default)); | ||
_this.vertexShaderSource = _nodeVert2.default; | ||
_this.fragmentShaderSource = _nodeFrag2.default; | ||
_this.gl = gl; | ||
// Array data | ||
_this.array = null; | ||
// Initializing buffers | ||
_this.buffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, _this.buffer); | ||
// Locations | ||
_this.positionLocation = gl.getAttribLocation(_this.program, 'a_position'); | ||
_this.sizeLocation = gl.getAttribLocation(_this.program, 'a_size'); | ||
_this.colorLocation = gl.getAttribLocation(_this.program, 'a_color'); | ||
_this.angleLocation = gl.getAttribLocation(_this.program, 'a_angle'); | ||
_this.resolutionLocation = gl.getUniformLocation(_this.program, 'u_resolution'); | ||
_this.matrixLocation = gl.getUniformLocation(_this.program, 'u_matrix'); | ||
_this.ratioLocation = gl.getUniformLocation(_this.program, 'u_ratio'); | ||
_this.scaleLocation = gl.getUniformLocation(_this.program, 'u_scale'); | ||
// Bindings | ||
gl.enableVertexAttribArray(_this.positionLocation); | ||
gl.enableVertexAttribArray(_this.sizeLocation); | ||
gl.enableVertexAttribArray(_this.colorLocation); | ||
gl.enableVertexAttribArray(_this.angleLocation); | ||
gl.vertexAttribPointer(_this.positionLocation, 2, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 0); | ||
gl.vertexAttribPointer(_this.sizeLocation, 1, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 8); | ||
gl.vertexAttribPointer(_this.colorLocation, 1, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 12); | ||
gl.vertexAttribPointer(_this.angleLocation, 1, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 16); | ||
return _this; | ||
@@ -59,6 +93,15 @@ } | ||
_createClass(NodeProgram, [{ | ||
key: 'allocate', | ||
value: function allocate(capacity) { | ||
this.array = new Float32Array(POINTS * ATTRIBUTES * capacity); | ||
} | ||
}, { | ||
key: 'process', | ||
value: function process(array, data, i) { | ||
value: function process(data, offset) { | ||
var color = (0, _utils.floatColor)(data.color); | ||
var i = offset * POINTS * ATTRIBUTES; | ||
var array = this.array; | ||
array[i++] = data.x; | ||
@@ -80,45 +123,26 @@ array[i++] = data.y; | ||
array[i++] = color; | ||
array[i++] = ANGLE_3; | ||
array[i] = ANGLE_3; | ||
} | ||
}, { | ||
key: 'bufferData', | ||
value: function bufferData() { | ||
var gl = this.gl; | ||
gl.bufferData(gl.ARRAY_BUFFER, this.array, gl.DYNAMIC_DRAW); | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render(gl, array, params) { | ||
value: function render(params) { | ||
var gl = this.gl; | ||
var program = this.program; | ||
gl.useProgram(program); | ||
// Attribute locations | ||
var positionLocation = gl.getAttribLocation(program, 'a_position'), | ||
sizeLocation = gl.getAttribLocation(program, 'a_size'), | ||
colorLocation = gl.getAttribLocation(program, 'a_color'), | ||
angleLocation = gl.getAttribLocation(program, 'a_angle'), | ||
resolutionLocation = gl.getUniformLocation(program, 'u_resolution'), | ||
matrixLocation = gl.getUniformLocation(program, 'u_matrix'), | ||
ratioLocation = gl.getUniformLocation(program, 'u_ratio'), | ||
scaleLocation = gl.getUniformLocation(program, 'u_scale'); | ||
gl.uniform2f(this.resolutionLocation, params.width, params.height); | ||
gl.uniform1f(this.ratioLocation, 1 / Math.pow(params.ratio, params.nodesPowRatio)); | ||
gl.uniform1f(this.scaleLocation, params.scalingRatio); | ||
gl.uniformMatrix3fv(this.matrixLocation, false, params.matrix); | ||
var buffer = gl.createBuffer(); | ||
// TODO: might be possible not to buffer data each time if only the camera changes | ||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, array, gl.DYNAMIC_DRAW); | ||
gl.uniform2f(resolutionLocation, params.width, params.height); | ||
gl.uniform1f(ratioLocation, 1 / Math.pow(params.ratio, params.nodesPowRatio)); | ||
gl.uniform1f(scaleLocation, params.scalingRatio); | ||
gl.uniformMatrix3fv(matrixLocation, false, params.matrix); | ||
gl.enableVertexAttribArray(positionLocation); | ||
gl.enableVertexAttribArray(sizeLocation); | ||
gl.enableVertexAttribArray(colorLocation); | ||
gl.enableVertexAttribArray(angleLocation); | ||
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, NodeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 0); | ||
gl.vertexAttribPointer(sizeLocation, 1, gl.FLOAT, false, NodeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 8); | ||
gl.vertexAttribPointer(colorLocation, 1, gl.FLOAT, false, NodeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 12); | ||
gl.vertexAttribPointer(angleLocation, 1, gl.FLOAT, false, NodeProgram.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 16); | ||
gl.drawArrays(gl.TRIANGLES, 0, array.length / NodeProgram.ATTRIBUTES); | ||
gl.drawArrays(gl.TRIANGLES, 0, this.array.length / ATTRIBUTES); | ||
} | ||
@@ -130,6 +154,2 @@ }]); | ||
exports.default = NodeProgram; | ||
NodeProgram.POINTS = 3; | ||
NodeProgram.ATTRIBUTES = 5; | ||
exports.default = NodeProgram; |
@@ -15,4 +15,8 @@ 'use strict'; | ||
exports.createCompoundProgram = createCompoundProgram; | ||
var _utils = require('../shaders/utils'); | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
@@ -26,16 +30,21 @@ | ||
var Program = function () { | ||
function Program() { | ||
function Program(gl, vertexShaderSource, fragmentShaderSource) { | ||
_classCallCheck(this, Program); | ||
this.vertexShaderSource = vertexShaderSource; | ||
this.fragmentShaderSource = fragmentShaderSource; | ||
this.load(gl); | ||
} | ||
/** | ||
* Method used to load the program into a webgl context. | ||
* | ||
* @param {WebGLContext} gl - The WebGL context. | ||
* @return {WebGLProgram} | ||
*/ | ||
_createClass(Program, [{ | ||
key: 'load', | ||
/** | ||
* Method used to load the program into a webgl context. | ||
* | ||
* @param {WebGLContext} gl - The WebGL context. | ||
* @return {WebGLProgram} | ||
*/ | ||
value: function load(gl) { | ||
@@ -54,2 +63,68 @@ this.vertexShader = (0, _utils.loadVertexShader)(gl, this.vertexShaderSource); | ||
exports.default = Program; | ||
/** | ||
* Helper function combining two or more programs into a single compound one. | ||
* Note that this is more a quick & easy way to combine program than a really | ||
* performant option. More performant programs can be written entirely. | ||
* | ||
* @param {array} programClasses - Program classes to combine. | ||
* @return {function} | ||
*/ | ||
exports.default = Program; | ||
function createCompoundProgram(programClasses) { | ||
return function () { | ||
function CompoundProgram(gl) { | ||
_classCallCheck(this, CompoundProgram); | ||
this.programs = programClasses.map(function (ProgramClass) { | ||
return new ProgramClass(gl); | ||
}); | ||
} | ||
_createClass(CompoundProgram, [{ | ||
key: 'allocate', | ||
value: function allocate(capacity) { | ||
this.programs.forEach(function (program) { | ||
return program.allocate(capacity); | ||
}); | ||
} | ||
}, { | ||
key: 'process', | ||
value: function process() { | ||
var args = arguments; | ||
this.programs.forEach(function (program) { | ||
return program.process.apply(program, _toConsumableArray(args)); | ||
}); | ||
} | ||
}, { | ||
key: 'computeIndices', | ||
value: function computeIndices() { | ||
this.programs.forEach(function (program) { | ||
if (typeof program.computeIndices === 'function') program.computeIndices(); | ||
}); | ||
} | ||
}, { | ||
key: 'bufferData', | ||
value: function bufferData() { | ||
this.programs.forEach(function (program) { | ||
return program.bufferData(); | ||
}); | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
var args = arguments; | ||
this.programs.forEach(function (program) { | ||
program.bind(); | ||
program.bufferData(); | ||
program.render.apply(program, _toConsumableArray(args)); | ||
}); | ||
} | ||
}]); | ||
return CompoundProgram; | ||
}(); | ||
} |
@@ -37,3 +37,3 @@ 'use strict'; | ||
gl.deleteShader(shader); | ||
throw new Error('sigma/renderers/weblg/shaders/utils.loadShader: error while compiling the shader:\n' + infoLog); | ||
throw new Error('sigma/renderers/weblg/shaders/utils.loadShader: error while compiling the shader:\n' + infoLog + '\n' + source); | ||
} | ||
@@ -40,0 +40,0 @@ |
@@ -10,3 +10,3 @@ 'use strict'; | ||
var _glMatrix = require('gl-matrix'); | ||
var _matrices = require('./matrices'); | ||
@@ -71,3 +71,3 @@ /** | ||
// TODO: it's possible to optimize this drastically! | ||
function matrixFromCamera(state) { | ||
function matrixFromCamera(state, dimensions) { | ||
var angle = state.angle, | ||
@@ -77,12 +77,21 @@ ratio = state.ratio, | ||
y = state.y; | ||
var width = dimensions.width, | ||
height = dimensions.height; | ||
var matrix = _glMatrix.mat3.create(), | ||
scale = _glMatrix.mat3.fromScaling(_glMatrix.mat3.create(), [1 / ratio, 1 / ratio]), | ||
rotation = _glMatrix.mat3.fromRotation(_glMatrix.mat3.create(), -angle), | ||
translation = _glMatrix.mat3.fromTranslation(_glMatrix.mat3.create(), [-x, -y]); | ||
var matrix = (0, _matrices.identity)(); | ||
_glMatrix.mat3.multiply(matrix, scale, rotation); | ||
_glMatrix.mat3.multiply(matrix, matrix, translation); | ||
var smallestDimension = Math.min(width, height); | ||
var cameraCentering = (0, _matrices.translate)((0, _matrices.identity)(), -x, -y), | ||
cameraScaling = (0, _matrices.scale)((0, _matrices.identity)(), 1 / ratio), | ||
cameraRotation = (0, _matrices.rotate)((0, _matrices.identity)(), -angle), | ||
viewportScaling = (0, _matrices.scale)((0, _matrices.identity)(), 2 * (smallestDimension / width), 2 * (smallestDimension / height)); | ||
// Logical order is reversed | ||
(0, _matrices.multiply)(matrix, viewportScaling); | ||
(0, _matrices.multiply)(matrix, cameraRotation); | ||
(0, _matrices.multiply)(matrix, cameraScaling); | ||
(0, _matrices.multiply)(matrix, cameraCentering); | ||
return matrix; | ||
@@ -89,0 +98,0 @@ } |
57
sigma.js
@@ -118,59 +118,2 @@ 'use strict'; | ||
} | ||
/** | ||
* Method returning the extent of the bound graph. | ||
* | ||
* @return {object} - The graph's extent. | ||
*/ | ||
}, { | ||
key: 'getGraphExtent', | ||
value: function getGraphExtent() { | ||
var graph = this.graph; | ||
var nodes = graph.nodes(), | ||
edges = graph.edges(); | ||
var maxX = -Infinity, | ||
maxY = -Infinity, | ||
minX = Infinity, | ||
minY = Infinity, | ||
maxNodeSize = -Infinity, | ||
maxEdgeSize = -Infinity; | ||
for (var i = 0, l = nodes.length; i < l; i++) { | ||
var node = nodes[i]; | ||
var data = this.getNodeData(node); | ||
if (data.x > maxX) maxX = data.x; | ||
if (data.y > maxY) maxY = data.y; | ||
if (data.x < minX) minX = data.x; | ||
if (data.y < minY) minY = data.y; | ||
var size = data.size || 1; | ||
if (size > maxNodeSize) maxNodeSize = size; | ||
} | ||
for (var _i = 0, _l = edges.length; _i < _l; _i++) { | ||
var edge = edges[_i]; | ||
var _data = this.getEdgeData(edge); | ||
var _size = _data.size || 1; | ||
if (_size > maxEdgeSize) maxEdgeSize = _size; | ||
} | ||
return { | ||
maxX: maxX, | ||
maxY: maxY, | ||
minX: minX, | ||
minY: minY, | ||
maxNodeSize: maxNodeSize, | ||
maxEdgeSize: maxEdgeSize | ||
}; | ||
} | ||
}]); | ||
@@ -177,0 +120,0 @@ |
@@ -1,13 +0,18 @@ | ||
var path = require('path'); | ||
var path = require('path'), | ||
glob = require('glob'); | ||
var shaders = glob.sync(path.join(__dirname, 'src', 'renderers', 'webgl', 'shaders', '*.glsl')); | ||
var entry = {}; | ||
shaders.forEach(function(p) { | ||
entry[path.basename(p, '.glsl')] = p; | ||
}); | ||
module.exports = { | ||
entry: { | ||
'node.vert': './src/renderers/webgl/shaders/node.vert.glsl', | ||
'edge.vert': './src/renderers/webgl/shaders/edge.vert.glsl', | ||
'node.frag': './src/renderers/webgl/shaders/node.frag.glsl', | ||
'edge.frag': './src/renderers/webgl/shaders/edge.frag.glsl' | ||
}, | ||
entry: entry, | ||
output: { | ||
path: path.join(__dirname, 'renderers', 'webgl', 'shaders'), | ||
filename: '[name].glsl' | ||
filename: '[name].glsl', | ||
libraryTarget: 'commonjs2' | ||
}, | ||
@@ -14,0 +19,0 @@ module: { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
50
0
461602
21
7070
+ Addedgraphology-metrics@1.5.0
+ Added@yomguithereal/helpers@1.1.1(transitive)
+ Addedevents@2.1.0(transitive)
+ Addedgraphology-indices@0.16.6(transitive)
+ Addedgraphology-metrics@1.5.0(transitive)
+ Addedgraphology-shortest-path@1.5.2(transitive)
+ Addedgraphology-utils@2.5.2(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedmnemonist@0.38.50.39.8(transitive)
+ Addedobliterator@2.0.5(transitive)
- Removedgl-matrix@^2.3.2
- Removedevents@1.1.1(transitive)
- Removedgl-matrix@2.8.1(transitive)
Updatedevents@^2.0.0
Updatedgraphology-utils@^1.3.0