diff --git a/www/heatmap.html b/www/heatmap.html
new file mode 100644
index 000000000..afd479317
--- /dev/null
+++ b/www/heatmap.html
@@ -0,0 +1,38 @@
+
+
+
+ Webgazer.js + heatmap.js demo
+
+
+
+
+
+
+
+
+
+
diff --git a/www/js/heatmap-demo.js b/www/js/heatmap-demo.js
new file mode 100644
index 000000000..a258c154f
--- /dev/null
+++ b/www/js/heatmap-demo.js
@@ -0,0 +1,102 @@
+// Set to true if you want to save the data even if you reload the page.
+window.saveDataAcrossSessions = false;
+
+// heatmap configuration
+const config = {
+ radius: 25,
+ maxOpacity: .5,
+ minOpacity: 0,
+ blur: .75
+};
+
+// Global variables
+let heatmapInstance;
+
+window.addEventListener('load', async function() {
+ // Init webgazer
+ if (!window.saveDataAcrossSessions) {
+ var localstorageDataLabel = 'webgazerGlobalData';
+ localforage.setItem(localstorageDataLabel, null);
+ var localstorageSettingsLabel = 'webgazerGlobalSettings';
+ localforage.setItem(localstorageSettingsLabel, null);
+ }
+ const webgazerInstance = await webgazer.setRegression('ridge') /* currently must set regression and tracker */
+ .setTracker('TFFacemesh')
+ .begin();
+
+ // Turn off video
+ webgazerInstance.showVideoPreview(false) /* shows all video previews */
+ .showPredictionPoints(false); /* shows a square every 100 milliseconds where current prediction is */
+
+ // Enable smoothing
+ webgazerInstance.applyKalmanFilter(true); // Kalman Filter defaults to on.
+
+ // Set up heatmap parts
+ setupHeatmap();
+ webgazer.setGazeListener( eyeListener );
+});
+
+window.addEventListener('beforeunload', function() {
+ if (window.saveDataAcrossSessions) {
+ webgazer.end();
+ } else {
+ localforage.clear();
+ }
+});
+
+// Trimmed down version of webgazer's click listener since the built-in one isn't exported
+// Needed so we can have just the click listener without the move listener
+// (The move listener was creating a lot of drift)
+async function clickListener(event) {
+ webgazer.recordScreenPosition(event.clientX, event.clientY, 'click'); // eventType[0] === 'click'
+}
+
+function setupHeatmap() {
+ // Don't use mousemove listener
+ webgazer.removeMouseEventListeners();
+ document.addEventListener('click', clickListener);
+
+ // Get the window size
+ let height = window.innerHeight;
+ let width = window.innerWidth;
+
+ // Set up the container
+ let container = document.getElementById('heatmapContainer');
+ container.style.height = `${height}px`;
+ container.style.width = `${width}px`;
+ config.container = container;
+
+ // create heatmap
+ heatmapInstance = h337.create(config);
+}
+
+// Heatmap buffer
+let lastTime;
+let lastGaze;
+
+async function eyeListener(data, clock) {
+ // data is the gaze data, clock is the time since webgazer.begin()
+
+ // Init if lastTime not set
+ if(!lastTime) {
+ lastTime = clock;
+ }
+
+ // In this we want to track how long a point was being looked at,
+ // so we need to buffer where the gaze moves to and then on next move
+ // we calculate how long the gaze stayed there.
+ if(!!lastGaze) {
+ if(!!lastGaze.x && !!lastGaze.y) {
+ let duration = clock-lastTime;
+ let point = {
+ x: Math.floor(lastGaze.x),
+ y: Math.floor(lastGaze.y),
+ value: duration
+ }
+ heatmapInstance.addData(point);
+ }
+ }
+
+ lastGaze = data;
+ lastTime = clock;
+}
diff --git a/www/media/example/starrynight.jpg b/www/media/example/starrynight.jpg
new file mode 100644
index 000000000..ff470f73c
Binary files /dev/null and b/www/media/example/starrynight.jpg differ
diff --git a/www/package-lock.json b/www/package-lock.json
index 4b90fc59b..8e8b2c54c 100644
--- a/www/package-lock.json
+++ b/www/package-lock.json
@@ -11,6 +11,7 @@
"bootstrap": "^5.2.3",
"browser-sync": "^2.26.12",
"d3": "^5.16.0",
+ "heatmap.js": "^2.0.5",
"sweetalert": "^2.1.2"
}
},
@@ -932,19 +933,6 @@
"universalify": "^0.1.0"
}
},
- "node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -1017,6 +1005,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/heatmap.js": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/heatmap.js/-/heatmap.js-2.0.5.tgz",
+ "integrity": "sha512-CG2gYFP5Cv9IQCXEg3ZRxnJDyAilhWnQlAuHYGuWVzv6mFtQelS1bR9iN80IyDmFECbFPbg6I0LR5uAFHgCthw=="
+ },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
diff --git a/www/package.json b/www/package.json
index 5b9375a6b..412a7d7de 100644
--- a/www/package.json
+++ b/www/package.json
@@ -12,6 +12,7 @@
"bootstrap": "^5.2.3",
"browser-sync": "^2.26.12",
"d3": "^5.16.0",
+ "heatmap.js": "^2.0.5",
"sweetalert": "^2.1.2"
},
"overrides": {