UNPKG

aframe-babia-components

Version:

A data visualization set of components for A-Frame.

707 lines (620 loc) 25.8 kB
let findTargetComponent = require('../others/common').findTargetComponent; /* global AFRAME */ if (typeof AFRAME === 'undefined') { throw new Error('Component attempted to register before AFRAME was available.'); } let MAX_SIZE_BAR = 10 /** * A-Charts component for A-Frame. */ AFRAME.registerComponent('babia-ui', { schema: { target: { type: 'string' }, hideFields: { type: 'array' }, hideRows: { type: 'array' }, showOnly: { type: 'array' }, maxPerRow: { type: 'number', default: 5 }, linesSeparation: { type: 'number', default: 0.3 }, customQuerierLabel: { type: 'string', default: 'Data' }, customAttributeSwitch: { type: 'array' }, customAttributeLabel: { type: 'array' } }, /** * Set if component needs multiple instancing. */ multiple: false, /** * Called once when component is attached. Generally for initial setup. */ init: function () { console.log("WELCOME TO UI: Control your data.") }, /** * Called when component is attached and when component data changes. * Generally modifies the entity based on the data. */ update: function (oldData) { const self = this let data = this.data // Unregister from old producer if (this.targetComponent) { this.targetComponent.notiBuffer.unregister(this.notiBufferId) }; // Find the target component self.targetComponent = findTargetComponent(data, this) // Find querier components this.findQuerierComponents(this) // Target component properties if (this.targetComponent.visProperties) { this.targetComponentVisProperties = this.targetComponent.visProperties } // Register to target component notiBuffer if (this.targetComponent.notiBuffer) { this.notiBufferId = this.targetComponent.notiBuffer .register(this.updateInterface.bind(this)) } }, targetComponent: undefined, targetComponentVisProperties: undefined, dataMetrics: undefined, interface: undefined, dataQueriers: undefined, handController: undefined, /** * Only for babia-network */ nodesQueriers: undefined, linksQueriers: undefined, updateInterface: function (data) { let self = this; if (data) { // if (!self.targetComponent.el.components['babia-network']){ getDataMetrics(self, data, self.targetComponentVisProperties) //} } while (self.el.firstChild) self.el.firstChild.remove(); // Generate interface console.log('Generating interface...') if (document.querySelector('#babia-menu-hand')) { let hand_ui = document.querySelector('#babia-menu-hand') hand_ui.parentNode.removeChild(hand_ui) insertInterfaceOnHand(self, self.handController) } else { self.interface = generateInterface(self, self.dataMetrics, self.el) } document.addEventListener('controllerconnected', (event) => { while (self.el.firstChild) self.el.firstChild.remove(); // event.detail.name ----> which VR controller controller = event.detail.name; let hand = event.target.getAttribute(controller).hand if (hand === 'left' && !document.querySelector('#babia-menu-hand')) { self.handController = event.target.id insertInterfaceOnHand(self, self.handController) } }); }, findQuerierComponents: function () { if (this.targetComponent.el.components['babia-network'] && (this.targetComponent.el.components['babia-network'].data.nodesFrom || this.targetComponent.el.components['babia-network'].data.nodes)) { console.log("nodesFrom or nodes") this.nodesQueriers = []; this.linksQueriers = []; document.querySelectorAll('[babia-queryjson]').forEach(querier => { // Skip querier data when the target visualizer has included filtered data too. if (querier.id != this.data.target || (querier.id == this.data.target && !this.targetComponent.prodComponent.attrName == 'babia-filter')) { this.nodesQueriers.push(querier.id) this.linksQueriers.push(querier.id) } }); document.querySelectorAll('[babia-queryes]').forEach(querier => { this.nodesQueriers.push(querier.id) this.linksQueriers.push(querier.id) }); document.querySelectorAll('[babia-querygithub]').forEach(querier => { this.nodesQueriers.push(querier.id) this.linksQueriers.push(querier.id) }); document.querySelectorAll('[babia-filter]').forEach(querier => { this.nodesQueriers.push(querier.id) this.linksQueriers.push(querier.id) }); } else if (this.targetComponent.el.components['babia-boats']) { this.dataQueriers = [] document.querySelectorAll('[babia-treebuilder]').forEach(querier => { this.dataQueriers.push(querier.id) }); } else { this.dataQueriers = [] // All queriers and filterdatas of the scene document.querySelectorAll('[babia-queryjson]').forEach(querier => { // Skip querier data when the target visualizer has included filtered data too. if (querier.id != this.data.target || (querier.id == this.data.target && !this.targetComponent.prodComponent.attrName == 'babia-filter')) { this.dataQueriers.push(querier.id) } }); document.querySelectorAll('[babia-queryes]').forEach(querier => { this.dataQueriers.push(querier.id) }); document.querySelectorAll('[babia-querygithub]').forEach(querier => { this.dataQueriers.push(querier.id) }); document.querySelectorAll('[babia-filter]').forEach(querier => { this.dataQueriers.push(querier.id) }); } }, hideAndInsertMetrics: function (dataMetrics, property, metrics) { const self = this let toAdd = { property: property, metrics: metrics } if (self.data.hideFields.length != 0) { let metricsFiltered = [] metrics.forEach(metric => { if (!self.data.hideFields.includes(metric)) { metricsFiltered.push(metric) } }) toAdd.metrics = metricsFiltered } // Show only activated if (self.data.showOnly.length > 0) { let onlyShowThis = [] for (let i = 0; i < self.data.showOnly.length; i++) { const toShow = self.data.showOnly[i].split(":"); if (property == toShow[0]) { onlyShowThis.push(toShow[1]) } } toAdd.metrics = onlyShowThis } if (!self.data.hideRows.includes(property)) { dataMetrics.push(toAdd) } } }) let insertInterfaceOnHand = (self, hand) => { let hand_entity = document.getElementById(hand) let scale = 0.03 self.interface = generateInterface(self, self.dataMetrics, hand_entity) self.interface.id = 'babia-menu-hand' self.interface.setAttribute('scale', { x: scale, y: scale, z: scale }) self.interface.setAttribute('position', { x: -scale * self.interface.width / 2, y: scale * self.interface.height / 2, z: -0.1 }) self.interface.setAttribute('rotation', { x: -60 }) openCloseMenu(self.handController, self.interface) } let getDataMetrics = (self, data, properties) => { if (self.targetComponent.attrName == 'babia-network') { if (data.nodes) { self.dataMetrics = { 'nodes': [], 'links': [] } // Create structure let number_properties = ['height', 'radius', 'width', 'size', 'farea', 'fheight', 'area', 'depth'] let number_metrics = [] let last_child last_child = getLastChild(data.nodes[0]) Object.keys(last_child).forEach(metric => { if (typeof last_child[metric] == 'number') { number_metrics.push(metric) } }); properties['nodes'].forEach(property => { if (number_properties.includes(property)) { self.hideAndInsertMetrics(self.dataMetrics['nodes'], property, number_metrics) } else { self.hideAndInsertMetrics(self.dataMetrics['nodes'], property, Object.keys(data.nodes[0])) } }); properties['links1'].forEach(property => { if (number_properties.includes(property)) { self.hideAndInsertMetrics(self.dataMetrics['links'], property, number_metrics) } else { self.hideAndInsertMetrics(self.dataMetrics['links'], property, Object.keys(data.links[0])) } }); } else { self.dataMetrics = [] // Create structure let number_properties = ['height', 'radius', 'width', 'size', 'farea', 'fheight', 'area', 'depth'] let number_metrics = [] let last_child last_child = getLastChild(data[0]) Object.keys(last_child).forEach(metric => { if (typeof last_child[metric] == 'number') { number_metrics.push(metric) } }); properties['nodes'].forEach(property => { if (number_properties.includes(property)) { self.hideAndInsertMetrics(self.dataMetrics, property, number_metrics) } else { self.hideAndInsertMetrics(self.dataMetrics, property, Object.keys(data[0])) } }); properties['links0'].forEach(property => { if (number_properties.includes(property)) { self.hideAndInsertMetrics(self.dataMetrics, property, number_metrics) } else { self.hideAndInsertMetrics(self.dataMetrics, property, Object.keys(data[0])) } }); } } else { self.dataMetrics = [] // Create structure let number_properties = ['height', 'radius', 'width', 'size', 'farea', 'fheight', 'area', 'depth', 'color'] let number_metrics = [] let last_child if (self.targetComponent.attrName == 'babia-city') { // Get last child of the tree last_child = getLastChild(data) } else if (self.targetComponent.attrName == 'babia-boats') { last_child = getLastChild(data[0]) } else { last_child = data[0] } Object.keys(last_child).forEach(metric => { if (typeof last_child[metric] == 'number') { number_metrics.push(metric) } }); properties.forEach(property => { if (number_properties.includes(property)) { self.hideAndInsertMetrics(self.dataMetrics, property, number_metrics) // Specific case for categoric color in boats if (property === "color") { if (self.targetComponent.attrName == 'babia-boats') { let categoric_colors = [] for (const [key, value] of Object.entries(last_child)) { if (typeof value === 'string') { categoric_colors.push(key) } } // Solo si no está activado if (!self.data.showOnly) { self.hideAndInsertMetrics(self.dataMetrics, property, categoric_colors) } } } } else { self.hideAndInsertMetrics(self.dataMetrics, property, Object.keys(data[0])) } }); } } let getLastChild = (data) => { if (data.children) { child = getLastChild(data.children[0]) } else { child = data } return child } let generateInterface = (self, metrics, parent) => { self.interface = document.createElement('a-entity') self.interface.id = "babia-menu" let posY = 0 let posX = 0 let maxX = 0 // Custom attribute changes if (self.data.customAttributeSwitch.length > 0) { if (self.data.customAttributeLabel) { self.customAttributesLabel = {} self.data.customAttributeLabel.forEach(pair => { let el = pair.split(":") Object.defineProperty(self.customAttributesLabel, el[0], { value: el[1] }) //self.customAttributeLabel[el[0]] = el[1] }); } let button = createProperty("Atrributes", posX, posY) self.interface.appendChild(button) self.data.customAttributeSwitch.forEach((data, i) => { let attributeValue = data.split(":") posX += 3.25 let button = createAttributeSelect(self, attributeValue, posX, posY) button.classList.add("babiaxraycasterclass") self.interface.appendChild(button) // Two lines if (((i + 1) % self.data.maxPerRow) == 0) { --posY posX = 0 } }); --posY --posY if (maxX < posX) { maxX = posX } posX = 0 } if (self.targetComponent.el.components['babia-network'] && (self.targetComponent.el.components['babia-network'].data.nodesFrom || self.targetComponent.el.components['babia-network'].data.nodes)) { // Nodes files if (self.nodesQueriers.length > 1) { let button = createProperty("Nodes", posX, posY) self.interface.appendChild(button) self.nodesQueriers.forEach((data, i) => { posX += 3.25 let button = createDataSelect(self, data, posX, posY, 'nodes') button.classList.add("babiaxraycasterclass") self.interface.appendChild(button) // Two lines if (((i + 1) % self.data.maxPerRow) == 0) { --posY posX = 0 } }); } --posY posY = posY - self.data.linesSeparation if (maxX < posX) { maxX = posX } posX = 0 // Links files if (self.linksQueriers.length > 1) { let button = createProperty("Links", posX, posY) self.interface.appendChild(button) self.linksQueriers.forEach((data, i) => { posX += 3.25 let button = createDataSelect(self, data, posX, posY, 'links') button.classList.add("babiaxraycasterclass") self.interface.appendChild(button) // Two lines if (((i + 1) % self.data.maxPerRow) == 0) { --posY posX = 0 } }); } --posY posY = posY - self.data.linesSeparation if (maxX < posX) { maxX = posX } posX = 0 // Properties and metrics metrics.nodes.forEach(property => { let button = createProperty(property.property, posX, posY) self.interface.appendChild(button) property.metrics.forEach((metric, i) => { posX += 3.25 let button = createMetric(self, property.property, metric, posX, posY) button.classList.add("babiaxraycasterclass") self.interface.appendChild(button) // Two lines if (((i + 1) % self.data.maxPerRow) == 0) { --posY posX = 0 } }); --posY if (maxX < posX) { maxX = posX } posX = 0 }); metrics.links.forEach(property => { let button = createProperty(property.property, posX, posY) self.interface.appendChild(button) property.metrics.forEach((metric, i) => { posX += 3.25 let button = createMetric(self, property.property, metric, posX, posY) button.classList.add("babiaxraycasterclass") self.interface.appendChild(button) // Two lines if (((i + 1) % self.data.maxPerRow) == 0) { --posY posX = 0 } }); --posY if (maxX < posX) { maxX = posX } posX = 0 }); } else { // Data files if (self.dataQueriers.length > 1) { let button = createProperty(self.data.customQuerierLabel, posX, posY) self.interface.appendChild(button) self.dataQueriers.forEach(data => { posX += 3.25 let button = createDataSelect(self, data, posX, posY) button.classList.add("babiaxraycasterclass") self.interface.appendChild(button) }); } --posY posY = posY - self.data.linesSeparation if (maxX < posX) { maxX = posX } posX = 0 // Properties and metrics metrics.forEach(property => { let button = createProperty(property.property, posX, posY) self.interface.appendChild(button) property.metrics.forEach((metric, i) => { posX += 3.25 let button = createMetric(self, property.property, metric, posX, posY) button.classList.add("babiaxraycasterclass") self.interface.appendChild(button) // Two lines if (((i + 1) % self.data.maxPerRow) == 0) { --posY posX = 0 } }); --posY posY = posY - self.data.linesSeparation if (maxX < posX) { maxX = posX } posX = 0 }); } self.interface.width = maxX + 3; self.interface.height = Math.abs(posY) self.interface.setAttribute('position', { x: -self.interface.width / 2, y: self.interface.height, z: 0 }) parent.appendChild(self.interface) return self.interface } let createMetric = (self, property, metric, positionX, positionY) => { let entity = document.createElement('a-box') entity.property = property entity.metric = metric entity.classList.add("babiaxraycasterclass") entity.setAttribute('position', { x: positionX, y: positionY, z: 0 }) entity.setAttribute('rotation', { x: 0, y: 0, z: 0 }) entity.setAttribute('height', 0.8) entity.setAttribute('width', 3) entity.setAttribute('depth', 0.01) let text = document.createElement('a-entity') text.setAttribute('text', { 'value': metric, 'align': 'center', 'width': '10', 'color': 'black' }) text.setAttribute('position', "0 0 0.01") entity.appendChild(text) if (self.targetComponent.data[property] == metric) { entity.setAttribute('color', '#555555') } selection_events(entity, self.targetComponent, false) return entity } let selection_events = (entity, visualizer, isData) => { entity.addEventListener('mouseenter', function () { entity.children[0].setAttribute('text', { color: '#FFFFFF' }) entity.setAttribute('color', '#333333') }); if (isData) { entity.addEventListener('mouseleave', function () { entity.children[0].setAttribute('text', { color: 'black' }) if (visualizer.data.from == entity.from) { entity.setAttribute('color', '#555555') } else if (visualizer.data.from == "" || visualizer.data.from != entity.from) { entity.setAttribute('color', '#FFFFFF') } }); } else { entity.addEventListener('mouseleave', function () { entity.children[0].setAttribute('text', { color: 'black' }) if (visualizer.data[entity.property] == entity.metric) { entity.setAttribute('color', '#555555') } else { entity.setAttribute('color', '#FFFFFF') } }); } entity.addEventListener('click', function () { // Change parameters if (entity.property && entity.metric) { // When change from width/depth to area or vice-versa to boats component if (visualizer.attrName == 'babia-boats' && entity.property == "area") { visualizer.el.removeAttribute(visualizer.attrName, 'width') visualizer.el.removeAttribute(visualizer.attrName, 'depth') } else if (visualizer.attrName == 'babia-boats' && (entity.property == "width" || entity.property == "depth") && visualizer.el.getAttribute('babia-boats').area) { visualizer.el.removeAttribute(visualizer.attrName, 'area') if (entity.property == "width") { visualizer.el.setAttribute(visualizer.attrName, 'depth', entity.metric) } else { visualizer.el.setAttribute(visualizer.attrName, 'width', entity.metric) } } visualizer.el.setAttribute(visualizer.attrName, entity.property, entity.metric) // Change selected querier in visualializer (from) } else if (entity.from) { visualizer.el.setAttribute(visualizer.attrName, "from", entity.from) } else if (entity.nodes) { visualizer.el.setAttribute(visualizer.attrName, 'nodesFrom', entity.nodes) } else if (entity.links) { visualizer.el.setAttribute(visualizer.attrName, 'linksFrom', entity.links) } }); } let createProperty = (property, positionX, positionY) => { let entity = document.createElement('a-plane') entity.setAttribute('position', { x: positionX, y: positionY, z: 0 }) entity.setAttribute('rotation', { x: 0, y: 0, z: 0 }) entity.setAttribute('height', 0.8) entity.setAttribute('width', 3) entity.setAttribute('text', { 'value': property, 'align': 'center', 'width': '10', 'color': '#FFFFFF' }) entity.setAttribute('color', 'black') return entity } let createDataSelect = (self, id, positionX, positionY, networkType) => { let entity = document.createElement('a-box') if (networkType == 'nodes') { entity.nodes = id } else if (networkType == 'links') { entity.links = id } else { entity.from = id; } entity.classList.add("babiaxraycasterclass") entity.setAttribute('position', { x: positionX, y: positionY, z: 0 }) entity.setAttribute('rotation', { x: 0, y: 0, z: 0 }) entity.setAttribute('height', 0.8) entity.setAttribute('width', 3) entity.setAttribute('depth', 0.01) let text = document.createElement('a-entity') text.setAttribute('text', { 'value': id, 'align': 'center', 'width': '10', 'color': 'black' }) text.setAttribute('position', "0 0 0.01") entity.appendChild(text) if (self.targetComponent.prodComponent && self.targetComponent.prodComponent.el.id == id) { entity.setAttribute('color', '#555555') } else { entity.setAttribute('color', '#FFFFFF') } selection_events(entity, self.targetComponent, true) return entity } let createAttributeSelect = (self, [name, value], positionX, positionY) => { let entity = document.createElement('a-box') entity.classList.add("babiaxraycasterclass") entity.setAttribute('position', { x: positionX, y: positionY, z: 0 }) entity.setAttribute('rotation', { x: 0, y: 0, z: 0 }) entity.setAttribute('height', 0.8) entity.setAttribute('width', 3) entity.setAttribute('depth', 0.01) let text = document.createElement('a-entity') let textToButton = name if (self.customAttributesLabel && self.customAttributesLabel[name]) { textToButton = self.customAttributesLabel[name] } text.setAttribute('text', { 'value': textToButton, 'align': 'center', 'width': '10', 'color': 'white' }) text.setAttribute('position', "0 0 0.01") entity.appendChild(text) entity.setAttribute('color', 'blue') // Change attributes entity.addEventListener('click', function () { let attrsComponent = self.targetComponent.el.getAttribute(self.targetComponent.attrName) if (attrsComponent[name] && attrsComponent[name] == value) { self.targetComponent.el.removeAttribute("babia-boats", name) } else { self.targetComponent.el.setAttribute("babia-boats", name, value) } }) return entity } let openCloseMenu = (hand_id, entity_menu) => { let menu_opened = true let entity_hand = document.getElementById(hand_id) entity_hand.addEventListener('gripdown', function () { if (menu_opened) { menu_opened = false entity_menu.setAttribute('visible', false) } else { menu_opened = true entity_menu.setAttribute('visible', true) } }) }