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": {