certainTrustHTI.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /**
  2. * CertainTrust SDK
  3. *
  4. * Implements the computational trust model "CertainTrust"
  5. * in JavaScript.
  6. * See <http://www.tk.informatik.tu-darmstadt.de/de/research/smart-security-and-trust/> for further details.
  7. *
  8. *
  9. * Telecooperation Department, Technische Universität Darmstadt
  10. * <http://www.tk.informatik.tu-darmstadt.de/>
  11. *
  12. * Prof. Dr. Max Mühlhäuser <max@informatik.tu-darmstadt.de>
  13. * Florian Volk <florian.volk@cased.de>
  14. *
  15. *
  16. * @author David Kalnischkies
  17. * @author Florian Volk
  18. * @version 1.0
  19. */
  20. /* This Source Code Form is subject to the terms of the Mozilla Public
  21. * License, v. 2.0. If a copy of the MPL was not distributed with this
  22. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  23. /** Certain Trust Widget
  24. *
  25. * Creates a color-coded graph created from an initial trust value
  26. * in which the user can pick certainty (y-axis) and trust (x-axis)
  27. * with the mouse in the graph.
  28. *
  29. * The values are displayed and can be modified inside a form
  30. * while the graph in the canvas will be updated from those values.
  31. *
  32. * Options can be given in an object as parameter to the constructor
  33. * The following keys are understood:
  34. * id is the id the top-div of the widget will get. Its used as basename for all element ids in it, too
  35. * label is an object for various messages which are shown to the user
  36. * lang is the language to use. Available are 'de' and 'en' (default)
  37. * Alternatively f, t, c and e can be used to set the text explicitly
  38. * canvas is an object containing subkeys 'width' and 'height' defining dimensions of the canvas element
  39. * line is an object defining the style of the lines building the cross to mark the chosen trust/certainty
  40. * cap is the canvas.lineCap defining the style used to smooth the end of the lines
  41. * height is the length of the line from start to the crosspoint
  42. * width is the thickness of the line
  43. * style is the color of the line
  44. * readonly can be used to disable input in the HTI as a whole,
  45. * Alternatively f, t, c, e, inputs for all of the previous and mouse can be used to disable specific elements.
  46. * show can be used to disable display of certain inputs refered to as
  47. * f, t, c, e, inputs for all of the previous, title and axes.
  48. *
  49. * One of the following keys can be used to specify the position of the widget in the DOM,
  50. * the first defined will be used. If none is provided domAfter will be set to the <script>-tag calling
  51. * the creation of the widget (= the last <script>-tag currently in the DOM)
  52. * Either the DOM element itself can be given or the ID of the element as a string.
  53. * domReturn is set to true to get the DOM subtree as return value of the constructor instead [update() is NOT called]
  54. * domParent is (the id of) the parent for the widget
  55. * domBefore is (the id of) the element which the widget will be in front of
  56. * domAfter is (the id of) the element after which the widget will be inserted
  57. */
  58. var CertainTrustHTI = function(certainlogic, config) {
  59. if (certainlogic === undefined)
  60. this.certainTrust = new CertainTrust(5);
  61. else
  62. this.certainTrust = certainlogic;
  63. this.NR = CertainTrustHTIElement.length();
  64. CertainTrustHTIElement.push(this);
  65. this.certainTrust.addObserver(this);
  66. // set sane defaults for config if nothing is set
  67. if (config === undefined) config = {};
  68. if (config.id === undefined) config.id = 'certaintrust-hti-' + this.NR;
  69. // design your widget
  70. if (config.canvas === undefined) config.canvas = {};
  71. if (config.canvas.height === undefined) config.canvas.height = 100;
  72. if (config.canvas.width === undefined) config.canvas.width = 120;
  73. if (config.line === undefined) config.line = {};
  74. if (config.line.cap === undefined) config.line.cap = 'round';
  75. if (config.line.height === undefined) config.line.height = 5;
  76. if (config.line.width === undefined) config.line.width = 1;
  77. if (config.line.style === undefined) config.line.style = 'black';
  78. if (config.line.baserad === undefined) config.line.baserad = 45;
  79. else config.line.baserad %= 90;
  80. if (config.confidence === undefined) config.confidence = {};
  81. if (config.confidence.cap === undefined) config.confidence.cap = 'round';
  82. if (config.confidence.height === undefined) config.confidence.height = 5;
  83. if (config.confidence.width === undefined) config.confidence.width = 0;
  84. if (config.confidence.style === undefined) config.confidence.style = 'gray';
  85. if (config.confidence.quantil === undefined) config.confidence.quantil = 1.96;
  86. // language settings
  87. if (config.label === undefined) config.label = {};
  88. if (config.label.lang === undefined) config.label.lang = 'en';
  89. if (config.label.lang === 'de') {
  90. if (config.label.f === undefined) config.label.f = 'Initialwert';
  91. if (config.label.t === undefined) config.label.t = 'Vertrauen';
  92. if (config.label.c === undefined) config.label.c = 'Sicherheit';
  93. if (config.label.e === undefined) config.label.e = 'Erwartung';
  94. } else {
  95. if (config.label.f === undefined) config.label.f = 'Init. value';
  96. if (config.label.t === undefined) config.label.t = 'Trust';
  97. if (config.label.c === undefined) config.label.c = 'Certainty';
  98. if (config.label.e === undefined) config.label.e = 'Expectation';
  99. }
  100. // readonly forms maybe?
  101. var readonlyflag = false;
  102. if (config.readonly === undefined) {
  103. config.readonly = {};
  104. config.readonly.e = true;
  105. } else {
  106. readonlyflag = config.readonly;
  107. config.readonly = {};
  108. }
  109. if (config.readonly.inputs === undefined) config.readonly.inputs = readonlyflag;
  110. if (config.readonly.f === undefined) config.readonly.f = config.readonly.inputs;
  111. if (config.readonly.t === undefined) config.readonly.t = config.readonly.inputs;
  112. if (config.readonly.c === undefined) config.readonly.c = config.readonly.inputs;
  113. if (config.readonly.e === undefined) config.readonly.e = config.readonly.inputs;
  114. if (config.readonly.mouse === undefined) config.readonly.mouse = readonlyflag;
  115. // show/disable elements
  116. var showflag = true;
  117. if (config.show === undefined) config.show = {};
  118. else {
  119. showflag = config.show;
  120. config.show = {};
  121. }
  122. if (config.show.inputs === undefined) config.show.inputs = showflag;
  123. if (config.show.f === undefined) config.show.f = config.show.inputs;
  124. if (config.show.t === undefined) config.show.t = config.show.inputs;
  125. if (config.show.c === undefined) config.show.c = config.show.inputs;
  126. if (config.show.e === undefined) config.show.e = config.show.inputs;
  127. if (config.show.title === undefined) config.show.title = showflag;
  128. if (config.show.axes === undefined) config.show.axes = showflag;
  129. this.ID = config.id;
  130. this.config = config;
  131. var element = document.createElement('div');
  132. element.setAttribute('id', this.ID);
  133. element.setAttribute('class', 'certaintrust-hti');
  134. if (config.show.title === true && this.certainTrust.getName().length !== 0)
  135. {
  136. var title = document.createElement('h1');
  137. var msg = document.createTextNode(this.certainTrust.getName());
  138. title.appendChild(msg);
  139. element.appendChild(title);
  140. }
  141. var form = document.createElement('form');
  142. form.setAttribute('id', this.ID + '-form');
  143. var appendInput = function (form, id, nr, type, value, text, readonly, show) {
  144. if (show === false)
  145. return;
  146. var div = document.createElement('div');
  147. div.setAttribute('class', 'certaintrust-hti-' + type);
  148. var label = document.createElement('label');
  149. label.setAttribute('for', id + '-' + type);
  150. div.appendChild(label);
  151. var labeltext = document.createTextNode(text);
  152. label.appendChild(labeltext);
  153. var input = document.createElement('input');
  154. input.setAttribute('type', 'text');
  155. input.setAttribute('id', id + '-' + type);
  156. var cte = CertainTrustHTIElement.ByNr(nr);
  157. input.setAttribute('value', value);
  158. if (readonly === true) {
  159. input.setAttribute('readonly', 'readonly');
  160. input.setAttribute('tabindex', '-1');
  161. } else {
  162. input.addEventListener('keypress', cte._onKeyPress, false);
  163. input.addEventListener('blur', cte._onBlur, false);
  164. }
  165. div.appendChild(input);
  166. form.appendChild(div);
  167. };
  168. appendInput(form, this.ID, this.NR, 'f', this.certainTrust.getF(), config.label.f, config.readonly.f, config.show.f);
  169. appendInput(form, this.ID, this.NR, 't', this.certainTrust.getT(), config.label.t, config.readonly.t, config.show.t);
  170. appendInput(form, this.ID, this.NR, 'c', this.certainTrust.getC(), config.label.c, config.readonly.c, config.show.c);
  171. appendInput(form, this.ID, this.NR, 'e', this.certainTrust.getExpectation(), config.label.e, config.readonly.e, config.show.e);
  172. if (form.hasChildNodes())
  173. element.appendChild(form);
  174. var hti = document.createElement('div');
  175. hti.style.cssFloat=hti.style.styleFloat='left';
  176. if (config.show.axes === true)
  177. {
  178. var yaxis = document.createElement('span');
  179. yaxis.setAttribute('class', 'certaintrust-hti-yaxis');
  180. yaxis.style.cssFloat=yaxis.style.styleFloat='left';
  181. // width still defines the width of the box even if the
  182. // element in it is rotated, so we need to set this to 1em
  183. // even if the text will overflow it to have the canvas close
  184. yaxis.style.width='1em';
  185. // transform in theory:
  186. yaxis.style.transform='rotate(270deg) translate(-4em,0em)';
  187. yaxis.style.transformOrigin='100% 100%';
  188. // transform in practice:
  189. yaxis.style.MozTransform='rotate(270deg) translate(-4em,0em)';
  190. yaxis.style.MozTransformOrigin='100% 100%';
  191. yaxis.style.webkitTransform='rotate(270deg) translate(-4em,0em)';
  192. yaxis.style.webkitTransformOrigin='100% 100%';
  193. yaxis.style.msTransform='rotate(270deg) translate(-4em,0em)';
  194. yaxis.style.msTransformOrigin='100% 100%';
  195. yaxis.style.OTransform='rotate(270deg) translate(-4em,0em)';
  196. yaxis.style.OTransformOrigin='100% 100%';
  197. // \u00a0 is a non-breaking space, \u2192 is a right arrow
  198. var yaxislabel = document.createTextNode(config.label.c + '\u00a0\u2192');
  199. yaxis.appendChild(yaxislabel);
  200. hti.appendChild(yaxis);
  201. }
  202. this.canvas = document.createElement('canvas');
  203. this.canvas.style.cssFloat=this.canvas.style.styleFloat='left';
  204. this.canvas.setAttribute('id', this.ID + '-canvas');
  205. this.canvas.setAttribute('width', config.canvas.width);
  206. this.canvas.setAttribute('height', config.canvas.height);
  207. // this.canvas.setAttribute('title', this.ID); // useful to identify which widget is which
  208. this.canvas.addEventListener("mousedown", this._onClick, false);
  209. this.canvas.addEventListener("mousemove", this._onMove, false);
  210. hti.appendChild(this.canvas);
  211. if (config.show.axes === true)
  212. {
  213. var origin = document.createElement('span');
  214. origin.style.textAlign='center';
  215. origin.style.width='1em';
  216. origin.style.clear='both';
  217. origin.style.cssFloat=origin.style.styleFloat='left';
  218. var originlabel = document.createTextNode('0');
  219. origin.appendChild(originlabel);
  220. hti.appendChild(origin);
  221. var xaxis = document.createElement('span');
  222. xaxis.style.cssFloat=xaxis.style.styleFloat='left';
  223. var xaxislabel = document.createTextNode(config.label.t + '\u00a0\u2192');
  224. xaxis.appendChild(xaxislabel);
  225. hti.appendChild(xaxis);
  226. }
  227. element.appendChild(hti);
  228. var dom = this.certainTrust._insertElement(config, element);
  229. if (dom !== undefined)
  230. return dom;
  231. this.update();
  232. };
  233. /** (re)draws the canvas
  234. * The widget must be in the DOM tree to be able to be drawn */
  235. CertainTrustHTI.prototype.update = function() {
  236. var ctx = this.canvas.getContext('2d');
  237. var width = parseInt(this.canvas.getAttribute('width'), 10);
  238. var height = parseInt(this.canvas.getAttribute('height'), 10);
  239. var initf = this.certainTrust.getF();
  240. var imageData = ctx.createImageData(width, height);
  241. var d = 0;
  242. for (var y = 0; y < height; ++y) {
  243. var certainty = 1 - (y / height);
  244. for (var x = 0; x < width; ++x) {
  245. var trust = x / width;
  246. var color = this.certainTrust._getColor(certainty, trust, initf);
  247. // each pixel consists of four numbers: red, green, blue and alpha ranging from 0 to 255
  248. imageData.data[d++] = color[0];
  249. imageData.data[d++] = color[1];
  250. imageData.data[d++] = color[2];
  251. imageData.data[d++] = 255; // set no alpha-transparency
  252. }
  253. }
  254. ctx.putImageData(imageData, 0, 0);
  255. // put a 'cross' on the 'pixel' representing certainty/trust
  256. var dotmiddle = Math.floor(this.config.line.width / 2);
  257. var doty = Math.round(((1 - this.certainTrust.getC()) * height) - dotmiddle);
  258. var dotx = Math.round((this.certainTrust.getT() * width) - dotmiddle);
  259. // confidence interval
  260. if (this.config.confidence.width !== 0) {
  261. // if the line has an odd-size we have to place it on half-pixels or it looks odd
  262. var middle = (this.config.confidence.width % 2 === 0) ? 0 : 0.5;
  263. // calculate upper/lower bound of Wilson confidence interval
  264. var K = this.config.confidence.quantil;
  265. var x = this.certainTrust.getR();
  266. var n = this.certainTrust.getR() + this.certainTrust.getS();
  267. var p = this.certainTrust.getT();
  268. var wilson = this.intervalCertainty(K, x, n, p, width);
  269. ctx.beginPath();
  270. ctx.moveTo(Math.floor(wilson.upper - dotmiddle), doty + middle - this.config.confidence.height);
  271. ctx.lineTo(Math.floor(wilson.upper - dotmiddle), doty + middle + this.config.confidence.height);
  272. ctx.moveTo(Math.floor(wilson.upper - dotmiddle), doty + middle);
  273. ctx.lineTo(Math.floor(wilson.lower - dotmiddle), doty + middle);
  274. ctx.moveTo(Math.floor(wilson.lower - dotmiddle), doty + middle - this.config.confidence.height);
  275. ctx.lineTo(Math.floor(wilson.lower - dotmiddle), doty + middle + this.config.confidence.height);
  276. ctx.lineWidth = this.config.confidence.width;
  277. ctx.lineCap = this.config.confidence.cap;
  278. ctx.strokeStyle = this.config.confidence.style;
  279. ctx.stroke();
  280. ctx.closePath();
  281. }
  282. // if the line has an odd-size we have to place it on half-pixels or it looks odd
  283. var middle = (this.config.line.width % 2 === 0) ? 0 : 0.5;
  284. var line1 = new Array(
  285. this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 0), this.config.line.height),
  286. this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 180), this.config.line.height)
  287. );
  288. var line2 = new Array(
  289. this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 90), this.config.line.height),
  290. this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 270), this.config.line.height)
  291. );
  292. ctx.beginPath();
  293. ctx.moveTo(Math.round(line1[0][0]) + middle, Math.round(line1[0][1]) + middle);
  294. ctx.lineTo(Math.round(line1[1][0]) + middle, Math.round(line1[1][1]) + middle);
  295. ctx.moveTo(Math.round(line2[0][0]) + middle, Math.round(line2[0][1]) + middle);
  296. ctx.lineTo(Math.round(line2[1][0]) + middle, Math.round(line2[1][1]) + middle);
  297. ctx.lineWidth = this.config.line.width;
  298. ctx.lineCap = this.config.line.cap;
  299. ctx.strokeStyle = this.config.line.style;
  300. ctx.stroke();
  301. ctx.closePath();
  302. // display the certainTrust values in a user-friendly way without modifying their internal state
  303. this._setElementValue(this.ID + '-f', this.certainTrust.getF());
  304. this._setElementValue(this.ID + '-c', this.certainTrust.getC());
  305. this._setElementValue(this.ID + '-t', this.certainTrust.getT());
  306. this._setElementValue(this.ID + '-e', this.certainTrust.getExpectation());
  307. };
  308. CertainTrustHTI.prototype._setElementValue = function(id, value) {
  309. var element = document.getElementById(id);
  310. if (element === null)
  311. return;
  312. element.value = this._formatNumber(value);
  313. };
  314. /** calculating Wilson/Goldman interval boundaries */
  315. CertainTrustHTI.prototype.intervalCertainty = function(K, x, n, p, width) {
  316. var K2 = Math.pow(K, 2);
  317. var nK2 = n + K2;
  318. var part1 = (x + (K2 / 2)) / nK2;
  319. var part2 = (K * Math.sqrt(n)) / nK2;
  320. var part3 = Math.sqrt(p * (1 - p) + (K2 / (4 * n)));
  321. var uwx = (part1 + part2 * part3) * width;
  322. var lwx = (part1 - part2 * part3) * width;
  323. return { upper: uwx, lower: lwx };
  324. };
  325. CertainTrustHTI.prototype._onMove = function(e) {
  326. if (CertainTrustHTIElement._isMouseDown !== true)
  327. return;
  328. var cte = CertainTrustHTIElement.ByCanvas(this);
  329. if (cte.config.readonly.mouse === true)
  330. return;
  331. cte._onClick(e, this);
  332. };
  333. CertainTrustHTI.prototype._onClick = function(e, clkcanvas) {
  334. // if it's called by onMove this is not the clicked canvas but the element, so we have to pass it on
  335. if (clkcanvas === undefined)
  336. clkcanvas = this;
  337. var cte = CertainTrustHTIElement.ByCanvas(clkcanvas);
  338. if (cte.config.readonly.mouse === true)
  339. return;
  340. // this could be the start of a drag across the canvas
  341. CertainTrustHTIElement._isMouseDown = true;
  342. // convert screen-relative coordinates to canvas-relatives to trust/certainty and limit these values to [0;1]
  343. var x = e.clientX;
  344. var y = e.clientY;
  345. // https://developer.mozilla.org/en-US/docs/Web/API/element.getBoundingClientRect
  346. var ctBounding = clkcanvas.getBoundingClientRect();
  347. var cx = ctBounding.left;
  348. var cy = ctBounding.top;
  349. var newT = Math.max(0, Math.min(1, (x - cx) / clkcanvas.width));
  350. var newC = Math.max(0, Math.min(1, 1 - ((y - cy) / clkcanvas.height)));
  351. cte.certainTrust.setTC(newT, newC);
  352. };
  353. CertainTrustHTI.prototype._onKeyPress = function(e) {
  354. if (e.keyCode != 13)
  355. return;
  356. // update values only in case the user pressed the return key
  357. var cid = this.id.substring(0, this.id.lastIndexOf('-'));
  358. var cte = CertainTrustHTIElement.ById(cid);
  359. cte._updateInput(this.id);
  360. };
  361. CertainTrustHTI.prototype._onBlur = function(e) {
  362. // update values if focus left the input field
  363. var cid = this.id.substring(0, this.id.lastIndexOf('-'));
  364. var cte = CertainTrustHTIElement.ById(cid);
  365. cte._updateInput(this.id);
  366. };
  367. CertainTrustHTI.prototype._updateInput = function(id) {
  368. // this is the input-element the key was pressed in - thankfully the id for
  369. // these input fields are autogenerated from the id of the widget
  370. var cid = id.substring(0, id.lastIndexOf('-'));
  371. if (id.substring(id.lastIndexOf('-')) === '-f') {
  372. var newF = this._normalizeInput(document.getElementById(cid + '-f').value);
  373. this.certainTrust.setF((isNaN(newF)) ? this.certainTrust.getF() : newF);
  374. } else { // if ( == '-c' || == '-t')
  375. var newT = this._normalizeInput(document.getElementById(cid + '-t').value);
  376. var newC = this._normalizeInput(document.getElementById(cid + '-c').value);
  377. this.certainTrust.setTC(
  378. (isNaN(newT)) ? this.certainTrust.getT() : newT,
  379. (isNaN(newC)) ? this.certainTrust.getC() : newC
  380. );
  381. }
  382. };
  383. // parse the user-input to a number (best attempt approach)
  384. CertainTrustHTI.prototype._normalizeInput = function(input) {
  385. // first, replace the first "," with "." to enable German-language style floating point input
  386. var rawInput = (input+"").replace(/,/, ".");
  387. // now, strip out all leading 0s to prevent parseFloat from treating the input as octal
  388. rawInput = rawInput.replace(/^0+\./, ".");
  389. // convert to a number
  390. var floatInput = parseFloat(rawInput); // attention, this may be NaN -> _updateInput handles this
  391. if ((1 < floatInput) || (0 > floatInput)) {
  392. return NaN;
  393. } else {
  394. return floatInput;
  395. }
  396. };
  397. // rounds numbers to at most 3 decimal places
  398. CertainTrustHTI.prototype._formatNumber = function(number) {
  399. // return number.toFixed(3);
  400. return Math.round(number * 1000) / 1000;
  401. };
  402. // global var for storing and accessing all the widgets
  403. var CertainTrustHTIElement = { _elements: [],
  404. _isMouseDown: false,
  405. ByCanvas: function(canvas) {
  406. for (var i = 0; i < CertainTrustHTIElement._elements.length; ++i) {
  407. if (CertainTrustHTIElement._elements[i].canvas === canvas)
  408. return CertainTrustHTIElement._elements[i];
  409. }
  410. return null;
  411. },
  412. ById: function(id) {
  413. for (var i = 0; i < CertainTrustHTIElement._elements.length; ++i) {
  414. if (CertainTrustHTIElement._elements[i].ID === id)
  415. return CertainTrustHTIElement._elements[i];
  416. }
  417. return null;
  418. },
  419. ByNr: function(nr) { return CertainTrustHTIElement._elements[nr]; },
  420. push: function(cte) { CertainTrustHTIElement._elements.push(cte); },
  421. length: function() { return CertainTrustHTIElement._elements.length; }
  422. };
  423. // react on every mouseup - even outside of a canvas
  424. document.addEventListener("mouseup", function() { CertainTrustHTIElement._isMouseDown = false; }, false);