This is a tool to add a distance weighted heatmap to a Floorplan for Home Assitant.
A JSON structure is generated that allows to calculate path distance from every choosen sensor to every point
in your floorplan to generate a weighted interpolated value.
The heatmap data is then calculated and rendererd with WebGL2 to a <canvas> that is inserted into your floorplan.
Requirements for Floorplan .svg:
path.ha-fp-hm-area element
that is composed only of straight lines and does not contain any holes.
The enclosed area defines the area in which to generate the heatmap. These require an id.
.ha-fp-hm-sensor Element. Their bounding box center
set the sensor locations for the heatmap. These require an id.
rect.ha-fp-hm element. This element defines the location of
the canvas that will be used to show the heatmap.
<foreingObject> element to contain the custom element that controls
rendering the heatmap.
I didn't like that I wasn't able to control the size/position
of the <foreingObject> in Inkscape, so a normal
<rect> is used.
This object won't render anything itself for now.
The tool will generate a JSON structure that contains all the distance data for all the sensors. If you change your layout or any sensor position you will have to regenerate that data.
To add it to Home Assistant you will need to:
draw.js
and configure it as a local Dashboard resource.
earcut.min.js
dependency.
Does not have to be added as a dashboard resource.
Has to be next to the draw.js, as it is imported with a relative path.
ha-floorplan to load the custom element into the
<foreingObject> and supply the configuration at startup:
startup_action:
- action: call-service
service: floorplan.card_set
service_data:
container_id: heatmapcontrollercontainer # id of foreingObject in svg file
config:
type: custom:mppr-heatmap
idw_p: 2
cuthi: false
dataUrl: /local/floorplan/my_home/heatmap_data.json # web path to your JSON data
colormapCode: /local/floorplan/my_home/viridis.glsl # web path to your colormap shader
colorExpression: viridis(clamp(value, 0.0, 1.0)) # expression that calls your colormap shader function
value_preprocessor: | # Function that receives a map sensorId => value and can transform sensor values.
(values) => {
let avg = 0;
let min = Infinity;
let max = -Infinity;
values.forEach((v, _) => {
avg += v;
if (v > max) {
max = v;
}
if (v < min) {
min = v;
}
})
avg /= values.size;
const spread = max-min;
values.forEach((v, sensorId) => {
if (spread != 0) {
const newValue = 0.5 + (v - avg)/(spread);
values.set(sensorId, newValue)
} else {
values.set(sensorId, 0.5);
}
})
return values;
}
ha-floorplan to update the data-ha-fp-hm-sensor-value attribute
of a .ha-fp-hm-sensor element when a sensor value changes:
rules:
- entities:
- entity: sensor.th_i1_temperature
element: bedroom1
- entity: sensor.th_i2_temperature
element: livingroom1
state_action:
action: call-service
service: floorplan.dataset_set
service_data:
key: haFpHmSensorValue # Note this uses the JavaScript instead of the HTML notation
value: ${entity.state}
Load your augmented floorplan here to generate the distance data for your floorplan. After loading your floorplan you have the option to simulate and randomize sensor data (0.0-1.0) to see the heatmap result. Sensors without a valid value will be ignored.
The algorithm to generate the distance-data contains a lot of literal and figurative edge cases. It would be a miracle if I managed to properly handle them all.
[1] Benmoshe, N. (2025). A Simple Solution for the Inverse Distance Weighting Interpolation (IDW) Clustering Problem. Sci, 7(1), 30. https://doi.org/10.3390/sci7010030