<commit_before>/*
   Copyright (c) 2013, GEM Foundation.

      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU Affero 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 Affero General Public License for more details.

      You should have received a copy of the GNU Affero General Public License
      along with this program.  If not, see <https://www.gnu.org/licenses/agpl.html>.
*/

    // const is not supported by all the browsers, so we are using var instead
    var CIRCLE_SCALE = 30;
    var MIN_CIRCLE_SIZE = 0.001;

    // For checking if a string is blank or contains only white-space
    String.prototype.isEmpty = function() {
        return (this.length === 0 || !this.trim());
    };

    $(document).ready(function() {
        //  Project definition weight dialog
        $("#projectDefWeightDialog").dialog({
            title: "Set weights and operator",
            autoOpen: false,
            modal: true,
            minWidth: 400
        });
        //  Dialog to set up a new node to insert into the project definition
        $("#projectDefNewNodeDialog").dialog({
            title: "Add new indicator",
            autoOpen: false,
            modal: true,
            dialogClass: "no-close",
            closeOnEscape: false
        });
    });


    ////////////////////////////////////////////
    //// Project Definition Collapsible Tree ///
    ////////////////////////////////////////////

    function loadPD(project_definition, qt_page) {
        var DEFAULT_OPERATOR = qt_page.DEFAULT_OPERATOR;
        var OPERATORS = qt_page.OPERATORS.split(';');
        var ACTIVE_LAYER_NUMERIC_FIELDS = qt_page.ACTIVE_LAYER_NUMERIC_FIELDS.split(';');
        var NODE_TYPES = qt_page.NODE_TYPES.split(';');
        var node_types_dict = {};
        for (var i = 0; i < NODE_TYPES.length; i++) {
            var keyval = NODE_TYPES[i];
            var keyvalsplit = keyval.split(':');
            var type_key = keyvalsplit[0];
            var type_name = keyvalsplit[1];
            node_types_dict[type_key] = type_name;
        }

        var margin = {top: 20, right: 120, bottom: 20, left: 60};
        var width = 960 - margin.right - margin.left;
        var height = 800 - margin.top - margin.bottom;

        var i = 0;
        var duration = 750;
        var root;

        var tooltipdiv = d3.select("#projectDefDialog").append("div")
            .attr("class", "tooltip")
            .style("opacity", 0);

        var tree = d3.layout.tree().size([height, width]);

        var nodeEnter;

        var diagonal = d3.svg.diagonal()
            .projection(function(d) { return [d.y, d.x]; });

        function createSpinner(id, weight, name, field) {
            pdTempSpinnerIds.push("spinner-"+id);
            $('#projectDefWeightDialog').dialog("open");
            if (field === undefined) {
                $('#projectDefWeightDialog').append(
                    '<p><label for="spinner'+id+'">'+name+': </label><input id="spinner-'+id+'" name="spinner" value="'+weight+'"></p>');
            } else {
                $('#projectDefWeightDialog').append(
                    '<p><label for="spinner'+id+'">'+name+'('+field+'): </label><input id="spinner-'+id+'" name="spinner" value="'+weight+'"></p>');
            }
            $(function() {
                $("#spinner-"+id).width(100).spinner({
                    min: 0,
                    max: 100,
                    step: 0.01,
                    numberFormat: "n"
                });
            });
        }

        function operatorSelect(pdOperator){
            $('#projectDefWeightDialog')
                .append('<br/><label for="operator">Operator: </label>')
                .append('<select id="operator">'+ operatorOptions() + '</select>');
            //TODO use selectmenu when the bug there is fixed9?
            //$(selector).selectmenu()
            //$(selector).prop('selectedIndex', 4)
            $('#operator').val(pdOperator);
        }

        function operatorOptions(){
            var options = '';
            for (var i = 0; i < OPERATORS.length; i++) {
                var c = OPERATORS[i];
                if (c == DEFAULT_OPERATOR){
                    options += '<option value="' + c + '" selected="selected">' + c + '</option>';
                }
                else{
                    options += '<option value="' + c + '">' + c + '</option>';
                }
            }
            return options;
        }

        function fieldSelect(node, node_type){
            // TODO: Add more stuff to the node
            dialog = $('#projectDefNewNodeDialog');
            //TODO use selectmenu when the bug there is fixed9?
            //$(selector).selectmenu()
            //$(selector).prop('selectedIndex', 4)
            // $('#field').val(node.field);
            dialog.empty();

            dialog
                .append('<label for="name">Name: </label>')
                .append('<input id="newNodeName" type="text" name="newNodeName" value="">');

            newNodeName = $('#newNodeName')
            newNodeName.blur(function(){
                if (newNodeName.val().isEmpty()){
                    newNodeName.addClass("ui-state-error");
                }
                else{
                    newNodeName.removeClass("ui-state-error");
                }
            })

            if (node_type != node_types_dict.SV_THEME) {
                dialog
                    .append('<br/><label for="field">Field: </label>')
                    .append('<select id="field">' + fieldOptions(node) + '</select><br/>')

                // By default, set the name to be equal to the fieldname selected
                var defaultName = $('#field').val();
                newNodeName.val(defaultName);

                $('#field').on('change', function() {
                    newNodeName.val(this.value);
                });
            }
        }

        function node_type_to_class(node_type){
            return node_type.toLowerCase().replace(/ /g, '_');
        }

        function getRootNode(node){
            if (node.parent === undefined) {
                return node;
            } else {
                return getRootNode(node.parent);
            }
        }

        function listTakenFields(node) {
            var takenFields = [];
            if (typeof node.field !== "undefined") {
                takenFields.push(node.field);
            }
            // Recursive search inside the children of the node
            if (typeof node.children !== "undefined"){
                for (var i = 0; i < node.children.length; i++){
                    var takenInChild = listTakenFields(node.children[i]);
                    takenFields.push.apply(takenFields, takenInChild);
                }
            }
            return takenFields;
        }

        function fieldOptions(pdData) {
            var options = '';
            // Retrieve the root (TODO: we should keep the root node visible and use that here)
            var rootNode = getRootNode(pdData);
            // Get a list of fields that are already in the Project Definition
            var takenFields = listTakenFields(rootNode);
            for (var i = 0; i < ACTIVE_LAYER_NUMERIC_FIELDS.length; i++) {
                var field_name = ACTIVE_LAYER_NUMERIC_FIELDS[i];
                // Add only numeric fields in the active layer that are not already in the PD
                if (takenFields.indexOf(field_name) === -1){
                    options += '<option value="' + field_name + '">' + field_name + '</option>';
                }
            }
            return options;
        }

        function updateButton(pdId){
            // FIXME: Are we using pdId?
            pdId = typeof pdId !== 'undefined' ? pdId : false;
            $('#projectDefWeightDialog').append('<br/><br/><button type="button" id="update-button">Update</button>');
            $('#update-button').click(
                function(){
                    updateButtonClicked();
                }
            );
            $('#projectDefWeightDialog').append('<button type="button" id="updateandclose-button">Update and close</button>');
            $('#updateandclose-button').click(function(){
                updateButtonClicked();
                $('#projectDefWeightDialog').dialog("close");
            });

            function updateButtonClicked() {
                pdTempWeights = [];
                pdTempWeightsComputed = [];

                // Get the values of the spinners
                for (var i = 0; i < pdTempSpinnerIds.length; i++) {
                    pdTempWeights.push($('#'+pdTempSpinnerIds[i]).val());
                }

                // Adjust the values into percentages
                pdTempWeights = pdTempWeights.map(Number);
                var totalWeights = 0;
                $.each(pdTempWeights,function() {
                    totalWeights += this;
                });

                for (var i = 0; i < pdTempWeights.length; i++) {
                    var tempMath = Math.floor((pdTempWeights[i] * 100) / totalWeights);
                    pdTempWeightsComputed.push(tempMath / 100);
                }

                // Update the results back into the spinners and to the d3.js chart
                for (var i = 0; i < pdTempSpinnerIds.length; i++) {
                    $('#'+pdTempSpinnerIds[i]).spinner("value", pdTempWeightsComputed[i]);
                }

                // Update the json with new values
                for (var i = 0; i < pdTempWeightsComputed.length; i++) {
                    updateTreeBranch(pdData, [pdTempIds[i]], pdTempWeightsComputed[i]);
                }

                if ($('#operator').length !== 0) {
                    var operator = $('#operator').val();
                    updateOperator(pdData, pdId, operator);
                }

                nodeEnter.remove("text");
                updateD3Tree(pdData);
            }
        }

        function findTreeBranchInfo(pdData, pdName, pdLevel, pdField, node) {
            // Find out how many elements are in tree branch
            if (pdLevel.some(
                function(currentValue) {
                    return (pdData.level == currentValue);
                }
            ))
            {
                pdTempIds.push(pdData.id);
                createSpinner(pdData.id, pdData.weight, pdData.name, pdData.field);
            }

            (pdData.children || []).forEach(function(child) {
                findTreeBranchInfo(child, [pdName], [pdLevel], pdField, node);
            });


        }

        function updateTreeBranch(pdData, id, pdWeight) {
            if (id.some(function(currentValue) {
                return (pdData.id == currentValue);
            })) {
                pdData.weight = pdWeight;
            }

            (pdData.children || []).forEach(function(currentItem) {
                updateTreeBranch(currentItem, id, pdWeight);
            });
        }

        function updateOperator(pdData, id, pdOperator) {
            if (pdData.id == id){
                pdData.operator = pdOperator;
            }

            (pdData.children || []).forEach(function(currentItem) {
                updateOperator(currentItem, id, pdOperator);
            });
        }

        function updateNode(node, field, name) {
            node.field = field;
            node.name = name;
            updateD3Tree(node);
        }

        function updateNodeOLD(pdData, id, pdField) {
            if (pdData.id == id){
                pdData.field = pdField;
                // FIXME Let the user type a name for the field
                pdData.name = pdField;
            }

            (pdData.children || []).forEach(function(currentItem) {
                updateNode(currentItem, id, pdField);
            });
        }

        var svg = d3.select("#projectDefDialog").append("svg")
            .attr("width", width + margin.right + margin.left)
            .attr("height", height + margin.top + margin.bottom)
            .attr("id", "project-definition-svg")
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        d3.json(project_definition, function() {
            data = JSON.parse(project_definition);
            root = data;
            root.x0 = height / 2;
            root.y0 = 0;

            //root.children.forEach(collapse);
            updateD3Tree(root);
        });

        d3.select(self.frameElement).style("height", "800px");


        function updateD3Tree(source) {
            // Compute the new tree layout.
            var nodes = tree.nodes(root).reverse(),
                links = tree.links(nodes);

            // Normalize for fixed-depth.
            nodes.forEach(function(d) { d.y = d.depth * 180; });

            // Update the nodes…
            var node = svg.selectAll("g.node")
                .data(nodes, function(d) { return d.id || (d.id = ++i); });

            // Enter any new nodes at the parent's previous position.
            nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; });

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .attr("class", function(d){return node_type_to_class(d.type)})
                .on("mouseover", function(d) {
                    var info = d.name;
                    tooltipdiv .transition()
                        .duration(500)
                        .style("opacity", 0.9);
                    tooltipdiv .html(info)
                        .style("left", (d3.event.pageX) + "px")
                        .style("top", (d3.event.pageY - 28) + "px");
                    })
                .on("mouseout", function(d) {
                    tooltipdiv .transition()
                        .duration(500)
                        .style("opacity", 0);
                    })
                .on("click", function(parent) {
                    // NOTE: Only fields that are not already in the tree should be selectable
                    // By default, assign equal weights to the new node and to its siblings
                    pdData = data; // PAOLO: What's data?
                    var node_type;
                    switch (parent.type) {
                        // clicked on IR, we allow generating a node
                        case node_types_dict.RI:
                            node_type = node_types_dict.RISK_INDICATOR;
                            //alert("You clicked a node with type " + parent.type);
                            break;
                        case node_types_dict.SVI:
                            //alert("You clicked a node with type " + parent.type);
                            node_type = node_types_dict.SV_THEME;
                            break;
                        // clicked on an SVI theme, we allow generating a node
                        case node_types_dict.SV_THEME:
                            //alert("You clicked a node with type " + parent.type);
                            node_type = node_types_dict.SV_INDICATOR;
                            break;
                        // cases where we don't allow generating a node
                        case node_types_dict.SV_INDICATOR:
                            //alert("You clicked a node with type " + parent.type + ". You can't add new nodes there");
                            return false;
                        case undefined:
                            //alert("You clicked a node with type " + parent.type + ". You can't add new nodes there");
                            return false;
                        default:
                            //alert("You clicked a node with type " + parent.type + ". You can't add new nodes there");
                            return false;
                    }

                    if (undefined === parent.children) {
                        parent['children'] = []
                        parent['operator'] = DEFAULT_OPERATOR
                    }
                    var siblings = parent.children;

                    //TODO fix level
                    //Prepare for the new node
                    // Using Math.floor is ok because levels can't be negative
                    var parent_level = Math.floor(parent.level);
                    var siblings_dec_level = 0;
                    var avg_weight = 1.0 / (siblings.length + 1);

                    var old_weights = []
                    for (var i = 0; i < siblings.length; i++) {
                        old_weights[i] = siblings[i].weight
                        siblings[i].weight = avg_weight;
                    }

                    if (siblings.length > 0) {
                        // the parent already has a child
                        siblings_dec_level = siblings[0].level;
                    }
                    else {
                        // TODO implement this by checking all nodesparent level
                        var parents_siblings = 0
                        siblings_dec_level = parent.level + 1;
                        siblings_dec_level = siblings_dec_level + '.' + parents_siblings;
                    }

                    dec_level = (siblings_dec_level - Math.floor(siblings_dec_level)) + 1;
                    new_node_level = parent_level + dec_level;

                    var new_node = {
                        // field and name are assigned through a dialog,
                        // after the node is created
                        'parent': parent,
                        'children':[],
                        'weight': avg_weight,
                        'type': node_type,
                        'x0': parent.x,
                        'y0': parent.y,
                        'level': new_node_level,
                        'depth': parent.depth + 1,
                        'field': "",
                        'name': ""
                    };
                    console.log(new_node)

                    // Add node, appending it to the node that has been clicked<commit_msg>clean up<commit_after>/*
   Copyright (c) 2013, GEM Foundation.

      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU Affero 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 Affero General Public License for more details.

      You should have received a copy of the GNU Affero General Public License
      along with this program.  If not, see <https://www.gnu.org/licenses/agpl.html>.
*/

    // const is not supported by all the browsers, so we are using var instead
    var CIRCLE_SCALE = 30;
    var MIN_CIRCLE_SIZE = 0.001;

    // For checking if a string is blank or contains only white-space
    String.prototype.isEmpty = function() {
        return (this.length === 0 || !this.trim());
    };

    $(document).ready(function() {
        //  Project definition weight dialog
        $("#projectDefWeightDialog").dialog({
            title: "Set weights and operator",
            autoOpen: false,
            modal: true,
            minWidth: 400
        });
        //  Dialog to set up a new node to insert into the project definition
        $("#projectDefNewNodeDialog").dialog({
            title: "Add new indicator",
            autoOpen: false,
            modal: true,
            dialogClass: "no-close",
            closeOnEscape: false
        });
    });


    ////////////////////////////////////////////
    //// Project Definition Collapsible Tree ///
    ////////////////////////////////////////////

    function loadPD(project_definition, qt_page) {
        var DEFAULT_OPERATOR = qt_page.DEFAULT_OPERATOR;
        var OPERATORS = qt_page.OPERATORS.split(';');
        var ACTIVE_LAYER_NUMERIC_FIELDS = qt_page.ACTIVE_LAYER_NUMERIC_FIELDS.split(';');
        var NODE_TYPES = qt_page.NODE_TYPES.split(';');
        var node_types_dict = {};
        for (var i = 0; i < NODE_TYPES.length; i++) {
            var keyval = NODE_TYPES[i];
            var keyvalsplit = keyval.split(':');
            var type_key = keyvalsplit[0];
            var type_name = keyvalsplit[1];
            node_types_dict[type_key] = type_name;
        }

        var margin = {top: 20, right: 120, bottom: 20, left: 60};
        var width = 960 - margin.right - margin.left;
        var height = 800 - margin.top - margin.bottom;

        var i = 0;
        var duration = 750;
        var root;

        var tooltipdiv = d3.select("#projectDefDialog").append("div")
            .attr("class", "tooltip")
            .style("opacity", 0);

        var tree = d3.layout.tree().size([height, width]);

        var nodeEnter;

        var diagonal = d3.svg.diagonal()
            .projection(function(d) { return [d.y, d.x]; });

        function createSpinner(id, weight, name, field) {
            pdTempSpinnerIds.push("spinner-"+id);
            $('#projectDefWeightDialog').dialog("open");
            if (field === undefined) {
                $('#projectDefWeightDialog').append(
                    '<p><label for="spinner'+id+'">'+name+': </label><input id="spinner-'+id+'" name="spinner" value="'+weight+'"></p>');
            } else {
                $('#projectDefWeightDialog').append(
                    '<p><label for="spinner'+id+'">'+name+'('+field+'): </label><input id="spinner-'+id+'" name="spinner" value="'+weight+'"></p>');
            }
            $(function() {
                $("#spinner-"+id).width(100).spinner({
                    min: 0,
                    max: 100,
                    step: 0.01,
                    numberFormat: "n"
                });
            });
        }

        function operatorSelect(pdOperator){
            $('#projectDefWeightDialog')
                .append('<br/><label for="operator">Operator: </label>')
                .append('<select id="operator">'+ operatorOptions() + '</select>');
            //TODO use selectmenu when the bug there is fixed9?
            //$(selector).selectmenu()
            //$(selector).prop('selectedIndex', 4)
            $('#operator').val(pdOperator);
        }

        function operatorOptions(){
            var options = '';
            for (var i = 0; i < OPERATORS.length; i++) {
                var c = OPERATORS[i];
                if (c == DEFAULT_OPERATOR){
                    options += '<option value="' + c + '" selected="selected">' + c + '</option>';
                }
                else{
                    options += '<option value="' + c + '">' + c + '</option>';
                }
            }
            return options;
        }

        function fieldSelect(node, node_type){
            // TODO: Add more stuff to the node
            dialog = $('#projectDefNewNodeDialog');
            //TODO use selectmenu when the bug there is fixed9?
            //$(selector).selectmenu()
            //$(selector).prop('selectedIndex', 4)
            // $('#field').val(node.field);
            dialog.empty();

            dialog
                .append('<label for="name">Name: </label>')
                .append('<input id="newNodeName" type="text" name="newNodeName" value="">');

            newNodeName = $('#newNodeName')
            newNodeName.blur(function(){
                if (newNodeName.val().isEmpty()){
                    newNodeName.addClass("ui-state-error");
                }
                else{
                    newNodeName.removeClass("ui-state-error");
                }
            })

            if (node_type != node_types_dict.SV_THEME) {
                dialog
                    .append('<br/><label for="field">Field: </label>')
                    .append('<select id="field">' + fieldOptions(node) + '</select><br/>')

                // By default, set the name to be equal to the fieldname selected
                var defaultName = $('#field').val();
                newNodeName.val(defaultName);

                $('#field').on('change', function() {
                    newNodeName.val(this.value);
                });
            }
        }

        function node_type_to_class(node_type){
            return node_type.toLowerCase().replace(/ /g, '_');
        }

        function getRootNode(node){
            if (node.parent === undefined) {
                return node;
            } else {
                return getRootNode(node.parent);
            }
        }

        function listTakenFields(node) {
            var takenFields = [];
            if (typeof node.field !== "undefined") {
                takenFields.push(node.field);
            }
            // Recursive search inside the children of the node
            if (typeof node.children !== "undefined"){
                for (var i = 0; i < node.children.length; i++){
                    var takenInChild = listTakenFields(node.children[i]);
                    takenFields.push.apply(takenFields, takenInChild);
                }
            }
            return takenFields;
        }

        function fieldOptions(pdData) {
            var options = '';
            // Retrieve the root (TODO: we should keep the root node visible and use that here)
            var rootNode = getRootNode(pdData);
            // Get a list of fields that are already in the Project Definition
            var takenFields = listTakenFields(rootNode);
            for (var i = 0; i < ACTIVE_LAYER_NUMERIC_FIELDS.length; i++) {
                var field_name = ACTIVE_LAYER_NUMERIC_FIELDS[i];
                // Add only numeric fields in the active layer that are not already in the PD
                if (takenFields.indexOf(field_name) === -1){
                    options += '<option value="' + field_name + '">' + field_name + '</option>';
                }
            }
            return options;
        }

        function updateButton(pdId){
            // FIXME: Are we using pdId?
            pdId = typeof pdId !== 'undefined' ? pdId : false;
            $('#projectDefWeightDialog').append('<br/><br/><button type="button" id="update-button">Update</button>');
            $('#update-button').click(
                function(){
                    updateButtonClicked();
                }
            );
            $('#projectDefWeightDialog').append('<button type="button" id="updateandclose-button">Update and close</button>');
            $('#updateandclose-button').click(function(){
                updateButtonClicked();
                $('#projectDefWeightDialog').dialog("close");
            });

            function updateButtonClicked() {
                pdTempWeights = [];
                pdTempWeightsComputed = [];

                // Get the values of the spinners
                for (var i = 0; i < pdTempSpinnerIds.length; i++) {
                    pdTempWeights.push($('#'+pdTempSpinnerIds[i]).val());
                }

                // Adjust the values into percentages
                pdTempWeights = pdTempWeights.map(Number);
                var totalWeights = 0;
                $.each(pdTempWeights,function() {
                    totalWeights += this;
                });

                for (var i = 0; i < pdTempWeights.length; i++) {
                    var tempMath = Math.floor((pdTempWeights[i] * 100) / totalWeights);
                    pdTempWeightsComputed.push(tempMath / 100);
                }

                // Update the results back into the spinners and to the d3.js chart
                for (var i = 0; i < pdTempSpinnerIds.length; i++) {
                    $('#'+pdTempSpinnerIds[i]).spinner("value", pdTempWeightsComputed[i]);
                }

                // Update the json with new values
                for (var i = 0; i < pdTempWeightsComputed.length; i++) {
                    updateTreeBranch(pdData, [pdTempIds[i]], pdTempWeightsComputed[i]);
                }

                if ($('#operator').length !== 0) {
                    var operator = $('#operator').val();
                    updateOperator(pdData, pdId, operator);
                }

                nodeEnter.remove("text");
                updateD3Tree(pdData);
            }
        }

        function findTreeBranchInfo(pdData, pdName, pdLevel, pdField, node) {
            // Find out how many elements are in tree branch
            if (pdLevel.some(
                function(currentValue) {
                    return (pdData.level == currentValue);
                }
            ))
            {
                pdTempIds.push(pdData.id);
                createSpinner(pdData.id, pdData.weight, pdData.name, pdData.field);
            }

            (pdData.children || []).forEach(function(child) {
                findTreeBranchInfo(child, [pdName], [pdLevel], pdField, node);
            });


        }

        function updateTreeBranch(pdData, id, pdWeight) {
            if (id.some(function(currentValue) {
                return (pdData.id == currentValue);
            })) {
                pdData.weight = pdWeight;
            }

            (pdData.children || []).forEach(function(currentItem) {
                updateTreeBranch(currentItem, id, pdWeight);
            });
        }

        function updateOperator(pdData, id, pdOperator) {
            if (pdData.id == id){
                pdData.operator = pdOperator;
            }

            (pdData.children || []).forEach(function(currentItem) {
                updateOperator(currentItem, id, pdOperator);
            });
        }

        function updateNode(node, field, name) {
            node.field = field;
            node.name = name;
            updateD3Tree(node);
        }

        function updateNodeOLD(pdData, id, pdField) {
            if (pdData.id == id){
                pdData.field = pdField;
                // FIXME Let the user type a name for the field
                pdData.name = pdField;
            }

            (pdData.children || []).forEach(function(currentItem) {
                updateNode(currentItem, id, pdField);
            });
        }

        var svg = d3.select("#projectDefDialog").append("svg")
            .attr("width", width + margin.right + margin.left)
            .attr("height", height + margin.top + margin.bottom)
            .attr("id", "project-definition-svg")
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        d3.json('', function() {
            data = JSON.parse(project_definition);
            root = data;
            root.x0 = height / 2;
            root.y0 = 0;

            //root.children.forEach(collapse);
            updateD3Tree(root);
        });

        d3.select(self.frameElement).style("height", "800px");


        function updateD3Tree(source) {
            // Compute the new tree layout.
            var nodes = tree.nodes(root).reverse(),
                links = tree.links(nodes);

            // Normalize for fixed-depth.
            nodes.forEach(function(d) { d.y = d.depth * 180; });

            // Update the nodes…
            var node = svg.selectAll("g.node")
                .data(nodes, function(d) { return d.id || (d.id = ++i); });

            // Enter any new nodes at the parent's previous position.
            nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; });

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .attr("class", function(d){return node_type_to_class(d.type)})
                .on("mouseover", function(d) {
                    var info = d.name;
                    tooltipdiv .transition()
                        .duration(500)
                        .style("opacity", 0.9);
                    tooltipdiv .html(info)
                        .style("left", (d3.event.pageX) + "px")
                        .style("top", (d3.event.pageY - 28) + "px");
                    })
                .on("mouseout", function(d) {
                    tooltipdiv .transition()
                        .duration(500)
                        .style("opacity", 0);
                    })
                .on("click", function(parent) {
                    // NOTE: Only fields that are not already in the tree should be selectable
                    // By default, assign equal weights to the new node and to its siblings
                    pdData = data; // PAOLO: What's data?
                    var node_type;
                    switch (parent.type) {
                        // clicked on IR, we allow generating a node
                        case node_types_dict.RI:
                            node_type = node_types_dict.RISK_INDICATOR;
                            //alert("You clicked a node with type " + parent.type);
                            break;
                        case node_types_dict.SVI:
                            //alert("You clicked a node with type " + parent.type);
                            node_type = node_types_dict.SV_THEME;
                            break;
                        // clicked on an SVI theme, we allow generating a node
                        case node_types_dict.SV_THEME:
                            //alert("You clicked a node with type " + parent.type);
                            node_type = node_types_dict.SV_INDICATOR;
                            break;
                        // cases where we don't allow generating a node
                        case node_types_dict.SV_INDICATOR:
                            //alert("You clicked a node with type " + parent.type + ". You can't add new nodes there");
                            return false;
                        case undefined:
                            //alert("You clicked a node with type " + parent.type + ". You can't add new nodes there");
                            return false;
                        default:
                            //alert("You clicked a node with type " + parent.type + ". You can't add new nodes there");
                            return false;
                    }

                    if (undefined === parent.children) {
                        parent['children'] = []
                        parent['operator'] = DEFAULT_OPERATOR
                    }
                    var siblings = parent.children;

                    //TODO fix level
                    //Prepare for the new node
                    // Using Math.floor is ok because levels can't be negative
                    var parent_level = Math.floor(parent.level);
                    var siblings_dec_level = 0;
                    var avg_weight = 1.0 / (siblings.length + 1);

                    var old_weights = []
                    for (var i = 0; i < siblings.length; i++) {
                        old_weights[i] = siblings[i].weight
                        siblings[i].weight = avg_weight;
                    }

                    if (siblings.length > 0) {
                        // the parent already has a child
                        siblings_dec_level = siblings[0].level;
                    }
                    else {
                        // TODO implement this by checking all nodesparent level
                        var parents_siblings = 0
                        siblings_dec_level = parent.level + 1;
                        siblings_dec_level = siblings_dec_level + '.' + parents_siblings;
                    }

                    dec_level = (siblings_dec_level - Math.floor(siblings_dec_level)) + 1;
                    new_node_level = parent_level + dec_level;

                    var new_node = {
                        // field and name are assigned through a dialog,
                        // after the node is created
                        'parent': parent,
                        'children':[],
                        'weight': avg_weight,
                        'type': node_type,
                        'x0': parent.x,
                        'y0': parent.y,
                        'level': new_node_level,
                        'depth': parent.depth + 1,
                        'field': "",
                        'name': ""
                    };
                    console.log("newnode:")
                    console.log(new_node)

                    // Add node, appending it to the node that has been clicked
{'fraction_non_alphanumeric': 0.1217003678938747, 'fraction_numerical': 0.006391149064153173, 'mean_word_length': 1.3563654770944065, 'pattern_counts': {'":': 0, '<': 95, '<?xml version=': 0, '>': 79, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '119182', 'n_tokens_mistral': 9767, 'n_tokens_neox': 9145, 'n_words': 2357}