2015-04-11 3 views
0

Я делаю карту treemap с d3.js. Мое дерево имеет гораздо больше листовых узлов, чем я мог бы визуализировать, поэтому я хочу обрезать дерево, чтобы поддерживать процент листовых узлов для каждой ветви, но уменьшать общее количество листьев до небольшого размера n.Почему `treemap.nodes` не видит обновления для моего объекта?

Я успешно реализовал цикл for, который упорядочивает дочерние объекты моего объекта data желаемым образом, но когда я вызываю treemap.nodes(data), я получаю список с исходным числом узлов как возвращаемое значение, а не сокращенный список узлов с меньшим количеством листьев.

Вот минимальный пример, который воспроизводит проблему.

<!DOCTYPE html> 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> 
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> 
</head> 

<body> 
    <script type="text/javascript"> 

    var w = 1280 - 80, 
     h = 800 - 180; 

    // Declare treemap 
    var treemap = d3.layout.treemap() 
     .round(false) 
     .size([w, h]) 
     .sticky(true) 
     .value(function(d) { return d.size; }); 

    // Load data 
    d3.json("demo.json", function(data) { 

     // Trim data 
     var categories = treemap.nodes(data) 
      .filter(function(d) { return d.depth==1; }); 

     n = 10; //This is the maximum number of leaves I want to have 
     var total_value = treemap.nodes(data)[0].value; 
     for(var vertex of categories) 
     { 
      var num_children = vertex.children.length; 
      var percentage_screen = num_children/total_value; 
      var max_leaves = Math.max(1, Math.floor(n * percentage_screen)); 
      vertex.children = vertex.children.slice(0,max_leaves) 
     } 

     // Here I try to get a list of the leaves  
     var nodes = treemap.nodes(data) 
      .filter(function(d) { return !d.children; }); 

     // This print statement prints 20, the original number of leaves 
     console.log(nodes.length); 
     // But if I recursively count the number of leaves, I get the new smaller number, 10 
     console.log(recursive_node_counter(data)); 
    }); 

    function recursive_node_counter(d){ 
     if('children' in d){ 
      var num_child = 0; 
      for(child of d.children){ 
       num_child += recursive_node_counter(child); 
      } 
      return num_child; 
     } else { 
      return 1 
     } 
    }; 


    </script> 
</body> 
</html> 

Использование вместе с этим JSON: demo.json

