3d-force-graph-vr
Version:
UI component for a 3D force-directed graph in VR
473 lines (454 loc) • 16.5 kB
JavaScript
import 'aframe-extras';
import 'aframe-forcegraph-component';
import Kapsule from 'kapsule';
import accessorFn from 'accessor-fn';
import { parseToRgb, opacify } from 'polished';
function styleInject(css, ref) {
if (ref === void 0) ref = {};
var insertAt = ref.insertAt;
if (typeof document === 'undefined') {
return;
}
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z = ".graph-nav-info {\n position: absolute;\n bottom: 5px;\n width: 100%;\n text-align: center;\n color: slategrey;\n opacity: 0.7;\n font-size: 10px;\n font-family: Sans-serif;\n z-index: 1000;\n}";
styleInject(css_248z);
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return _arrayLikeToArray(r);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: true,
configurable: true,
writable: true
}) : e[r] = t, e;
}
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = true,
o = false;
try {
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = true, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function (r) {
return Object.getOwnPropertyDescriptor(e, r).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
_defineProperty(e, r, t[r]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
});
}
return e;
}
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r);
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == typeof i ? i : i + "";
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
//
var _3dForceGraphVr = Kapsule({
props: {
width: {
"default": window.innerWidth,
triggerUpdate: false,
onChange: function onChange(width, state) {
if (state.container) state.container.style.width = "".concat(width, "px");
}
},
height: {
"default": window.innerHeight,
triggerUpdate: false,
onChange: function onChange(height, state) {
if (state.container) state.container.style.height = "".concat(height, "px");
}
},
jsonUrl: {},
graphData: {
"default": {
nodes: [],
links: []
}
},
numDimensions: {
"default": 3
},
dagMode: {},
dagLevelDistance: {},
dagNodeFilter: {
"default": function _default() {
return true;
}
},
onDagError: {
"default": undefined
},
backgroundColor: {
"default": '#002'
},
showNavInfo: {
"default": true
},
nodeRelSize: {
"default": 4
},
// volume per val unit
nodeId: {
"default": 'id'
},
nodeLabel: {
"default": 'name'
},
nodeDesc: {
"default": 'desc'
},
onNodeHover: {},
onNodeClick: {},
nodeVal: {
"default": 'val'
},
nodeResolution: {
"default": 8
},
// how many slice segments in the sphere's circumference
nodeVisibility: {
"default": true
},
nodeColor: {
"default": 'color'
},
nodeAutoColorBy: {},
nodeOpacity: {
"default": 0.75
},
nodeThreeObject: {},
nodeThreeObjectExtend: {
"default": false
},
linkSource: {
"default": 'source'
},
linkTarget: {
"default": 'target'
},
linkLabel: {
"default": 'name'
},
linkDesc: {
"default": 'desc'
},
onLinkHover: {},
onLinkClick: {},
linkVisibility: {
"default": true
},
linkColor: {
"default": 'color'
},
linkAutoColorBy: {},
linkOpacity: {
"default": 0.2
},
linkWidth: {
"default": 0
},
linkResolution: {
"default": 6
},
// how many radial segments in each line cylinder's geometry
linkCurvature: {
"default": 0
},
linkCurveRotation: {
"default": 0
},
linkMaterial: {},
linkThreeObject: {},
linkThreeObjectExtend: {
"default": false
},
linkPositionUpdate: {},
linkDirectionalArrowLength: {
"default": 0
},
linkDirectionalArrowColor: {},
linkDirectionalArrowRelPos: {
"default": 0.5
},
// value between 0<>1 indicating the relative pos along the (exposed) line
linkDirectionalArrowResolution: {
"default": 8
},
// how many slice segments in the arrow's conic circumference
linkDirectionalParticles: {
"default": 0
},
// animate photons travelling in the link direction
linkDirectionalParticleSpeed: {
"default": 0.01
},
// in link length ratio per frame
linkDirectionalParticleOffset: {
"default": 0
},
linkDirectionalParticleWidth: {
"default": 0.5
},
linkDirectionalParticleColor: {},
linkDirectionalParticleResolution: {
"default": 4
},
// how many slice segments in the particle sphere's circumference
linkDirectionalParticleThreeObject: {},
forceEngine: {
"default": 'd3'
},
// d3 or ngraph
d3AlphaMin: {
"default": 0
},
d3AlphaDecay: {
"default": 0.0228
},
d3VelocityDecay: {
"default": 0.4
},
ngraphPhysics: {},
warmupTicks: {
"default": 0
},
// how many times to tick the force engine at init before starting to render
cooldownTicks: {},
cooldownTime: {
"default": 15000
},
// ms
onEngineTick: {},
onEngineStop: {}
},
methods: _objectSpread2(_objectSpread2({}, Object.assign.apply(Object, [{}].concat(_toConsumableArray(['getGraphBbox', 'emitParticle', 'd3Force', 'd3ReheatSimulation', 'refresh'].map(function (method) {
return _defineProperty({}, method, function (state) {
var aframeComp = state.forcegraph.components.forcegraph;
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var returnVal = aframeComp[method].apply(aframeComp, args);
return returnVal === aframeComp ? this // chain based on this object, not the inner aframe component
: returnVal;
});
}))))), {}, {
_destructor: function _destructor() {
this.graphData({
nodes: [],
links: []
});
}
}),
init: function init(domNode, state) {
// Wipe DOM
domNode.innerHTML = '';
state.container = document.createElement('div');
domNode.appendChild(state.container);
state.container.style.position = 'relative';
state.container.style.width = "".concat(state.width, "px");
state.container.style.height = "".concat(state.height, "px");
// Add nav info section
state.container.appendChild(state.navInfo = document.createElement('div'));
state.navInfo.className = 'graph-nav-info';
state.navInfo.textContent = 'Mouse drag: look, gamepad/arrow/wasd keys: move';
// Create scene
var scene = document.createElement('a-scene');
scene.setAttribute('embedded', '');
//scene.setAttribute('stats', null);
scene.appendChild(state.sky = document.createElement('a-sky'));
state.sky.setAttribute('radius', 3000);
// Add camera
var cameraG;
scene.appendChild(cameraG = document.createElement('a-entity'));
cameraG.setAttribute('position', '0 0 300');
cameraG.setAttribute('movement-controls', 'controls: gamepad, touch; fly: true; speed: 7');
var camera;
cameraG.appendChild(camera = document.createElement('a-entity'));
camera.setAttribute('camera', '');
camera.setAttribute('position', '0 0 0');
camera.setAttribute('look-controls', 'pointerLockEnabled: false');
camera.setAttribute('wasd-controls', 'fly: true; acceleration: 700');
// display cursor in middle of screen
// let cursor;
// camera.appendChild(cursor = document.createElement('a-cursor'));
// cursor.setAttribute('color', 'lavender');
// cursor.setAttribute('opacity', 0.5);
// cursor.setAttribute('raycaster', 'objects: ----none----'); // disable cursor raycaster
// Setup tooltip
var tooltipEl;
camera.appendChild(tooltipEl = document.createElement('a-text'));
tooltipEl.setAttribute('position', '0 -0.3 -1');
tooltipEl.setAttribute('width', 2);
tooltipEl.setAttribute('align', 'center');
tooltipEl.setAttribute('color', 'lavender');
tooltipEl.setAttribute('value', '');
// Setup sub-tooltip
var subTooltipEl;
camera.appendChild(subTooltipEl = document.createElement('a-text'));
subTooltipEl.setAttribute('position', '0 -0.4 -1');
subTooltipEl.setAttribute('width', 1.3);
subTooltipEl.setAttribute('align', 'center');
subTooltipEl.setAttribute('color', 'lavender');
subTooltipEl.setAttribute('value', '');
// Setup mouse cursor and laser raycasting controls
state.raycasterEls = [];
var mouseCursor;
scene.appendChild(mouseCursor = document.createElement('a-entity'));
mouseCursor.setAttribute('cursor', 'rayOrigin: mouse; mouseCursorStylesEnabled: true');
mouseCursor.setAttribute('raycaster', 'objects: [forcegraph]; interval: 100');
state.raycasterEls.push(mouseCursor);
['left', 'right'].forEach(function (hand) {
var laser;
cameraG.appendChild(laser = document.createElement('a-entity'));
laser.setAttribute('laser-controls', "hand: ".concat(hand, "; model: false;")); // Oculus touch offsets are slightly off
laser.setAttribute('raycaster', 'objects: [forcegraph]; interval: 100; lineColor: steelblue; lineOpacity: 0.85');
state.raycasterEls.push(laser);
});
// Add forcegraph entity
scene.appendChild(state.forcegraph = document.createElement('a-entity'));
state.forcegraph.setAttribute('forcegraph', null);
// attach scene
state.container.appendChild(scene);
// update tooltips
state.forcegraph.setAttribute('forcegraph', Object.assign.apply(Object, _toConsumableArray(['node', 'link'].map(function (t) {
var cct = {
node: 'Node',
link: 'Link'
}[t]; // camel-case version
return _defineProperty({}, "on".concat(cct, "Hover"), function onHover(obj, prevObj) {
var label = obj ? accessorFn(state["".concat(t, "Label")])(obj) || '' : '';
var subLabel = obj ? accessorFn(state["".concat(t, "Desc")])(obj) || '' : '';
tooltipEl.setAttribute('value', label);
subTooltipEl.setAttribute('value', subLabel);
state["on".concat(cct, "Hover")] && state["on".concat(cct, "Hover")](obj, prevObj);
});
}))));
},
update: function update(state, changedProps) {
if (changedProps.hasOwnProperty('backgroundColor')) {
var alpha = parseToRgb(state.backgroundColor).alpha;
if (alpha === undefined) alpha = 1;
state.sky.setAttribute('color', opacify(1, state.backgroundColor));
state.sky.setAttribute('opacity', alpha);
}
changedProps.hasOwnProperty('showNavInfo') && (state.navInfo.style.display = state.showNavInfo ? null : 'none');
// deactivate raycasting against the forcegraph if no interaction props are set
var isInteractive = ['onNodeHover', 'onLinkHover', 'onNodeClick', 'onLinkClick'].some(function (p) {
return state[p];
}) || ['nodeLabel', 'linkLabel'].some(function (p) {
return state[p] !== 'name';
}) || ['nodeDesc', 'linkDesc'].some(function (p) {
return state[p] !== 'desc';
});
state.raycasterEls.forEach(function (el) {
return el.setAttribute('raycaster', isInteractive ? 'objects: [forcegraph]; interval: 100' : 'objects: __none__');
});
var passThroughProps = ['jsonUrl', 'numDimensions', 'dagMode', 'dagLevelDistance', 'dagNodeFilter', 'onDagError', 'nodeRelSize', 'nodeId', 'onNodeClick', 'nodeVal', 'nodeResolution', 'nodeVisibility', 'nodeColor', 'nodeAutoColorBy', 'nodeOpacity', 'nodeThreeObject', 'nodeThreeObjectExtend', 'linkSource', 'linkTarget', 'onLinkClick', 'linkVisibility', 'linkColor', 'linkAutoColorBy', 'linkOpacity', 'linkWidth', 'linkResolution', 'linkCurvature', 'linkCurveRotation', 'linkMaterial', 'linkThreeObject', 'linkThreeObjectExtend', 'linkPositionUpdate', 'linkDirectionalArrowLength', 'linkDirectionalArrowColor', 'linkDirectionalArrowRelPos', 'linkDirectionalArrowResolution', 'linkDirectionalParticles', 'linkDirectionalParticleSpeed', 'linkDirectionalParticleOffset', 'linkDirectionalParticleWidth', 'linkDirectionalParticleColor', 'linkDirectionalParticleResolution', 'linkDirectionalParticleThreeObject', 'forceEngine', 'd3AlphaMin', 'd3AlphaDecay', 'd3VelocityDecay', 'ngraphPhysics', 'warmupTicks', 'cooldownTicks', 'cooldownTime', 'onEngineTick', 'onEngineStop'];
var newProps = Object.assign.apply(Object, [{}].concat(_toConsumableArray(Object.entries(state).filter(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 2),
prop = _ref4[0],
val = _ref4[1];
return changedProps.hasOwnProperty(prop) && passThroughProps.indexOf(prop) !== -1 && val !== undefined && val !== null;
}).map(function (_ref5) {
var _ref6 = _slicedToArray(_ref5, 2),
key = _ref6[0],
val = _ref6[1];
return _defineProperty({}, key, val);
})), _toConsumableArray(Object.entries(state.graphData).map(function (_ref8) {
var _ref9 = _slicedToArray(_ref8, 2),
key = _ref9[0],
val = _ref9[1];
return _defineProperty({}, key, val);
}))));
state.forcegraph.setAttribute('forcegraph', newProps);
}
});
export { _3dForceGraphVr as default };