UNPKG

3d-force-graph

Version:

UI component for a 3D force-directed graph using ThreeJS and ngraph.forcelayout3d layout engine

362 lines (273 loc) 11.2 kB
function __$styleInject(css, returnValue) { if (typeof document === 'undefined') { return returnValue; } css = css || ''; var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; head.appendChild(style); if (style.styleSheet){ style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } return returnValue; } import { AmbientLight, Color, DirectionalLight, PerspectiveCamera, Raycaster, Scene, Vector2, WebGLRenderer } from 'three'; import ThreeTrackballControls from 'three-trackballcontrols'; import ThreeForceGraph from 'three-forcegraph'; import accessorFn from 'accessor-fn'; import Kapsule from 'kapsule'; import tinyColor from 'tinycolor2'; __$styleInject(".graph-nav-info {\n bottom: 5px;\n width: 100%;\n text-align: center;\n color: slategrey;\n opacity: 0.7;\n font-size: 10px;\n}\n\n.graph-info-msg {\n top: 50%;\n width: 100%;\n text-align: center;\n color: lavender;\n opacity: 0.7;\n font-size: 22px;\n}\n\n.graph-tooltip {\n color: lavender;\n font-size: 18px;\n}\n\n.graph-info-msg, .graph-nav-info, .graph-tooltip {\n position: absolute;\n font-family: Sans-serif;\n}", undefined); function linkKapsule (kapsulePropName, kapsuleType) { var dummyK = new kapsuleType(); // To extract defaults return { linkProp: function linkProp(prop) { // link property config return { default: dummyK[prop](), onChange: function onChange(v, state) { state[kapsulePropName][prop](v); }, triggerUpdate: false }; }, linkMethod: function linkMethod(method) { // link method pass-through return function (state) { var kapsuleInstance = state[kapsulePropName]; for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } var returnVal = kapsuleInstance[method].apply(kapsuleInstance, args); return returnVal === kapsuleInstance ? this // chain based on the parent object, not the inner kapsule : returnVal; }; } }; } var colorStr2Hex = (function (str) { return isNaN(str) ? parseInt(tinyColor(str).toHex(), 16) : str; }); var defineProperty = function (obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var toConsumableArray = function (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); } }; var three$1 = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists : { WebGLRenderer: WebGLRenderer, Scene: Scene, PerspectiveCamera: PerspectiveCamera, AmbientLight: AmbientLight, DirectionalLight: DirectionalLight, Raycaster: Raycaster, Vector2: Vector2, Color: Color }; // var CAMERA_DISTANCE2NODES_FACTOR = 150; // // Expose config from forceGraph var bindFG = linkKapsule('forceGraph', ThreeForceGraph); var linkedFGProps = Object.assign.apply(Object, toConsumableArray(['jsonUrl', 'graphData', 'numDimensions', 'nodeRelSize', 'nodeId', 'nodeVal', 'nodeResolution', 'nodeColor', 'nodeAutoColorBy', 'nodeOpacity', 'nodeThreeObject', 'linkSource', 'linkTarget', 'linkColor', 'linkAutoColorBy', 'linkOpacity', 'forceEngine', 'd3AlphaDecay', 'd3VelocityDecay', 'warmupTicks', 'cooldownTicks', 'cooldownTime'].map(function (p) { return defineProperty({}, p, bindFG.linkProp(p)); }))); var linkedFGMethods = Object.assign.apply(Object, toConsumableArray(['d3Force'].map(function (p) { return defineProperty({}, p, bindFG.linkMethod(p)); }))); // var _3dForceGraph$1 = Kapsule({ props: _extends({ width: { default: window.innerWidth }, height: { default: window.innerHeight }, backgroundColor: { default: '#000011', onChange: function onChange(bckgColor, state) { state.scene.background = new three$1.Color(colorStr2Hex(bckgColor)); }, triggerUpdate: false }, showNavInfo: { default: true }, nodeLabel: { default: 'name', triggerUpdate: false }, linkLabel: { default: 'name', triggerUpdate: false }, linkHoverPrecision: { default: 1, triggerUpdate: false }, enablePointerInteraction: { default: true, onChange: function onChange(_, state) { state.onHover = null; }, triggerUpdate: false }, onNodeClick: { default: function _default() {}, triggerUpdate: false }, onNodeHover: { default: function _default() {}, triggerUpdate: false }, onLinkClick: { default: function _default() {}, triggerUpdate: false }, onLinkHover: { default: function _default() {}, triggerUpdate: false } }, linkedFGProps), aliases: { // Prop names supported for backwards compatibility nameField: 'nodeLabel', idField: 'nodeId', valField: 'nodeVal', colorField: 'nodeColor', autoColorBy: 'nodeAutoColorBy', linkSourceField: 'linkSource', linkTargetField: 'linkTarget', linkColorField: 'linkColor', lineOpacity: 'linkOpacity' }, methods: _extends({}, linkedFGMethods), stateInit: function stateInit() { return { renderer: new three$1.WebGLRenderer(), scene: new three$1.Scene(), camera: new three$1.PerspectiveCamera(), lastSetCameraZ: 0, forceGraph: new ThreeForceGraph() }; }, init: function init(domNode, state) { // Wipe DOM domNode.innerHTML = ''; // Add nav info section domNode.appendChild(state.navInfo = document.createElement('div')); state.navInfo.className = 'graph-nav-info'; state.navInfo.textContent = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan"; // Add info space var infoElem = void 0; domNode.appendChild(infoElem = document.createElement('div')); infoElem.className = 'graph-info-msg'; infoElem.textContent = ''; state.forceGraph.onLoading(function () { infoElem.textContent = 'Loading...'; }); state.forceGraph.onFinishLoading(function () { infoElem.textContent = ''; // re-aim camera, if still in default position (not user modified) if (state.camera.position.x === 0 && state.camera.position.y === 0 && state.camera.position.z === state.lastSetCameraZ) { state.camera.lookAt(state.forceGraph.position); state.lastSetCameraZ = state.camera.position.z = Math.cbrt(state.forceGraph.graphData().nodes.length) * CAMERA_DISTANCE2NODES_FACTOR; } }); // Setup tooltip var toolTipElem = document.createElement('div'); toolTipElem.classList.add('graph-tooltip'); domNode.appendChild(toolTipElem); // Capture mouse coords on move var raycaster = new three$1.Raycaster(); var mousePos = new three$1.Vector2(); mousePos.x = -2; // Initialize off canvas mousePos.y = -2; domNode.addEventListener("mousemove", function (ev) { // update the mouse pos var offset = getOffset(domNode), relPos = { x: ev.pageX - offset.left, y: ev.pageY - offset.top }; mousePos.x = relPos.x / state.width * 2 - 1; mousePos.y = -(relPos.y / state.height) * 2 + 1; // Move tooltip toolTipElem.style.top = relPos.y - 40 + 'px'; toolTipElem.style.left = relPos.x - 20 + 'px'; function getOffset(el) { var rect = el.getBoundingClientRect(), scrollLeft = window.pageXOffset || document.documentElement.scrollLeft, scrollTop = window.pageYOffset || document.documentElement.scrollTop; return { top: rect.top + scrollTop, left: rect.left + scrollLeft }; } }, false); // Handle click events on nodes domNode.addEventListener("click", function (ev) { if (state.hoverObj) { state['on' + (state.hoverObj.__graphObjType === 'node' ? 'Node' : 'Link') + 'Click'](state.hoverObj.__data); } }, false); // Setup renderer, camera and controls domNode.appendChild(state.renderer.domElement); var tbControls = new ThreeTrackballControls(state.camera, state.renderer.domElement); state.renderer.setSize(state.width, state.height); state.camera.far = 20000; // Populate scene state.scene.add(state.forceGraph); state.scene.add(new three$1.AmbientLight(0xbbbbbb)); state.scene.add(new three$1.DirectionalLight(0xffffff, 0.6)); // // Kick-off renderer (function animate() { // IIFE if (state.enablePointerInteraction) { // Update tooltip and trigger onHover events raycaster.linePrecision = state.linkHoverPrecision; raycaster.setFromCamera(mousePos, state.camera); var intersects = raycaster.intersectObjects(state.forceGraph.children).filter(function (o) { return ['node', 'link'].indexOf(o.object.__graphObjType) !== -1; }) // Check only node/link objects .sort(function (a, b) { // Prioritize nodes over links var isNode = function isNode(o) { return o.object.__graphObjType === 'node'; }; return isNode(b) - isNode(a); }); var topObject = intersects.length ? intersects[0].object : null; if (topObject !== state.hoverObj) { var prevObjType = state.hoverObj ? state.hoverObj.__graphObjType : null; var prevObjData = state.hoverObj ? state.hoverObj.__data : null; var objType = topObject ? topObject.__graphObjType : null; var objData = topObject ? topObject.__data : null; if (prevObjType && prevObjType !== objType) { // Hover out state['on' + (prevObjType === 'node' ? 'Node' : 'Link') + 'Hover'](null, prevObjData); } if (objType) { // Hover in state['on' + (objType === 'node' ? 'Node' : 'Link') + 'Hover'](objData, prevObjType === objType ? prevObjData : null); } toolTipElem.textContent = topObject ? accessorFn(state[objType + 'Label'])(objData) : ''; state.hoverObj = topObject; } } // Frame cycle state.forceGraph.tickFrame(); tbControls.update(); state.renderer.render(state.scene, state.camera); requestAnimationFrame(animate); })(); }, update: function updateFn(state) { // resize canvas if (state.width && state.height) { state.renderer.setSize(state.width, state.height); state.camera.aspect = state.width / state.height; state.camera.updateProjectionMatrix(); } state.navInfo.style.display = state.showNavInfo ? null : 'none'; } }); export { _3dForceGraph$1 as default };