UNPKG

sigma

Version:

A JavaScript library dedicated to graph drawing.

257 lines (256 loc) 9.31 kB
"use strict"; 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;