123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- /**
- * CertainTrust SDK
- *
- * Implements the computational trust model "CertainTrust"
- * in JavaScript.
- * See <http://www.tk.informatik.tu-darmstadt.de/de/research/smart-security-and-trust/> for further details.
- *
- *
- * Telecooperation Department, Technische Universität Darmstadt
- * <http://www.tk.informatik.tu-darmstadt.de/>
- *
- * Prof. Dr. Max Mühlhäuser <max@informatik.tu-darmstadt.de>
- * Florian Volk <florian.volk@cased.de>
- *
- *
- * @author David Kalnischkies
- * @author Florian Volk
- * @version 1.0
- */
-
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
- /** Certain Trust Widget
- *
- * Creates a color-coded graph created from an initial trust value
- * in which the user can pick certainty (y-axis) and trust (x-axis)
- * with the mouse in the graph.
- *
- * The values are displayed and can be modified inside a form
- * while the graph in the canvas will be updated from those values.
- *
- * Options can be given in an object as parameter to the constructor
- * The following keys are understood:
- * id is the id the top-div of the widget will get. Its used as basename for all element ids in it, too
- * label is an object for various messages which are shown to the user
- * lang is the language to use. Available are 'de' and 'en' (default)
- * Alternatively f, t, c and e can be used to set the text explicitly
- * canvas is an object containing subkeys 'width' and 'height' defining dimensions of the canvas element
- * line is an object defining the style of the lines building the cross to mark the chosen trust/certainty
- * cap is the canvas.lineCap defining the style used to smooth the end of the lines
- * height is the length of the line from start to the crosspoint
- * width is the thickness of the line
- * style is the color of the line
- * readonly can be used to disable input in the HTI as a whole,
- * Alternatively f, t, c, e, inputs for all of the previous and mouse can be used to disable specific elements.
- * show can be used to disable display of certain inputs refered to as
- * f, t, c, e, inputs for all of the previous, title and axes.
- *
- * One of the following keys can be used to specify the position of the widget in the DOM,
- * the first defined will be used. If none is provided domAfter will be set to the <script>-tag calling
- * the creation of the widget (= the last <script>-tag currently in the DOM)
- * Either the DOM element itself can be given or the ID of the element as a string.
- * domReturn is set to true to get the DOM subtree as return value of the constructor instead [update() is NOT called]
- * domParent is (the id of) the parent for the widget
- * domBefore is (the id of) the element which the widget will be in front of
- * domAfter is (the id of) the element after which the widget will be inserted
- */
- var CertainTrustHTI = function(certainlogic, config) {
- if (certainlogic === undefined)
- this.certainTrust = new CertainTrust(5);
- else
- this.certainTrust = certainlogic;
- this.NR = CertainTrustHTIElement.length();
- CertainTrustHTIElement.push(this);
- this.certainTrust.addObserver(this);
- // set sane defaults for config if nothing is set
- if (config === undefined) config = {};
- if (config.id === undefined) config.id = 'certaintrust-hti-' + this.NR;
- // design your widget
- if (config.canvas === undefined) config.canvas = {};
- if (config.canvas.height === undefined) config.canvas.height = 100;
- if (config.canvas.width === undefined) config.canvas.width = 120;
- if (config.line === undefined) config.line = {};
- if (config.line.cap === undefined) config.line.cap = 'round';
- if (config.line.height === undefined) config.line.height = 5;
- if (config.line.width === undefined) config.line.width = 1;
- if (config.line.style === undefined) config.line.style = 'black';
- if (config.line.baserad === undefined) config.line.baserad = 45;
- else config.line.baserad %= 90;
- if (config.confidence === undefined) config.confidence = {};
- if (config.confidence.cap === undefined) config.confidence.cap = 'round';
- if (config.confidence.height === undefined) config.confidence.height = 5;
- if (config.confidence.width === undefined) config.confidence.width = 0;
- if (config.confidence.style === undefined) config.confidence.style = 'gray';
- if (config.confidence.quantil === undefined) config.confidence.quantil = 1.96;
- // language settings
- if (config.label === undefined) config.label = {};
- if (config.label.lang === undefined) config.label.lang = 'en';
- if (config.label.lang === 'de') {
- if (config.label.f === undefined) config.label.f = 'Initialwert';
- if (config.label.t === undefined) config.label.t = 'Vertrauen';
- if (config.label.c === undefined) config.label.c = 'Sicherheit';
- if (config.label.e === undefined) config.label.e = 'Erwartung';
- } else {
- if (config.label.f === undefined) config.label.f = 'Init. value';
- if (config.label.t === undefined) config.label.t = 'Trust';
- if (config.label.c === undefined) config.label.c = 'Certainty';
- if (config.label.e === undefined) config.label.e = 'Expectation';
- }
- // readonly forms maybe?
- var readonlyflag = false;
- if (config.readonly === undefined) {
- config.readonly = {};
- config.readonly.e = true;
- } else {
- readonlyflag = config.readonly;
- config.readonly = {};
- }
- if (config.readonly.inputs === undefined) config.readonly.inputs = readonlyflag;
- if (config.readonly.f === undefined) config.readonly.f = config.readonly.inputs;
- if (config.readonly.t === undefined) config.readonly.t = config.readonly.inputs;
- if (config.readonly.c === undefined) config.readonly.c = config.readonly.inputs;
- if (config.readonly.e === undefined) config.readonly.e = config.readonly.inputs;
- if (config.readonly.mouse === undefined) config.readonly.mouse = readonlyflag;
- // show/disable elements
- var showflag = true;
- if (config.show === undefined) config.show = {};
- else {
- showflag = config.show;
- config.show = {};
- }
- if (config.show.inputs === undefined) config.show.inputs = showflag;
- if (config.show.f === undefined) config.show.f = config.show.inputs;
- if (config.show.t === undefined) config.show.t = config.show.inputs;
- if (config.show.c === undefined) config.show.c = config.show.inputs;
- if (config.show.e === undefined) config.show.e = config.show.inputs;
- if (config.show.title === undefined) config.show.title = showflag;
- if (config.show.axes === undefined) config.show.axes = showflag;
- this.ID = config.id;
- this.config = config;
- var element = document.createElement('div');
- element.setAttribute('id', this.ID);
- element.setAttribute('class', 'certaintrust-hti');
- if (config.show.title === true && this.certainTrust.getName().length !== 0)
- {
- var title = document.createElement('h1');
- var msg = document.createTextNode(this.certainTrust.getName());
- title.appendChild(msg);
- element.appendChild(title);
- }
- var form = document.createElement('form');
- form.setAttribute('id', this.ID + '-form');
- var appendInput = function (form, id, nr, type, value, text, readonly, show) {
- if (show === false)
- return;
- var div = document.createElement('div');
- div.setAttribute('class', 'certaintrust-hti-' + type);
- var label = document.createElement('label');
- label.setAttribute('for', id + '-' + type);
- div.appendChild(label);
- var labeltext = document.createTextNode(text);
- label.appendChild(labeltext);
- var input = document.createElement('input');
- input.setAttribute('type', 'text');
- input.setAttribute('id', id + '-' + type);
- var cte = CertainTrustHTIElement.ByNr(nr);
- input.setAttribute('value', value);
- if (readonly === true) {
- input.setAttribute('readonly', 'readonly');
- input.setAttribute('tabindex', '-1');
- } else {
- input.addEventListener('keypress', cte._onKeyPress, false);
- input.addEventListener('blur', cte._onBlur, false);
- }
- div.appendChild(input);
- form.appendChild(div);
- };
- appendInput(form, this.ID, this.NR, 'f', this.certainTrust.getF(), config.label.f, config.readonly.f, config.show.f);
- appendInput(form, this.ID, this.NR, 't', this.certainTrust.getT(), config.label.t, config.readonly.t, config.show.t);
- appendInput(form, this.ID, this.NR, 'c', this.certainTrust.getC(), config.label.c, config.readonly.c, config.show.c);
- appendInput(form, this.ID, this.NR, 'e', this.certainTrust.getExpectation(), config.label.e, config.readonly.e, config.show.e);
- if (form.hasChildNodes())
- element.appendChild(form);
- var hti = document.createElement('div');
- hti.style.cssFloat=hti.style.styleFloat='left';
- if (config.show.axes === true)
- {
- var yaxis = document.createElement('span');
- yaxis.setAttribute('class', 'certaintrust-hti-yaxis');
- yaxis.style.cssFloat=yaxis.style.styleFloat='left';
- // width still defines the width of the box even if the
- // element in it is rotated, so we need to set this to 1em
- // even if the text will overflow it to have the canvas close
- yaxis.style.width='1em';
- // transform in theory:
- yaxis.style.transform='rotate(270deg) translate(-4em,0em)';
- yaxis.style.transformOrigin='100% 100%';
- // transform in practice:
- yaxis.style.MozTransform='rotate(270deg) translate(-4em,0em)';
- yaxis.style.MozTransformOrigin='100% 100%';
- yaxis.style.webkitTransform='rotate(270deg) translate(-4em,0em)';
- yaxis.style.webkitTransformOrigin='100% 100%';
- yaxis.style.msTransform='rotate(270deg) translate(-4em,0em)';
- yaxis.style.msTransformOrigin='100% 100%';
- yaxis.style.OTransform='rotate(270deg) translate(-4em,0em)';
- yaxis.style.OTransformOrigin='100% 100%';
- // \u00a0 is a non-breaking space, \u2192 is a right arrow
- var yaxislabel = document.createTextNode(config.label.c + '\u00a0\u2192');
- yaxis.appendChild(yaxislabel);
- hti.appendChild(yaxis);
- }
- this.canvas = document.createElement('canvas');
- this.canvas.style.cssFloat=this.canvas.style.styleFloat='left';
- this.canvas.setAttribute('id', this.ID + '-canvas');
- this.canvas.setAttribute('width', config.canvas.width);
- this.canvas.setAttribute('height', config.canvas.height);
- // this.canvas.setAttribute('title', this.ID); // useful to identify which widget is which
- this.canvas.addEventListener("mousedown", this._onClick, false);
- this.canvas.addEventListener("mousemove", this._onMove, false);
- hti.appendChild(this.canvas);
- if (config.show.axes === true)
- {
- var origin = document.createElement('span');
- origin.style.textAlign='center';
- origin.style.width='1em';
- origin.style.clear='both';
- origin.style.cssFloat=origin.style.styleFloat='left';
- var originlabel = document.createTextNode('0');
- origin.appendChild(originlabel);
- hti.appendChild(origin);
- var xaxis = document.createElement('span');
- xaxis.style.cssFloat=xaxis.style.styleFloat='left';
- var xaxislabel = document.createTextNode(config.label.t + '\u00a0\u2192');
- xaxis.appendChild(xaxislabel);
- hti.appendChild(xaxis);
- }
- element.appendChild(hti);
- var dom = this.certainTrust._insertElement(config, element);
- if (dom !== undefined)
- return dom;
- this.update();
- };
- /** (re)draws the canvas
- * The widget must be in the DOM tree to be able to be drawn */
- CertainTrustHTI.prototype.update = function() {
- var ctx = this.canvas.getContext('2d');
- var width = parseInt(this.canvas.getAttribute('width'), 10);
- var height = parseInt(this.canvas.getAttribute('height'), 10);
- var initf = this.certainTrust.getF();
- var imageData = ctx.createImageData(width, height);
- var d = 0;
- for (var y = 0; y < height; ++y) {
- var certainty = 1 - (y / height);
- for (var x = 0; x < width; ++x) {
- var trust = x / width;
- var color = this.certainTrust._getColor(certainty, trust, initf);
- // each pixel consists of four numbers: red, green, blue and alpha ranging from 0 to 255
- imageData.data[d++] = color[0];
- imageData.data[d++] = color[1];
- imageData.data[d++] = color[2];
- imageData.data[d++] = 255; // set no alpha-transparency
- }
- }
- ctx.putImageData(imageData, 0, 0);
- // put a 'cross' on the 'pixel' representing certainty/trust
- var dotmiddle = Math.floor(this.config.line.width / 2);
- var doty = Math.round(((1 - this.certainTrust.getC()) * height) - dotmiddle);
- var dotx = Math.round((this.certainTrust.getT() * width) - dotmiddle);
- // confidence interval
- if (this.config.confidence.width !== 0) {
- // if the line has an odd-size we have to place it on half-pixels or it looks odd
- var middle = (this.config.confidence.width % 2 === 0) ? 0 : 0.5;
- // calculate upper/lower bound of Wilson confidence interval
- var K = this.config.confidence.quantil;
- var x = this.certainTrust.getR();
- var n = this.certainTrust.getR() + this.certainTrust.getS();
- var p = this.certainTrust.getT();
- var wilson = this.intervalCertainty(K, x, n, p, width);
- ctx.beginPath();
- ctx.moveTo(Math.floor(wilson.upper - dotmiddle), doty + middle - this.config.confidence.height);
- ctx.lineTo(Math.floor(wilson.upper - dotmiddle), doty + middle + this.config.confidence.height);
- ctx.moveTo(Math.floor(wilson.upper - dotmiddle), doty + middle);
- ctx.lineTo(Math.floor(wilson.lower - dotmiddle), doty + middle);
- ctx.moveTo(Math.floor(wilson.lower - dotmiddle), doty + middle - this.config.confidence.height);
- ctx.lineTo(Math.floor(wilson.lower - dotmiddle), doty + middle + this.config.confidence.height);
- ctx.lineWidth = this.config.confidence.width;
- ctx.lineCap = this.config.confidence.cap;
- ctx.strokeStyle = this.config.confidence.style;
- ctx.stroke();
- ctx.closePath();
- }
- // if the line has an odd-size we have to place it on half-pixels or it looks odd
- var middle = (this.config.line.width % 2 === 0) ? 0 : 0.5;
- var line1 = new Array(
- this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 0), this.config.line.height),
- this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 180), this.config.line.height)
- );
- var line2 = new Array(
- this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 90), this.config.line.height),
- this.certainTrust._pointOnCircle(dotx, doty + middle, (this.config.line.baserad + 270), this.config.line.height)
- );
- ctx.beginPath();
- ctx.moveTo(Math.round(line1[0][0]) + middle, Math.round(line1[0][1]) + middle);
- ctx.lineTo(Math.round(line1[1][0]) + middle, Math.round(line1[1][1]) + middle);
- ctx.moveTo(Math.round(line2[0][0]) + middle, Math.round(line2[0][1]) + middle);
- ctx.lineTo(Math.round(line2[1][0]) + middle, Math.round(line2[1][1]) + middle);
- ctx.lineWidth = this.config.line.width;
- ctx.lineCap = this.config.line.cap;
- ctx.strokeStyle = this.config.line.style;
- ctx.stroke();
- ctx.closePath();
- // display the certainTrust values in a user-friendly way without modifying their internal state
- this._setElementValue(this.ID + '-f', this.certainTrust.getF());
- this._setElementValue(this.ID + '-c', this.certainTrust.getC());
- this._setElementValue(this.ID + '-t', this.certainTrust.getT());
- this._setElementValue(this.ID + '-e', this.certainTrust.getExpectation());
- };
- CertainTrustHTI.prototype._setElementValue = function(id, value) {
- var element = document.getElementById(id);
- if (element === null)
- return;
- element.value = this._formatNumber(value);
- };
- /** calculating Wilson/Goldman interval boundaries */
- CertainTrustHTI.prototype.intervalCertainty = function(K, x, n, p, width) {
- var K2 = Math.pow(K, 2);
- var nK2 = n + K2;
- var part1 = (x + (K2 / 2)) / nK2;
- var part2 = (K * Math.sqrt(n)) / nK2;
- var part3 = Math.sqrt(p * (1 - p) + (K2 / (4 * n)));
- var uwx = (part1 + part2 * part3) * width;
- var lwx = (part1 - part2 * part3) * width;
- return { upper: uwx, lower: lwx };
- };
- CertainTrustHTI.prototype._onMove = function(e) {
- if (CertainTrustHTIElement._isMouseDown !== true)
- return;
- var cte = CertainTrustHTIElement.ByCanvas(this);
- if (cte.config.readonly.mouse === true)
- return;
- cte._onClick(e, this);
- };
- CertainTrustHTI.prototype._onClick = function(e, clkcanvas) {
- // if it's called by onMove this is not the clicked canvas but the element, so we have to pass it on
- if (clkcanvas === undefined)
- clkcanvas = this;
- var cte = CertainTrustHTIElement.ByCanvas(clkcanvas);
- if (cte.config.readonly.mouse === true)
- return;
- // this could be the start of a drag across the canvas
- CertainTrustHTIElement._isMouseDown = true;
- // convert screen-relative coordinates to canvas-relatives to trust/certainty and limit these values to [0;1]
- var x = e.clientX;
- var y = e.clientY;
- // https://developer.mozilla.org/en-US/docs/Web/API/element.getBoundingClientRect
- var ctBounding = clkcanvas.getBoundingClientRect();
- var cx = ctBounding.left;
- var cy = ctBounding.top;
- var newT = Math.max(0, Math.min(1, (x - cx) / clkcanvas.width));
- var newC = Math.max(0, Math.min(1, 1 - ((y - cy) / clkcanvas.height)));
- cte.certainTrust.setTC(newT, newC);
- };
- CertainTrustHTI.prototype._onKeyPress = function(e) {
- if (e.keyCode != 13)
- return;
- // update values only in case the user pressed the return key
- var cid = this.id.substring(0, this.id.lastIndexOf('-'));
- var cte = CertainTrustHTIElement.ById(cid);
- cte._updateInput(this.id);
- };
- CertainTrustHTI.prototype._onBlur = function(e) {
- // update values if focus left the input field
- var cid = this.id.substring(0, this.id.lastIndexOf('-'));
- var cte = CertainTrustHTIElement.ById(cid);
- cte._updateInput(this.id);
- };
- CertainTrustHTI.prototype._updateInput = function(id) {
- // this is the input-element the key was pressed in - thankfully the id for
- // these input fields are autogenerated from the id of the widget
- var cid = id.substring(0, id.lastIndexOf('-'));
- if (id.substring(id.lastIndexOf('-')) === '-f') {
- var newF = this._normalizeInput(document.getElementById(cid + '-f').value);
- this.certainTrust.setF((isNaN(newF)) ? this.certainTrust.getF() : newF);
- } else { // if ( == '-c' || == '-t')
- var newT = this._normalizeInput(document.getElementById(cid + '-t').value);
- var newC = this._normalizeInput(document.getElementById(cid + '-c').value);
- this.certainTrust.setTC(
- (isNaN(newT)) ? this.certainTrust.getT() : newT,
- (isNaN(newC)) ? this.certainTrust.getC() : newC
- );
- }
- };
- // parse the user-input to a number (best attempt approach)
- CertainTrustHTI.prototype._normalizeInput = function(input) {
- // first, replace the first "," with "." to enable German-language style floating point input
- var rawInput = (input+"").replace(/,/, ".");
- // now, strip out all leading 0s to prevent parseFloat from treating the input as octal
- rawInput = rawInput.replace(/^0+\./, ".");
- // convert to a number
- var floatInput = parseFloat(rawInput); // attention, this may be NaN -> _updateInput handles this
- if ((1 < floatInput) || (0 > floatInput)) {
- return NaN;
- } else {
- return floatInput;
- }
- };
- // rounds numbers to at most 3 decimal places
- CertainTrustHTI.prototype._formatNumber = function(number) {
- // return number.toFixed(3);
- return Math.round(number * 1000) / 1000;
- };
- // global var for storing and accessing all the widgets
- var CertainTrustHTIElement = { _elements: [],
- _isMouseDown: false,
- ByCanvas: function(canvas) {
- for (var i = 0; i < CertainTrustHTIElement._elements.length; ++i) {
- if (CertainTrustHTIElement._elements[i].canvas === canvas)
- return CertainTrustHTIElement._elements[i];
- }
- return null;
- },
- ById: function(id) {
- for (var i = 0; i < CertainTrustHTIElement._elements.length; ++i) {
- if (CertainTrustHTIElement._elements[i].ID === id)
- return CertainTrustHTIElement._elements[i];
- }
- return null;
- },
- ByNr: function(nr) { return CertainTrustHTIElement._elements[nr]; },
- push: function(cte) { CertainTrustHTIElement._elements.push(cte); },
- length: function() { return CertainTrustHTIElement._elements.length; }
- };
- // react on every mouseup - even outside of a canvas
- document.addEventListener("mouseup", function() { CertainTrustHTIElement._isMouseDown = false; }, false);
|