aframe-babia-components
Version:
A data visualization set of components for A-Frame.
1,824 lines (1,772 loc) • 6.94 MB
JavaScript
/******/ (() => { // 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',