{"name": "root", "children": 
    [{"name": "branch_A", "children": 
     [{"name": "A_leaf", "size": 1}, 
     {"name": "A_leaf", "size": 1}, 
     {"name": "A_leaf", "size": 1}, 
     {"name": "A_leaf", "size": 1}, 
     {"name": "A_leaf", "size": 1}, 
     {"name": "A_leaf", "size": 1}]}, 
    {"name": "branch_B", "children": 
     [{"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}, 
     {"name": "B_leaf", "size": 1}]}, 
    {"name": "branch_C", "children": 
     [{"name": "C_leaf", "size": 1}, 
     {"name": "C_leaf", "size": 1}, 
     {"name": "C_leaf", "size": 1}, 
     {"name": "C_leaf", "size": 1}]}]} 

Почему treemap.nodes(data) не видит изменения в переменной data?

+0

Вы не делаете любые изменения в переменной 'data'. Если вы измените его перед выполнением объединения данных, эти изменения будут отражены в древовидной структуре. –

+0

@cool, если я не вношу никаких изменений в переменную данных, почему второй оператор печати печатает 10? – Cecilia

+1

Предполагая, что закрытие всегда плохое в javascript. Макет имеет побочные эффекты для массива данных, так что одного достаточно, чтобы заставить меня очень неудобно пытаться туннелировать данные через макет. –

ответ

1

Следующие операторы консольных более иллюстративный выпуска:

d3.json("./demo.json", function(data) { 

    // Trim data 
    var categories = treemap.nodes(data) 
     .filter(function(d) { return d.depth==1; }); 

    n = 10; //This is the maximum number of leaves I want to have 
    var total_value = treemap.nodes(data)[0].value; 
    for(var vertex of categories) 
    { 
     var num_children = vertex.children.length; 
     var percentage_screen = num_children/total_value; 
     var max_leaves = Math.max(1, Math.floor(n * percentage_screen)); 
     vertex.children = vertex.children.slice(0,max_leaves); 
    } 

    // Here I try to get a list of the leaves  
    console.log(data); 
    var nodes = treemap.nodes(data) 
     .filter(function(d) { return !d.children; }); 
    console.log(data); 
}); 

Когда мы запустим этот код, мы получаем тот же неожиданный выход

20 
10 


Почему? Если мы посмотрим на treemap.nodes (данные) вызова, то становится ясно:

function treemap(d) { 
    var nodes = stickies || hierarchy(d), root = nodes[0]; 
    root.x = 0; 
    root.y = 0; 
    root.dx = size[0]; 
    root.dy = size[1]; 
    if (stickies) hierarchy.revalue(root); 
    scale([ root ], root.dx * root.dy/root.value); 
    (stickies ? stickify : squarify)(root); 
    if (sticky) stickies = nodes; 
    return nodes; 
} 

... 
... 

hierarchy.revalue = function(root) { 
    if (value) { 
    d3_layout_hierarchyVisitBefore(root, function(node) { 
     if (node.children) node.value = 0; 
    }); 
    d3_layout_hierarchyVisitAfter(root, function(node) { 
     var parent; 
     if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; 
     if (parent = node.parent) parent.value += node.value; 
    }); 
    } 
    return root; 
}; 

На первом вызове treemap.nodes (данные), липкие устанавливается на необрезанных данных. При втором вызове treemap.nodes (данные) функция использует данные первого вызова через переменную «липкие». В этом же втором вызове функция затем вызывает иерархию.revalue (root) на обрезанных данных, которые обновляют соответствующие объекты.

+0

В моем браузере мне нужно изменить оператор печати на 'console.log (data.value)', чтобы получить это поведение. Но в остальном это очень хорошее объяснение лежащего в основе поведения. – Cecilia

2

Моя точка зрения заключается в том, что d3 должен быть данных, приводимых в движение, поэтому вместо того, чтобы пытаться взломать объект макета, всегда лучше модифицировать данные, прежде чем присоединять его к макету.

Одно из решений: Используйте версию блокнота данных и TreeMap, чтобы получить выгоду от поведения TreeMap сделать обрезку, а затем создать новый экземпляр с измененными данными ...

var w = 1280 - 80, 
     h = 800 - 180; 

// Load data 
d3.json("demo.json", function (data) { 

    // make a temp object to use treemap behaviour 
    var treemap = d3.layout.treemap() 
      .sticky(true) 
      .value(function (d) { return d.size; }), 
    // cache original data 
      tempData = $.extend(true, {}, data), 
      //***snap shot 1 
    // Trim original data using temp data 
      tempNodesData = treemap.nodes(tempData), 
      //***snap shot 2 
      total_value = tempNodesData[0].value, 

      categories = tempNodesData 
      .filter(function (d) { return d.depth == 1; }), 

      n = 10; //This is the maximum number of leaves I want to have 

    categories.forEach(function (vertex, i, categories) { 
     var num_children = vertex.children.length, 
       percentage_screen = num_children/total_value, 
       max_leaves = Math.max(1, Math.floor(n * percentage_screen)); 

     data.children[i].children = vertex.children.slice(0,max_leaves) 
    }) 

    // re-Declare treemap 
    var treemap = d3.layout.treemap() 
      .round(false) 
      .size([w, h]) 
      .sticky(true) 
      .value(function (d) { return d.size; }), 

    // Here I try to get a list of the leaves  
      nodes = treemap.nodes(data) 
         .filter(function(d) { return !d.children; }); 

    // This print statement prints 20, the original number of leaves 
    alert('nodes.length: ' + nodes.length + '\n' + 'recursive_node_counter: ' + recursive_node_counter(data)); 
    d3.select('body').insert('div', 'script').attr('class', 'logWindow').style({'color': 'red;'}) 
    .html('nodes.length: ' + nodes.length + '</br>' + 'recursive_node_counter: ' + recursive_node_counter(data)); 

}); 

function recursive_node_counter(d){ 
    if('children' in d){ 
     var num_child = 0; 

     d.children.forEach(function(child){ 
       num_child += recursive_node_counter(child); 
     }) 
    return num_child; 
} else { 
         return 1 
} 
}; 

В ответ на комментарий ниже, tempData изначально выглядит следующим образом:

***snap shot 1

и после treemap.nodes(tempData) заявления он выглядит следующим образом

***snap shot 2

Если я хранить данные в своем первоначальном виде, то я не придется тратить время, выясняя, если дополнительные свойства материи или нет (сейчас и в будущем!)

+0

Какова цель кэширования исходных данных? Просто придерживаясь неизменной копии? – Cecilia

+0

Да, метод '.nodes (data)' имеет побочные эффекты в аргументе 'data': он добавляет дополнительные свойства. Таким образом, структура 'tempData' фактически изменяется фазой обрезки. Делая это таким образом, «данные» изменяются, но структура не изменяется. –

Смежные вопросы