Add the html and javascript
A html template is provided to serve as default index.html.
A javascript file is used to draw finger traces on the document
canvas which basically mimics the old mtplot. It also supports
some hot keys:
ESC: the clear the screen
'b': toggle the background color
'f': toggle between full and non-full screen
'p': toggle between pressure mode and point mode
'q': notify the server that this client quits
's': notify the server to save the event file
BUG=chromium:443539
TEST=Manually test.
1. Scp the program to a chromebook.
2. Launch a web server to serve touchpad data on the chromebook.
$ python webplot.py
3. Switch to the chrome browser. Type "localhost" in the omnibox.
It will display a dark background. Fingers move on the touchpad
and observe the corresponding finger traces shown on the
browser tab.
Change-Id: I5d654080d4cf93565db4ef0e1b9c7d44ceabcc5f
Reviewed-on: https://chromium-review.googlesource.com/237525
Reviewed-by: Charlie Mooney <[email protected]>
Commit-Queue: Shyh-In Hwang <[email protected]>
Tested-by: Shyh-In Hwang <[email protected]>
diff --git a/webplot.html b/webplot.html
new file mode 100644
index 0000000..235e6ef
--- /dev/null
+++ b/webplot.html
@@ -0,0 +1,24 @@
+<!--
+Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<html>
+<head>
+ <script src="webplot.js"></script>
+</head>
+
+<body style="margin:0; padding:0; background-color:black"
+ onbeforeunload="quit()"
+ onload="createWS()"
+ onkeyup="keyupHandler()"
+ onresize="resizeCanvas()">
+
+ <div id="websocketUrl" hidden>%(websocketUrl)s</div>
+ <div id="touchMaxX" hidden>%(touchMaxX)s</div>
+ <div id="touchMaxY" hidden>%(touchMaxY)s</div>
+ <canvas id="canvasWebplot"></canvas>
+
+</body>
+</html>
diff --git a/webplot.js b/webplot.js
new file mode 100644
index 0000000..274daa8
--- /dev/null
+++ b/webplot.js
@@ -0,0 +1,317 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+/**
+ * Choose finger colors for circles and the click color for rectangles.
+ * @constructor
+ */
+function Color() {
+ this.tids = [];
+ this.lastIndex = -1;
+ this.COLOR_TABLE = [
+ 'Blue', 'Gold', 'LimeGreen', 'Red', 'Cyan',
+ 'Magenta', 'Brown', 'Wheat', 'DarkGreen', 'Coral',
+ ];
+ this.length = this.COLOR_TABLE.length;
+ this.COLOR_CLICK = 'Gray';
+ this.COLOR_FRAME = 'Gray';
+
+ for (var i = 0; i < this.length; i++) {
+ this.tids[i] = -1;
+ }
+}
+
+
+/**
+ * Get the color to draw a circle for a given Tracking ID (tid).
+ * @param {int} tid
+ * @return {string}
+ */
+Color.prototype.getCircleColor = function(tid) {
+ index = this.tids.indexOf(tid);
+ // Find next color for this new tid.
+ if (index == -1) {
+ var i = (this.lastIndex + 1) % this.length;
+ while (i != this.lastIndex) {
+ if (this.tids[i] == -1) {
+ this.tids[i] = tid;
+ this.lastIndex = i;
+ return this.COLOR_TABLE[i];
+ }
+ i = (i + 1) % this.length;
+ }
+
+ // It is very unlikely that all slots in this.tids have been occupied.
+ // Should it happen, just assign a color to it.
+ return this.COLOR_TABLE[0];
+ } else {
+ return this.COLOR_TABLE[index];
+ }
+}
+
+
+/**
+ * Get the color to draw a rectangle for a given Tracking ID (tid).
+ * @param {int} tid
+ * @return {string}
+ */
+Color.prototype.getRectColor = function(tid) {
+ return this.COLOR_CLICK;
+}
+
+
+/**
+ * Remove the Tracking ID (tid) from the tids array.
+ * @param {int} tid
+ */
+Color.prototype.remove = function(tid) {
+ index = this.tids.indexOf(tid);
+ if (index >= 0) {
+ this.tids[index] = -1;
+ }
+}
+
+
+/**
+ * Pick up colors for circles and rectangles.
+ * @constructor
+ * @param {Element} canvas the canvas to draw circles and clicks.
+ * @param {int} touchMaxX the max x value of the touch device.
+ * @param {int} touchMaxY the max y value of the touch device.
+ */
+function Webplot(canvas, touchMaxX, touchMaxY) {
+ this.canvas = canvas;
+ this.ctx = canvas.getContext('2d');
+ this.color = new Color();
+ this.minX = 0;
+ this.maxX = touchMaxX;
+ this.minY = 0;
+ this.maxY = touchMaxY;
+ this.minPressure = 0;
+ // TODO: it is better to get maxPressure from a touch device.
+ // This requires a change in remote._GetDimensions() in
+ // touch_firmware_test.
+ this.maxPressure = 255;
+ this.maxRadiusRatio = 0.03;
+ this.maxRadius = null;
+ this.clickEdge = null;
+ this.clickDown = false;
+ this.pressureMode = true;
+ this.pointRadius = 2;
+}
+
+
+/**
+ * Update the width and height of the canvas, the max radius of circles,
+ * and the edge of click rectangles.
+ */
+Webplot.prototype.updateCanvasDimension = function() {
+ var newWidth = document.body.clientWidth;
+ var newHeight = document.body.clientHeight;
+
+ if (this.canvas.width != newWidth || this.canvas.height != newHeight) {
+ this.canvas.width = newWidth;
+ this.canvas.height = newHeight;
+ this.maxRadius = Math.min(newWidth, newHeight) * this.maxRadiusRatio;
+ this.clickEdge = (this.pressureMode ? this.maxRadius : this.maxRadius / 2);
+ }
+ this.drawRect(0, 0, newWidth, newHeight, this.color.COLOR_FRAME);
+}
+
+
+/**
+ * Draw a circle.
+ * @param {int} x the x coordinate of the circle.
+ * @param {int} y the y coordinate of the circle.
+ * @param {int} r the radius of the circle.
+ * @param {string} colorName
+ */
+Webplot.prototype.drawCircle = function(x, y, r, colorName) {
+ this.ctx.beginPath();
+ this.ctx.fillStyle = colorName;
+ this.ctx.arc(x, y, r, 0, 2 * Math.PI);
+ this.ctx.fill();
+}
+
+
+/**
+ * Draw a rectangle.
+ * @param {int} x the x coordinate of upper left corner of the rectangle.
+ * @param {int} y the y coordinate of upper left corner of the rectangle.
+ * @param {int} width the width of the rectangle.
+ * @param {int} height the height of the rectangle.
+ * @param {string} colorName
+ */
+Webplot.prototype.drawRect = function(x, y, width, height, colorName) {
+ this.ctx.beginPath();
+ this.ctx.lineWidth = "4";
+ this.ctx.strokeStyle = colorName;
+ this.ctx.rect(x, y, width, height);
+ this.ctx.stroke();
+}
+
+
+/**
+ * Process an incoming snapshot.
+ * @param {object} snapshot
+ *
+ * A 2f snapshot received from the python server looks like:
+ * MtbSnapshot(
+ * syn_time=1420522152.269537,
+ * button_pressed=False,
+ * fingers=[
+ * MtbFinger(tid=13, slot=0, syn_time=1420522152.269537, x=440, y=277,
+ * pressure=33),
+ * MtbFinger(tid=14, slot=1, syn_time=1420522152.269537, x=271, y=308,
+ * pressure=38)
+ * ]
+ * )
+ */
+Webplot.prototype.processSnapshot = function(snapshot) {
+ var edge = this.clickEdge;
+
+ for (var i = 0; i < snapshot.fingers.length; i++) {
+ var finger = snapshot.fingers[i];
+
+ // Update the color object if the finger is leaving.
+ if (finger.leaving) {
+ this.color.remove(finger.tid);
+ continue;
+ }
+
+ var x = (finger.x - this.minX) / (this.maxX - this.minX) *
+ this.canvas.width;
+ var y = (finger.y - this.minY) / (this.maxY - this.minY) *
+ this.canvas.height;
+ if (this.pressureMode)
+ var r = (finger.pressure - this.minPressure) /
+ (this.maxPressure - this.minPressure) * this.maxRadius;
+ else
+ var r = this.pointRadius;
+
+ this.drawCircle(x, y, r, this.color.getCircleColor(finger.tid));
+
+ // If there is a click, draw the click with finger 0.
+ // The flag clickDown is used to draw the click exactly once
+ // during the click down period.
+ if (snapshot.button_pressed == 1 && i == 0 && !this.clickDown) {
+ this.drawRect(x, y, edge, edge, this.color.getRectColor());
+ this.clickDown = true;
+ }
+ }
+
+ // In some special situation, the click comes with no fingers.
+ // This may happen if an insulated object is used to click the touchpad.
+ // Just draw the click at a random position.
+ if (snapshot.fingers.length == 0 && snapshot.button_pressed == 1 &&
+ !this.clickDown) {
+ var x = Math.random() * this.canvas.width;
+ var y = Math.random() * this.canvas.height;
+ this.drawRect(x, y, edge, edge, this.color.getRectColor());
+ this.clickDown = true;
+ }
+
+ if (snapshot.button_pressed == 0) {
+ this.clickDown = false;
+ }
+}
+
+
+/**
+ * An handler for onresize event to update the canvas dimensions.
+ */
+function resizeCanvas() {
+ webplot.updateCanvasDimension();
+}
+
+
+/**
+ * Send a 'quit' message to the server.
+ */
+function quit() {
+ window.ws.send('quit');
+}
+
+
+/**
+ * A handler for keyup events to handle user hot keys.
+ */
+function keyupHandler() {
+ var webplot = window.webplot;
+ var canvas = document.getElementById('canvasWebplot');
+ var key = String.fromCharCode(event.which).toLowerCase();
+ var ESC = String.fromCharCode(27);
+
+ switch(String.fromCharCode(event.which).toLowerCase()) {
+ // ESC: clearing the canvas
+ case ESC:
+ canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
+ webplot.updateCanvasDimension();
+ break;
+
+ // 'b': toggle the background color between black and white
+ // default: black
+ case 'b':
+ document.bgColor = (document.bgColor == 'Black' ? 'White' : 'Black');
+ break;
+
+ // 'f': toggle full screen
+ // default: non-full screen
+ // Note: entering or leaving full screen will trigger onresize events.
+ case 'f':
+ if (document.documentElement.webkitRequestFullscreen) {
+ if (document.webkitFullscreenElement)
+ document.webkitCancelFullScreen();
+ else
+ document.documentElement.webkitRequestFullscreen(
+ Element.ALLOW_KEYBOARD_INPUT);
+ }
+ webplot.updateCanvasDimension();
+ break;
+
+ // 'p': toggle between pressure mode and point mode.
+ // pressure mode: the circle radius corresponds to the pressure
+ // point mode: the circle radius is fixed and small
+ // default: pressure mode
+ case 'p':
+ webplot.pressureMode = webplot.pressureMode ? false : true;
+ webplot.updateCanvasDimension();
+ break;
+
+ // 'q': Quit the server
+ case 'q':
+ quit();
+ break;
+
+ // 's': save the touch events in a specified file name.
+ // default: /tmp/webplot.dat
+ case 's':
+ window.ws.send('save:/tmp/webplot.dat')
+ break;
+ }
+}
+
+
+/**
+ * Create a web socket and a new webplot object.
+ */
+function createWS() {
+ var websocket = document.getElementById('websocketUrl').innerText;
+ var touchMaxX = document.getElementById('touchMaxX').innerText;
+ var touchMaxY = document.getElementById('touchMaxY').innerText;
+ if (window.WebSocket) {
+ ws = new WebSocket(websocket);
+ ws.addEventListener("message", function(event) {
+ var snapshot = JSON.parse(event.data);
+ webplot.processSnapshot(snapshot);
+ });
+ } else {
+ alert('WebSocket is not supported on this browser!')
+ }
+
+ webplot = new Webplot(document.getElementById('canvasWebplot'),
+ touchMaxX, touchMaxY);
+ webplot.updateCanvasDimension();
+}