From c19bf4145c08a5a6edee5b0b07a7a69c66236ca2 Mon Sep 17 00:00:00 2001 From: Jon Ziebell Date: Thu, 25 Aug 2022 20:47:01 -0400 Subject: [PATCH] Tweaks and improvements to visualizer --- css/dashboard.css | 75 +++-- js/beestat/style.js | 9 +- js/component/card/three_d.js | 409 +++++++++++++++++++---- js/component/card/visualize_affiliate.js | 2 +- js/component/floor_plan.js | 2 +- js/component/scene.js | 103 ++++-- 6 files changed, 468 insertions(+), 132 deletions(-) diff --git a/css/dashboard.css b/css/dashboard.css index d1bc108..844a6da 100644 --- a/css/dashboard.css +++ b/css/dashboard.css @@ -88,40 +88,6 @@ body { src:url("../font/montserrat/montserrat_900.eot?") format("embedded-opentype"),url("../font/montserrat/montserrat_900.woff") format("woff"),url("../font/montserrat/montserrat_900.ttf") format("truetype"),url("../font/montserrat/montserrat_900.svg#Montserrat") format("svg") } - - - - - - - - - - - -/* Beestat logo */ -.beestat { - font-weight: 200; - font-size: 40px; - font-family: Montserrat; -} - -.beestat > .bee { - color: #f7b731; -} - -.beestat > .stat { - color: #20bf6b; -} - - - - - - - - - /* Link styles */ a { cursor: pointer; @@ -311,6 +277,43 @@ input[type=radio] { opacity: 0.25; } +/* Range input */ +input[type=range]{ + -webkit-appearance: none; + background:transparent; + margin-top: 5px; +} + +input[type=range]::-webkit-slider-runnable-track { + background: #37474f; + height: 5px; + border-radius: 5px; +} + +input[type=range]::-moz-range-track { + background: #37474f; + height: 5px; + border-radius: 5px; +} + +input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + height: 14px; + width: 14px; + border-radius: 50%; + border: none; + background-color: #ffffff; + transform: translateY(-5px); +} + +input[type=range]::-moz-range-thumb { + -webkit-appearance: none; + height: 14px; + width: 14px; + border-radius: 50%; + border: none; + background-color: #ffffff; +} /** * This is a stripped down version of https://flexgridlite.elliotdahl.com/ @@ -482,6 +485,7 @@ input[type=radio] { .icon.menu_down:before { content: "\F035D"; } .icon.menu_up:before { content: "\F0360"; } .icon.message:before { content: "\F0361"; } +.icon.moon_waning_crescent:before { content: "\F0F65"; } .icon.network_strength_4:before { content: "\F08FA"; } .icon.network_strength_off:before { content: "\F08FC"; } .icon.numeric_0:before { content: "\F0B39"; } @@ -516,6 +520,8 @@ input[type=radio] { .icon.redo:before { content: "\F044E"; } .icon.refresh:before { content: "\F0450"; } .icon.resistor:before { content: "\F0B44"; } +.icon.restart:before { content: "\F0709"; } +.icon.restart_off:before { content: "\F0D95"; } .icon.snowflake:before { content: "\F0717"; } .icon.swap_horizontal:before { content: "\F04E1"; } .icon.thermometer:before { content: "\F050F"; } @@ -542,6 +548,7 @@ input[type=radio] { .icon.weather_sunny:before { content: "\F0599"; } .icon.weather_tornado:before { content: "\F0F38"; } .icon.weather_windy:before { content: "\F059D"; } +.icon.white_balance_sunny:before { content: "\F05A8"; } .icon.wifi_strength_1_alert:before { content: "\F0920"; } .icon.wifi_strength_4:before { content: "\F0928"; } .icon.zigbee:before { content: "\F0D41"; } diff --git a/js/beestat/style.js b/js/beestat/style.js index c5dd717..531cba1 100644 --- a/js/beestat/style.js +++ b/js/beestat/style.js @@ -394,6 +394,13 @@ beestat.style.hex_to_rgb = function(hex) { } : null; }; +/** + * Convert RGB components to a hex string. + * + * @param {object} RGB + * + * @return {string} hex + */ beestat.style.rgb_to_hex = function(rgb) { return "#" + ((1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b).toString(16).slice(1); -} +}; diff --git a/js/component/card/three_d.js b/js/component/card/three_d.js index 2e208d7..24458f0 100644 --- a/js/component/card/three_d.js +++ b/js/component/card/three_d.js @@ -14,7 +14,10 @@ beestat.component.card.three_d = function() { 'setting.visualize.heat_map_absolute.temperature.max', 'setting.visualize.heat_map_absolute.occupancy.min', 'setting.visualize.heat_map_absolute.occupancy.max' - ], self.update_scene_.bind(this)); + ], function() { + self.update_scene_(); + self.update_hud_(); + }); beestat.dispatcher.addEventListener('cache.floor_plan', function() { self.scene_.rerender(); @@ -88,22 +91,66 @@ beestat.component.card.three_d.prototype.decorate_contents_ = function(parent) { parent.appendChild(drawing_pane_container); this.decorate_drawing_pane_(drawing_pane_container); - // Decorate everything. + // Watermark + const watermark_container = document.createElement('div'); + Object.assign(watermark_container.style, { + 'position': 'absolute', + 'height': '20px', + 'bottom': `${beestat.style.size.gutter}px`, + 'right': `${beestat.style.size.gutter}px` + }); + parent.appendChild(watermark_container); + this.decorate_watermark_(watermark_container); + + // Toolbar + const toolbar_container = document.createElement('div'); + Object.assign(toolbar_container.style, { + 'position': 'absolute', + 'width': '1px', + 'top': `${beestat.style.size.gutter}px`, + 'left': `${beestat.style.size.gutter}px` + }); + parent.appendChild(toolbar_container); + this.decorate_toolbar_(toolbar_container); + + const top_container = document.createElement('div'); + Object.assign(top_container.style, { + 'display': 'flex', + 'position': 'absolute', + 'width': '100%', + 'top': `${beestat.style.size.gutter}px`, + 'padding-left': '55px', + 'padding-right': `${beestat.style.size.gutter}px` + }); + parent.appendChild(top_container); + + // Floors + const floors_container = document.createElement('div'); + Object.assign(floors_container.style, { + 'flex-shrink': '0' + }); + top_container.appendChild(floors_container); + this.decorate_floors_(floors_container); + + // Controls const controls_container = document.createElement('div'); Object.assign(controls_container.style, { - 'position': 'absolute', - 'top': `${beestat.style.size.gutter}px`, - 'left': '50%', - 'width': '300px', - 'margin-left': '-150px', - 'background': beestat.style.color.bluegray.base, - 'padding': `${beestat.style.size.gutter / 2}px`, - 'border-radius': `${beestat.style.size.border_radius}px` + 'flex-grow': '1' }); - parent.appendChild(controls_container); + top_container.appendChild(controls_container); this.decorate_controls_(controls_container); - // var thermostat = beestat.cache.thermostat[this.thermostat_id_]; + // Legend + const legend_container = document.createElement('div'); + Object.assign(legend_container.style, { + 'position': 'absolute', + 'top': '50%', + 'margin-top': '-90px', + 'right': `${beestat.style.size.gutter}px`, + 'height': '180px' + }); + parent.appendChild(legend_container); + this.decorate_legend_(legend_container); let required_begin; let required_end; @@ -311,7 +358,7 @@ beestat.component.card.three_d.prototype.decorate_controls_ = function(parent) { // Hoisting const range = new beestat.component.input.range(); - const right_container = document.createElement('div'); + const time_container = document.createElement('div'); const container = document.createElement('div'); Object.assign(container.style, { @@ -326,13 +373,11 @@ beestat.component.card.three_d.prototype.decorate_controls_ = function(parent) { const play_tile = new beestat.component.tile() .set_icon('play') .set_shadow(false) - .set_text_hover_color(beestat.style.color.green.base) + .set_text_hover_color(beestat.style.color.gray.base) .render($(left_container)); play_tile.addEventListener('click', function() { if (self.interval_ === undefined) { - play_tile - .set_icon('pause') - .set_text_hover_color(beestat.style.color.red.base); + play_tile.set_icon('pause'); self.interval_ = window.setInterval(function() { self.date_m_.add(5, 'minutes'); @@ -340,51 +385,260 @@ beestat.component.card.three_d.prototype.decorate_controls_ = function(parent) { range.set_value( ((self.date_m_.hours() * 60) + self.date_m_.minutes()) / 1440 * 288 ); - right_container.innerText = self.date_m_.format('h:mm a'); + time_container.innerText = self.date_m_.format('h:mm a'); }, 100); } else { play_tile - .set_icon('play') - .set_text_hover_color(beestat.style.color.green.base); + .set_icon('play'); window.clearInterval(self.interval_); delete self.interval_; } }); - const center_container = document.createElement('div'); - Object.assign(center_container.style, { + const range_container = document.createElement('div'); + Object.assign(range_container.style, { + 'position': 'relative', 'flex-grow': '1' }); - container.appendChild(center_container); + container.appendChild(range_container); + // Sunrise/Sunset + const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_]; + if (floor_plan.address_id !== undefined) { + const address = beestat.cache.address[floor_plan.address_id]; + + const times = SunCalc.getTimes( + self.date_m_.toDate(), + address.normalized.metadata.latitude, + address.normalized.metadata.longitude + ); + + const sunrise_m = moment(times.sunrise); + const sunrise_percentage = ((sunrise_m.hours() * 60) + sunrise_m.minutes()) / 1440 * 100; + + const sunset_m = moment(times.sunset); + const sunset_percentage = ((sunset_m.hours() * 60) + sunset_m.minutes()) / 1440 * 100; + + const sunrise_container = document.createElement('div'); + Object.assign(sunrise_container.style, { + 'position': 'absolute', + 'top': '-10px', + 'left': `${sunrise_percentage}%` + }); + new beestat.component.icon('white_balance_sunny', 'Sunrise @ ' + sunrise_m.format('h:mm')) + .set_size(16) + .set_color(beestat.style.color.yellow.base) + .render($(sunrise_container)); + range_container.appendChild(sunrise_container); + + const sunset_container = document.createElement('div'); + Object.assign(sunset_container.style, { + 'position': 'absolute', + 'top': '-10px', + 'left': `${sunset_percentage}%` + }); + new beestat.component.icon('moon_waning_crescent', 'Sunset @ ' + sunset_m.format('h:mm')) + .set_size(16) + .set_color(beestat.style.color.purple.base) + .render($(sunset_container)); + range_container.appendChild(sunset_container); + } + + // Range input range .set_min(0) .set_max(287) .set_value(0) - .render($(center_container)); + .render($(range_container)); - right_container.innerText = '12:00 am'; - Object.assign(right_container.style, { - 'width': '70px', + time_container.innerText = '12:00 am'; + Object.assign(time_container.style, { + 'margin-top': '-8px', 'text-align': 'right' }); - container.appendChild(right_container); + parent.appendChild(time_container); range.addEventListener('input', function() { - play_tile - .set_icon('play') - .set_text_hover_color(beestat.style.color.green.base); + play_tile.set_icon('play'); window.clearInterval(self.interval_); delete self.interval_; const minute_of_day = range.get_value() * 5; self.date_m_.hours(Math.floor(minute_of_day / 60)); self.date_m_.minutes(Math.floor(minute_of_day % 60)); - right_container.innerText = self.date_m_.format('h:mm a'); + time_container.innerText = self.date_m_.format('h:mm a'); self.scene_.set_date(self.date_m_); }); }; +/** + * Watermark. + * + * @param {HTMLDivElement} parent + */ +beestat.component.card.three_d.prototype.decorate_watermark_ = function(parent) { + const img = document.createElement('img'); + img.setAttribute('src', 'img/logo.png'); + Object.assign(img.style, { + 'height': '100%', + 'opacity': '0.7' + }); + parent.appendChild(img); +}; + +/** + * Toolbar. + * + * @param {HTMLDivElement} parent + */ +beestat.component.card.three_d.prototype.decorate_toolbar_ = function(parent) { + const self = this; + + const tile_group = new beestat.component.tile_group(); + + // Add floor + tile_group.add_tile(new beestat.component.tile() + .set_icon('layers') + .set_shadow(false) + .set_text_color(beestat.style.color.lightblue.base) + ); + + let auto_rotate = false; + + // Add room + tile_group.add_tile(new beestat.component.tile() + .set_icon('restart_off') + .set_title('Auto-Rotate') + .set_text_color(beestat.style.color.gray.light) + .set_background_color(beestat.style.color.bluegray.base) + .set_background_hover_color(beestat.style.color.bluegray.light) + .addEventListener('click', function() { + auto_rotate = !auto_rotate; + this.set_icon( + 'restart' + (auto_rotate === false ? '_off' : '') + ); + self.scene_.set_auto_rotate(auto_rotate); + }) + ); + + tile_group.render($(parent)); +}; + +/** + * Floors. + * + * @param {HTMLDivElement} parent + */ +beestat.component.card.three_d.prototype.decorate_floors_ = function(parent) { + const self = this; + + const tile_group = new beestat.component.tile_group(); + + const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_]; + + const sorted_groups = Object.values(floor_plan.data.groups) + .sort(function(a, b) { + return a.elevation > b.elevation; + }); + + let icon_number = 1; + sorted_groups.forEach(function(group, i) { + const button = new beestat.component.tile() + .set_title(group.name) + .set_shadow(false) + .set_text_hover_color(beestat.style.color.lightblue.light) + .set_text_color(beestat.style.color.lightblue.base); + + let icon; + if (group.elevation < 0) { + icon = 'alpha_b'; + } else { + icon = 'numeric_' + icon_number++; + } + + let layer_visible = true; + button + .set_icon(icon + '_box') + .addEventListener('click', function() { + if (layer_visible === true) { + self.scene_.set_layer_visible('group_' + i, false); + button.set_icon(icon); + } else { + self.scene_.set_layer_visible('group_' + i, true); + button.set_icon(icon + '_box'); + } + layer_visible = !layer_visible; + }); + + tile_group.add_tile(button); + }); + + tile_group.render($(parent)); +}; + +/** + * Legend. + * + * @param {HTMLDivElement} parent + */ +beestat.component.card.three_d.prototype.decorate_legend_ = function(parent) { + if (parent !== undefined) { + this.legend_container_ = parent; + } + + this.legend_container_.innerHTML = ''; + + const gradient = this.get_gradient_(); + const gradient_parts = [ + beestat.style.rgb_to_hex(gradient[0]) + ' 0%', + beestat.style.rgb_to_hex(gradient[Math.round(gradient.length / 2)]) + ' 50%', + beestat.style.rgb_to_hex(gradient[gradient.length - 1]) + ' 100%' + ]; + + const gradient_container = document.createElement('div'); + Object.assign(gradient_container.style, { + 'background': 'linear-gradient(0deg, ' + gradient_parts.join(', ') + ')', + 'height': '100%', + 'width': '6px', + 'border-radius': '6px', + 'position': 'relative' + }); + this.legend_container_.appendChild(gradient_container); + + let units; + let min = this.get_heat_map_min_(); + let max = this.get_heat_map_max_(); + if (beestat.setting('visualize.data_type') === 'temperature') { + min = beestat.temperature(min); + max = beestat.temperature(max); + units = beestat.setting('temperature_unit'); + } else { + min *= 100; + max *= 100; + units = '%'; + } + + const min_container = document.createElement('div'); + Object.assign(min_container.style, { + 'position': 'absolute', + 'bottom': '0', + 'right': '10px', + 'font-size': beestat.style.font_size.small + }); + min_container.innerText = min + units; + gradient_container.appendChild(min_container); + + const max_container = document.createElement('div'); + Object.assign(max_container.style, { + 'position': 'absolute', + 'top': '0', + 'right': '10px', + 'font-size': beestat.style.font_size.small + }); + max_container.innerText = max + units; + gradient_container.appendChild(max_container); +}; + /** * Get data. This doesn't directly or indirectly make any API calls, but it * caches the data so it doesn't have to loop over everything more than once. @@ -498,32 +752,13 @@ beestat.component.card.three_d.prototype.has_data_ = function() { beestat.component.card.three_d.prototype.update_scene_ = function() { this.scene_.set_data_type(beestat.setting('visualize.data_type')); - switch (beestat.setting('visualize.heat_map_type')) { - case 'relative': - this.scene_.set_heat_map_min( - this.data_.metadata.series[beestat.setting('visualize.data_type')].min - ); - this.scene_.set_heat_map_max( - this.data_.metadata.series[beestat.setting('visualize.data_type')].max - ); - break; - case 'absolute': - this.scene_.set_heat_map_min( - beestat.setting( - 'visualize.heat_map_absolute.' + - beestat.setting('visualize.data_type') + - '.min' - ) / (beestat.setting('visualize.data_type') === 'occupancy' ? 100 : 1) - ); - this.scene_.set_heat_map_max( - beestat.setting( - 'visualize.heat_map_absolute.' + - beestat.setting('visualize.data_type') + - '.max' - ) / (beestat.setting('visualize.data_type') === 'occupancy' ? 100 : 1) - ); - break; - } + this.scene_.set_gradient(this.get_gradient_()); + this.scene_.set_heat_map_min(this.get_heat_map_min_()); + this.scene_.set_heat_map_max(this.get_heat_map_max_()); +}; + +beestat.component.card.three_d.prototype.update_hud_ = function() { + this.decorate_legend_(); }; /** @@ -542,3 +777,61 @@ beestat.component.card.three_d.prototype.set_floor_plan_id = function(floor_plan return this; }; + +/** + * Get the effective minimum heat map value based on the settings. + * + * @return {number} + */ +beestat.component.card.three_d.prototype.get_heat_map_min_ = function() { + if (beestat.setting('visualize.heat_map_type') === 'relative') { + return this.data_.metadata.series[beestat.setting('visualize.data_type')].min; + } + return beestat.setting( + 'visualize.heat_map_absolute.' + + beestat.setting('visualize.data_type') + + '.min' + ) / (beestat.setting('visualize.data_type') === 'occupancy' ? 100 : 1); +}; + +/** + * Get the effective maximum heat map value based on the settings. + * + * @return {number} + */ +beestat.component.card.three_d.prototype.get_heat_map_max_ = function() { + if (beestat.setting('visualize.heat_map_type') === 'relative') { + return this.data_.metadata.series[beestat.setting('visualize.data_type')].max; + } + return beestat.setting( + 'visualize.heat_map_absolute.' + + beestat.setting('visualize.data_type') + + '.max' + ) / (beestat.setting('visualize.data_type') === 'occupancy' ? 100 : 1); +}; + +/** + * Get the gradient based on the settings. + * + * @return {object} + */ +beestat.component.card.three_d.prototype.get_gradient_ = function() { + if (beestat.setting('visualize.data_type') === 'temperature') { + return beestat.style.generate_gradient( + [ + beestat.style.hex_to_rgb(beestat.style.color.blue.dark), + beestat.style.hex_to_rgb(beestat.style.color.gray.base), + beestat.style.hex_to_rgb(beestat.style.color.red.dark) + ], + 100 + ); + } else { + return beestat.style.generate_gradient( + [ + beestat.style.hex_to_rgb(beestat.style.color.gray.base), + beestat.style.hex_to_rgb(beestat.style.color.orange.dark) + ], + 200 + ); + } +}; diff --git a/js/component/card/visualize_affiliate.js b/js/component/card/visualize_affiliate.js index c82bb79..19f3f56 100644 --- a/js/component/card/visualize_affiliate.js +++ b/js/component/card/visualize_affiliate.js @@ -46,7 +46,7 @@ beestat.component.card.visualize_affiliate.prototype.decorate_top_right_ = funct .set_shadow(false) .set_icon('close') .set_text_color('#fff') - .set_background_hover_color(beestat.style.color.green.light) + .set_background_hover_color('rgba(255, 255, 255, 0.1') .addEventListener('click', function() { beestat.setting('visualize.hide_affiliate', true); }) diff --git a/js/component/floor_plan.js b/js/component/floor_plan.js index ef2bd2a..620e2d0 100644 --- a/js/component/floor_plan.js +++ b/js/component/floor_plan.js @@ -78,7 +78,7 @@ beestat.component.floor_plan.prototype.render = function(parent) { this.floors_container_.style({ 'position': 'absolute', 'top': beestat.style.size.gutter, - 'left': 40 + beestat.style.size.gutter + (beestat.style.size.gutter / 2) + 'left': beestat.style.size.gutter * 4 }); parent.appendChild(this.floors_container_); diff --git a/js/component/scene.js b/js/component/scene.js index 89f45fb..245de48 100644 --- a/js/component/scene.js +++ b/js/component/scene.js @@ -48,8 +48,8 @@ beestat.component.scene.ambient_light_intensity = 0.3; * having to manually save camera info etc. */ beestat.component.scene.prototype.rerender = function() { - this.scene_.remove(this.group_); - this.add_group_(); + this.scene_.remove(this.main_group_); + this.add_main_group_(); this.add_floor_plan_(); }; @@ -102,7 +102,7 @@ beestat.component.scene.prototype.decorate_ = function(parent) { // this.add_ground_(); // this.add_ground_limited_(); - this.add_group_(); + this.add_main_group_(); this.add_floor_plan_(); /** @@ -410,27 +410,6 @@ beestat.component.scene.prototype.update_ = function() { const time = this.date_.format('HH:mm'); - // TODO Memoize this - let gradient; - if (self.data_type_ === 'temperature') { - gradient = beestat.style.generate_gradient( - [ - beestat.style.hex_to_rgb(beestat.style.color.blue.dark), - beestat.style.hex_to_rgb(beestat.style.color.gray.base), - beestat.style.hex_to_rgb(beestat.style.color.red.dark) - ], - 100 - ); - } else if (self.data_type_ === 'occupancy') { - gradient = beestat.style.generate_gradient( - [ - beestat.style.hex_to_rgb(beestat.style.color.gray.base), - beestat.style.hex_to_rgb(beestat.style.color.orange.dark) - ], - 200 - ); - } - // Set the color of each room floor_plan.data.groups.forEach(function(group) { group.rooms.forEach(function(room) { @@ -449,7 +428,9 @@ beestat.component.scene.prototype.update_ = function() { (value - self.heat_map_min_) / (self.heat_map_max_ - self.heat_map_min_) ) ); - color = beestat.style.rgb_to_hex(gradient[Math.floor((gradient.length - 1) * percentage)]); + color = beestat.style.rgb_to_hex( + self.gradient_[Math.floor((self.gradient_.length - 1) * percentage)] + ); } else { color = beestat.style.color.gray.dark; } @@ -739,10 +720,11 @@ beestat.component.scene.prototype.add_background_ = function() { /** * Add a room. Room coordinates are absolute. * + * @param {THREE.Group} layer The layer the room belongs to. * @param {object} group The group the room belongs to. * @param {object} room The room to add. */ -beestat.component.scene.prototype.add_room_ = function(group, room) { +beestat.component.scene.prototype.add_room_ = function(layer, group, room) { const color = beestat.style.color.gray.dark; var clipper_offset = new ClipperLib.ClipperOffset(); @@ -805,13 +787,15 @@ beestat.component.scene.prototype.add_room_ = function(group, room) { // mesh.receiveShadow = true; // Add the mesh to the group. - this.group_.add(mesh); + this.main_group_.add(mesh); // Store a reference to the mesh representing each room. if (this.rooms_ === undefined) { this.rooms_ = {}; } this.rooms_[room.room_id] = mesh; + + layer.add(mesh); }; /** @@ -849,9 +833,10 @@ beestat.component.scene.prototype.update_debug_ = function() { }; /** - * Add a group containing all of the extruded geometry. + * Add a group containing all of the extruded geometry that can be transformed + * all together. */ -beestat.component.scene.prototype.add_group_ = function() { +beestat.component.scene.prototype.add_main_group_ = function() { const bounding_box = beestat.floor_plan.get_bounding_box(this.floor_plan_id_); // this.floor_plan_center_x_ = ; // this.floor_plan_center_y_ = (bounding_box.bottom + bounding_box.top) / 2; @@ -859,13 +844,13 @@ beestat.component.scene.prototype.add_group_ = function() { // this.view_box_.x = center_x - (this.view_box_.width / 2); // this.view_box_.y = center_y - (this.view_box_.height / 2); - this.group_ = new THREE.Group(); - this.group_.translateX((bounding_box.right + bounding_box.left) / -2); - this.group_.translateZ((bounding_box.bottom + bounding_box.top) / -2); - // this.group_.rotation.x = -Math.PI / 2; + this.main_group_ = new THREE.Group(); + this.main_group_.translateX((bounding_box.right + bounding_box.left) / -2); + this.main_group_.translateZ((bounding_box.bottom + bounding_box.top) / -2); + // this.main_group_.rotation.x = -Math.PI / 2; // - this.group_.rotation.x = Math.PI / 2; - this.scene_.add(this.group_); + this.main_group_.rotation.x = Math.PI / 2; + this.scene_.add(this.main_group_); }; /** @@ -875,9 +860,13 @@ beestat.component.scene.prototype.add_floor_plan_ = function() { const self = this; const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_]; - floor_plan.data.groups.forEach(function(group) { + this.layers_ = {}; + floor_plan.data.groups.forEach(function(group, i) { + const layer = new THREE.Group(); + self.main_group_.add(layer); + self.layers_['group_' + i] = layer; group.rooms.forEach(function(room) { - self.add_room_(group, room); + self.add_room_(layer, group, room); }); }); }; @@ -949,3 +938,43 @@ beestat.component.scene.prototype.set_heat_map_max = function(heat_map_max) { return this; }; + +/** + * Set the visibility of a layer. + * + * @param {string} layer_name + * @param {boolean} visible + * + * @return {beestat.component.scene} + */ +beestat.component.scene.prototype.set_layer_visible = function(layer_name, visible) { + this.layers_[layer_name].visible = visible; + + return this; +}; + +/** + * Set whether or not auto-rotate is enabled. + * + * @param {boolean} auto_rotate + * + * @return {beestat.component.scene} + */ +beestat.component.scene.prototype.set_auto_rotate = function(auto_rotate) { + this.controls_.autoRotate = auto_rotate; + + return this; +}; + +/** + * Set the gradient. + * + * @param {boolean} gradient + * + * @return {beestat.component.scene} + */ +beestat.component.scene.prototype.set_gradient = function(gradient) { + this.gradient_ = gradient; + + return this; +};