SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.pmm.nodes/js-src/de/bund/bfr/knime/pmm/js/modelplotter/knime-model-plotter.js

Summary

Maintainability
C
7 hrs
Test Coverage
/*******************************************************************************
 * Copyright (c) 2015 Federal Institute for Risk Assessment (BfR), Germany
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Department Biological Safety - BfR
 *******************************************************************************/
bfr_model_plotter = function() {

    var modelPlotter = {
            version: "1.0.0"
    };
    modelPlotter.name = "Model Plotter";
    
    var plotterValue;
    var plotterRep;
    var variableSliders;
    var variableIndices;
    var functionGraph;
    var jsxBoard;
    
    modelPlotter.init = function(representation, value) {
        //alert(JSON.stringify(representation));
        plotterValue = value;
        plotterRep = representation;
        
        var body = document.getElementsByTagName("body")[0];
        body.setAttribute("style", 
                "width:100%; height:100%; font-family:Arial,Helvetica,sans-serif; font-size:14px; overflow:hidden;");
        
        var table = document.createElement("table");
        var row1 = document.createElement("tr");
        var td1 = document.createElement("td");
        row1.appendChild(td1);
        var row2 = document.createElement("tr")
        var td2 = document.createElement("td");
        td2.setAttribute("align", "right");
        row2.appendChild(td2);
        table.appendChild(row1);
        table.appendChild(row2);
        body.appendChild(table);
        
        var layoutContainer = document.createElement("div");
        layoutContainer.setAttribute("style", "width:810px; height:660px;");
        td1.appendChild(layoutContainer);
        
        var constantsBox = document.createElement("div");
        constantsBox.setAttribute("style", "border-style:solid; border-width:1px; width:35%; text-align:left; padding:10px;");
        td2.appendChild(constantsBox);
        
        var constantsHeadline = document.createElement("h3");
        constantsHeadline.innerHTML = "Model constants";
        constantsHeadline.setAttribute("style", "margin:5px;");
        constantsBox.appendChild(constantsHeadline);
        
        var selectBox = document.createElement("select");
        selectBox.setAttribute("id", "funcConstants");
        selectBox.setAttribute("style", "margin:5px;");
        selectBox.addEventListener("change", function() { constantSelected(); });
        constantsBox.appendChild(selectBox);
        
        var textInput = document.createElement("input");
        textInput.setAttribute("id", "funcConstantValue");
        textInput.setAttribute("type", "text");
        textInput.setAttribute("size", "7");
        textInput.setAttribute("style", "margin:5px;");
        textInput.addEventListener("change", function() { checkNumberInput(); });
        constantsBox.appendChild(textInput);
        
        var applyButton = document.createElement("button");
        applyButton.innerHTML = "Apply";
        applyButton.setAttribute("id", "funcConstantValueChange");
        applyButton.setAttribute("style", "margin:5px;");
        applyButton.addEventListener("click", function() { applyNewValue(); });
        constantsBox.appendChild(applyButton);
        
        var h = document.createElement("h1");
        h.innerHTML = plotterValue.chartTitle;
        layoutContainer.appendChild(h);
        
        var div = document.createElement("div");
        div.setAttribute("id", "box");
        div.setAttribute("class", "jxgbox");
        div.setAttribute("style", "width:800px; height:600px;");
        layoutContainer.appendChild(div);
        
        // add constants in view representation to select box as options
        var constantsSelectBox = document.getElementById('funcConstants');
        for (var c in plotterValue.constants) {
            var opt = document.createElement('option');
            opt.value = c;
            opt.innerHTML = c;
            constantsSelectBox.appendChild(opt);
        }
        
        // set value in text field to value of selected constant
        constantSelected();
        
        // Initialize JSX board to draw function graphs, etc.
        var minXAxis = plotterValue.minXAxis;
        var maxXAxis = plotterValue.maxXAxis;
        var minYAxis = plotterValue.minYAxis;
        var maxYAxis = plotterValue.maxYAxis;        
        jsxBoard = JXG.JSXGraph.initBoard('box', {boundingbox: [minXAxis, maxYAxis, maxXAxis, minYAxis], axis:false});
        
        var xunit = "[" + plotterValue.xUnit + "]";
        var yunit = "[" + plotterValue.yUnit + "]";
        
        // Set Y axis, ticks, labels
        var yaxis = jsxBoard.create('axis', [[0, 0], [0, 1]], {name:yunit, withLabel: true, 
                ticks: {insertTicks: true, ticksDistance: 1, label: {offset: [-20, -20]}}});
             yaxis.defaultTicks.ticksFunction = function () { return 5; };

        // Set X axis, ticks, labels
        var xaxis = jsxBoard.create('axis', [[0, 0], [1, 0]], {name:xunit, withLabel: true, 
                ticks: {insertTicks: true, ticksDistance: 1, label: {offset: [-20, -20]}}});
             xaxis.defaultTicks.ticksFunction = function () { return 5; };
             
        variableSliders = [];
        variableIndices = [];
        // Append one slider for each function variable        
        for (var i = 0; i < plotterValue.variables.length; i++) {
            var v = plotterValue.variables[i];
            if (v.name != 'Time') {                
                variableSliders.push(jsxBoard.create('slider', [[50,-2 - i], [80,-2 - i], [v.min, v.def, v.max]],
                        {name:v.name, point1: {frozen: true}, point2: {frozen: true}}));
                variableIndices.push(i);
            }
        }        
                
        plotterValue.constants.Y0 = plotterValue.y0;
        
        // Creates time based function and updates plottable model graph 
        updateFunctionGraph();
        
        // On zoom change event remove current function graph and create/draw new function graph 
        // with new max value of x axis
        jsxBoard.on('boundingbox', function() {
            maxXValue = jsxBoard.plainBB[2] + 10;
            jsxBoard.removeObject(functionGraph);
            functionGraph = jsxBoard.create('functiongraph', [timeModelFunction, 0, maxXValue]);        
        });        
        
        // Auto resize view when shown in WebPortal
        if (parent != undefined && parent.KnimePageLoader != undefined) {
               parent.KnimePageLoader.autoResize(window.frameElement.id);
        }
    }
    
    // read selected constant from select box and show value of constant in text field 
    constantSelected = function() {
        var constantsSelectBox = document.getElementById('funcConstants');
        var constant = constantsSelectBox.options[constantsSelectBox.selectedIndex].value;
        document.getElementById('funcConstantValue').value = plotterValue.constants[constant];
    }
    
    // checks if input of text field is number
    checkNumberInput = function() {
        var constantsSelectBox = document.getElementById('funcConstants');
        var constant = constantsSelectBox.options[constantsSelectBox.selectedIndex].value;
        var newValueField = document.getElementById('funcConstantValue');
        var newValue = newValueField.value;
        
        var newNumberValue = parseFloat(newValue);
        if (isNaN(newNumberValue)) {
            newValueField.style.background = "red";
        } else {
            newValueField.style.background = "white";
        }
    }
    
    applyNewValue = function() {    
        var constantsSelectBox = document.getElementById('funcConstants');
        var constant = constantsSelectBox.options[constantsSelectBox.selectedIndex].value;
        var newValue = document.getElementById('funcConstantValue').value;
        
        var newNumberValue = parseFloat(newValue);
        if (!isNaN(newNumberValue)) {
            plotterValue.constants[constant] = newNumberValue;
            if (constant == "Y0") {
                // set new Y0 value to view value
                plotterValue.y0 = newNumberValue;
            }
            
            jsxBoard.removeObject(functionGraph);
            updateFunctionGraph();
        }
    }
    
    function createFunctionStr() {
        var functionStr = 'f(Time';
        // Append variable name to function string, separated by ","        
        for (var i = 0; i < plotterValue.variables.length; i++) {
            var v = plotterValue.variables[i];
            if (v.name != 'Time') {                
                functionStr += ", " + v.name;
            }
        }
        // Close parameter brackets of function and add function term
        functionStr += ") = " + plotterValue.func;
        
        return functionStr;
    }
    
    function createMath() {
        // Note that due to math.js the root math object can be either "mathjs" or "math",
        // which depends on the used browser.        
        var myMath;
        if (typeof define === 'function' && define.amd) {
            myMath = mathjs;
        } else {
            myMath = math;
        }
        
        // Set all constants of the given model representation
        myMath.import(plotterValue.constants, { override: true });
        
        // CUSTOMIZED FUNCTIONS
        myMath.import({
          ln: function (x) { return myMath.log(x); },
          log10: function (x) { return myMath.log(x)/myMath.log(10); }
        });
        
        return myMath;
    }
    
    function createTimeBasedModelFunction(baseModelFunction) {
        var timeModelFunction = function(Time){
             var varValues = [Time];
             for (var i = 0; i < variableSliders.length; i++) {
                 varValues.push(variableSliders[i].Value());
             }         
             return baseModelFunction.apply(null, varValues);
          };    
          return timeModelFunction;
    }
    
    function updateFunctionGraph() {
        // Save max value of x axis
        var maxXValue = jsxBoard.attr.boundingbox[2] + 10;
        
        // Prepare string of function f of time
        var functionStr = createFunctionStr();
        plotterValue.functionFull = functionStr;
        
        // Prepare myMath object to call math functions         
        var myMath = createMath();
        
        // Create js function from function string with time and all given parameters as input
        // variables of the function
        var baseModelFunction = myMath.eval(functionStr);
        
        // Create js function ONLY WITH TIME as input variable of the function. As values of all
        // other input variables the slider values are set
        var timeModelFunction = createTimeBasedModelFunction(baseModelFunction);
        
         // Create graph of function with time parameter only
        functionGraph = jsxBoard.create('functiongraph', [timeModelFunction, 0, maxXValue]);
    }
    
    modelPlotter.validate = function() {
        return true;
    }
    
    modelPlotter.setValidationError = function() { }
    
    modelPlotter.getComponentValue = function() {
        // set current slider values as variables values
        for (var i = 0; i < variableIndices.length; i++) {
            var v = plotterValue.variables[variableIndices[i]];
            v.def = variableSliders[i].Value();
            plotterValue.variables[variableIndices[i]] = v;
        }
        return plotterValue;
    }
    
    return modelPlotter;    
}();