https://github.com/thu-vis/MutualDetector
Raw File
Tip revision: 2b019233d6851facadec8e9215cc805eef47932c authored by Changjian Chen on 20 May 2024, 01:52:04 UTC
update readme
Tip revision: 2b01923
treecut.js
function assert(flag, string){
    if (!flag){
      throw Error(string);
    }
}

const TreeCut = function (bbox_width, bbox_height, layer_height) {
    let _this = this;
    let _tree = null;
    let _tree_layout = null;
    let _bbox_width = bbox_width;
    let _bbox_height = bbox_height;
    let _layer_height = layer_height;
    let pinArray = [];
    let clickQueue = [];
    let tmp_children = {};
    clickQueue.insertAt = function (index, obj) {
        this.splice(index, 0, obj);
    };
    clickQueue.removeAt = function (index) {
        this.splice(index, 1);
    };

    this._update_tree = function(tree){
        _tree = tree;
        return true;
    };

    this._update_tree_layout = function(tree_layout){
        _tree_layout = tree_layout;
        return true;
    };

    this.candraw = function(root){
        let nodes = _tree_layout(root);
        _this.recover_children(root);
        let x1 = Number.MAX_VALUE, x2 = Number.MIN_VALUE, y1 = Number.MAX_VALUE, y2 = Number.MIN_VALUE;
        for (let i = 0; i < nodes.length; i++) {
            let node = nodes[i];
            if ((node.x - node._total_width/2) < x1) {
                x1 = node.x - node._total_width/2;
            }
            if ((node.x + node._total_width/2) > x2) {
                x2 = node.x + node._total_width/2;
            }
            if (node.y < y1) {
                y1 = node.y;
            }
            if (node.y > y2) {
                y2 = node.y;
            }
        }
        //if(max_depth === 5){
        //    console.log("candraw max_depth", max_depth, y2, y1);
        //}
        // console.log(x1, x2, y1, y2, _bbox_width, _bbox_height, _layer_height);
        if (x2 - x1 > _bbox_width || y2 - y1 > (_bbox_height - _layer_height)) {
            return false;
        }
        //if(max_depth === 5){
        //    console.log("candraw max_depth", max_depth);
        //}
        let offset = (x1 + x2) / 2 + 10;
        // console.log("offset:", offset);
        if (!offset){
            offset = 0.001;
        }
        return offset;
    };

    this.recover_children = function(source){
        let that = this;
        if(!source.children) source.children=[];
        source.all_children.forEach(d => that.recover_children(d));
    };

    this.initial = function(root){
        _this._initial(root);
    };

    this._initial = function (d) {
        // if (!d._children) {
        //     d._children = [];
        // }
        if (!d.children) {
            d.children = [];
        }
        if (!d.all_children){
            d.all_children = [];
        }
        if (!d.window) {
            d.window = {};
        }
        if (!d.window.x) {
            d.window.x = 0;
        }
        if (!d.window.y) {
            d.window.y = 0;
        }
        if (!d.beforeList) {
            d.beforeList = [];
        }
        if (!d.afterList) {
            d.afterList = [];
        }
        // let ratio = d.value_new.reduce((acc,x) => acc + x) / d.value.reduce((acc,x) => acc + x);
        //d.api = d._total_width * (1 + 10 * ratio) - 5 * Uncertainty(d);
        // d.api = 2 - (d.data.precision * 2 + d.data.recall) / 2; //Math.sqrt(d._total_width) * (1 + 10 * ratio) ;
        assert(!isNaN(d.api), "api NaN error");
        d.doi = d.api;
        d.all_children.forEach(_this._initial);
    };

    this.drawPin = function () {
        for (let i = 0; i < pinArray.length; i++) {
            _this.draw_single_node(pinArray[i]);
        }
    };

    this.update_before_after_cnt_field = function (source) {
        // update before list and after list according window.x and window.y
        source.beforeList = [];
        for (let i = source.window.x - 1; i >= 0; i--) {
            if (source.children.indexOf(source.all_children[i]) < 0) {
                source.beforeList.push(source.all_children[i]);
            }
        }
        source.afterList = [];
        for (let i = source.window.y; i < source.all_children.length; i++) {
            if (source.children.indexOf(source.all_children[i]) < 0) {
                source.afterList.push(source.all_children[i]);
            }
        }
    };

    this.update_has_after_cnt_field = function (source) {
        // initial before list and update after list
        source.beforeList = [];
        source.afterList = [];
        if (source.children.length === 0){
            return;
        }

        let min_idx = 1000, max_idx = -1;
        for (let i = 0; i < source.children.length; i++){
            let siblings_id = source.children[i].siblings_id;
            if (siblings_id > max_idx){
                max_idx = siblings_id;
            }
            if (siblings_id < min_idx){
                min_idx = siblings_id;
            }
        }
        // console.log("update has after cnt field", min_idx, max_idx, source.children.map(d => d.siblings_id), source.all_children.map(d => d.siblings_id));
        for (let i = max_idx - 1; i >= 0; i--) {
            if (source.children.indexOf(source.all_children[i]) < 0) {
                source.beforeList.push(source.all_children[i]);
            }
        }
        for (let i = max_idx + 1; i < source.all_children.length; i++) {
            // if (source.children.indexOf(source.all_children[i]) < 0) {
                source.afterList.push(source.all_children[i]);
            // }
        }

        // for (let i = 0; i < source.all_children.length; i++) {
        //     if (source.children.indexOf(source.all_children[i]) < 0) {
        //         source.afterList.push(source.all_children[i]);
        //     }
        // }
    };

    this.check_order = function(arr){
        if (arr.length < 2) return;
        for(let i = 0; i < arr.length-1; i++){
            assert(arr[i].siblings_id <= arr[i+1].siblings_id, "check order error");
        }
    };

    this.from_rest_to_children = function (node) {
        // put node to children
        if (node.parent.all_children.indexOf(node) < 0) {
            return false;
        }
        if (node.parent.children.indexOf(node) < 0) {
            let parent_id = node.parent.id;
            assert(parent_id !== undefined, "parent id error");
            tmp_children[parent_id] = node.parent.children.slice(); // shallow copy

            // // added in random
            // node.parent.children.push(node);

            // added in order
            let i = 0;
            for(; i < node.parent.children.length; i++){
                if (node.parent.children[i].siblings_id > node.siblings_id){
                    break;
                }
            }
            node.parent.children.splice(i,0,node);
            this.check_order(node.parent.children);

            // reordering(node.parent);

            this.update_has_after_cnt_field(node.parent);
            return true;
        } else {
            // assert(0, "from rest to children error");
            return false;
        }
    };

    this.from_children_to_rest = function (node) {
        let id = node.parent.children.indexOf(node);
        if (id >= 0){
            // node.parent.children.splice(id, 1);
            let parent_id = node.parent.id;
            assert(tmp_children[parent_id].length === node.parent.children.length - 1, "children length error");
            node.parent.children = tmp_children[parent_id];
            _this.update_has_after_cnt_field(node.parent);
            return true;
        }
        else{
            assert(0, "from children to rest error");
            return false;
        }
    };

    this.draw_single_node = function (source) {
        //
        let node = source;
        let drawArr = [];
        while (node != null) {
            if (node.parent != null && _this.from_rest_to_children(node)) {
                drawArr.push(node);
                node = node.parent;
            } else {
                break;
            }
        }
        if (_this.candraw(_tree)) {
            return true;
        } else {
            for (let i = 0; i < drawArr.length; i++) {
                _this.from_children_to_rest(drawArr[i]);

            }
            return false;
        }
    };

    this.searchUpY = function(source){
        for (let y = source.window.y; y < source.all_children.length; y++) {
            if (source.children.indexOf(source.all_children[y]) < 0) {
                let node = source.all_children[y];
                _this.from_rest_to_children(node);
                if (!_this.candraw(_tree)) {
                    _this.from_children_to_rest(node);
                    break;
                }
            }
            source.window.y = y + 1;
        }
    };

    this.searchDownX = function (source) {
        for (var x = source.window.x - 1; x >= 0; x--) {
            if (source.children.indexOf(source.all_children[x]) < 0) {
                var node = source.all_children[x];
                _this.from_rest_to_children(node);
                if (!_this.candraw(_tree)) {
                    _this.from_children_to_rest(node);
                    break;
                }
            }
            source.window.x = x;
        }
    }

    this.draw_my_children = function(source){
        source.window.x = 0;
        source.window.y = 0;
        _this.searchUpY(source);
    };

    this.draw_node_as_much_as_possible = function(source){
        let node = source;
        if (node != null) {
            node = node.parent;
        }
        while (node != null) {
            _this.draw_my_children(node);
            node = node.parent;
        }
    };

    this.draw_nodes_according_to_click_queue = function () {
        // try to draw as many nodes in click queue as possible
        let nodes_in_click_queue_drawn = [];
        for (let i = 0; i < clickQueue.length; i++) {
            if (_this.draw_single_node(clickQueue[i].node)){
                nodes_in_click_queue_drawn.push(clickQueue[i]);
            }
        }
        // if all nodes in click queue cannot be drawn
        if (nodes_in_click_queue_drawn.length === 0){
            _this.draw_my_children(_tree);
        }
        // try to draw the children of nodes that have been drawn
        else{
            for(let i = 0; i < nodes_in_click_queue_drawn.length; i++){
                if(nodes_in_click_queue_drawn[i].drawChildren){
                    _this.draw_my_children(nodes_in_click_queue_drawn[i].node);
                }
                _this.draw_node_as_much_as_possible(nodes_in_click_queue_drawn[i].node);
            }
        }
    };

    // this.update_rest_node = function(source){
    //     if (source.id.length > 5 && source.id.slice(0,5) === "rest-") return;
    //     if (source.beforeList.length > 0){
    //         // create a rest node
    //         let rest_node = new TreeNode();
    //         rest_node.id = "rest-before-node-" + source.id;
    //         rest_node._widthes = new Array(20).fill(0); // TODO:  20
    //         // insert the rest node in the front of children list
    //         source.children = [rest_node].concat(source.children);
    //     }
    //     if (source.afterList.length > 0){
    //         // create a rest node
    //         let rest_node = new TreeNode();
    //         rest_node.id = "rest-after-node-" + source.id;
    //         rest_node._widthes = new Array(20).fill(0); // TODO:  20
    //         // insert the rest node in the back of children list
    //         source.children.push(rest_node);
    //     }
    //     source.children.forEach(_this.update_rest_node);
    // };

    
    
    this.treeCut = function (sources, tree, tree_layout) {
        console.log("bbox width and height", bbox_width, bbox_height);
        _this._update_tree(tree);
        _this._update_tree_layout(tree_layout);
        if(!sources){
            sources = [_tree];
        }
        let source = sources[0];
        _this.initial(_tree);
        let nodes = _tree_layout(_tree).filter(d => !d.is_rest_node); // update depth
        nodes.forEach(d=>d.debug_visited=false);
        traverse_for_doi(_tree, source);
        console.log("source in treecut", sources);
        clickQueue = get_selection_array(_tree, sources);
        // console.log("clickqueue", clickQueue.map(d => d.node.name));
        pinArray = nodes.filter(d=>d.pinned);
        console.log("pinArray: ", pinArray);
        collapse(_tree);
        _this.drawPin();
        _this.draw_nodes_according_to_click_queue();
          
        // update__children(_tree);
        let offset = _this.candraw(_tree);
        assert(offset, "offset error");
        return offset;
    };

    function tree_distance(r1, r2){
        let LCA2 = function(a,b){
            while (a.depth > b.depth) {
                a = a.parent;
            }
            while (b.depth > a.depth) {
                b = b.parent;
            }
            while (a.id !== b.id) {
                a = a.parent;
                b = b.parent;
            }
            return a;
        };
        let p = LCA2(r1, r2);
        let d = r1.depth + r2.depth - p.depth * 2;
        if (r1.id != r2.id){
            d = d + 0.1 - 0.1 / (r1.order - r2.order) / (r1.order - r2.order);
        }
        if (r1.id != r2.id && r1.depth === r2.depth && r1.parent.all_children.indexOf(r2) > 0){
            console.log("siblings");
            let siblings_dis = (r1.siblings_id - r2.siblings_id) * (r1.siblings_id - r2.siblings_id);
            siblings_dis = 1 / siblings_dis / 5;
            d = d - siblings_dis;
        }
        // console.log(d, r1, r2);
        assert(d >= 0, "distance error!!!");
        if (p == r2) return d / 5;
        if (p == r2.parent) return d / 2;
        return d;
    }

    function traverse_for_doi(r, source) {
        assert(source, "source is empty");
        //calculate present doi
        // let alpha = 0.6, beta = 0.4, decay = 0.9;
        let beta = 1;
        let dis = tree_distance(r, source);
        // r.doi = r.doi * (decay * alpha + 1.0 / (dis + 1) / (dis + 1) * beta);
        r.doi = r.api + 1.0 / (dis + 1) / (dis + 1) * beta;
        assert(!isNaN(r.doi), "doi NaN error!");
        r.in_selection_array = false;
        if(!r.children){return;}
        for(let i = 0; i < r.all_children.length; i++){
            traverse_for_doi(r.all_children[i], source);
        }
    }

    function get_selection_array(root, sources) {
        let selection_array = [];
        // add selected node and his children to selection array
        let source = sources[0];
        // selection_array.push({
        //     "node": source,
        //     "drawChildren": true
        // });
        sources.forEach(d=>{
            selection_array.push({
                "node": d,
                "drawChildren": true
            });
            d.in_selection_array = true;
        });
        source.in_selection_array = true;
        for(let i = 0; i < source.all_children.length; i++){
            selection_array.push({
                "node": source.all_children[i],
                "drawChildren": true
            });
            source.all_children[i].in_selection_array = true;
        }
        // add other nodes according to doi
        let all_node = [];
        let first_not_visited_node = 0;
        all_node.push(root);
        while(first_not_visited_node !== all_node.length){
            let visited_node = all_node[first_not_visited_node];
            first_not_visited_node ++;
            if (visited_node.all_children){
                for(let i = 0; i < visited_node.all_children.length; i++ ){
                    all_node.push(visited_node.all_children[i]);
                }
            }
        }
        all_node.sort(function(a,b){return b.doi - a.doi;});
        for (let i = 0; i < all_node.length; i++){
            if (!all_node[i].in_selection_array){
                selection_array.push({
                    "node": all_node[i],
                    "drawChildren": true
                })
            }
        }

        // // remove redundant nodes in selection array: clickQueue.removeRedundantNodes in tree.cut.js
        // for (let i = selection_array.length - 1; i >= 1; i--){
        //     if (inPath(source, selection_array[i].node)){
        //         selection_array.splice(i, 1);
        //     }
        // }

        // // preserve nodes with top-k doi (optional)
        // selection_array.splice(5);

        return selection_array
    }

    function collapse(node) {
        //collapse all children, namely all children are stored in node.children and node._children is empty
        node.children = [];
        if (node.all_children.length > 0){
            node.all_children.forEach(collapse);
        }
    }

    // function update__children(node) {
    //     // update node._children according to node.children
    //     node._children = [];
    //     for (let i = 0; i < node.all_children.length; i++ ){
    //         if (node.children.indexOf(node.all_children[i]) < 0){
    //             node._children.push(node.all_children[i]);
    //         }
    //     }
    //     node.all_children.forEach(update__children);
    // }

    // function inPath(source, item) {
    //     let node = item;
    //     while (node != null) {
    //         if (node === source)
    //             return true;
    //         node = node.parent;
    //     }
    //     node = source;
    //     while (node != null) {
    //         if (node === item)
    //             return true;
    //         node = node.parent;
    //     }
    //     return false;
    // }
};

export {TreeCut}
back to top