sigma
Version:
A JavaScript library dedicated to graph drawing.
257 lines (256 loc) • 9.31 kB
JavaScript
;
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateGraph = exports.canUse32BitsIndices = exports.extractPixel = exports.matrixFromCamera = exports.floatColor = exports.zIndexOrdering = exports.createNormalizationFunction = exports.getPixelRatio = exports.createElement = exports.cancelFrame = exports.requestFrame = exports.assignDeep = exports.isPlainObject = void 0;
var is_graph_1 = __importDefault(require("graphology-utils/is-graph"));
var matrices_1 = require("./matrices");
/**
* Checks whether the given value is a plain object.
*
* @param {mixed} value - Target value.
* @return {boolean}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
function isPlainObject(value) {
return typeof value === "object" && value !== null && value.constructor === Object;
}
exports.isPlainObject = isPlainObject;
/**
* Very simple recursive Object.assign-like function.
*
* @param {object} target - First object.
* @param {object} [...objects] - Objects to merge.
* @return {object}
*/
function assignDeep(target) {
var objects = [];
for (var _i = 1; _i < arguments.length; _i++) {
objects[_i - 1] = arguments[_i];
}
target = target || {};
for (var i = 0, l = objects.length; i < l; i++) {
var o = objects[i];
if (!o)
continue;
for (var k in o) {
if (isPlainObject(o[k])) {
target[k] = assignDeep(target[k], o[k]);
}
else {
target[k] = o[k];
}
}
}
return target;
}
exports.assignDeep = assignDeep;
/**
* Just some dirty trick to make requestAnimationFrame and cancelAnimationFrame "work" in Node.js, for unit tests:
*/
exports.requestFrame = typeof requestAnimationFrame !== "undefined"
? function (callback) { return requestAnimationFrame(callback); }
: function (callback) { return setTimeout(callback, 0); };
exports.cancelFrame = typeof cancelAnimationFrame !== "undefined"
? function (requestID) { return cancelAnimationFrame(requestID); }
: function (requestID) { return clearTimeout(requestID); };
/**
* Function used to create DOM elements easily.
*
* @param {string} tag - Tag name of the element to create.
* @param {object} style - Styles map.
* @param {object} attributes - Attributes map.
* @return {HTMLElement}
*/
function createElement(tag, style, attributes) {
var element = document.createElement(tag);
if (style) {
for (var k in style) {
element.style[k] = style[k];
}
}
if (attributes) {
for (var k in attributes) {
element.setAttribute(k, attributes[k]);
}
}
return element;
}
exports.createElement = createElement;
/**
* Function returning the browser's pixel ratio.
*
* @return {number}
*/
function getPixelRatio() {
if (typeof window.devicePixelRatio !== "undefined")
return window.devicePixelRatio;
return 1;
}
exports.getPixelRatio = getPixelRatio;
function createNormalizationFunction(extent) {
var _a = __read(extent.x, 2), minX = _a[0], maxX = _a[1], _b = __read(extent.y, 2), minY = _b[0], maxY = _b[1];
var ratio = Math.max(maxX - minX, maxY - minY);
if (ratio === 0)
ratio = 1;
var dX = (maxX + minX) / 2, dY = (maxY + minY) / 2;
var fn = function (data) {
return {
x: 0.5 + (data.x - dX) / ratio,
y: 0.5 + (data.y - dY) / ratio,
};
};
// TODO: possibility to apply this in batch over array of indices
fn.applyTo = function (data) {
data.x = 0.5 + (data.x - dX) / ratio;
data.y = 0.5 + (data.y - dY) / ratio;
};
fn.inverse = function (data) {
return {
x: dX + ratio * (data.x - 0.5),
y: dY + ratio * (data.y - 0.5),
};
};
fn.ratio = ratio;
return fn;
}
exports.createNormalizationFunction = createNormalizationFunction;
/**
* Function ordering the given elements in reverse z-order so they drawn
* the correct way.
*
* @param {number} extent - [min, max] z values.
* @param {function} getter - Z attribute getter function.
* @param {array} elements - The array to sort.
* @return {array} - The sorted array.
*/
function zIndexOrdering(extent, getter, elements) {
// If k is > n, we'll use a standard sort
return elements.sort(function (a, b) {
var zA = getter(a) || 0, zB = getter(b) || 0;
if (zA < zB)
return -1;
if (zA > zB)
return 1;
return 0;
});
// TODO: counting sort optimization
}
exports.zIndexOrdering = zIndexOrdering;
/**
* WebGL utils
* ===========
*/
/**
* Memoized function returning a float-encoded color from various string
* formats describing colors.
*/
var FLOAT_COLOR_CACHE = {};
var INT8 = new Int8Array(4);
var INT32 = new Int32Array(INT8.buffer, 0, 1);
var FLOAT32 = new Float32Array(INT8.buffer, 0, 1);
var RGBA_TEST_REGEX = /^\s*rgba?\s*\(/;
var RGBA_EXTRACT_REGEX = /^\s*rgba?\s*\(\s*([0-9]*)\s*,\s*([0-9]*)\s*,\s*([0-9]*)(?:\s*,\s*(.*)?)?\)\s*$/;
function floatColor(val) {
// If the color is already computed, we yield it
if (typeof FLOAT_COLOR_CACHE[val] !== "undefined")
return FLOAT_COLOR_CACHE[val];
var r = 0, g = 0, b = 0, a = 1;
// Handling hexadecimal notation
if (val[0] === "#") {
if (val.length === 4) {
r = parseInt(val.charAt(1) + val.charAt(1), 16);
g = parseInt(val.charAt(2) + val.charAt(2), 16);
b = parseInt(val.charAt(3) + val.charAt(3), 16);
}
else {
r = parseInt(val.charAt(1) + val.charAt(2), 16);
g = parseInt(val.charAt(3) + val.charAt(4), 16);
b = parseInt(val.charAt(5) + val.charAt(6), 16);
}
}
// Handling rgb notation
else if (RGBA_TEST_REGEX.test(val)) {
var match = val.match(RGBA_EXTRACT_REGEX);
if (match) {
r = +match[1];
g = +match[2];
b = +match[3];
if (match[4])
a = +match[4];
}
}
a = (a * 255) | 0;
INT32[0] = ((a << 24) | (b << 16) | (g << 8) | r) & 0xfeffffff;
var color = FLOAT32[0];
FLOAT_COLOR_CACHE[val] = color;
return color;
}
exports.floatColor = floatColor;
/**
* Function returning a matrix from the current state of the camera.
*/
// TODO: it's possible to optimize this drastically!
function matrixFromCamera(state, dimensions) {
var angle = state.angle, ratio = state.ratio, x = state.x, y = state.y;
var width = dimensions.width, height = dimensions.height;
var matrix = matrices_1.identity();
var smallestDimension = Math.min(width, height);
var cameraCentering = matrices_1.translate(matrices_1.identity(), -x, -y), cameraScaling = matrices_1.scale(matrices_1.identity(), 1 / ratio), cameraRotation = matrices_1.rotate(matrices_1.identity(), -angle), viewportScaling = matrices_1.scale(matrices_1.identity(), 2 * (smallestDimension / width), 2 * (smallestDimension / height));
// Logical order is reversed
matrices_1.multiply(matrix, viewportScaling);
matrices_1.multiply(matrix, cameraRotation);
matrices_1.multiply(matrix, cameraScaling);
matrices_1.multiply(matrix, cameraCentering);
return matrix;
}
exports.matrixFromCamera = matrixFromCamera;
/**
* Function extracting the color at the given pixel.
*/
function extractPixel(gl, x, y, array) {
var data = array || new Uint8Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, data);
return data;
}
exports.extractPixel = extractPixel;
/**
* Function used to know whether given webgl context can use 32 bits indices.
*/
function canUse32BitsIndices(gl) {
var webgl2 = typeof WebGL2RenderingContext !== "undefined" && gl instanceof WebGL2RenderingContext;
return webgl2 || !!gl.getExtension("OES_element_index_uint");
}
exports.canUse32BitsIndices = canUse32BitsIndices;
/**
* Check if the graph variable is a valid graph, and if sigma can render it.
*/
function validateGraph(graph) {
// check if it's a valid graphology instance
if (!is_graph_1.default(graph))
throw new Error("Sigma: invalid graph instance.");
// check if nodes have x/y attributes
graph.forEachNode(function (key, attributes) {
if (!Number.isFinite(attributes.x) || !Number.isFinite(attributes.y)) {
throw new Error("Sigma: Coordinates of node " + key + " are invalid. A node must have a numeric 'x' and 'y' attribute.");
}
});
}
exports.validateGraph = validateGraph;