diff --git a/js/beestat/area.js b/js/beestat/area.js
new file mode 100644
index 0000000..cf581e0
--- /dev/null
+++ b/js/beestat/area.js
@@ -0,0 +1,73 @@
+/**
+ * Format a area in a number of different ways.
+ *
+ * @param {object} args Instructions on how to format:
+ * area (required) - area to work with
+ * output_area_unit (optional, default ft) - Output area unit; default matches setting.
+ * convert (optional, default true) - Whether or not to convert to Celcius if necessary
+ * round (optional, default 1) - Number of decimal points to round to
+ * units (optional, default false) - Whether or not to include units in the result
+ * type (optional, default number) - Type of value to return (string|number)
+ *
+ * @return {string} The formatted area.
+ */
+beestat.area = function(args) {
+ // Allow passing a single argument of area for convenience.
+ if (typeof args !== 'object' || args === null) {
+ args = {
+ 'area': args
+ };
+ }
+
+ const input_area_unit = 'in²';
+ var output_area_unit = beestat.default_value(
+ args.output_area_unit,
+ beestat.setting('units.area')
+ );
+ var round = beestat.default_value(args.round, 1);
+ var units = beestat.default_value(args.units, false);
+ var type = beestat.default_value(args.type, 'number');
+
+ var area = parseFloat(args.area);
+
+ // Check for invalid values.
+ if (isNaN(area) === true || isFinite(area) === false) {
+ return null;
+ }
+
+ const conversion_factors = {
+ 'in²': {
+ 'ft²': 0.00694444,
+ 'm²': 0.00064516
+ }
+ };
+
+ // Convert if necessary and asked for.
+ if (input_area_unit !== output_area_unit) {
+ area *= conversion_factors[input_area_unit][output_area_unit];
+ }
+
+ /*
+ * Get to the appropriate number of decimal points. This will turn the number
+ * into a string. Then do a couple silly operations to fix -0.02 from showing
+ * up as -0.0 in string form.
+ */
+ area = area.toFixed(round);
+ area = parseFloat(area);
+ area = area.toFixed(round);
+
+ /*
+ * Convert the previous string back to a number if requested. Format matters
+ * because HighCharts doesn't accept strings in some cases.
+ */
+ if (type === 'number' && units === false) {
+ area = Number(area);
+ }
+
+ // Append units if asked for.
+ if (units === true) {
+ area = Number(area).toLocaleString() + ' ' + output_area_unit;
+ }
+
+ return area;
+};
diff --git a/js/beestat/comparisons.js b/js/beestat/comparisons.js
index 953a3fd..ac5943d 100644
--- a/js/beestat/comparisons.js
+++ b/js/beestat/comparisons.js
@@ -36,7 +36,7 @@ beestat.comparisons.get_attributes = function() {
};
}
- // Always a 1000sqft size delta on both sides (total 2000 sqft).
+ // Always a 1000ft² size delta on both sides (total 2000 ft²).
if (thermostat.property.square_feet !== null) {
var property_square_feet_delta = 1000;
var min_property_square_feet = Math.max(
diff --git a/js/beestat/distance.js b/js/beestat/distance.js
new file mode 100644
index 0000000..114d8d5
--- /dev/null
+++ b/js/beestat/distance.js
@@ -0,0 +1,84 @@
+/**
+ * Format a distance in a number of different ways.
+ *
+ * @param {object} args Instructions on how to format:
+ * distance (required) - distance to work with
+ * output_distance_unit (optional, default ft) - Output distance unit; default matches setting.
+ * convert (optional, default true) - Whether or not to convert to Celcius if necessary
+ * round (optional, default 1) - Number of decimal points to round to
+ * units (optional, default false) - Whether or not to include units in the result
+ * type (optional, default number) - Type of value to return (string|number)
+ *
+ * @return {string} The formatted distance.
+ */
+beestat.distance = function(args) {
+ // Allow passing a single argument of distance for convenience.
+ if (typeof args !== 'object' || args === null) {
+ args = {
+ 'distance': args
+ };
+ }
+
+ var input_distance_unit = beestat.default_value(
+ args.input_distance_unit,
+ 'in'
+ );
+ var output_distance_unit = beestat.default_value(
+ args.output_distance_unit,
+ beestat.setting('units.distance')
+ );
+ var round = beestat.default_value(args.round, 1);
+ var units = beestat.default_value(args.units, false);
+ var type = beestat.default_value(args.type, 'number');
+
+ var distance = parseFloat(args.distance);
+
+ // Check for invalid values.
+ if (isNaN(distance) === true || isFinite(distance) === false) {
+ return null;
+ }
+
+ const conversion_factors = {
+ 'in': {
+ 'ft': 0.0833,
+ 'm': 0.0254
+ },
+ 'm': {
+ 'in': 39.3701,
+ 'ft': 3.28084
+ },
+ 'ft': {
+ 'm': 0.3048,
+ 'in': 12
+ }
+ };
+
+ // Convert if necessary and asked for.
+ if (input_distance_unit !== output_distance_unit) {
+ distance *= conversion_factors[input_distance_unit][output_distance_unit];
+ }
+
+ /*
+ * Get to the appropriate number of decimal points. This will turn the number
+ * into a string. Then do a couple silly operations to fix -0.02 from showing
+ * up as -0.0 in string form.
+ */
+ distance = distance.toFixed(round);
+ distance = parseFloat(distance);
+ distance = distance.toFixed(round);
+
+ /*
+ * Convert the previous string back to a number if requested. Format matters
+ * because HighCharts doesn't accept strings in some cases.
+ */
+ if (type === 'number' && units === false) {
+ distance = Number(distance);
+ }
+
+ // Append units if asked for.
+ if (units === true) {
+ distance = Number(distance).toLocaleString() + ' ' + output_distance_unit;
+ }
+
+ return distance;
+};
diff --git a/js/beestat/floor_plan.js b/js/beestat/floor_plan.js
index c6a9fc0..34a5eb5 100644
--- a/js/beestat/floor_plan.js
+++ b/js/beestat/floor_plan.js
@@ -5,7 +5,7 @@ beestat.floor_plan = {};
*
* @param {number} floor_plan_id
*
- * @return {number} The area of the floor plan in sqft.
+ * @return {number} The area of the floor plan as in².
*/
beestat.floor_plan.get_area = function(floor_plan_id) {
const floor_plan = beestat.cache.floor_plan[floor_plan_id];
@@ -24,7 +24,7 @@ beestat.floor_plan.get_area = function(floor_plan_id) {
* @param {object} group The group.
* @param {boolean} round Whether or not to round the result.
*
- * @return {number} Area of the group in sqft.
+ * @return {number} Area of the group as in².
*/
beestat.floor_plan.get_area_group = function(group, round = true) {
let area = 0;
@@ -46,10 +46,10 @@ beestat.floor_plan.get_area_group = function(group, round = true) {
* @param {object} room The room.
* @param {boolean} round Whether or not to round the result.
*
- * @return {number} Area of the room in sqft.
+ * @return {number} Area of the room as in².
*/
beestat.floor_plan.get_area_room = function(room, round = true) {
- let area = Math.abs(ClipperLib.Clipper.Area(room.points) / 144);
+ let area = Math.abs(ClipperLib.Clipper.Area(room.points));
if (round === true) {
return Math.round(area);
diff --git a/js/beestat/setting.js b/js/beestat/setting.js
index 345d69a..d825e78 100644
--- a/js/beestat/setting.js
+++ b/js/beestat/setting.js
@@ -62,8 +62,6 @@ beestat.setting = function(argument_1, opt_value, opt_callback) {
'comparison_region': 'global',
'comparison_property_type': 'similar',
- 'temperature_unit': '°F',
-
'first_run': true,
'thermostat.#.profile.ignore_solar_gain': false,
diff --git a/js/beestat/temperature.js b/js/beestat/temperature.js
index 0f9aa90..d8a4b69 100644
--- a/js/beestat/temperature.js
+++ b/js/beestat/temperature.js
@@ -6,7 +6,7 @@
* @param {object} args Instructions on how to format:
* temperature (required) - Temperature to work with
* input_temperature_unit (optional, default °F) - Input temperature unit
- * output_temperature_unit (optional, default °F|°C) - Input temperature unit; default matches setting.
+ * output_temperature_unit (optional, default current setting) - Output temperature unit; default matches setting.
* convert (optional, default true) - Whether or not to convert to Celcius if necessary
* delta (optional, default false) - Whether or not the convert action is for a delta instead of a normal value
* round (optional, default 1) - Number of decimal points to round to
@@ -29,7 +29,7 @@ beestat.temperature = function(args) {
);
var output_temperature_unit = beestat.default_value(
args.output_temperature_unit,
- beestat.setting('temperature_unit')
+ beestat.setting('units.temperature')
);
var delta = beestat.default_value(args.delta, false);
var round = beestat.default_value(args.round, 1);
diff --git a/js/component/card/comparison_settings.js b/js/component/card/comparison_settings.js
index 65b68c3..67d58a3 100644
--- a/js/component/card/comparison_settings.js
+++ b/js/component/card/comparison_settings.js
@@ -299,7 +299,7 @@ beestat.component.card.comparison_settings.prototype.decorate_detail_ = function
}
if (comparison_attributes.property_square_feet !== undefined) {
- strings.push(this.get_comparison_string_(comparison_attributes.property_square_feet, 'sqft'));
+ strings.push(this.get_comparison_string_(comparison_attributes.property_square_feet, 'ft²'));
} else {
strings.push('Any square footage');
}
diff --git a/js/component/card/floor_plan_editor.js b/js/component/card/floor_plan_editor.js
index 642b588..279bf4e 100644
--- a/js/component/card/floor_plan_editor.js
+++ b/js/component/card/floor_plan_editor.js
@@ -331,7 +331,7 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = f
.set_label('Floor Name')
.set_placeholder('Unnamed Floor')
.set_width('100%')
- .set_maxlength('50')
+ .set_maxlength(50)
.set_requirements({
'required': true
})
@@ -354,26 +354,41 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = f
div = $.createElement('div');
grid.appendChild(div);
const elevation_input = new beestat.component.input.text()
- .set_label('Elevation (feet)')
- .set_placeholder(this.state_.active_group.elevation / 12)
- .set_value(this.state_.active_group.elevation / 12 || '')
+ .set_label('Elevation (' + beestat.setting('units.distance') + ')')
+ .set_placeholder(beestat.distance({
+ 'distance': this.state_.active_group.elevation,
+ 'round': 2
+ }))
+ .set_value(beestat.distance({
+ 'distance': this.state_.active_group.elevation,
+ 'round': 2
+ }) || '')
.set_width('100%')
- .set_maxlength('5')
+ .set_maxlength(5)
.set_requirements({
- 'type': 'integer',
- 'min_value': -50,
- 'max_value': 50,
+ 'type': 'decimal',
+ 'min_value': beestat.distance(-600),
+ 'max_value': beestat.distance(600),
'required': true
})
+ .set_transform({
+ 'type': 'round',
+ 'decimals': 2
+ })
.render(div);
elevation_input.addEventListener('change', function() {
if (elevation_input.meets_requirements() === true) {
- self.state_.active_group.elevation = elevation_input.get_value() * 12;
+ self.state_.active_group.elevation = beestat.distance({
+ 'distance': elevation_input.get_value(),
+ 'input_distance_unit': beestat.setting('units.distance'),
+ 'output_distance_unit': 'in',
+ 'round': 2
+ });
self.update_floor_plan_();
self.rerender();
} else {
- elevation_input.set_value(self.state_.active_group.elevation / 12, false);
+ elevation_input.set_value(beestat.distance(self.state_.active_group.elevation), false);
new beestat.component.modal.floor_plan_elevation_help().render();
}
});
@@ -382,21 +397,36 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = f
div = $.createElement('div');
grid.appendChild(div);
const height_input = new beestat.component.input.text()
- .set_label('Ceiling Height (feet)')
- .set_placeholder(this.state_.active_group.height / 12)
- .set_value(this.state_.active_group.height / 12 || '')
+ .set_label('Ceiling Height (' + beestat.setting('units.distance') + ')')
+ .set_placeholder(beestat.distance({
+ 'distance': this.state_.active_group.height,
+ 'round': 2
+ }))
+ .set_value(beestat.distance({
+ 'distance': this.state_.active_group.height,
+ 'round': 2
+ }) || '')
.set_width('100%')
- .set_maxlength('4')
+ .set_maxlength(5)
.set_requirements({
- 'type': 'integer',
- 'min_value': 1,
+ 'type': 'decimal',
+ 'min_value': beestat.distance(60),
'required': true
})
+ .set_transform({
+ 'type': 'round',
+ 'decimals': 2
+ })
.render(div);
height_input.addEventListener('change', function() {
if (height_input.meets_requirements() === true) {
- self.state_.active_group.height = height_input.get_value() * 12;
+ self.state_.active_group.height = beestat.distance({
+ 'distance': height_input.get_value(),
+ 'input_distance_unit': beestat.setting('units.distance'),
+ 'output_distance_unit': 'in',
+ 'round': 2
+ });
self.update_floor_plan_();
} else {
height_input.set_value(self.state_.active_group.height, false);
@@ -433,7 +463,7 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = fu
.set_label('Room Name')
.set_placeholder('Unnamed Room')
.set_width('100%')
- .set_maxlength('50')
+ .set_maxlength(50)
.set_requirements({
'required': true
})
@@ -456,21 +486,36 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = fu
div = $.createElement('div');
grid.appendChild(div);
const elevation_input = new beestat.component.input.text()
- .set_label('Elevation (feet)')
- .set_placeholder(this.state_.active_group.elevation / 12)
- .set_value(this.state_.active_room_entity.get_room().elevation / 12 || '')
+ .set_label('Elevation (' + beestat.setting('units.distance') + ')')
+ .set_placeholder(beestat.distance({
+ 'distance': this.state_.active_group.elevation,
+ 'round': 2
+ }))
+ .set_value(beestat.distance({
+ 'distance': this.state_.active_room_entity.get_room().elevation,
+ 'round': 2
+ }) || '')
.set_width('100%')
- .set_maxlength('5')
+ .set_maxlength(5)
.set_requirements({
- 'min_value': -50,
- 'max_value': 50,
- 'type': 'integer'
+ 'type': 'decimal',
+ 'min_value': beestat.distance(-600),
+ 'max_value': beestat.distance(600)
+ })
+ .set_transform({
+ 'type': 'round',
+ 'decimals': 2
})
.render(div);
elevation_input.addEventListener('change', function() {
if (elevation_input.meets_requirements() === true) {
- self.state_.active_room_entity.get_room().elevation = elevation_input.get_value() * 12;
+ self.state_.active_room_entity.get_room().elevation = beestat.distance({
+ 'distance': elevation_input.get_value(),
+ 'input_distance_unit': beestat.setting('units.distance'),
+ 'output_distance_unit': 'in',
+ 'round': 2
+ });
self.update_floor_plan_();
self.rerender();
} else {
@@ -483,20 +528,35 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = fu
div = $.createElement('div');
grid.appendChild(div);
const height_input = new beestat.component.input.text()
- .set_label('Ceiling Height (feet)')
- .set_placeholder(this.state_.active_group.height / 12)
- .set_value(this.state_.active_room_entity.get_room().height / 12 || '')
+ .set_label('Ceiling Height (' + beestat.setting('units.distance') + ')')
+ .set_placeholder(beestat.distance({
+ 'distance': this.state_.active_group.height,
+ 'round': 2
+ }))
+ .set_value(beestat.distance({
+ 'distance': this.state_.active_room_entity.get_room().height,
+ 'round': 2
+ }) || '')
.set_width('100%')
- .set_maxlength('4')
+ .set_maxlength(5)
.set_requirements({
- 'type': 'integer',
- 'min_value': 1
+ 'type': 'decimal',
+ 'min_value': beestat.distance(60)
+ })
+ .set_transform({
+ 'type': 'round',
+ 'decimals': 2
})
.render(div);
height_input.addEventListener('change', function() {
if (height_input.meets_requirements() === true) {
- self.state_.active_room_entity.get_room().height = height_input.get_value() * 12;
+ self.state_.active_room_entity.get_room().height = beestat.distance({
+ 'distance': height_input.get_value(),
+ 'input_distance_unit': beestat.setting('units.distance'),
+ 'output_distance_unit': 'in',
+ 'round': 2
+ });
self.update_floor_plan_();
} else {
height_input.set_value('', false);
diff --git a/js/component/card/my_home.js b/js/component/card/my_home.js
index b877ad3..117ad66 100644
--- a/js/component/card/my_home.js
+++ b/js/component/card/my_home.js
@@ -186,7 +186,7 @@ beestat.component.card.my_home.prototype.decorate_property_ = function(parent) {
.set_background_color(beestat.style.color.purple.base)
.set_text_color('#fff')
.set_icon('view_quilt')
- .set_text(Number(thermostat.property.square_feet).toLocaleString() + ' sqft'));
+ .set_text(Number(thermostat.property.square_feet).toLocaleString() + ' ft²'));
}
if (thermostat.property.age !== null) {
diff --git a/js/component/card/settings.js b/js/component/card/settings.js
index 10e1b57..27a3160 100644
--- a/js/component/card/settings.js
+++ b/js/component/card/settings.js
@@ -16,6 +16,73 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
beestat.setting('thermostat_id')
];
+ /**
+ * Units
+ */
+ parent.appendChild(
+ $.createElement('p')
+ .style('font-weight', '400')
+ .innerText('Units')
+ );
+
+ // Temperature
+ parent.appendChild(
+ $.createElement('p')
+ .innerText('Temperature')
+ );
+
+ const temperature_radio_group = new beestat.component.radio_group()
+ .set_arrangement('horizontal');
+ [
+ '°F',
+ '°C'
+ ].forEach(function(temperature_unit) {
+ temperature_radio_group.add_radio(
+ new beestat.component.input.radio()
+ .set_label(temperature_unit)
+ .set_value(temperature_unit)
+ .set_checked(beestat.setting('units.temperature') === temperature_unit)
+ );
+ });
+
+ temperature_radio_group.addEventListener('change', function() {
+ beestat.setting('units.temperature', temperature_radio_group.get_value());
+ });
+
+ temperature_radio_group.render(parent);
+
+ // Distance
+ parent.appendChild(
+ $.createElement('p')
+ .innerText('Distance / Area')
+ );
+
+ const distance_radio_group = new beestat.component.radio_group()
+ .set_arrangement('horizontal');
+ [
+ 'ft',
+ 'm'
+ ].forEach(function(distance_unit) {
+ distance_radio_group.add_radio(
+ new beestat.component.input.radio()
+ .set_label(distance_unit + ' / ' + distance_unit + '²')
+ .set_value(distance_unit)
+ .set_checked(beestat.setting('units.distance') === distance_unit)
+ );
+ });
+
+ distance_radio_group.addEventListener('change', function() {
+ beestat.setting({
+ 'units.distance': distance_radio_group.get_value(),
+ 'units.area': distance_radio_group.get_value() + '²'
+ });
+ });
+
+ distance_radio_group.render(parent);
+
+ /**
+ * Thermosat Summary
+ */
parent.appendChild(
$.createElement('p')
.style('font-weight', '400')
@@ -58,6 +125,9 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
);
});
+ /**
+ * Temperature Profiles
+ */
parent.appendChild(
$.createElement('p')
.style({
diff --git a/js/component/card/temperature_profiles.js b/js/component/card/temperature_profiles.js
index 8ffbb76..d40994c 100644
--- a/js/component/card/temperature_profiles.js
+++ b/js/component/card/temperature_profiles.js
@@ -144,7 +144,7 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
*/
var increment;
var fixed;
- if (beestat.setting('temperature_unit') === '°F') {
+ if (beestat.setting('units.temperature') === '°F') {
increment = 1;
fixed = 0;
} else {
diff --git a/js/component/card/three_d.js b/js/component/card/three_d.js
index 59230d6..c453648 100644
--- a/js/component/card/three_d.js
+++ b/js/component/card/three_d.js
@@ -625,7 +625,7 @@ beestat.component.card.three_d.prototype.decorate_legend_ = function(parent) {
if (beestat.setting('visualize.data_type') === 'temperature') {
min = beestat.temperature(min);
max = beestat.temperature(max);
- units = beestat.setting('temperature_unit');
+ units = beestat.setting('units.temperature');
} else {
min *= 100;
max *= 100;
diff --git a/js/component/card/visualize_settings.js b/js/component/card/visualize_settings.js
index 1f3c585..04f1ffa 100644
--- a/js/component/card/visualize_settings.js
+++ b/js/component/card/visualize_settings.js
@@ -176,6 +176,10 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_type_ = fu
'type': type,
'required': true
})
+ .set_transform({
+ 'type': 'round',
+ 'decimals': 1
+ })
.set_value(
beestat.temperature(beestat.setting(
'visualize.heat_map_absolute.' + beestat.setting('visualize.data_type') + '.min'
@@ -184,15 +188,11 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_type_ = fu
.set_width(50);
min.addEventListener('change', function() {
if (min.meets_requirements() === true) {
- // Round to one decimal.
- const value = Math.round(min.get_value() * 10) / 10;
- min.set_value(value, false);
-
beestat.setting(
'visualize.heat_map_absolute.' + beestat.setting('visualize.data_type') + '.min',
beestat.temperature({
- 'temperature': value,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'temperature': min.get_value(),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'output_temperature_unit': '°F'
})
);
@@ -230,7 +230,7 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_type_ = fu
'visualize.heat_map_absolute.' + beestat.setting('visualize.data_type') + '.max',
beestat.temperature({
'temperature': max.get_value(),
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'output_temperature_unit': '°F'
})
);
@@ -268,7 +268,7 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_type_ = fu
span = document.createElement('span');
switch (beestat.setting('visualize.data_type')) {
case 'temperature':
- span.innerText = beestat.setting('temperature_unit');
+ span.innerText = beestat.setting('units.temperature');
break;
case 'occupancy':
span.innerText = '%';
diff --git a/js/component/chart/runtime_sensor_detail_temperature.js b/js/component/chart/runtime_sensor_detail_temperature.js
index 2a0a1d4..1436398 100644
--- a/js/component/chart/runtime_sensor_detail_temperature.js
+++ b/js/component/chart/runtime_sensor_detail_temperature.js
@@ -118,7 +118,7 @@ beestat.component.chart.runtime_sensor_detail_temperature.prototype.get_options_
'labels': {
'style': {'color': beestat.style.color.gray.base},
'formatter': function() {
- return this.value + beestat.setting('temperature_unit');
+ return this.value + beestat.setting('units.temperature');
}
}
}
@@ -230,7 +230,7 @@ beestat.component.chart.runtime_sensor_detail_temperature.prototype.get_options_
} else {
value = beestat.temperature({
'temperature': point.value,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'units': true
});
point_value = point.value;
diff --git a/js/component/chart/runtime_thermostat_detail_temperature.js b/js/component/chart/runtime_thermostat_detail_temperature.js
index b66602b..c17811c 100644
--- a/js/component/chart/runtime_thermostat_detail_temperature.js
+++ b/js/component/chart/runtime_thermostat_detail_temperature.js
@@ -131,7 +131,7 @@ beestat.component.chart.runtime_thermostat_detail_temperature.prototype.get_opti
'labels': {
'style': {'color': beestat.style.color.gray.base},
'formatter': function() {
- return this.value + beestat.setting('temperature_unit');
+ return this.value + beestat.setting('units.temperature');
}
}
},
@@ -288,7 +288,7 @@ beestat.component.chart.runtime_thermostat_detail_temperature.prototype.get_opti
value = beestat.temperature({
'temperature': value,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'units': true
});
} else if (point.series_code.includes('humidity') === true) {
diff --git a/js/component/chart/runtime_thermostat_summary.js b/js/component/chart/runtime_thermostat_summary.js
index c3d7ec5..b1cb2dd 100755
--- a/js/component/chart/runtime_thermostat_summary.js
+++ b/js/component/chart/runtime_thermostat_summary.js
@@ -187,7 +187,7 @@ beestat.component.chart.runtime_thermostat_summary.prototype.get_options_yAxis_
'color': beestat.style.color.gray.base
},
'formatter': function() {
- return this.value + beestat.setting('temperature_unit');
+ return this.value + beestat.setting('units.temperature');
}
}
}
@@ -234,14 +234,14 @@ beestat.component.chart.runtime_thermostat_summary.prototype.get_options_tooltip
) {
value = beestat.temperature({
'temperature': values.min_outdoor_temperature,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'units': true,
'round': 0
});
value += ' to ';
value += beestat.temperature({
'temperature': values.max_outdoor_temperature,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'units': true,
'round': 0
});
@@ -252,7 +252,7 @@ beestat.component.chart.runtime_thermostat_summary.prototype.get_options_tooltip
color = point.series.color;
value = beestat.temperature({
'temperature': values.avg_outdoor_temperature,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'units': true,
'round': 0
});
diff --git a/js/component/chart/temperature_profiles.js b/js/component/chart/temperature_profiles.js
index d71d806..4c7ba87 100644
--- a/js/component/chart/temperature_profiles.js
+++ b/js/component/chart/temperature_profiles.js
@@ -17,7 +17,7 @@ beestat.extend(beestat.component.chart.temperature_profiles, beestat.component.c
*/
beestat.component.chart.temperature_profiles.prototype.get_options_xAxis_labels_formatter_ = function() {
return function() {
- return this.value + beestat.setting('temperature_unit');
+ return this.value + beestat.setting('units.temperature');
};
};
@@ -261,7 +261,7 @@ beestat.component.chart.temperature_profiles.prototype.get_options_yAxis_ = func
'labels': {
'style': {'color': beestat.style.color.gray.base},
'formatter': function() {
- return this.value + beestat.setting('temperature_unit');
+ return this.value + beestat.setting('units.temperature');
}
},
'min': y_min,
@@ -296,7 +296,7 @@ beestat.component.chart.temperature_profiles.prototype.get_options_tooltip_forma
var value = beestat.temperature({
'temperature': point.y,
'units': true,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'delta': true,
'type': 'string'
}) + ' / h';
@@ -321,7 +321,7 @@ beestat.component.chart.temperature_profiles.prototype.get_options_tooltip_forma
'temperature': this.x,
'round': 0,
'units': true,
- 'input_temperature_unit': beestat.setting('temperature_unit')
+ 'input_temperature_unit': beestat.setting('units.temperature')
}),
sections
);
@@ -380,7 +380,7 @@ beestat.component.chart.temperature_profiles.prototype.get_options_xAxis_ = func
'useHTML': true,
'text': 'Now: ' + beestat.temperature({
'temperature': this.data_.metadata.chart.outdoor_temperature,
- 'input_temperature_unit': beestat.setting('temperature_unit'),
+ 'input_temperature_unit': beestat.setting('units.temperature'),
'units': true,
'round': 0
})
diff --git a/js/component/floor_plan.js b/js/component/floor_plan.js
index ddad377..1c0d652 100644
--- a/js/component/floor_plan.js
+++ b/js/component/floor_plan.js
@@ -657,14 +657,20 @@ beestat.component.floor_plan.prototype.update_infobox = function() {
if (this.state_.active_room_entity !== undefined) {
parts.push(this.state_.active_room_entity.get_room().name || 'Unnamed Room');
parts.push(
- beestat.floor_plan.get_area_room(this.state_.active_room_entity.get_room())
- .toLocaleString() + ' sqft'
+ beestat.area({
+ 'area': beestat.floor_plan.get_area_room(this.state_.active_room_entity.get_room()),
+ 'round': 0,
+ 'units': true
+ })
);
} else {
parts.push(this.state_.active_group.name || 'Unnamed Floor');
parts.push(
- beestat.floor_plan.get_area_group(this.state_.active_group)
- .toLocaleString() + ' sqft'
+ beestat.area({
+ 'area': beestat.floor_plan.get_area_group(this.state_.active_group),
+ 'round': 0,
+ 'units': true
+ })
);
}
this.infobox_container_.innerText(parts.join(' • '));
diff --git a/js/component/floor_plan_entity/wall.js b/js/component/floor_plan_entity/wall.js
index 3522b96..2d3cec3 100644
--- a/js/component/floor_plan_entity/wall.js
+++ b/js/component/floor_plan_entity/wall.js
@@ -46,9 +46,6 @@ beestat.component.floor_plan_entity.wall.prototype.decorate_line_ = function(par
this.path_.addEventListener('mousedown', function() {
self.dispatchEvent('mousedown');
});
- // this.path_.addEventListener('touchstart', function() {
- // self.dispatchEvent('mousedown');
- // });
this.decorate_text_(parent);
@@ -236,14 +233,24 @@ beestat.component.floor_plan_entity.wall.prototype.update_text_ = function() {
this.text_.style.fontSize = '11px';
}
- const length_feet = Math.floor(length / 12);
- const length_inches = length % 12;
+ let length_string;
+ if (beestat.setting('units.distance') === 'ft') {
+ const length_feet = Math.floor(length / 12);
+ const length_inches = length % 12;
- let length_parts = [];
- length_parts.push(length_feet + '\'');
- length_parts.push(length_inches + '"');
+ let length_parts = [];
+ length_parts.push(length_feet + '\'');
+ length_parts.push(length_inches + '"');
+
+ length_string = length_parts.join(' ');
+ } else {
+ length_string = beestat.distance({
+ 'distance': length,
+ 'units': true,
+ 'round': 2
+ });
+ }
- const length_string = length_parts.join(' ');
this.text_path_.textContent = length_string;
};
diff --git a/js/component/input.js b/js/component/input.js
index ab09880..46660a2 100644
--- a/js/component/input.js
+++ b/js/component/input.js
@@ -80,7 +80,7 @@ beestat.component.input.prototype.meets_requirements = function() {
this.requirements_.regexp = /^-?\d+$/;
break;
case 'decimal':
- this.requirements_.regexp = /^-?\d+(?:\.\d+)?$/;
+ this.requirements_.regexp = /^-?\d*(?:\.\d+)?$/;
break;
}
diff --git a/js/component/input/checkbox.js b/js/component/input/checkbox.js
index 8264520..5920f41 100644
--- a/js/component/input/checkbox.js
+++ b/js/component/input/checkbox.js
@@ -36,7 +36,7 @@ beestat.component.input.checkbox.prototype.decorate_ = function(parent) {
const span = document.createElement('span');
span.style.cursor = 'pointer';
- span.style.marginLeft = (beestat.style.size.gutter / 2) + 'px';
+ span.style.paddingLeft = (beestat.style.size.gutter / 4) + 'px';
span.innerText = this.label_;
span.addEventListener('click', function() {
self.input_.click();
diff --git a/js/component/input/radio.js b/js/component/input/radio.js
index adfeaa8..a5542f0 100644
--- a/js/component/input/radio.js
+++ b/js/component/input/radio.js
@@ -37,7 +37,7 @@ beestat.component.input.radio.prototype.decorate_ = function(parent) {
const span = document.createElement('span');
span.style.cursor = 'pointer';
- span.style.marginLeft = (beestat.style.size.gutter / 2) + 'px';
+ span.style.paddingLeft = (beestat.style.size.gutter / 4) + 'px';
span.innerText = this.label_;
span.addEventListener('click', function() {
self.input_.click();
diff --git a/js/component/input/text.js b/js/component/input/text.js
index 8b45845..04c7220 100644
--- a/js/component/input/text.js
+++ b/js/component/input/text.js
@@ -19,6 +19,22 @@ beestat.component.input.text = function() {
});
this.input_.addEventListener('change', function() {
+ if (
+ self.transform_ !== undefined &&
+ self.meets_requirements() === true
+ ) {
+ switch (self.transform_.type) {
+ case 'round':
+ // If the value is a number, then round it.
+ if (new RegExp(/^[\d\.]+$/).test(self.input_.value) === true) {
+ self.input_.value = Math.round(
+ self.input_.value * 10 ** self.transform_.decimals
+ ) / 10 ** self.transform_.decimals;
+ }
+ break;
+ }
+ }
+
self.dispatchEvent('change');
});
@@ -212,3 +228,16 @@ beestat.component.input.text.prototype.set_maxlength = function(maxlength) {
return this;
};
+
+/**
+ * Set the auto format properties.
+ *
+ * @param {object} transform
+ *
+ * @return {beestat.component.input.text} This.
+ */
+beestat.component.input.text.prototype.set_transform = function(transform) {
+ this.transform_ = transform;
+
+ return this;
+};
diff --git a/js/component/metric/balance_point.js b/js/component/metric/balance_point.js
index 50ac463..1d88d27 100644
--- a/js/component/metric/balance_point.js
+++ b/js/component/metric/balance_point.js
@@ -20,7 +20,7 @@ beestat.component.metric.balance_point.prototype.is_temperature_ = true;
* @return {string} The units for this metric.
*/
beestat.component.metric.balance_point.prototype.get_units_ = function() {
- return beestat.setting('temperature_unit');
+ return beestat.setting('units.temperature');
};
/**
diff --git a/js/component/metric/setback.js b/js/component/metric/setback.js
index 8be0c99..6bf435e 100644
--- a/js/component/metric/setback.js
+++ b/js/component/metric/setback.js
@@ -22,7 +22,7 @@ beestat.component.metric.setback.prototype.is_temperature_delta_ = true;
* @return {string} The units for this metric.
*/
beestat.component.metric.setback.prototype.get_units_ = function() {
- return beestat.setting('temperature_unit');
+ return beestat.setting('units.temperature');
};
/**
diff --git a/js/component/metric/setpoint.js b/js/component/metric/setpoint.js
index e9ec191..4a1377a 100644
--- a/js/component/metric/setpoint.js
+++ b/js/component/metric/setpoint.js
@@ -20,7 +20,7 @@ beestat.component.metric.setpoint.prototype.is_temperature_ = true;
* @return {string} The units for this metric.
*/
beestat.component.metric.setpoint.prototype.get_units_ = function() {
- return beestat.setting('temperature_unit');
+ return beestat.setting('units.temperature');
};
/**
diff --git a/js/component/modal/create_floor_plan.js b/js/component/modal/create_floor_plan.js
index fe34d9e..2d152b6 100644
--- a/js/component/modal/create_floor_plan.js
+++ b/js/component/modal/create_floor_plan.js
@@ -92,27 +92,34 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
}
// Ceiling height
- (new beestat.component.title('How tall are your ceilings (feet)?')).render(parent);
+ (new beestat.component.title('How tall are your ceilings (' + beestat.setting('units.distance') + ')?')).render(parent);
- const height_input = new beestat.component.input.text()
+ const ceiling_height_input = new beestat.component.input.text()
.set_icon('arrow_expand_vertical')
- .set_maxlength(2)
+ .set_maxlength(5)
+ .set_transform({
+ 'type': 'round',
+ 'decimals': 2
+ })
.set_requirements({
- 'min_value': 1,
- 'type': 'integer',
+ 'min_value': beestat.distance(60),
+ 'type': 'decimal',
'required': true
})
.render(parent);
- height_input.addEventListener('change', function() {
- self.state_.height = height_input.get_value();
- self.state_.error.height = !height_input.meets_requirements();
+ ceiling_height_input.addEventListener('change', function() {
+ self.state_.ceiling_height = ceiling_height_input.get_value();
+ self.state_.error.height = !ceiling_height_input.meets_requirements();
});
- if (self.state_.height !== undefined) {
- height_input.set_value(self.state_.height);
+ if (self.state_.ceiling_height !== undefined) {
+ ceiling_height_input.set_value(self.state_.ceiling_height);
} else if (self.state_.error.height !== true) {
- height_input.set_value(8);
+ ceiling_height_input.set_value(beestat.distance({
+ 'distance': 96,
+ 'round': 2
+ }));
}
// Address
@@ -203,6 +210,13 @@ beestat.component.modal.create_floor_plan.prototype.get_buttons_ = function() {
return;
}
+ const ceiling_height = beestat.distance({
+ 'distance': self.state_.ceiling_height,
+ 'input_distance_unit': beestat.setting('units.distance'),
+ 'output_distance_unit': 'in',
+ 'round': 2
+ });
+
const attributes = {
'name': self.state_.name
};
@@ -212,7 +226,7 @@ beestat.component.modal.create_floor_plan.prototype.get_buttons_ = function() {
attributes.data = {
'groups': []
};
- let elevation = (self.state_.basement === true) ? (self.state_.height * -12) : 0;
+ let elevation = (self.state_.basement === true) ? (ceiling_height * -1) : 0;
let floor = (self.state_.basement === true) ? 0 : 1;
const ordinals = [
'First',
@@ -229,12 +243,12 @@ beestat.component.modal.create_floor_plan.prototype.get_buttons_ = function() {
attributes.data.groups.push({
'name': floor === 0 ? 'Basement' : (ordinals[floor - 1] + ' Floor'),
'elevation': elevation,
- 'height': self.state_.height * 12,
+ 'height': ceiling_height,
'rooms': []
});
floor++;
- elevation += (self.state_.height * 12);
+ elevation += (ceiling_height);
}
new beestat.api()
diff --git a/js/component/radio_group.js b/js/component/radio_group.js
index 29ad93c..67c67df 100644
--- a/js/component/radio_group.js
+++ b/js/component/radio_group.js
@@ -3,7 +3,7 @@
*/
beestat.component.radio_group = function() {
this.radios_ = [];
- this.name_ = Math.random();
+ this.name_ = window.crypto.randomUUID();
beestat.component.apply(this, arguments);
};
beestat.extend(beestat.component.radio_group, beestat.component);
@@ -16,33 +16,42 @@ beestat.extend(beestat.component.radio_group, beestat.component);
beestat.component.radio_group.prototype.decorate_ = function(parent) {
const self = this;
- const container = $.createElement('div');
- container.style('margin-bottom', beestat.style.size.gutter);
+ // Outer container
+ const container = document.createElement('div');
+ if (this.arrangement_ === 'horizontal') {
+ Object.assign(container.style, {
+ 'display': 'flex',
+ 'grid-gap': `${beestat.style.size.gutter}px`
+ });
+ }
+ parent.appendChild(container);
+ // Radios
this.radios_.forEach(function(radio) {
- radio.set_name('name', this.name_);
+ radio.set_name(self.name_);
radio.addEventListener('change', function() {
self.value_ = radio.get_value();
self.dispatchEvent('change');
});
- radio.render(container);
+ radio.render($(container));
});
-
- parent.appendChild(container);
};
/**
* Add a radio to this group.
*
* @param {beestat.component.radio} radio The radio to add.
+ *
+ * @return {beestat.component.radio_group}
*/
beestat.component.radio_group.prototype.add_radio = function(radio) {
this.radios_.push(radio);
if (this.rendered_ === true) {
this.rerender();
}
+ return this;
};
/**
@@ -69,3 +78,16 @@ beestat.component.radio_group.prototype.get_value = function() {
return null;
};
+
+/**
+ * Set the arrangement of the radio buttons in the group.
+ *
+ * @param {string} arrangement horizontal|vertical
+ *
+ * @return {beestat.component.radio_group}
+ */
+beestat.component.radio_group.prototype.set_arrangement = function(arrangement) {
+ this.arrangement_ = arrangement;
+
+ return this;
+};
diff --git a/js/component/tile/floor_plan.js b/js/component/tile/floor_plan.js
index 1ed5486..030bbf7 100644
--- a/js/component/tile/floor_plan.js
+++ b/js/component/tile/floor_plan.js
@@ -2,6 +2,7 @@
* A tile representing a floor plan.
*
* @param {integer} floor_plan_id
+ *
*/
beestat.component.tile.floor_plan = function(floor_plan_id) {
this.floor_plan_id_ = floor_plan_id;
@@ -30,7 +31,13 @@ beestat.component.tile.floor_plan.prototype.get_text_ = function() {
const line_2_parts = [];
let floor_count = floor_plan.data.groups.length;
line_2_parts.push(floor_count + (floor_count === 1 ? ' Floor' : ' Floors'));
- line_2_parts.push(beestat.floor_plan.get_area(this.floor_plan_id_).toLocaleString() + ' sqft');
+ line_2_parts.push(
+ beestat.area({
+ 'area': beestat.floor_plan.get_area(this.floor_plan_id_),
+ 'round': 0,
+ 'units': true
+ })
+ );
return [
floor_plan.name,
diff --git a/js/component/tile/floor_plan_group.js b/js/component/tile/floor_plan_group.js
index 36c424e..9563e87 100644
--- a/js/component/tile/floor_plan_group.js
+++ b/js/component/tile/floor_plan_group.js
@@ -28,7 +28,13 @@ beestat.component.tile.floor_plan_group.prototype.get_text_ = function() {
const line_2_parts = [];
let room_count = this.floor_plan_group_.rooms.length;
line_2_parts.push(room_count + (room_count === 1 ? ' Room' : ' Rooms'));
- line_2_parts.push(beestat.floor_plan.get_area_group(this.floor_plan_group_).toLocaleString() + ' sqft');
+ line_2_parts.push(
+ beestat.area({
+ 'area': beestat.floor_plan.get_area_group(this.floor_plan_group_),
+ 'round': 0,
+ 'units': true
+ })
+ );
return [
this.floor_plan_group_.name,
diff --git a/js/js.php b/js/js.php
index 2d6e238..539c4e7 100755
--- a/js/js.php
+++ b/js/js.php
@@ -16,6 +16,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
// Beestat
echo '' . PHP_EOL;
@@ -28,6 +29,8 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
diff --git a/js/layer/load.js b/js/layer/load.js
index bb9e51d..dc950ab 100644
--- a/js/layer/load.js
+++ b/js/layer/load.js
@@ -170,8 +170,38 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
document.title = 'beestat';
}
- // Set the active temperature unit.
- beestat.setting('temperature_unit', thermostat.temperature_unit);
+ // Set the temperature unit if it hasn't been set before.
+ if (beestat.setting('units.temperature') === undefined) {
+ beestat.setting('units.temperature', thermostat.temperature_unit);
+ }
+
+ // Set the distance/area units if they hasn't been set before.
+ const imperial_countries = [
+ 'USA',
+ 'CAN'
+ ];
+ if (
+ beestat.setting('units.distance') === undefined &&
+ thermostat.address_id !== null &&
+ beestat.address.is_valid(thermostat.address_id) === true
+ ) {
+ const address = beestat.cache.address[thermostat.address_id];
+ beestat.setting(
+ 'units.distance',
+ imperial_countries.includes(address.normalized.components.country_iso_3) === true ? 'ft' : 'm'
+ );
+ }
+ if (
+ beestat.setting('units.area') === undefined &&
+ thermostat.address_id !== null &&
+ beestat.address.is_valid(thermostat.address_id) === true
+ ) {
+ const address = beestat.cache.address[thermostat.address_id];
+ beestat.setting(
+ 'units.area',
+ imperial_countries.includes(address.normalized.components.country_iso_3) === true ? 'ft²' : 'm²'
+ );
+ }
// Rename series if there are multiple stages.
if (beestat.thermostat.get_stages(thermostat.thermostat_id, 'heat') > 1) {
diff --git a/js/lib/polylabel/polylabel.js b/js/lib/polylabel/polylabel.js
new file mode 100644
index 0000000..7d2916f
--- /dev/null
+++ b/js/lib/polylabel/polylabel.js
@@ -0,0 +1,3 @@
+/* eslint-disable */
+
+!function(a){"object"==typeof exports&&"undefined"!=typeof module?module.exports=a():"function"==typeof define&&define.amd?define([],a):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).polylabel=a()}(function(){return(function d(e,f,b){function c(a,k){if(!f[a]){if(!e[a]){var i="function"==typeof require&&require;if(!k&&i)return i(a,!0);if(g)return g(a,!0);var j=new Error("Cannot find module '"+a+"'");throw j.code="MODULE_NOT_FOUND",j}var h=f[a]={exports:{}};e[a][0].call(h.exports,function(b){return c(e[a][1][b]||b)},h,h.exports,d,e,f,b)}return f[a].exports}for(var g="function"==typeof require&&require,a=0;ab!=c[1]>b&&j<(c[0]-a[0])*(b-a[1])/(c[1]-a[1])+a[0]&&(d=!d),e=Math.min(e,h(j,b,a,c))}return(d?1:-1)*Math.sqrt(e)}function h(g,h,i,e){var c=i[0],d=i[1],a=e[0]-c,b=e[1]-d;if(0!==a||0!==b){var f=((g-c)*a+(h-d)*b)/(a*a+b*b);f>1?(c=e[0],d=e[1]):f>0&&(c+=a*f,d+=b*f)}return(a=g-c)*a+(b=h-d)*b}b.exports=function(c,o,t){o=o||1;for(var k,l,m,n,i=0;im)&&(m=g[0]),(!i||g[1]>n)&&(n=g[1])}for(var p=Math.min(m-k,n-l),a=p/2,h=new d(null,e),q=k;qj.d&&(j=b,t&&console.log("found best %d after %d probes",Math.round(1e4*b.d)/1e4,s)),b.max-j.d<=o||(a=b.h/2,h.push(new f(b.x-a,b.y-a,a,c)),h.push(new f(b.x+a,b.y-a,a,c)),h.push(new f(b.x-a,b.y+a,a,c)),h.push(new f(b.x+a,b.y+a,a,c)),s+=4)}return t&&(console.log("num probes: "+s),console.log("best distance: "+j.d)),[j.x,j.y]}},{tinyqueue:2}],2:[function(c,b,d){"use strict";function a(b,d){if(!(this instanceof a))return new a(b,d);if(this.data=b||[],this.length=this.data.length,this.compare=d||e,b)for(var c=Math.floor(this.length/2);c>=0;c--)this._down(c)}function e(a,b){return ab?1:0}function f(a,b,c){var d=a[b];a[b]=a[c],a[c]=d}b.exports=a,a.prototype={push:function(a){this.data.push(a),this.length++,this._up(this.length-1)},pop:function(){var a=this.data[0];return this.data[0]=this.data[this.length-1],this.length--,this.data.pop(),this._down(0),a},peek:function(){return this.data[0]},_up:function(a){for(var b=this.data,d=this.compare;a>0;){var c=Math.floor((a-1)/2);if(0>d(b[a],b[c]))f(b,c,a),a=c;else break}},_down:function(b){for(var c=this.data,g=this.compare,h=this.length;;){var d=2*b+1,e=d+1,a=b;if(dg(c[d],c[a])&&(a=d),eg(c[e],c[a])&&(a=e),a===b)return;f(c,a,b),b=a}}}},{}]},{},[1])(1)})