UNPKG

aframe-babia-components

Version:

A data visualization set of components for A-Frame.

232 lines (196 loc) 9.39 kB
AFRAME.registerComponent('babia-html', { schema: { html: { type: 'string' }, distanceLevels: { type: 'float', default: 0.7 }, renderHTML: { type: 'boolean', default: false }, renderHTMLOnlyLeafs: { type: 'boolean', default: false }, }, babiaDiv: null, clickedBoxes: new Map(), init: function () { // let el = this.el; const data = this.data; // Create an observer to detect the addition of the div to the DOM this.observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes) { mutation.addedNodes.forEach((node) => { if (node.id === 'babiaHtmlDiv') { console.log('A div with id "babiaHtmlDiv" has been rendered in the DOM'); for (let i = 0; i < node.children.length; i++) { this.processNodeNoOffset(node.children[i], null, 0); } // Disconnect the observer since the div has been added this.observer.disconnect(); } }); } }); }); // Configuring the observer to observe changes to the body of the document this.observer.observe(document.body, { childList: true, subtree: true }); // Insert the HTML in order to render it babiaDiv = document.createElement('div'); babiaDiv.setAttribute('id', 'babiaHtmlDiv'); babiaDiv.innerHTML = data.html; document.body.appendChild(babiaDiv); }, processNodeNoOffset: function (node, firstOffsetToDelete, childrenLevel) { const el = this.el; const rect = node.getBoundingClientRect(); const NODE_SCALAR = 0.01; // Remove the first Y and X offset, since the HTML is rendered below the scene if (!firstOffsetToDelete) { firstOffsetToDelete = { x: rect.left * NODE_SCALAR, y: rect.top * NODE_SCALAR } } // Calculate positions, adjusting the center of the box and removing the firstOffset const offsetX = rect.right * NODE_SCALAR - (rect.width * NODE_SCALAR) / 2 - firstOffsetToDelete.x; const offsetY = -rect.bottom * NODE_SCALAR + (rect.height * NODE_SCALAR) / 2 + firstOffsetToDelete.y; // Create box const box = document.createElement('a-box'); box.setAttribute('position', `${offsetX} ${offsetY} ${childrenLevel * this.data.distanceLevels}`); box.setAttribute('width', rect.width * NODE_SCALAR); box.setAttribute('height', rect.height * NODE_SCALAR); box.setAttribute('depth', 0.01); // Make box clickable box.classList.add("babiaxraycasterclass") // Store the HTML content in the box for later use box.setAttribute('html-content', node.outerHTML); // Add mouseenter and mouseleave events box.addEventListener('mouseenter', () => { if (!box.classList.contains('clicked')) { this.showHtmlContent(box, node.outerHTML, 'temp'); } }); box.addEventListener('mouseleave', () => { if (!box.classList.contains('clicked')) { this.removeHtmlContent('temp'); } }); // Add click event to toggle the plane with the HTML content box.addEventListener('click', (event) => { event.stopPropagation(); // Prevent mouseenter/leave events from firing if (box.classList.contains('clicked')) { box.classList.remove('clicked'); this.removeHtmlContent('permanent', box); } else { box.classList.add('clicked'); this.showHtmlContent(box, node.outerHTML, 'permanent'); } }); // Select if we want to render the HTML content as a plane or a texture, if not, color if (this.data.renderHTML && typeof html2canvas !== "undefined") { // Render all, or render only the last child if (this.data.renderHTMLOnlyLeafs) { if (node.children.length == 0) { // Found a leaf node html2canvas(node).then(function (canvas) { box.setAttribute('material', { shader: 'flat', src: canvas.toDataURL() }); }) }else{ // Render inner node box.setAttribute('color', this.colors_grad[childrenLevel % this.colors_grad.length]); } } else { // Render node with texture html2canvas(node).then(function (canvas) { box.setAttribute('material', { shader: 'flat', src: canvas.toDataURL() }); }) } } else { // If renderHTML is not set, we use the color attribute to color the box box.setAttribute('color', this.colors_grad[childrenLevel % this.colors_grad.length]); } el.appendChild(box); // Create line to the parent if (childrenLevel > 0) { let line = document.createElement('a-entity') line.setAttribute('line', { start: `${offsetX} ${offsetY} ${childrenLevel * this.data.distanceLevels}`, end: `${offsetX} ${offsetY} ${(childrenLevel * this.data.distanceLevels) - this.data.distanceLevels}`, color: 'yellow' }) el.appendChild(line); } for (let i = 0; i < node.children.length; i++) { this.processNodeNoOffset(node.children[i], firstOffsetToDelete, childrenLevel + 1); } }, showHtmlContent: function (box, htmlContent, type) { // Remove any existing temporary plane if (type === 'temp') { this.removeHtmlContent('temp'); } // Create the white plane const plane = document.createElement('a-plane'); plane.setAttribute('class', `${type}-plane`); let boxposition = box.getAttribute('position') plane.setAttribute('position', { x: boxposition.x, y: boxposition.y + 1, z: boxposition.z + 0.2 }); plane.setAttribute('width', '3'); // Adjust width as needed const lines = htmlContent.split('\n').length; const lineHeight = 0.15; // Adjust based on font size and desired spacing const planeHeight = Math.max(lines * lineHeight, 0.2); plane.setAttribute('height', planeHeight); // Adjust height as needed plane.setAttribute('color', 'white'); // Create the text element const text = document.createElement('a-text'); text.setAttribute('value', htmlContent); text.setAttribute('color', 'black'); text.setAttribute('align', 'left'); text.setAttribute('width', '2.5'); // Adjust text width as needed text.setAttribute('position', '-1 0 0.01'); plane.appendChild(text); this.el.appendChild(plane); // Store a reference to the plane in the box element for later removal if (type === 'permanent') { box.permanentPlane = plane; } }, removeHtmlContent: function (type, box) { if (type === 'temp') { const tempPlane = document.querySelector('.temp-plane'); if (tempPlane) { tempPlane.parentNode.removeChild(tempPlane); } } else if (type === 'permanent' && box) { if (box.permanentPlane) { box.permanentPlane.parentNode.removeChild(box.permanentPlane); box.permanentPlane = null; } // Remove box from clickedBoxes map this.clickedBoxes.delete(box); } }, // Gradient for coloring boxes (depending on depth level in DOM) colors_grad: [ "#4B0082", "#800080", "#B22222", "#A0522D", "#CD5C5C", "#8B008B", "#9932CC", "#FF4500", "#FF8C00", "#B8860B", "#D2691E", "#DAA520", "#ADFF2F", "#7FFF00", "#32CD32", "#00FF00", "#00FA9A", "#40E0D0", "#4682B4", "#1E90FF", "#0000FF", "#4169E1", "#8A2BE2", "#FF00FF", "#FF1493", "#FF69B4", "#FF6347", "#FFA07A", "#FFA500", "#FFD700", "#FFFF00", "#FFFACD", "#F0E68C", "#E6E6FA", "#F0F8FF", "#E0FFFF", "#AFEEEE", "#ADD8E6", "#87CEFA", "#B0C4DE", "#D3D3D3", "#DDA0DD", "#EE82EE", "#F5DEB3", "#FAFAD2", "#FFE4E1", "#FFF0F5", "#F5F5DC", "#FFFFE0", "#FFF8DC", "#F8F8FF", "#FFFFFF" ], update: function () { let data = this.data let oldData = this.previousOldData; if (data.html != oldData.html || data.distanceLevels != oldData.distanceLevels) { // Disconnect the old observer if it exists if (this.observer) { this.observer.disconnect(); } // Remove existing boxes and planes while (this.el.firstChild) { this.el.removeChild(this.el.firstChild); } // Call init again to reinitialize the component this.init(); } } });