aframe-babia-components
Version:
A data visualization set of components for A-Frame.
1,390 lines (1,261 loc) • 6.76 MB
JavaScript
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./index.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./common/noti-buffer.js":
/*!*******************************!*\
!*** ./common/noti-buffer.js ***!
\*******************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
/*
* 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: Functions to be executed by producer if details are received when registering
this.function1 = func1;
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 ***!
\********************************************/
/*! no static exports found */
/***/ (function(module, 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 ***!
\**********************************************/
/*! no static exports found */
/***/ (function(module, 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 {
/*
* @param list {array} List of items to select from
* @param field {string} Field name used to select (present in all items)
*/
constructor(list, field, current, step, speed, state, direction) {
this.data = {};
for (let item of list) {
let selector = item[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;
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;
}
}
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' },
// 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();
},
/**
* 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 != 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) {
// Create a Selectable object, and set the updating interval
if (!this.selectable) {
this.selectable = new Selectable(_data, this.data.select, this.data.current_value, this.data.step, this.data.speed);
}
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 ***!
\*************************************************/
/*! no static exports found */
/***/ (function(module, 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' },
// 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 tree = [];
for (let i = 0; i < paths.length; i++) {
let path = paths[i][data.field].split(data.split_by);
let currentLevel = tree;
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;
} else {
let newPart = {};
if (j === path.length - 1) {
newPart = paths[i];
} else {
newPart['children'] = [];
}
let findUid = paths[i][data.field].split(data.split_by + part + data.split_by);
// No pop if it is a building beacuse it breaks
if (findUid.length > 1) {
let rest = findUid.pop();
}
newPart['uid'] = findUid.join(data.split_by + part + data.split_by) + data.split_by + part;
newPart['name'] = part;
currentLevel.push(newPart);
currentLevel = newPart.children;
}
}
}
//console.log(tree)
this.notiBuffer.set(tree);
}
});
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 ***!
\********************************************/
/*! no static exports found */
/***/ (function(module, 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 ***!
\********************************************/
/*! no static exports found */
/***/ (function(module, 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/assets/tooltip.png":
/*!**********************************************!*\
!*** ./components/others/assets/tooltip.png ***!
\**********************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAC70lEQVR42u3by04TURzH8W+nFx0kMUBbE6eNiSCXjQlxZQKU2qUbSERfwUQQ0ATfwATYGCPxHSwoLtzZ9ObWGIMRSDDRlFlIW1xIUuxtXHQ0gC1MTRM6nfNPupmeaef3OTNnziRzbJqmcUy5gDH9cx24qG9r5toDtoEPwCvgNZCv1dh2DMA4sAD0YO7aAh4BL6t9KVXZZgfm9R3MHh49w4qeyX4SgB14DMzRejWnZ7PXAmjl8DURDo4B8y0e/mAt6OPCX4BbQBhr1QSwbNM0zQV8AXwWA1CBy5IuYbXwAApwW9InOVatMZumaV+BS0b3KBSK5XgyqcYSSXXt01o2k87k8sVi+TSOvk2WHV6vV+7r7e0IBoaVkaFhxel0SHX8xDebpmn7wBkjrWPxhPp06fnHlLq914zd6Vd87VP37l4NBgJGL+m8TTvhYeBgz49P3HnzPb2Ta+Zz2uv2yKvLL24aPRMMny6RaDTV7OEBdjLp3NtIJGW0vWGAWDyhmmVkiyXfqQ0H+LyxvmsWgPU6jtUwQDa7u28WgGwmu99wgHyhUDYLQD23ZQmLlwAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAByqc22ywyyhXA7jL0oZbtjV6ZbNAtDl7jrbcIArPd3nzQIw0D/Q2XCA0cCIYhaA0eEhpeEAoWDQf8HjbfrLwOv2yKHQDX89AD+NNHQ6HdLs9NSgX/G1N2t4v+Jrfzhzf9DldBrt2LxN07RNoNfonxQKxXIkGk1F4wl1Y3PjR3onkyuUTudVWafdIXm8brm/r78jGBhRQsGg/39elQ1TWS9gxVqRgFULz4NWrb5goluisqjwgQV7fxb49WfACAOLFgq/qGc+tGrMDjwBJls8/BIwDZSOToRKwAzwzCrhq80EWxnhn/C1psIlYEqfG2y1QPAtKivjJo+GPzoGVH201nceB65RWWrmbPLABf0W957K8vkwxyyf/w0kIOOe2pS1LAAAAABJRU5ErkJggg==");
/***/ }),
/***/ "./components/others/babia-axis.js":
/*!*****************************************!*\
!*** ./components/others/babia-axis.js ***!
\*****************************************/
/*! no static exports found */
/***/ (function(module, 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