aframe-babia-components
Version:
A data visualization set of components for A-Frame.
1,169 lines (1,030 loc) • 81.4 kB
JavaScript
let findProdComponent = require('../others/common').findProdComponent;
let updateFunction = require('../others/common').updateFunction;
const colorsArray = require('../others/common').colors.palettes['categoric'];
const NotiBuffer = require("../../common/noti-buffer").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-boats', {
schema: {
data: { type: 'asset' },
from: { type: 'string' },
border: { type: 'number', default: 0.5 },
width: { type: 'string', default: 'width' },
depth: { type: 'string', default: 'depth' },
area: { type: 'string' },
color: { type: 'string' },
height: { type: 'string', default: 'height' },
maxBuildingHeight: { type: 'number', default: 2 },
minBuildingHeight: { type: 'number', default: 0.03 },
zone_elevation: { type: 'number', default: 0.01 },
building_separation: { type: 'number', default: 0.25 },
extra: { type: 'number', default: 1.0 },
levels: { type: 'number' },
building_color: { type: 'string', default: "#E6B9A1" },
buildingAlpha: { type: 'number', default: 1 },
gradientBaseColor: { type: 'boolean', default: false },
base_color: { type: 'color', default: '#98e690' },
baseAlpha: { type: 'number', default: 1 },
// To add into the doc
height_quarter_legend_box: { type: 'number', default: 11 },
height_quarter_legend_title: { type: 'number', default: 12 },
height_building_legend: { type: 'number', default: 0 },
legend_scale: { type: 'number', default: 1 },
legend_lookat: { type: 'string', default: "[camera]" },
metricsInfoId: { type: 'string', default: "" },
highlightQuarter: { type: 'boolean', default: false },
field: { type: 'string', default: 'uid' },
// Numeric color legend entity to hide/show
numericColorLegendId: { type: 'string' },
// Highlight building by field
highlightBuildingByField: { type: 'string' },
highlightBuildingByFieldColor: { type: 'string', default: 'white' },
// Wireframe & Transparency by repeated IDs
wireframeByRepeatedField: { type: 'string' },
transparent80ByRepeatedField: { type: 'string' },
transparent20ByRepeatedField: { type: 'string' },
// Autoscale when animating or starting
autoscale: { type: 'boolean', default: false },
autoscaleSizeX: { type: 'number', default: 3 },
autoscaleSizeZ: { type: 'number', default: 3 },
//autoscaleSizeY: { type: 'number', default: 2 }
// New layout
treeLayout: { type: 'boolean', default: false },
treeQuartersLevelHeight: { type: 'number', default: 0.2 },
treeFixQuarterHeight: { type: 'boolean', default: false },
treeHideOneSonQuarters: { type: 'boolean', default: false }
},
/**
* Entities with legend activated
*/
entitiesWithLegend: [],
legendsActive: [],
/**
* Querier component target
*/
dataComponent: undefined,
/**
* Where the data is gonna be stored
*/
newData: undefined,
/**
* Where the metaddata is gonna be stored
*/
babiaMetadata: {
id: 0
},
/**
* List of visualization properties
*/
visProperties: ['height', 'area', 'width', 'depth', 'color'],
/**
* Set if component needs multiple instancing.
*/
multiple: false,
/**
*
*/
duration: 2000,
figures_del: [],
figures_in: [],
animation: 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) {
updateFunction(this, oldData)
},
/**
* Already autoscaled
*/
alreadyAutoscaled: false,
autoscaleBoats: function () {
const self = this
// 2 for X and 2 for Y
let bbox = new THREE.Box3().setFromObject(this.el.object3D)
let finalSizeX = bbox.max.x - bbox.min.x
let finalSizeY = bbox.max.y - bbox.min.y
let finalSizeZ = bbox.max.z - bbox.min.z
let currentScale = this.el.getAttribute("scale")
if (!currentScale) {
currentScale = { x: 1, y: 1, z: 1 }
}
this.el.setAttribute("scale", { x: eval(this.data.autoscaleSizeX / finalSizeX) * currentScale.x, y: currentScale.y, z: eval(this.data.autoscaleSizeZ / finalSizeZ) * currentScale.z })
},
/**
* Called on each scene tick.
*/
tick: function (t, delta) {
let self = this;
// First time to autoscale
if (this.data.autoscale && !this.alreadyAutoscaled) {
let bbox = new THREE.Box3().setFromObject(this.el.object3D)
if (bbox.min.x !== Infinity) {
this.autoscaleBoats()
this.alreadyAutoscaled = true
}
}
if (this.animation) {
let t = { x: 0, y: 0, z: 0 };
if ((Date.now() - this.start_time) > this.duration) {
this.animation = false;
this.setFigures(this.figures, t);
// Animation finished, set autoscale if activated
if (this.data.autoscale) {
this.autoscaleBoats()
}
//Reactivate legends, check PERFORMANCE
self.entitiesWithLegend = self.entitiesWithLegend.filter(item => document.getElementById(item.entity.id))
self.entitiesWithLegend.forEach(item => {
let entity = item.entity
let figure = item.figure
if (figure.children) {
// Quarter
entity.legend = generateLegend(figure.name, self.data.legend_scale, self.data.legend_lookat, 'black', 'white');
let worldPos = new THREE.Vector3();
let coordinates = worldPos.setFromMatrixPosition(entity.object3D.matrixWorld);
let coordinatesFinal = {
x: coordinates.x,
y: self.data.height_quarter_legend_title,
z: coordinates.z
}
entity.legend.setAttribute('position', coordinatesFinal)
entity.legend.setAttribute('visible', true);
self.el.parentElement.appendChild(entity.legend)
self.legendsActive.push(entity.legend)
entity.alreadyActive = true
} else {
// Building
entity.legend = generateLegend(figure.name, self.data.legend_scale, self.data.legend_lookat, 'white', 'black', entity.babiaRawData, self.data.height, self.data.area, self.data.depth, self.data.width, self.data.color);
let worldPos = new THREE.Vector3();
let coordinates = worldPos.setFromMatrixPosition(entity.object3D.matrixWorld);
let height_real = new THREE.Box3().setFromObject(entity.object3D)
let coordinatesFinal = {
x: coordinates.x,
y: height_real.max.y + 1 + self.data.height_building_legend,
z: coordinates.z
}
entity.legend.setAttribute('position', coordinatesFinal)
entity.legend.setAttribute('visible', true);
self.el.parentElement.appendChild(entity.legend);
self.legendsActive.push(entity.legend)
entity.alreadyActive = true
}
});
} else {
this.Animation(this.el, this.figures, this.figures_old, delta, t, t);
}
}
},
updateChart: function (items) {
console.log('Data Loaded.');
let t = { x: 0, y: 0, z: 0 };
this.idsToNotRepeatWireframe = []
this.idsToNotRepeat20Transparent = []
this.idsToNotRepeat80Transparent = []
// when animation not finished, delete figures and opa 1 to inserted
if (this.animation) {
this.figures_del.forEach(figure => {
let entity = document.getElementById(figure.id);
if (entity) {
entity.remove();
}
})
this.figures_in.forEach(figure => {
let entity = document.getElementById(figure.id);
if (entity) {
setOpacity(entity, 1.0);
}
})
this.setFigures(this.figures, t);
this.animation = false;
this.figures_del = [];
this.figures_in = [];
}
this.figures_old = this.figures;
this.figures = [];
let el = this.el;
let elements = items
// Calculate Increment
let increment;
if (this.data.levels) {
increment = this.data.border * this.data.extra * this.data.levels;
} else {
// Find last level
let levels = getLevels(elements, 0);
//console.log("Levels:" + levels);
increment = this.data.border * this.data.extra * (levels + 1);
}
// Register all figures before drawing
[x, y, t, this.figures] = this.generateElements(elements, this.figures, t, increment);
// Draw figures
t.x = 0;
t.z = 0;
if (this.figures_old.length == 0) {
// First, delete all elements of the component
while (this.el.firstChild)
this.el.firstChild.remove();
this.drawElements(el, this.figures, t);
} else {
if (this.figures_old !== this.figures) {
this.animation = true;
this.start_time = Date.now();
}
}
},
/**
* For the categoric colors
*/
categoricColorIndex: 0,
categoricColorMaps: {},
generateElements: function (elements, figures, translate, inc) {
const self = this;
var increment = inc;
// Vertical Limits
var limit_up = 0;
var limit_down = 0;
// Horizontal Limits
var limit_right = 0;
var limit_left = 0;
//Position Figure
var posX = 0;
var posY = 0;
// Aux to update the limits
// Save max limit to update last limit in the next step
var max_right = 0;
var max_left = 0;
var max_down = 0;
var max_up = 0;
// control points
var current_vertical = 0;
var current_horizontal = 0;
// Controllers
var up = false;
var down = false;
var left = false;
var right = true;
/**
* Get each element and set its position respectly
* Then save all data in figures array
*/
for (let i = 0; i < elements.length; i++) {
// To do not overwrite newData
let element = {}
if (this.data.width) {
element.width = elements[i][this.data.width] || 0.5
}
if (this.data.height) {
element.height = self.normalizeValues(self.babiaMetadata['heightMin'], self.babiaMetadata['heightMax'], self.data.minBuildingHeight, self.data.maxBuildingHeight, elements[i][this.data.height]) || this.data.minBuildingHeight
}
if (this.data.depth) {
element.depth = elements[i][this.data.depth] || 0.5
}
if (this.data.area) {
element.area = elements[i][this.data.area] || 0.5
}
if (elements[i].children) {
element.children = elements[i].children
this.quarter = true;
var children = [];
var translate_matrix;
// Save Zone's parameters
// Check scale because it should be abosulte, not relative
let scale = self.el.getAttribute("scale")
if (!scale) {
scale = { x: 1, y: 1, z: 1 }
}
element.height = this.data.zone_elevation / scale.y;
increment -= this.data.border * this.data.extra;
// TEST TREE
if (self.data.treeLayout) {
element.children.forEach(el => {
if (!el.children) {
element.treeMetaphorHeight = el[self.data.height]
return
}
});
}
[element.width, element.depth, translate_matrix, children] = this.generateElements(element.children, children, translate_matrix, increment);
translate_matrix.y = element.height;
increment = inc;
}
if (i == 0) {
if (this.data.area && !elements[i].children) {
limit_up += Math.sqrt(element.area) / 2;
limit_down -= Math.sqrt(element.area) / 2;
limit_right += Math.sqrt(element.area) / 2;
limit_left -= Math.sqrt(element.area) / 2;
} else {
limit_up += element.depth / 2;
limit_down -= element.depth / 2;
limit_right += element.width / 2;
limit_left -= element.width / 2;
}
//console.log("==== RIGHT SIDE ====");
current_horizontal = limit_up + this.data.building_separation / 2;
} else if (element.height > 0) {
if (up) {
[current_vertical, posX, posY, max_up] = this.UpSide(element, limit_up, current_vertical, max_up);
if (current_vertical > limit_right) {
current_vertical += this.data.building_separation / 2;
max_right = current_vertical;
up = false;
right = true;
if (max_left < limit_left) {
limit_left = max_left;
}
current_horizontal = limit_up + this.data.building_separation / 2;
}
} else if (right) {
[current_horizontal, posX, posY, max_right] = this.RightSide(element, limit_right, current_horizontal, max_right);
if (current_horizontal < limit_down) {
current_horizontal += this.data.building_separation / 2;
max_down = current_horizontal;
right = false;
down = true;
if (max_up > limit_up) {
limit_up = max_up;
}
current_vertical = limit_right + this.data.building_separation / 2;
}
} else if (down) {
[current_vertical, posX, posY, max_down] = this.DownSide(element, limit_down, current_vertical, max_down);
if (current_vertical < limit_left) {
current_vertical -= this.data.building_separation / 2;
max_left = current_vertical;
down = false;
left = true;
if (max_right > limit_right) {
limit_right = max_right;
}
current_horizontal = limit_down - this.data.building_separation / 2;
}
} else if (left) {
[current_horizontal, posX, posY, max_left] = this.LeftSide(element, limit_left, current_horizontal, max_left);
if (current_horizontal > limit_up) {
current_horizontal -= this.data.building_separation / 2;
max_up = current_horizontal;
left = false;
up = true;
if (max_down < limit_down) {
limit_down = max_down;
}
current_vertical = limit_left - this.data.building_separation / 2;
}
}
}
// Save information about the figure
let figure
if (elements[i].children) {
figure = {
id: "boat-" + elements[i][this.data.field],
name: elements[i][this.data.field],
posX: posX,
posY: posY,
width: element.width,
height: element.height,
depth: element.depth,
children: children,
alpha: self.data.baseAlpha,
translate_matrix: translate_matrix
}
// TEST TREE
if (self.data.treeLayout) {
figure.treeMetaphorHeight = element.treeMetaphorHeight
}
// Gradient color
let hierarchyLevel = figure.name.split("/").length - 1
figure.hierarchyLevel = hierarchyLevel
figure.renderOrder = hierarchyLevel
if (self.data.gradientBaseColor) {
figure.color = greenColorsurface(Math.round(self.normalizeValues(1, self.babiaMetadata['maxLevels'], 15, 100, hierarchyLevel)));
} else {
figure.color = self.data.base_color;
}
} else {
if (this.data.area) {
figure = {
id: "boat-" + elements[i][this.data.field],
name: (elements[i].name) ? elements[i].name : elements[i][this.data.field],
posX: posX,
posY: posY,
width: Math.sqrt(element.area),
height: element.height,
alpha: self.data.buildingAlpha,
depth: Math.sqrt(element.area)
}
} else {
figure = {
id: "boat-" + elements[i][this.data.field],
name: (elements[i].name) ? elements[i].name : elements[i][this.data.field],
posX: posX,
posY: posY,
width: element.width,
height: element.height,
alpha: self.data.buildingAlpha,
depth: element.depth
}
}
if (typeof elements[i][self.data.color] === 'number') {
figure.color = heatMapColorforValue(elements[i][self.data.color], self.babiaMetadata['color_max'], self.babiaMetadata['color_min'])
} else if (typeof elements[i][self.data.color] === 'string') {
// Categoric color
if (elements[i][self.data.color] in self.categoricColorMaps) {
figure.color = self.categoricColorMaps[elements[i][self.data.color]]
} else {
self.categoricColorMaps[elements[i][self.data.color]] = colorsArray[self.categoricColorIndex]
figure.color = colorsArray[self.categoricColorIndex]
self.categoricColorIndex = self.categoricColorIndex + 1
if (self.categoricColorIndex >= colorsArray.length) {
self.categoricColorIndex = 0
}
}
}
}
figure.rawData = elements[i]
// If transparency by a field on buildings
// Put transparent 80% to those buildings that share same value for a field selected
if (self.data.transparent80ByRepeatedField && (!figure.children && self.idsToNotRepeat80Transparent.includes(figure.rawData[self.data.transparent80ByRepeatedField]))) {
figure.alpha = 0.8
figure.renderOrder = 2000
} else {
self.idsToNotRepeat80Transparent.push(figure.rawData[self.data.transparent80ByRepeatedField])
}
// Put transparent 20% to those buildings that share same value for a field selected
if (self.data.transparent20ByRepeatedField && (!figure.children && self.idsToNotRepeat20Transparent.includes(figure.rawData[self.data.transparent20ByRepeatedField]))) {
figure.alpha = 0.45
figure.renderOrder = 2000
} else {
self.idsToNotRepeat20Transparent.push(figure.rawData[self.data.transparent20ByRepeatedField])
}
// Put wireframe to those buildings that share same value for a field selected
if (self.data.wireframeByRepeatedField && (!figure.children && self.idsToNotRepeatWireframe.includes(figure.rawData[self.data.wireframeByRepeatedField]))) {
figure.wireframe = true
} else {
figure.wireframe = false
self.idsToNotRepeatWireframe.push(figure.rawData[self.data.wireframeByRepeatedField])
}
figures.push(figure);
}
// Check and update last limits
if (max_down < limit_down) {
limit_down = max_down;
}
if (max_left < limit_left) {
limit_left = max_left;
}
if (max_up > limit_up) {
limit_up = max_up;
}
if (max_right > limit_right) {
limit_right = max_right;
}
if (current_vertical < limit_left) {
limit_left = current_vertical + this.data.building_separation / 2;
}
if (current_vertical > limit_right) {
limit_right = current_vertical - this.data.building_separation / 2;;
}
if (current_horizontal > limit_up) {
limit_up = current_horizontal - this.data.building_separation / 2;;
}
if (current_horizontal < limit_down) {
limit_down = current_horizontal + this.data.building_separation / 2;;
}
// Calculate translate of the center, width and depth of the zone
var width = Math.abs(limit_left) + Math.abs(limit_right);
var depth = Math.abs(limit_down) + Math.abs(limit_up);
width += 2 * increment;
depth += 2 * increment;
var translate_x = limit_left + width / 2 - increment;
var translate_z = limit_down + depth / 2 - increment;
translate = {
x: translate_x,
y: 0,
z: translate_z,
};
return [width, depth, translate, figures];
},
// If wireframerepeated activated
idsToNotRepeatWireframe: [],
idsToNotRepeat80Transparent: [],
idsToNotRepeat20Transparent: [],
drawElements: function (element, figures, translate) {
const self = this
for (let i in figures) {
let height = figures[i].height;
let x = figures[i].posX;
let y = figures[i].posY;
let position = {
x: x - translate.x,
y: (height / 2 + translate.y / 2),
z: -y + translate.z
}
// TEST TREE
if (self.data.treeLayout) {
// // Hide quarters that has not children
if (self.data.treeHideOneSonQuarters && (figures[i].children && figures[i].children.length === 1)) {
figures[i].alphaTest = 1
figures[i].dontAddEvents = true
figures[i].children[0].dontAddLine = true
}
if (self.data.treeFixQuarterHeight) {
// Fix position y for quarters
// Check if not undefined because if it is 0, it returns false (thx JS)
if (figures[i].treeMetaphorHeight !== undefined) {
position.y = (height / 2 + translate.y / 2) + self.data.treeQuartersLevelHeight
} else {
position.y = ((height / 2 + translate.y / 2)) - self.data.treeQuartersLevelHeight - self.data.zone_elevation
if (figures[i].height < self.data.treeQuartersLevelHeight) {
// If flag to hide activated and the parent has only one son, not needed to draw the line
if (!(self.data.treeHideOneSonQuarters && figures[i].dontAddLine)) {
// First get top
let topBuildingY = position.y
// Get where the quarter is
let quarterPosY = position.y + (self.data.treeQuartersLevelHeight - (height / 2))
// Draw the line in the space
let line = document.createElement('a-entity')
line.setAttribute('class', 'babiaboatstreelines')
line.setAttribute('line', {
start: { x: position.x, y: topBuildingY, z: position.z },
end: { x: position.x, y: quarterPosY, z: position.z },
color: 'yellow'
})
element.appendChild(line)
}
}
}
} else {
// Quarters on top of the buildings
if (figures[i].treeMetaphorHeight !== undefined) {
position.y = ((self.normalizeValues(self.babiaMetadata['heightMin'], self.babiaMetadata['heightMax'], self.data.minBuildingHeight, self.data.maxBuildingHeight, figures[i].treeMetaphorHeight)))
} else {
position.y = ((height / 2 + translate.y / 2)) - height - 0.001
}
}
}
let entity = this.createElement(figures[i], position);
this.addEvents(entity, figures[i]);
element.appendChild(entity);
}
},
RightSide: function (element, limit_right, current_horizontal, max_right) {
let separation = parseFloat(this.data.building_separation);
let width, depth;
if (this.data.area && !element.children) {
width = Math.sqrt(element.area);
depth = Math.sqrt(element.area) + separation;
} else {
width = parseFloat(element.width);
depth = parseFloat(element.depth) + separation;
}
// Calculate position
let posX = limit_right + (width / 2) + separation;
let posY = current_horizontal - (depth / 2);
// Calculate states
current_horizontal -= depth;
let total_x = limit_right + width + separation;
if (total_x > max_right) {
max_right = total_x;
}
return [current_horizontal, posX, posY, max_right];
},
DownSide: function (element, limit_down, current_vertical, max_down) {
let separation = parseFloat(this.data.building_separation);
let width, depth;
if (this.data.area && !element.children) {
width = Math.sqrt(element.area) + separation;
depth = Math.sqrt(element.area);
} else {
width = parseFloat(element.width) + separation;
depth = parseFloat(element.depth);
}
// Calculate position
let posX = current_vertical - (width / 2);
let posY = limit_down - (depth / 2) - separation;
// Calculate state
current_vertical -= width;
let total_y = limit_down - depth - separation;
if (total_y < max_down) {
max_down = total_y;
}
return [current_vertical, posX, posY, max_down];
},
LeftSide: function (element, limit_left, current_horizontal, max_left) {
let separation = parseFloat(this.data.building_separation);
let width, depth;
if (this.data.area && !element.children) {
width = Math.sqrt(element.area);
depth = Math.sqrt(element.area) + separation;
} else {
width = parseFloat(element.width);
depth = parseFloat(element.depth) + separation;
}
// Calculate position
let posX = limit_left - (width / 2) - separation;
let posY = current_horizontal + (depth / 2);
// Calculate state
current_horizontal += depth;
let total_x = limit_left - width - separation;
if (total_x < max_left) {
max_left = total_x;
}
return [current_horizontal, posX, posY, max_left];
},
UpSide: function (element, limit_up, current_vertical, max_up) {
let separation = parseFloat(this.data.building_separation);
let width, depth;
if (this.data.area && !element.children) {
width = Math.sqrt(element.area) + separation;
depth = Math.sqrt(element.area);
} else {
width = parseFloat(element.width) + separation;
depth = parseFloat(element.depth);
}
// Calculate position
let posX = current_vertical + (width / 2);
let posY = limit_up + (depth / 2) + separation;
// Calculate state
current_vertical += width;
let total_y = limit_up + depth + separation;
if (total_y > max_up) {
max_up = total_y;
}
return [current_vertical, posX, posY, max_up];
},
Animation: function (element, figures, figures_old, delta, translate, translate_old) {
let self = this
let new_time = Date.now();
let entity;
// First, remove all the legends that are active
self.legendsActive.forEach(legend => {
legend.remove()
});
self.legendsActive = []
for (let i in figures) {
if (document.getElementById(figures[i].id)) {
// If exists
entity = document.getElementById(figures[i].id);
// Creating... (next ticks)
if (figures[i].inserted) {
//TODO: This code increments the opacity in the animation part, but it adds several performance issues
//Increment opacity
// let opa_inc = delta / this.duration;
// let opacity = parseFloat(entity.getAttribute('material').opacity);
// if (opacity + opa_inc < 1) {
// opacity += opa_inc;
// } else {
// opacity = 1.0;
// figures[i].inserted = false;
// }
//setOpacity(entity, opacity);
// If animation stops before finish
let cond = 'id=' + figures[i].id
if (findIndex(self.figures_in, cond) < 0) {
self.figures_in.push(figures[i])
}
} else {
// find index in old_figures
let cond = 'id=' + figures[i].id
let index = findIndex(figures_old, cond)
if (index < 0) {
// encontrar el elemento que no esta en la escena con id y darle opacidad
// y cambiar sus propiedades a la nueva si es necesario
figures[i].inserted = true;
if (entity.getAttribute('width') != figures[i].width) {
entity.setAttribute('width', figures[i].width);
}
if (entity.getAttribute('height') != figures[i].height) {
entity.setAttribute('height', figures[i].height);
}
if (entity.getAttribute('depth') != figures[i].depth) {
entity.setAttribute('depth', figures[i].depth);
}
if (entity.getAttribute('color') != figures[i].color) {
entity.setAttribute('color', figures[i].color);
}
if (entity.getAttribute('material').wireframe != figures[i].wireframe) {
entity.setAttribute('material', 'wireframe', figures[i].wireframe);
entity.setAttribute('material', 'wireframeLinewidth', 0.1);
}
} else {
// RESIZE
this.resize(entity, new_time, delta, figures[i], figures_old[index]);
// TRASLATE
this.traslate(entity, new_time, delta, figures[i], figures_old[index], translate, translate_old);
// COLOR
if (entity.getAttribute('color') != figures[i].color && !self.inEntitiesWithLegend(entity)) {
entity.setAttribute('color', figures[i].color);
}
if (entity.getAttribute('material').wireframe != figures[i].wireframe) {
entity.setAttribute('material', 'wireframe', figures[i].wireframe);
entity.setAttribute('material', 'wireframeLinewidth', 0.1);
}
if (entity.getAttribute('material').opacity != figures[i].alpha) {
entity.setAttribute('material', 'opacity', figures[i].alpha);
entity.object3D.renderOrder = figures[i].renderOrder
}
if (figures[i].children) {
this.Animation(entity, figures[i].children, figures_old[index].children, delta, figures[i].translate_matrix, figures_old[index].translate_matrix);
} else {
// Building color if changed, force to the new one
if (figures[i].rawData[self.data.color] !== figures_old[index].rawData[self.data.color]) {
// Color numeric or categoric
let color
if (typeof figures[i].rawData[self.data.color] === 'number') {
color = heatMapColorforValue(figures[i].rawData[self.data.color], self.babiaMetadata['color_max'], self.babiaMetadata['color_min'])
} else if (typeof figures[i].rawData[self.data.color] === 'string') {
// Categoric color
if (figures[i].rawData[self.data.color] in self.categoricColorMaps) {
color = self.categoricColorMaps[figures[i].rawData[self.data.color]]
} else {
self.categoricColorMaps[figures[i].rawData[self.data.color]] = colorsArray[self.categoricColorIndex]
color = colorsArray[self.categoricColorIndex]
self.categoricColorIndex = self.categoricColorIndex + 1
if (self.categoricColorIndex >= colorsArray.length) {
self.categoricColorIndex = 0
}
}
}
let oldColor = entity.getAttribute('color')
if (color !== oldColor) {
entity.setAttribute('color', color)
}
}
}
}
}
} else {
// CREATE NEW
position = {
x: figures[i].posX - translate.x,
y: (figures[i].height / 2 + translate.y / 2),
z: -figures[i].posY + translate.z
}
let new_entity = this.createElement(figures[i], position);
this.addEvents(new_entity, figures[i]);
//TODO: bad perfomance when Opacity 0 at the beginning
//setOpacity(new_entity, 0);
// New building and quarter, it appears with 0.5 opacity
setOpacity(new_entity, 0.5);
element.appendChild(new_entity);
figures[i].inserted = true;
}
}
// Delete figures
let opa_dec = delta / this.duration;
for (let i in figures_old) {
let deleted = false;
let cond = 'id=' + figures_old[i].id
let index = findIndex(figures, cond)
if (index < 0) {
if (findIndex(self.figures_del, cond) < 0) {
self.figures_del.push(figures_old[i])
}
let entity_del = document.getElementById(figures_old[i].id);
if (entity_del) {
//TODO: Bad perfomance issue when changing dinamically the opacity, better to just make it non vissible
entity_del.object3D.visible = false
entity_del.remove();
// let opacity = parseFloat(entity_del.components.material.opacity);
// if (opacity - opa_dec > 0) {
// opacity -= opa_dec;
// } else {
// opacity = 0.0;
// deleted = true;
// self.figures_del.pop(figures_old[i]);
// }
// setOpacity(entity_del, opacity);
// if (deleted) {
// entity_del.remove();
// }
}
}
}
},
inEntitiesWithLegend(obj) {
let i;
for (i = 0; i < this.entitiesWithLegend.length; i++) {
if (this.entitiesWithLegend[i].entity === obj) {
return true;
}
}
return false;
},
setFigures: function (figures, translate) {
const self = this
figures.forEach(figure => {
let entity = document.getElementById(figure.id);
if (entity) {
if (entity.getAttribute('width') != figure.width) {
entity.setAttribute('width', figure.width);
}
if (entity.getAttribute('height') != figure.height) {
entity.setAttribute('height', figure.height);
}
if (entity.getAttribute('depth') != figure.depth) {
entity.setAttribute('depth', figure.depth);
}
// If legend active, don't force change color, put it as the color before
if (self.inEntitiesWithLegend(entity)) {
entity.setAttribute('babiaxrFirstColor', figure.color)
} else {
if (entity.getAttribute('color') != figure.color) {
entity.setAttribute('color', figure.color);
}
}
//TODO: Full opacity because it was in 0.5 if new building/quarter
if (entity.components.material.data.opacity < 1) {
setOpacity(entity, 1)
}
// Quarters to alpha if tree fix position
if (entity.getAttribute('material').opacity != figure.alpha) {
entity.setAttribute('material', 'opacity', figure.alpha);
}
// TEST TREE
if (self.data.treeLayout) {
// Hide quarters that has not children
if (self.data.treeHideOneSonQuarters && (figure.children && figure.children.length === 1)) {
figure.alphaTest = 1
figure.dontAddEvents = true
figure.children[0].dontAddLine = true
}
if (self.data.treeFixQuarterHeight) {
// Fix position y for quarters
if (figure.treeMetaphorHeight !== undefined) {
entity.object3D.position.set(
figure.posX - translate.x,
(figure.height / 2 + translate.y / 2) + self.data.treeQuartersLevelHeight,
(- figure.posY + translate.z),
)
} else {
let positionYFixed = ((figure.height / 2 + translate.y / 2)) - self.data.treeQuartersLevelHeight - self.data.zone_elevation
let positionX = figure.posX - translate.x
let positionZ = (- figure.posY + translate.z)
entity.object3D.position.set(
positionX,
positionYFixed,
positionZ,
)
// DRAW LINE WHEN DOES NOT ACHIEVE THE QUARTER (TOO LOW)
if (figure.height < self.data.treeQuartersLevelHeight) {
// If flag to hide activated and the parent has only one son, not needed to draw the line
if (!(self.data.treeHideOneSonQuarters && figure.dontAddLine)) {
// First get top
let topBuildingY = positionYFixed
// Get where the quarter is
let quarterPosY = positionYFixed + (self.treeQuartersLevelHeight - figure.height / 2)
// Draw the line in the space
let line = document.createElement('a-entity')
line.setAttribute('class', 'babiaboatstreelines')
line.setAttribute('line', {
start: { x: positionX, y: topBuildingY, z: positionZ },
end: { x: positionX, y: quarterPosY, z: positionZ },
color: 'yellow'
})
entity.parentElement.appendChild(line)
}
}
}
} else {
if (figure.treeMetaphorHeight !== undefined) {
entity.object3D.position.set(
figure.posX - translate.x,
((self.normalizeValues(self.babiaMetadata['heightMin'], self.babiaMetadata['heightMax'], self.data.minBuildingHeight, self.data.maxBuildingHeight, figure.treeMetaphorHeight))),
(- figure.posY + translate.z),
)
} else {
entity.object3D.position.set(
figure.posX - translate.x,
((figure.height / 2 + translate.y / 2)) - figure.height - 0.001,
(- figure.posY + translate.z),
)
}
}
} else {
// Lo de no tree, lo que estaba antes
entity.object3D.position.set(
figure.posX - translate.x,
((parseFloat(figure.height) + translate.y) / 2),
(- figure.posY + translate.z),
)
}
if (figure.children) {
this.setFigures(figure.children, figure.translate_matrix);
}
}
});
},
resize: function (entity, new_time, delta, figure, figure_old) {
const self = this
if (((new_time - this.start_time) < this.duration) &&
((figure.width != figure_old.width) ||
(figure.height != figure_old.height) ||
(figure.depth != figure_old.depth))) {
// Calulate increment
let diff_width = Math.abs(figure.width - figure_old.width);
let diff_height = Math.abs(figure.height - figure_old.height);
let diff_depth = Math.abs(figure.depth - figure_old.depth);
let inc_width = (delta * diff_width) / this.duration;
let inc_height = (delta * diff_height) / this.duration;
let inc_depth = (delta * diff_depth) / this.duration;
let last_width = parseFloat(entity.getAttribute('width'));
let last_height = parseFloat(entity.getAttribute('height'));
let last_depth = parseFloat(entity.getAttribute('depth'));
let new_width;
if (figure.width - figure_old.width < 0) {
new_width = last_width - inc_width;
} else {
new_width = last_width + inc_width;
}
let new_height;
if (figure.height - figure_old.height < 0) {
new_height = last_height - inc_height;
} else {
new_height = last_height + inc_height;
}
let new_depth;
if (figure.depth - figure_old.depth < 0) {
new_depth = last_depth - inc_depth;
} else {
new_depth = last_depth + inc_depth;
}
// Update size
entity.setAttribute('width', new_width);
entity.setAttribute('height', new_height);
entity.setAttribute('depth', new_depth);
entity.babiaRawData = figure.rawData
//Check if has transparent box as a quarter
if (entity.classList.contains('babiaquarterboxactivated')) {
entity.childNodes.forEach(child => {
if (child.classList.contains('babiaquarterlegendbox')) {
child.setAttribute('geometry', 'width', new_width);
child.setAttribute('geometry', 'depth', new_depth);
child.setAttribute('material', 'opacity', 0.4);
}
});
}
} else if (((new_time - this.start_time) > this.duration) &&
((figure.width != figure_old.width) ||
(figure.height != figure_old.height) ||
(figure.depth != figure_old.depth))) {
entity.setAttribute('width', figure.width);
entity.setAttribute('height', figure.height);
entity.setAttribute('depth', figure.depth);
entity.babiaRawData = figure.rawData
//Check if has transparent box as a quarter
if (entity.classList.contains('babiaquarterboxactivated')) {
entity.childNodes.forEach(child => {
if (child.classList.contains('babiaquarterlegendbox')) {
child.setAttribute('geometry', 'width', new_width);
child.setAttribute('geometry', 'depth', new_depth);
child.setAttribute('material', 'opacity', 0.4);
}
});
}
}
},
traslate: function (entity, new_time, delta, figure, figure_old, translate, translate_old) {
let dist_x = (figure_old.posX - translate_old.x) - (figure.posX - translate.x);
let dist_y = (figure.height - figure_old.height);
let dist_z = (figure_old.posY - translate_old.z) - (figure.posY - translate.z);
if (dist_x != 0 || dist_y != 0 || dist_z != 0) {
if ((new_time - this.start_time) < this.duration) {
// Calculate increment positions
let inc_x = (delta * dist_x) / this.duration;
let inc_y = (delta * dist_y) / (2 * this.duration);
let inc_z = (delta * dist_z) / this.duration;
let last_x = parseFloat(entity.object3D.position.x);
let last_y = parseFloat(entity.object3D.position.y);
let last_z = parseFloat(entity.object3D.position.z);
let new_x = last_x - inc_x;
let new_y = last_y + inc_y;
let new_z = last_z + inc_z;
// Update entity
entity.object3D.position.set(
new_x,
new_y,
new_z
)
} else if ((new_time - this.start_time) > this.duration) {
entity.object3D.position.set(
(figure.posX - translate.x),
((parseFloat(figure.height) + translate.y) / 2),
(- figure.posY + translate.z),
)
}
}
},
createElement: function (figure, pos