UNPKG

aframe-babia-components

Version:

A data visualization set of components for A-Frame.

1,824 lines (1,772 loc) 6.94 MB
/******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./common/noti-buffer.js" /*!*******************************!*\ !*** ./common/noti-buffer.js ***! \*******************************/ (module) { /* * Notifying buffer * * This buffer maintains an object (data). Its value is set with 'set', * and when set, it triggers notications to registered consumers * Registration is done via 'register'. */ class NotiBuffer { /* * notifiers: object with registered notifier objects * - key: identifier (unique integer) * - value: notify function */ constructor(func1, func2) { this.currentId = 0; this.notifiers = {}; this.data; // Optional: Function to be executed by producer // if details are received when registering this.function1 = func1; // Optional: Function to be executed by producer // if details are received when unregistering this.function2 = func2; } /* * Set data in the buffer */ set(data) { console.log("produced", data); this.data = data; for (const notify of Object.values(this.notifiers)) { notify(this.data); } } /* * Register a notifier for the buffer * Notifiers have the following signature: * function notifier (data) * data is the data stored in the buffer * Returns the id of the notifier */ register(notify, details) { if (this.data !== undefined) { console.log("Data was ready"); notify(this.data); }; let id = this.currentId; this.notifiers[id] = notify; this.currentId ++; // Optional: Details from consumer and function to be executed by producer if (details && this.function1) { this.function1(details); } return id; } /* * Unregister a notifier for the buffer * id is the the identifier returned when registering */ unregister(id, details) { delete(this.notifiers[id]); // Optional: Details from consumer and function to be executed by producer if (details && this.function2) { this.function2(details); } } } // Export (only if 'module' exists, that is, we're not in the browser) if (true) { module.exports.NotiBuffer = NotiBuffer; }; /***/ }, /***/ "./components/filters/babia-filter.js" /*!********************************************!*\ !*** ./components/filters/babia-filter.js ***! \********************************************/ (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { let findProdComponent = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").findProdComponent); let parseJson = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").parseJson); /* global AFRAME */ if (typeof AFRAME === 'undefined') { throw new Error('Component attempted to register before AFRAME was available.'); } const NotiBuffer = (__webpack_require__(/*! ../../common/noti-buffer */ "./common/noti-buffer.js").NotiBuffer); /** * A-Charts component for A-Frame. */ AFRAME.registerComponent('babia-filter', { schema: { from: { type: 'string' }, filter: { type: 'string' }, // data, for debugging, highest priority data: { type: 'string' } }, /** * Set if component needs multiple instancing. */ multiple: false, /** * Called once when component is attached. Generally for initial setup. */ init: function () { this.notiBuffer = new NotiBuffer(); }, /** * Called when component is attached and when component data changes. * Generally modifies the entity based on the data. */ update: function (oldData) { let data = this.data; let el = this.el; // Highest priority to data if (data.data && (oldData.data !== data.data || data.filter !== oldData.filter)) { let _data = parseJson(data.data); this.processData(_data); } else if (data.from !== oldData.from || data.filter !== oldData.filter) { // Unregister from old notiBuffer if (this.prodComponent) { this.prodComponent.notiBuffer.unregister(this.notiBufferId); } ; // Register for the new one // (It will also invoke processData once if there is already data) this.prodComponent = findProdComponent(data, el, "babia-filter"); if (this.prodComponent.notiBuffer) { this.notiBufferId = this.prodComponent.notiBuffer.register(this.processData.bind(this)); } } }, /** * Called when a component is removed (e.g., via removeAttribute). * Generally undoes all modifications to the entity. */ remove: function () {}, /** * Called when entity pauses. * Use to stop or remove any dynamic or background behavior such as events. */ pause: function () {}, /** * Called when entity resumes. * Use to continue or add any dynamic or background behavior such as events. */ play: function () {}, /** * Producer component */ prodComponent: undefined, /** * NotiBuffer identifier */ notiBufferId: undefined, processData: function (data) { // Filter data and save it let filter = this.data.filter.split('='); let dataFiltered; if (filter[0] && filter[1]) { dataFiltered = data.filter(key => key[filter[0]] == filter[1]); this.notiBuffer.set(dataFiltered); } else { console.error("Error on filter, please use key=value syntax"); } } }); /***/ }, /***/ "./components/filters/babia-selector.js" /*!**********************************************!*\ !*** ./components/filters/babia-selector.js ***! \**********************************************/ (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { let findProdComponent = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").findProdComponent); let findNavComponent = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").findNavComponent); let parseJson = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").parseJson); class Selectable { /** * Create a new Selectable object. * * @param {string} field - Field of the data to select. * @param {number} [current=-2] - Current value of the selection. If -2, it will be set to 0. * @param {number} [step=1] - Step value for the selection. * @param {number} [speed=1] - Speed value for the selection. * @param {string} [state='play'] - State of the selection. 'play' or 'pause'. * @param {string} [direction='forward'] - Direction of the selection. 'forward' or 'rewind'. */ constructor(field, current, step, speed, state, direction) { this.field = field; if (!step) { this.step = 1; } else { this.step = step; } if (!speed) { this.speed = 1; } else { this.speed = speed; } if (current == -2) { this.current = 0; } else { this.current = current; } if (!state) { this.state = 'play'; } else { this.state = state; } if (!direction) { this.direction = 'forward'; } else { this.direction = direction; } } /** * Update the internal data structure with a new list of items * @param list {array} List of items to select from */ updateData(list) { this.data = {}; for (let item of list) { let selector = item[this.field]; if (this.data[selector]) { this.data[selector].push(item); } else { this.data[selector] = [item]; } ; } this.selectors = Object.keys(this.data).sort(); this.length = this.selectors.length; } next() { let selected = this.data[this.selectors[this.current]]; if (this.current + this.step <= this.length - 1) { this.current += this.step; } else if (this.current = this.length - 1) { this.current = this.length; } else { this.current = this.length; } return selected; } prev() { let selected = this.data[this.selectors[this.current]]; if (this.current - this.step >= 0) { this.current -= this.step; } else { this.current = -1; } return selected; } setValue(value) { let selected = this.data[this.selectors[value]]; if (this.direction != 'rewind') { if (value > this.length) { this.current = this.length; } else if (value < 1) { this.current = 1; } else { this.current = value + 1; } } else { if (value > this.length - 2) { this.current = this.length - 2; } else if (value < -1) { this.current = -1; } else { this.current = value - 1; } } return selected; } } ; /* global AFRAME */ if (typeof AFRAME === 'undefined') { throw new Error('Component attempted to register before AFRAME was available.'); } const NotiBuffer = (__webpack_require__(/*! ../../common/noti-buffer */ "./common/noti-buffer.js").NotiBuffer); /** * Selector component for BabiaXR. */ AFRAME.registerComponent('babia-selector', { schema: { // Id of the querier where the data comes from from: { type: 'string' }, // Id of the querier where the data comes from controller: { type: 'string', default: '' }, // Field to use as selector select: { type: 'string', default: 'date' }, // Timeout for moving to the next selection timeout: { type: 'number', default: 6000 }, // data, for debugging, highest priority data: { type: 'string' }, // Current value of timeline current_value: { type: 'number', default: -2 }, // Current speed speed: { type: 'number', default: 0 }, // Current step step: { type: 'number', default: 0 }, // Current direction direction: { type: 'string', default: '' }, // Current state state: { type: 'string', default: '' } }, multiple: false, interval: undefined, /** * Called once when component is attached. Generally for initial setup. */ init: function () { this.notiBuffer = new NotiBuffer(); this.navNotiBuffer = new NotiBuffer(); // Create a Selectable object, set its values this.selectable = new Selectable(this.data.select, this.data.current_value, this.data.step, this.data.speed); }, /** * Called when component is attached and when component data changes. * Generally modifies the entity based on the data. */ babiaMetadata: { id: 0 }, update: function (oldData) { let data = this.data; let el = this.el; // Highest priority to data if (data.data && oldData.data !== data.data) { let _data = parseJson(data.data); this.processData(_data); } else { if (data.from !== oldData.from) { // Unregister for old producer if (this.prodComponent) { this.prodComponent.notiBuffer.unregister(this.notiBufferId); } ; // Register for the new one this.prodComponent = findProdComponent(data, el, 'babia-selector'); if (this.prodComponent.notiBuffer) { this.notiBufferId = this.prodComponent.notiBuffer.register(this.processData.bind(this)); } } ; } // find controller if (data.controller && data.controller != oldData.controller) { this.selectorController = document.querySelector('#' + data.controller); // Unregister for old navigator if (this.navComponent) { this.navComponent.notiBuffer.unregister(this.navNotiBufferId, this); if (el.components.networked) { this.removeOldNavListener(this.navComponent.el); } } ; // Register for the new one this.navComponent = findNavComponent(data, el); if (this.navComponent.notiBuffer) { this.navNotiBufferId = this.navComponent.notiBuffer.register(this.processEvent.bind(this), this); } if (el.components.networked) { this.addMultiuserMode(this.navComponent.el); } } if (data.direction && data.direction != oldData.direction) { if (data.direction != 'rewind') { if (this.selectable.current != this.selectable.length) { this.selectable.current += 2; } if (this.navNotiBuffer) { this.navNotiBuffer.set('forward'); } } else { if (this.selectable.current < 1) { this.selectable.current = 0; } else { this.selectable.current -= 2; } if (this.navNotiBuffer) { this.navNotiBuffer.set('rewind'); } } } if (data.step && data.step != this.selectable.step) { this.selectable.step = data.step; this.navNotiBuffer.set({ type: 'step', value: data.step }); if (this.data.direction != 'rewind') { if (this.selectable.current + data.step > this.selectable.length) { this.selectable.current = this.selectable.length; } else { this.selectable.current += data.step - 1; } } else { if (this.selectable.current - data.step <= 0) { this.selectable.current = -1; } else { this.selectable.current -= data.step + 1; } } } if (data.speed && data.speed != this.selectable.speed) { this.selectable.speed = data.speed; let timeout = this.data.timeout / data.speed; this.navNotiBuffer.set({ type: 'speed', value: data.speed }); let self = this; clearInterval(this.interval); this.interval = window.setInterval(function () { self.loop(); }, timeout); } // Only in multiuser if (el.components.networked) { // You are not the owner and you are not alone in the scene if (el.components.networked.data.owner != NAF.clientId && el.components.networked.data.owner != 'scene') { if (data.current_value != -2 && data.current_value != oldData.current_value) { // Initialize with the proper value if (data.direction != 'rewind') { this.navNotiBuffer.set({ type: 'position', value: data.current_value - 1, label: this.selectable.selectors[data.current_value - 1] }); this.setSelect(data.current_value - 1); } else { this.navNotiBuffer.set({ type: 'position', value: data.current_value + 1, label: this.selectable.selectors[data.current_value + 1] }); this.setSelect(data.current_value + 1); } } if (data.state == 'pause' && data.state != oldData.state) { this.navNotiBuffer.set('pause'); } } } }, nextSelect: function () { if (this.selectable.current > this.selectable.length - 1) { this.selectable.current = this.selectable.length; this.selectable.state = 'pause'; this.el.setAttribute('babia-selector', 'state', 'pause'); this.navNotiBuffer.set('pause'); } else { this.newData = this.selectable.next(); this.el.setAttribute('babia-selector', 'current_value', this.selectable.current); this.notiBuffer.set(this.newData); this.navNotiBuffer.set({ type: 'position', value: this.selectable.current, label: this.selectable.selectors[this.selectable.current - 1] }); this.babiaMetadata = { id: this.selectable.current }; } }, prevSelect: function () { if (this.selectable.current >= 0) { this.newData = this.selectable.prev(); this.el.setAttribute('babia-selector', 'current_value', this.selectable.current); this.notiBuffer.set(this.newData); this.navNotiBuffer.set({ type: 'position', value: this.selectable.current, label: this.selectable.selectors[this.selectable.current + 1] }); this.babiaMetadata = { id: this.selectable.current }; } else { this.selectable.current = -1; this.selectable.state = 'pause'; this.el.setAttribute('babia-selector', 'state', 'pause'); this.navNotiBuffer.set('pause'); } }, setSelect: function (value) { if (value != this.selectable.current - 1 && this.data.direction != 'rewind' || value != this.selectable.current + 1 && this.data.direction === 'rewind') { let label; this.newData = this.selectable.setValue(value); if (this.data.direction != 'rewind') { this.babiaMetadata = { id: value++ }; label = this.selectable.selectors[value - 1]; } else { this.babiaMetadata = { id: value-- }; this.selectable.current -= 2; label = this.selectable.selectors[value + 1]; } this.notiBuffer.set(this.newData); this.navNotiBuffer.set({ type: 'position', value: value, label: label }); } }, loop: function () { if (this.data.state != 'pause') { if (this.data.direction != 'rewind') { this.nextSelect(); } else { this.prevSelect(); } } }, /** * Producer component */ prodComponent: undefined, /** * Navigation component */ navComponent: undefined, /** * Producer NotiBuffer identifier */ prodNotiBufferId: undefined, /** * Navigation NotiBuffer identifier */ navNotiBufferId: undefined, /** * Where new data is stored */ newData: undefined, processData: function (_data) { this.selectable.updateData(_data); this.navNotiBuffer.set({ type: 'position', value: this.selectable.current, label: this.selectable.selectors[this.selectable.current - 1] }); let self = this; this.nextSelect(); this.interval = window.setInterval(function () { self.loop(); }, self.data.timeout * self.selectable.speed); }, processEvent: function (event) { if (event.includes('pause')) { this.selectable.state = 'pause'; this.el.setAttribute('babia-selector', 'state', 'pause'); } else if (event.includes('play')) { this.selectable.state = 'play'; this.el.setAttribute('babia-selector', 'state', 'play'); } else if (event.includes('forward')) { this.selectable.direction = 'forward'; this.el.setAttribute('babia-selector', 'direction', 'forward'); } else if (event.includes('rewind')) { this.selectable.direction = 'rewind'; this.el.setAttribute('babia-selector', 'direction', 'rewind'); } else if (event.includes('babiaSetPosition')) { this.selectable.state = 'pause'; let value = parseInt(event.substring(16), 10); if (this.data.direction != 'rewind') { this.el.setAttribute('babia-selector', { 'state': 'pause', 'current_value': value + 1 }); } else { this.el.setAttribute('babia-selector', { 'state': 'pause', 'current_value': value - 1 }); } this.setSelect(value); this.navNotiBuffer.set('pause'); } else if (event.includes('babiaSetStep')) { this.el.setAttribute('babia-selector', 'step', parseInt(event.substring(12), 10)); } else if (event.includes('babiaSetSpeed')) { this.el.setAttribute('babia-selector', 'speed', parseInt(event.substring(13), 10)); } }, removeOldNavListener: function (nav) { nav.removeEventListener('click', function () { if (!NAF.utils.isMine(selector) && selector.components.networked.data.owner != 'scene') { if (Object.keys(NAF.connection.connectedClients).length > 0) { NAF.utils.takeOwnership(selector); } } }); }, addMultiuserMode: function (nav) { let selector = this.el; document.body.addEventListener('clientConnected', function (event) { let clientId = event.detail.clientId; console.log('clientConnected event. clientId =', clientId); console.log("Selector owner: ", selector.components.networked.data.owner); let imFirst = true; if (selector.components.networked.data.owner == 'scene') { for (let client in NAF.connection.getConnectedClients()) { let otherTime = NAF.connection.getConnectedClients()[client].roomJoinTime; let myTime = NAF.connection.adapter._myRoomJoinTime; console.log("Other: ", otherTime); console.log("Mine: ", myTime); if (myTime > otherTime) { imFirst = false; } } if (imFirst) { NAF.utils.takeOwnership(selector); } else { console.log("I'm not first"); makeInvisible(); } } else if (selector.components.networked.data.owner != NAF.clientId) { makeInvisible(); } }); nav.addEventListener('click', function () { if (!NAF.utils.isMine(selector) && selector.components.networked.data.owner != 'scene') { if (Object.keys(NAF.connection.connectedClients).length > 0) { NAF.utils.takeOwnership(selector); } } }); selector.addEventListener('ownership-gained', e => { console.log("Selector ownership gained"); makeVisible(); }); selector.addEventListener('ownership-lost', e => { console.log("Selector ownership lost"); makeInvisible(); }); function makeInvisible() { nav.children[1].setAttribute('visible', false); nav.children[2].setAttribute('visible', false); nav.children[3].setAttribute('visible', false); } function makeVisible() { nav.children[1].setAttribute('visible', true); nav.children[2].setAttribute('visible', true); nav.children[3].setAttribute('visible', true); } } }); /***/ }, /***/ "./components/filters/babia-treebuilder.js" /*!*************************************************!*\ !*** ./components/filters/babia-treebuilder.js ***! \*************************************************/ (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { let findProdComponent = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").findProdComponent); let parseJson = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").parseJson); const NotiBuffer = (__webpack_require__(/*! ../../common/noti-buffer */ "./common/noti-buffer.js").NotiBuffer); /* global AFRAME */ if (typeof AFRAME === 'undefined') { throw new Error('Component attempted to register before AFRAME was available.'); } /** * A-Charts component for A-Frame. */ AFRAME.registerComponent('babia-treebuilder', { schema: { from: { type: 'string' }, field: { type: 'string' }, split_by: { type: 'string', default: '/' }, // Build a root node, hanging all the tree from it build_root: { type: 'boolean', default: false }, // Name of the root node, if build_root root_name: { type: 'string', default: 'Main' }, // data, for debugging, highest priority data: { type: 'string' } }, /** * Set if component needs multiple instancing. */ multiple: false, /** * Producer component */ prodComponent: undefined, /** * NotiBuffer identifier */ notiBufferId: undefined, /** * Called once when component is attached. Generally for initial setup. */ init: function () { this.notiBuffer = new NotiBuffer(); }, /** * Called when component is attached and when component data changes. * Generally modifies the entity based on the data. */ update: function (oldData) { let data = this.data; let el = this.el; if (data.data && (oldData.data !== data.data || data.field !== oldData.field || data.split_by !== oldData.split_by)) { let _data = parseJson(data.data); this.processData(_data); } else if (data.from !== oldData.from || data.field !== oldData.field || data.split_by !== oldData.split_by) { // Unregister from old notiBuffer if (this.prodComponent) { this.prodComponent.notiBuffer.unregister(this.notiBufferId); } ; // Register for the new one // (It will also invoke processData once if there is already data) this.prodComponent = findProdComponent(data, el, "babia-treebuilder"); if (this.prodComponent.notiBuffer) { this.notiBufferId = this.prodComponent.notiBuffer.register(this.processData.bind(this)); } } }, // Generate the datatree and save it processData: function (paths) { let data = this.data; let maintree = []; let tree = maintree; if (data.build_root) { maintree = [{ name: data.root_name, id: '', children: [] }]; tree = maintree[0].children; } ; for (let i = 0; i < paths.length; i++) { let path = paths[i][data.field].split(data.split_by); let currentLevel = tree; let currentUid = ''; for (let j = 0; j < path.length; j++) { // Check if starts with the split char if (!path[j]) { continue; } let part = path[j]; let existingPath = findWhere(currentLevel, 'name', part); if (existingPath) { currentLevel = existingPath.children; currentUid = existingPath.uid; // Update the UID accumulator } else { let newPart = {}; if (j === path.length - 1) { newPart = paths[i]; } else { newPart['children'] = []; } // Create the UID for the part of the path newPart['uid'] = currentUid ? currentUid + data.split_by + part : part; newPart['name'] = part; currentLevel.push(newPart); currentLevel = newPart.children; currentUid = newPart['uid']; // Update the UID accumulator } } } this.notiBuffer.set(maintree); } }); function findWhere(array, key, value) { t = 0; // find the index where the id is the as the a value while (t < array.length && array[t][key] !== value) { t++; } ; if (t < array.length) { return array[t]; } else { return false; } } /***/ }, /***/ "./components/geometries/babia-bar.js" /*!********************************************!*\ !*** ./components/geometries/babia-bar.js ***! \********************************************/ (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { const colors = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").colors); /* * BabiaXR Bar component * * Builds a bar (usually for the bar chart) */ AFRAME.registerComponent('babia-bar', { schema: { // Height of the bar height: { type: 'number' }, // Width of the bar width: { type: 'number' }, // Depth of the bar depth: { type: 'number' }, // Color for axis and labels color: { type: 'color', default: '#000' }, // Should this be animated animation: { type: 'boolean', default: true }, // Duration of animations dur: { type: 'number', default: 2000 }, // Label for the bar ('', 'fixed', 'events') label: { type: 'string', default: '' }, // Label text (valid if label is not empty) labelText: { type: 'string', default: '' }, // Label lookat for following labelLookat: { type: 'string', default: "[camera]" }, // Scale for the label labelScale: { type: 'number', default: 1 } }, init: function () { console.log("Starting bar:", this.data.height, this.data.color); let data = this.data; this.box = document.createElement('a-entity'); this.box.classList.add("babiaxraycasterclass"); this.el.appendChild(this.box); let props = {}; if (data.animation) { props = { 'height': 0, 'width': data.width, 'depth': data.depth }; } else { props = { 'height': data.height, 'width': data.width, 'depth': data.depth }; } this.box.setAttribute('geometry', { 'primitive': 'box', 'height': props.height, 'width': props.width, 'depth': props.depth }); this.box.setAttribute('material', { 'color': data.color }); }, update: function (oldData) { let data = this.data; let box = this.box; this.updateProperty(box, 'geometry', 'height', data.animation, data['height'], oldData.height); if (data.height != oldData.height) { // If there is change in height, update position if (data.animation) { box.setAttribute('animation__pos', { 'property': 'position', 'to': { x: 0, y: data.height / 2, z: 0 }, 'dur': data.dur }); if (data.height <= 0 && oldData.height > 0) { box.setAttribute('animation__opacity', { 'property': 'material.opacity', 'to': 0, 'dur': data.dur }); } else if (oldData.height <= 0 && data.height > 0) { box.setAttribute('animation__opacity', { 'property': 'material.opacity', 'to': 100, 'dur': data.dur }); } } else { box.setAttribute('position', { x: 0, y: data.height / 2, z: 0 }); if (data.height <= 0 && oldData.height > 0) { box.setAttribute('material', 'opacity', 0); } else if (oldData.height <= 0 && data.height > 0) { box.setAttribute('material', 'opacity', 100); } } ; } ; this.updateProperty(box, 'geometry', 'width', data.animation, data.width, oldData.width); this.updateProperty(box, 'geometry', 'depth', data.animation, data.depth, oldData.depth); this.updateProperty(box, 'material', 'color', data.animation, data.color, oldData.color); if (this.data.label === 'events') { box.addEventListener('mouseenter', this.showLabel.bind(this)); box.addEventListener('mouseleave', this.hideLabel.bind(this)); } else if (this.data.label === 'fixed') { this.showLabel(oldData); } ; }, /* * Update a property in an element, having animation into account * * @param el Element * @param component Component in which to update property * @param property Property to update in element * @param name: Property name in data, oldData * @param oldValue: Old value */ updateProperty: function (el, component, property, anim, newValue, oldValue) { let data = this.data; if (newValue !== oldValue) { if (anim) { let prop = component; if (property) { prop = prop + '.' + property; } ; el.setAttribute('animation__' + component + '_' + property, { 'property': prop, 'to': newValue, 'dur': data.dur }); } else { if (property) { el.setAttribute(component, { [property]: newValue }); } else { el.setAttribute(component, newValue); } } ; } ; }, showLabel: function (oldData) { let data = this.data; if (data.label === 'events') { this.el.setAttribute('scale', { x: 1.1, y: 1.1, z: 1.1 }); } ; text = data.labelText; let width = 2; if (text.length > 16) { width = text.length / 8; } ; let height = 1; oldHeight = oldData.height || 0; oldDepth = oldData.depth || 0; let oldPosition = { x: 0, y: oldHeight + 0.6 * height, z: 0 }; if (!this.labelEl) { this.labelEl = document.createElement('a-entity'); this.labelEl.setAttribute('babia-label', { 'width': width, 'textWidth': 6 }); if (data.animation && data.label === 'fixed') { this.labelEl.setAttribute('position', oldPosition); } ; this.el.appendChild(this.labelEl); } ; let position = { x: 0, y: data.height + 0.6 * height, z: 0.7 * data.depth }; let anim = data.animation && data.label === 'fixed'; this.updateProperty(this.labelEl, 'position', '', anim, position, oldPosition); this.labelEl.setAttribute('rotation', { x: 0, y: 0, z: 0 }); if (text != oldData.labelText) { this.labelEl.setAttribute('babia-label', { 'text': data.labelText }); } if (data.labelLookat && oldData.labelLookat !== data.labelLookat) { this.labelEl.setAttribute('babia-lookat', data.labelLookat); } if (data.labelScale && oldData.labelScale !== data.labelScale) { this.labelEl.setAttribute('scale', { x: data.labelScale, y: data.labelScale, z: data.labelScale }); } }, hideLabel: function () { if (this.data.label === 'events') { this.el.setAttribute('scale', { x: 1, y: 1, z: 1 }); } ; if (this.labelEl) { this.el.removeChild(this.labelEl); this.labelEl = null; } } }); /***/ }, /***/ "./components/geometries/babia-cyl.js" /*!********************************************!*\ !*** ./components/geometries/babia-cyl.js ***! \********************************************/ (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { const colors = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").colors); /* * BabiaXR Cyl component * * Builds a cylinder (usually for the cyls chart) */ AFRAME.registerComponent('babia-cyl', { schema: { // Height of the cylinder height: { type: 'number' }, // Width of the cylinder radius: { type: 'number' }, // Color for axis and labels color: { type: 'color', default: '#000' }, // Should this be animated animation: { type: 'boolean', default: true }, // Duration of animations dur: { type: 'number', default: 2000 }, // Label for the cylinder ('', 'fixed', 'events') label: { type: 'string', default: '' }, // Label text (valid if label is not empty) labelText: { type: 'string', default: '' }, // Label lookat for following labelLookat: { type: 'string', default: "[camera]" } }, init: function () { console.log("Starting cyl:", this.data.height, this.data.color); let data = this.data; this.cylinder = document.createElement('a-entity'); this.cylinder.classList.add("babiaxraycasterclass"); this.el.appendChild(this.cylinder); let props = {}; if (data.animation) { props = { 'height': 0, 'radius': data.radius }; } else { props = { 'height': data.height, 'radius': data.radius }; } this.cylinder.setAttribute('geometry', { 'primitive': 'cylinder', 'height': props.height, 'radius': props.radius }); this.cylinder.setAttribute('material', { 'color': data.color }); }, update: function (oldData) { let data = this.data; let cylinder = this.cylinder; this.updateProperty(cylinder, 'geometry', 'height', data.animation, data['height'], oldData.height); // Update height if (data.height != oldData.height) { // If there is change in height, update position if (data.animation) { cylinder.setAttribute('animation__pos', { 'property': 'position', 'to': { x: 0, y: data.height / 2, z: 0 }, 'dur': data.dur }); if (data.height <= 0 && oldData.height > 0) { cylinder.setAttribute('animation__opacity', { 'property': 'material.opacity', 'to': 0, 'dur': data.dur }); } else if (oldData.height <= 0 && data.height > 0) { cylinder.setAttribute('animation__opacity', { 'property': 'material.opacity', 'to': 100, 'dur': data.dur }); } } else { cylinder.setAttribute('position', { x: 0, y: data.height / 2, z: 0 }); if (data.height <= 0 && oldData.height > 0) { cylinder.setAttribute('material', 'opacity', 0); } else if (oldData.height <= 0 && data.height > 0) { cylinder.setAttribute('material', 'opacity', 100); } } ; } ; this.updateProperty(cylinder, 'geometry', 'radius', data.animation, data.radius, oldData.radius); this.updateProperty(cylinder, 'material', 'color', data.animation, data.color, oldData.color); if (this.data.label === 'events') { cylinder.addEventListener('mouseenter', this.showLabel.bind(this)); cylinder.addEventListener('mouseleave', this.hideLabel.bind(this)); } else if (this.data.label === 'fixed') { this.showLabel(oldData); } ; }, /* * Update a property in an element, having animation into account * * @param el Element * @param component Component in which to update property * @param property Property to update in element * @param name: Property name in data, oldData * @param oldValue: Old value */ updateProperty: function (el, component, property, anim, newValue, oldValue) { let data = this.data; if (newValue !== oldValue) { if (anim) { let prop = component; if (property) { prop = prop + '.' + property; } ; el.setAttribute('animation__' + component + '_' + property, { 'property': prop, 'to': newValue, 'dur': data.dur }); } else { if (property) { el.setAttribute(component, { [property]: newValue }); } else { el.setAttribute(component, newValue); } } ; } ; }, showLabel: function (oldData) { let data = this.data; if (data.label === 'events') { this.el.setAttribute('scale', { x: 1.1, y: 1.1, z: 1.1 }); } ; text = data.labelText; let width = 2; if (text.length > 16) { width = text.length / 8; } ; let height = 1; oldHeight = oldData.height || 0; let oldPosition = { x: 0, y: oldHeight + 0.6 * height, z: 0 }; if (!this.labelEl) { this.labelEl = document.createElement('a-entity'); this.labelEl.setAttribute('babia-label', { 'width': width, 'textWidth': 6 }); if (data.animation && data.label === 'fixed') { this.labelEl.setAttribute('position', oldPosition); } ; this.el.appendChild(this.labelEl); } ; let position = { x: 0, y: data.height + 0.7 * height, z: 0.7 * data.radius }; let anim = data.animation && data.label === 'fixed'; this.updateProperty(this.labelEl, 'position', '', anim, position, oldPosition); this.labelEl.setAttribute('rotation', { x: 0, y: 0, z: 0 }); if (text != oldData.labelText) { this.labelEl.setAttribute('babia-label', { 'text': data.labelText }); } if (data.labelLookat && oldData.labelLookat !== data.labelLookat) { this.labelEl.setAttribute('babia-lookat', data.labelLookat); } if (data.labelScale && oldData.labelScale !== data.labelScale) { this.labelEl.setAttribute('scale', { x: data.labelScale, y: data.labelScale, z: data.labelScale }); } }, hideLabel: function () { if (this.data.label === 'events') { this.el.setAttribute('scale', { x: 1, y: 1, z: 1 }); } ; if (this.labelEl) { this.el.removeChild(this.labelEl); this.labelEl = null; } } }); /***/ }, /***/ "./components/others/babia-axis.js" /*!*****************************************!*\ !*** ./components/others/babia-axis.js ***! \*****************************************/ (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { const colors = (__webpack_require__(/*! ../others/common */ "./components/others/common.js").colors); /* global AFRAME */ if (typeof AFRAME === 'undefined') { throw new Error('Component attempted to register before AFRAME was available.'); } /* * Class for building axis (x, y, or z), with labels and everything */ class Axis { /* * @param el Element * @param axis Axis ('x', 'y', 'z') * @param anim Is done with animation? * @param dur Duration of the animation */ constructor(el, axis, anim, dur) { this.el = el; this.axis = axis; this.anim = anim; this.dur = dur; } /* * Update axis line * * @param length Lenght of axis line * @param color Color of axis line */ updateLine(length, color) { const axis = this.axis; const el = this.el; const anim = this.anim; const dur = this.dur; const comp = `line__${axis}axis`; let lineEl = el.components[comp]; let end = { x: 0, y: 0, z: 0 }; end[axis] = length; if (anim && lineEl) { el.setAttribute(`animation__${axis}axis`, { 'property': `${comp}.end`, 'to': end, 'dur': dur }); el.setAttribute(comp, { 'color': color }); } else { el.setAttribute(comp, { 'start': { x: 0, y: 0, z: 0 }, 'end': end, 'color': color }); } ; } /* * Remove labels which are children of an element * * @param el Element * @param anim Is done with animation? * @param dur Duration of the animation */ removeLabels() { const el = this.el; const anim = this.anim; const dur = this.dur; let labels = el.querySelectorAll('[text]'); for (const label of labels) { if (anim) { label.addEventListener('animationcomplete', function (e) { e.target.remove(); }); label.setAttribute('animation', { 'property': 'text.opacity', 'to': 0, 'dur': dur }); } else { label.remove(); } } ; } /* * Update labels * * @param ticks Points in the axis for the labels * @param labels Labels to write in the ticks * @param color Color to use * @param palette Palette of colors to use */ updateLabels(ticks, labels, color, palette, align) { const axis = this.axis; let el = this.el; const anim = this.anim; const dur = this.dur; for (let i = 0; i < ticks.length; ++i) { let label = document.createElement('a-entity'); let icolor = color; if (palette) { icolor = colors.get(i, palette); } ; label.setAttribute('text', { 'value': labels[i], 'align': 'right', 'width': 10, 'color': icolor, 'opacity': 0 }); if (align == "behind" || align == "right") { label.setAttribute('text', { 'align': 'left' }); } let pos; if (axis === 'x') { if (align == 'behind') { pos = { x: ticks[i], y: 0, z: -5.15 }; } else { pos = { x: ticks[i], y: 0, z: 5.25 }; } } else if (axis === 'y') { if (align == "right") { pos = { x: 5.2, y: ticks[i], z: 0 }; } else { pos = { x: -5.2, y: ticks[i], z: 0 }; } } else if (axis === 'z') { if (align == "right") { pos = { x: 5.2, y: 0, z: ticks[i] }; } else { pos = { x: -5.2, y: 0, z: ticks[i] }; } } label.setAttribute('position', pos); if (axis === 'x') { label.setAttribute('rotation', { x: -90, y: 90, z: 0 }); } else if (axis === 'z') { label.setAttribute('rotation', { x: -90, y: 0, z: 0 }); } if (anim) { label.setAttribute('animation', { 'property': 'text.opacity', 'to': 1, 'dur': dur }); } else { label.setAttribute('text', { 'opacity': 1 }); } ; el.appendChild(label); } ; } updateName(name, align, length) { const axis = this.axis; let el = this.el; const anim = this.anim; const dur = this.dur; // for (let i = 0; i < ticks.length; ++i) { let label = document.createElement('a-entity'); label.setAttribute('text', { 'value': name, 'align': 'right', 'width': 10, 'color': '#000', 'opacity': 0 }); if (align == "behind" || align == "right") { label.setAttribute('text', { 'align': 'left' }); } let pos; if (axis === 'x') { if (align == 'behind') { pos = { x: length + 0.5, y: 0, z: -5.15 }; } else { pos = { x: length + 0.5, y: 0, z: 5.25 }; } } else if (axis === 'y') { if (align == "right") { pos = { x: 5.2, y: length + 0.5, z: 0 }; } else { pos = { x: -5.2, y: length + 0.5, z: 0 }; } } else if (axis === 'z') { if (align == "right") { pos = { x: 5.2, y: 0, z: length + 0.5 }; } else { pos = { x: -5.2, y: 0, z: length + 0.5 }; } } label.setAttribute('position', pos); if (axis === 'x') { label.setAttribute('rotation', { x: -90, y: 90, z: 0 }); } else if (axis === 'z') { label.setAttribute('rotation', { x: -90, y: 0, z: 0 }); } if (anim) { label.setAttribute('animation', { 'property': 'text.opacity', 'to': 1, 'dur': dur }); } else { label.setAttribute('text', { 'opacity': 1 }); } ; el.appendChild(label); // }; } } /* * BabiaXR Y Axis component * * Builds a Y axis for a chart */ AFRAME.registerComponent('babia-axis-y', { schema: { // Max value to show for this axis maxValue: { type: 'number' }, // Length of the axis length: { type: 'number' }, // Minimum number of steps minSteps: { type: 'number', default: 6 }, // Color for axis and labels color: { type: 'color', default: '#000' }, // Should this be animated animation: { type: 'boolean', default: true }, // Duration of animations dur: { type: 'number', default: 2000 }, // If we want the labels behind to axis. align: { type: 'string', default: 'left' }, // Name to show on label name: { type: 'string', default: '' } }, init: function () { this.axis = new Axis(this.el, 'y', this.data.animation, this.data.dur); }, update: function (oldData) { const data = this.data; let maxValue = this.data.maxValue; let length = this.data.length; let minSteps = this.data.minSteps; const animation = this.data.animation; const dur = this.data.dur; let decimals = 0; console.log('Starting babia-axis-y:', maxValue, length, this.data.color); if (maxValue != oldData.maxValue || length != oldData.maxValue || minSteps != oldData.minSteps) { // Get number of significant digits (negative is decimals) let maxValueLog = Math.floor(Math.log10(maxValue)); if (maxValueLog <= 0) { decimals = -maxValueLog + 1; } ; let axisScale = length / maxValue; let step = Math.pow(10, maxValueLog); let steps = maxValue / step; while (steps <= minSteps) { step = step / 2; steps = maxValue / step; } ; // Set axis line this.axis.updateLine(length, data.color); // Remove old labels this.axis.removeLabels(); // Create new labels let ticks = []; let labels = []; for (let tick = 1; step * tick < maxValue; tick++) { vtick = step * tick; ticks.push(vtick * axisScale); labels.push(vtick.toFixed(decimals)); } ; this.axis.updateLabels(ticks, labels, data.color, data.palette, data.align); } ; // Update name in axis label if (data.name) this.axis.updateName(data.name, data.align, data.length); } }); /* * BabiaXR X Axis component * * Builds a X axis for a chart */ AFRAME.registerComponent('babia-axis-x', { schema: { // Labels to show (list) labels: { type: 'array' }, // Points to have labels in the axis ticks: { type: 'array' }, // Length of the axis length: { type: 'number' }, // Color for axis and labels color: { type: 'color', default: '#000' }, // Color palette (if not 'None', have precedence over color) palette: { type: 'string', default: '' }, // Should this be animated animation: { type: 'boolean', default: true }, // Duration of animations dur: { type: 'number',