d3.js Tree - Paging with search

IlludiumPu36

I have a previous question which has a solution for paging child nodes.

This works well, but has caused an issue with the Select2 list for finding nodes of d.type == 'learning_event'.

The Select2 shows the nodes, but those that are paged in the tree do not show in the Select2.

Instead the name of the paging nodes, '...', displays instead of the names of the paged nodes.

See fiddle

Code in the fiddle:

    //===============================================
    function learningEventCollection(d) {
        if (d.children)
            d.children.forEach(learningEventCollection);
        else if (d._children)
            d._children.forEach(learningEventCollection);
        if (!d.children && d.type == 'learning_event') learningEventData.push(d.name);
    }

    //===============================================
    function searchTree(d) {
        if (d.children)
            d.children.forEach(searchTree);
        else if (d._children)
            d._children.forEach(searchTree);
        var searchFieldValue = eval(searchField);
        if (searchFieldValue && searchFieldValue.toLowerCase().match(searchText.toLowerCase())) {
            // Walk parent chain
            var ancestors = [];
            var parent = d;
            while (typeof(parent) !== "undefined") {
                ancestors.push(parent);
                //console.log(parent);
                parent.class = "found";
                parent = parent.parent;
            }
            //console.log(ancestors);
        }
    }

    //===============================================
    function clearAll(d) {
        d.class = "";
        if (d.children)
            d.children.forEach(clearAll);
        else if (d._children)
            d._children.forEach(clearAll);
    }
    //===============================================
    function collapse(d) {

        if (d.children) {
            d._children = d.children;
            //set the parent object in all the children
            d._children.forEach(function(d1) {
                d1.parent = d;
                collapse(d1);
            });
            d.children = null;
        }
    }
    //===============================================
    function collapseAllNotFound(d) {
        if (d.children) {
            if (d.class !== "found") {
                d._children = d.children;
                d._children.forEach(collapseAllNotFound);
                d.children = null;
            } else
                d.children.forEach(collapseAllNotFound);
        }
    }
    //===============================================
    function expandAll(d) {
        if (d._children) {
            d.children = d._children;
            d.children.forEach(expandAll);
            d._children = null;
        } else if (d.children)
            d.children.forEach(expandAll);
    }

    //===============================================
    // Toggle children on click.
    function toggle(d) {
        if (d.children) {
            d._children = d.children;
            d.children = null;
        } else {
            d.children = d._children;
            d._children = null;
        }
        clearAll(root);
        update(d);
        $("#search").select2("val", "");
    }

    //===============================================
    $("#search").on("select2-selecting", function(e) {
        clearAll(root);
        expandAll(root);
        update(root);
        searchField = "d.name";
        searchText = e.object.text;
        searchTree(root);
        root.children.forEach(collapseAllNotFound);
        update(root);
    })
    
        var colourScale = d3.scale.ordinal()
        .domain(["Program", "ProgramGroup1", "ProgramGroup2"])
        .range(["#abacab", "#b67a4e", "#5a6fbb"])
    

var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120
  },
  width = 2000 - margin.right - margin.left,
  height = 500 - margin.top - margin.bottom;

var i = 0,
  duration = 750,
  root;

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

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

var svg = d3.select("body").append("svg")
  .attr("width", width + margin.right + margin.left)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = treeData;
root.x0 = height / 2;
root.y0 = 0;

function pageNodes(d, options) {
  if (d.children) {
    d.children.forEach(c => pageNodes(c, options));
    if (options.page && options.page(d) && d.children.length > options.maxNode) {
      d.pages = {}
      const count = options.maxNode - 2;
      const l = Math.ceil(d.children.length / count);
      for (let i = 0; i < l; i++) {
        const startRange = i * count;
        const endRange = i * count + count;
        let pageNumber = i == 0 ? l - 1 : i - 1;
        d.pages[i] = d.children.slice(startRange, endRange);
        d.pages[i].unshift({
          ...d.pages[i][0],
          data: {
            name: options.getLabel ? options.getLabel(pageNumber) : "..."
          },
          pageNumber,
          name: "..."
        })

        // console.log(i, d.pages[i]);
        pageNumber = i != (l - 1) ? i + 1 : 0;
        d.pages[i].push({
          ...d.pages[i][0],
          data: {
            name: options.getLabel ? options.getLabel(pageNumber) : "..."
          },
          pageNumber,
          name: "..."
        });
      }
      d.children = d.pages[0];
      console.log(d.pages)
    }
  }
}
root.children.forEach(c => pageNodes(c, {
  maxNode: 8,
  page: function(d) {
    return d.type == "unit_group";
  }
}));


 learningEventData = [];
        learningEventCollection(root);
        learningEventDataObject = [];
        learningEventData.sort(function(a, b) {
                if (a > b) return 1; // sort
                if (a < b) return -1;
                return 0;
            })
            .filter(function(item, i, ar) {
                return ar.indexOf(item) === i;
            }) // remove duplicate items
            .filter(function(item, i, ar) {
                learningEventDataObject.push({
                    "id": i,
                    "text": item
                });
            });
        
        $("#search").select2({
            placeholder: "Select a Learning Event...",
            data: learningEventDataObject,
            containerCssClass: "search"
        });
        
        

function collapse(d) {
  if (d.children) {
    d._children = d.children;
    d._children.forEach(collapse);
    d.children = null;
  }
}

root.children.forEach(collapse);
update(root);

//svg.style("height", "500px");

function update(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.
  var nodeEnter = node.enter().append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click", click);

  nodeEnter.append("circle")
    .attr("r", 1e-6)
    .attr('stroke', function(d) {
      return d.color ? d.color : 'blue';
    })
    .style("fill", function(d) {
      return d._children ? "#ccc" : "#fff";
    });

  nodeEnter.append("text")
    .attr("x", function(d) {
      return d.children || d._children ? -13 : 13;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", function(d) {
      return d.children || d._children ? "end" : "start";
    })
    .text(function(d) {
      return d.name;
    })
    .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

   nodeUpdate.select("circle")
            .attr("r", 6.5)
            .attr("fill-opacity", "0.7")
            .attr("stroke-opacity", "1")
            .style("fill", function(d) {
            if (d.class !== "found") {
                if(d.type == "learning_event") return "#6eb7e3";
                if(d.type == "assessment") return "#4ecc44";
            }
                if (d.class === "found") {
                    return "#ff4136"; //red
                } else {
                    return (typeof d._children !== 'undefined') ? (colourScale(findParent(d))) : '#FFF';

                }
            })
            .style("stroke", function(d) {
             if (d.class !== "found") {
                if(d.type == "learning_event") return "#6eb7e3";
                if(d.type == "assessment") return "#4ecc44";
                if(d.name.match(/Learning Events/)) return "#6eb7e3";
                if(d.name.match(/Assessments/)) return "#4ecc44";
             }
                if (d.class === "found") {
                    return "#ff4136"; //red
                } else {
                    return colourScale(findParent(d));
                }
            });
    
    

  nodeUpdate.select("text")
    .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r", 1e-6);

  nodeExit.select("text")
    .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
    .data(links, function(d) {
      return d.target.id;
    });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("d", function(d) {
      var o = {
        x: source.x0,
        y: source.y0
      };
      return diagonal({
        source: o,
        target: o
      });
    });

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", function(d) {
      var o = {
        x: source.x,
        y: source.y
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

    function findParent(datum) {
        if (datum.depth < 2) {
            return datum.name
        } else {
            return findParent(datum.parent)
        }
    }

    function findParentLinks(datum) {
        if (datum.target.depth < 2) {
            return datum.target.name
        } else {
            return findParent(datum.target.parent)
        }
    }

// Toggle children on click.
function click(d) {
  if (d.hasOwnProperty('pageNumber')) {
    d.parent.children = d.parent.pages[d.pageNumber];
  } else if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}
Chandan
  • The problem was type was being assigned learning_event as other nodes in the same level so I changed the type to page-toggle-node.
  • For Select2 I have modified it in option-group for all the nodes and adjusted the search functionality accordingly
  • And there is a problem you have used match to compare text which would fail for the case when LE1 is compared to LE15 it would return true as LE15 contains LE1.
var treeData = {"name":"Program","column_to_sort_by":null,"type":"program","children":[{"name":"ProgramGroup1","column_to_sort_by":null,"type":"program_group","children":[{"name":"POGroup1","column_to_sort_by":null,"type":"1program_outcome_group","children":[{"name":"PO1","column_to_sort_by":null,"type":"program_outcome","children":[{"name":"Unit1","column_to_sort_by":"Unit1","children":[{"name":"UG1-LE","column_to_sort_by":null,"type":"unit_group","children":[{"name":"LE1","column_to_sort_by":"LE1","type":"learning_event"},{"name":"LE10","column_to_sort_by":"LE10","type":"learning_event"},{"name":"LE11","column_to_sort_by":"LE11","type":"learning_event"},{"name":"LE12","column_to_sort_by":"LE12","type":"learning_event"},{"name":"LE13","column_to_sort_by":"LE13","type":"learning_event"},{"name":"LE14","column_to_sort_by":"LE14","type":"learning_event"},{"name":"LE15","column_to_sort_by":"LE15","type":"learning_event"},{"name":"LE2","column_to_sort_by":"LE2","type":"learning_event"},{"name":"LE4","column_to_sort_by":"LE4","type":"learning_event"},{"name":"LE5","column_to_sort_by":"LE5","type":"learning_event"},{"name":"LE6","column_to_sort_by":"LE6","type":"learning_event"},{"name":"LE7","column_to_sort_by":"LE7","type":"learning_event"},{"name":"LE8","column_to_sort_by":"LE8","type":"learning_event"},{"name":"LE9","column_to_sort_by":"LE9","type":"learning_event"}]},{"name":"UG1-Assessments","column_to_sort_by":null,"type":"unit_group","children":[{"name":"ASST1","column_to_sort_by":"ASST1","type":"assessment"},{"name":"ASST10","column_to_sort_by":"ASST10","type":"assessment"},{"name":"ASST11","column_to_sort_by":"ASST11","type":"assessment"},{"name":"ASST13","column_to_sort_by":"ASST13","type":"assessment"},{"name":"ASST14","column_to_sort_by":"ASST14","type":"assessment"},{"name":"ASST15","column_to_sort_by":"ASST15","type":"assessment"},{"name":"ASST2","column_to_sort_by":"ASST2","type":"assessment"},{"name":"ASST3","column_to_sort_by":"ASST3","type":"assessment"},{"name":"ASST4","column_to_sort_by":"ASST4","type":"assessment"},{"name":"ASST5","column_to_sort_by":"ASST5","type":"assessment"},{"name":"ASST6","column_to_sort_by":"ASST6","type":"assessment"},{"name":"ASST7","column_to_sort_by":"ASST7","type":"assessment"},{"name":"ASST8","column_to_sort_by":"ASST8","type":"assessment"},{"name":"ASST9","column_to_sort_by":"ASST9","type":"assessment"}]}],"type":"unit"}]},{"name":"PO2","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO3","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO4","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO5","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO6","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO7","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO8","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO9","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO10","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO11","column_to_sort_by":null,"type":"program_outcome"}]},{"name":"POGroup2","column_to_sort_by":null,"type":"1program_outcome_group"}]},{"name":"ProgramGroup2","column_to_sort_by":null,"type":"program_group"}]};

//===============================================
function searchTree(d) {
  if (d.pages) {
    const pageKeys = Object.keys(d.pages)
    Object.keys(d.pages).forEach(page => {
      let currentNodes = d.pages[page];
      currentNodes.forEach((node, idx) => {
        if (node.name.toLowerCase() == searchText.toLowerCase()) {
          currentNodes[idx]['parent'] = d.children[0].parent;
          d.children = currentNodes;
        }
      })
    });
  }

  if (d.children)
    d.children.forEach(searchTree);
  else if (d._children)
    d._children.forEach(searchTree);
  var searchFieldValue = eval(searchField);
  if (searchFieldValue && searchFieldValue.toLowerCase().match(searchText.toLowerCase())) {
    // Walk parent chain
    var ancestors = [];
    var parent = d;
    while (typeof(parent) !== "undefined") {
      ancestors.push(parent);
      //console.https://jsfiddle.net/qyn5fd0t/#runlog(parent);
      parent.class = "found";
      parent = parent.parent;
    }
    // console.log(ancestors);
  }
}

//===============================================
function clearAll(d) {
  d.class = "";
  if (d.children)
    d.children.forEach(clearAll);
  else if (d._children)
    d._children.forEach(clearAll);
}
//===============================================
function collapse(d) {

  if (d.children) {
    d._children = d.children;
    //set the parent object in all the children
    d._children.forEach(function(d1) {
      d1.parent = d;
      collapse(d1);
    });
    d.children = null;
  }
}
//===============================================
function collapseAllNotFound(d) {
  if (d.children) {
    if (d.class !== "found") {
      d._children = d.children;
      d._children.forEach(collapseAllNotFound);
      d.children = null;
    } else
      d.children.forEach(collapseAllNotFound);
  }
}
//===============================================
function expandAll(d) {
  if (d._children) {
    d.children = d._children;
    d.children.forEach(expandAll);
    d._children = null;
  } else if (d.children)
    d.children.forEach(expandAll);
}

//===============================================
// Toggle children on click.
function toggle(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  clearAll(root);
  update(d);
  $("#search").select2("val", "");
}

//===============================================
$("#search").on("select2-selecting", function(e) {
  clearAll(root);
  expandAll(root);
  update(root);
  searchField = "d.name";
  searchText = e.object.text;
  searchTree(root);
  root.children.forEach(collapseAllNotFound);
  update(root);
})

var colourScale = d3.scale.ordinal()
  .domain(["Program", "ProgramGroup1", "ProgramGroup2"])
  .range(["#abacab", "#b67a4e", "#5a6fbb"])

var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120
  },
  width = 2000 - margin.right - margin.left,
  height = 500 - margin.top - margin.bottom;

var i = 0,
  duration = 750,
  root;

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

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

var svg = d3.select("body").append("svg")
  .attr("width", width + margin.right + margin.left)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = treeData;
root.x0 = height / 2;
root.y0 = 0;

function pageNodes(d, options) {
  if (d.children) {
    d.children.forEach(c => pageNodes(c, options));
    if (options.isPageable && options.isPageable(d) && d.children.length > options.maxNode) {
      d.pages = {}
      const count = options.maxNode - 2;
      const l = Math.ceil(d.children.length / count);
      for (let i = 0; i < l; i++) {
        const startRange = i * count;
        const endRange = i * count + count;
        let pageNumber = i == 0 ? l - 1 : i - 1;
        d.pages[i] = d.children.slice(startRange, endRange);
        options.page && options.page(d.pages[i]);
        d.pages[i].unshift({
          ...d.pages[i][0],
          data: {
            name: options.getLabel ? options.getLabel(pageNumber) : "..."
          },
          pageNumber,
          name: "...",
          type: "page-toggle-node"
        })

        // console.log(i, d.pages[i]);
        pageNumber = i != (l - 1) ? i + 1 : 0;
        d.pages[i].push({
          ...d.pages[i][0],
          data: {
            name: options.getLabel ? options.getLabel(pageNumber) : "..."
          },
          pageNumber,
          name: "...",
          type: "page-toggle-node"
        });
      }
      d.children = d.pages[0];
    }
  }
}

//===============================================
const groupLearningOptions = [];
let groupLearningOptionsCounter = 0;
root.children.forEach(c => pageNodes(c, {
  maxNode: 8,
  isPageable: function(d) {
    return d.type == "unit_group";
  },
  page: function(nodes) {
    if (nodes[0].type == "learning_event") {
      groupLearningOptions.push({
        text: `Group ${groupLearningOptions.length + 1}`,
        children: nodes.map(node => ({
          id: groupLearningOptionsCounter++,
          text: node.name
        }))
        .sort((a, b) => (a.text > b.text) ? 1 : -1)
      })
    }
  }
}));
// console.log(groupLearningOptions);

$("#search").select2({
  placeholder: "Select a Learning Event...",
  data: {
    results: groupLearningOptions
  },
  containerCssClass: "search"
});


function collapse(d) {
  if (d.children) {
    d._children = d.children;
    d._children.forEach(collapse);
    d.children = null;
  }
}

root.children.forEach(collapse);
update(root);

//svg.style("height", "500px");

function update(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.
  var nodeEnter = node.enter().append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click", click);

  nodeEnter.append("circle")
    .attr("r", 1e-6)
    .attr('stroke', function(d) {
      return d.color ? d.color : 'blue';
    })
    .style("fill", function(d) {
      return d._children ? "#ccc" : "#fff";
    });

  nodeEnter.append("text")
    .attr("x", function(d) {
      return d.children || d._children ? -13 : 13;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", function(d) {
      return d.children || d._children ? "end" : "start";
    })
    .text(function(d) {
      return d.name;
    })
    .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  nodeUpdate.select("circle")
    .attr("r", 6.5)
    .attr("fill-opacity", "0.7")
    .attr("stroke-opacity", "1")
    .style("fill", function(d) {
      if (d.class !== "found") {
        if (d.type == "learning_event") return "#6eb7e3";
        if (d.type == "assessment") return "#4ecc44";
      }
      if (d.class === "found") {
        return "#ff4136"; //red
      } else {
        return (typeof d._children !== 'undefined') ? (colourScale(findParent(d))) : '#FFF';

      }
    })
    .style("stroke", function(d) {
      if (d.class !== "found") {
        if (d.type == "learning_event") return "#6eb7e3";
        if (d.type == "assessment") return "#4ecc44";
        if (d.name.match(/Learning Events/)) return "#6eb7e3";
        if (d.name.match(/Assessments/)) return "#4ecc44";
      }
      if (d.class === "found") {
        return "#ff4136"; //red
      } else {
        return colourScale(findParent(d));
      }
    });



  nodeUpdate.select("text")
    .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r", 1e-6);

  nodeExit.select("text")
    .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
    .data(links, function(d) {
      return d.target.id;
    });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("d", function(d) {
      var o = {
        x: source.x0,
        y: source.y0
      };
      return diagonal({
        source: o,
        target: o
      });
    });

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", function(d) {
      var o = {
        x: source.x,
        y: source.y
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

function findParent(datum) {
  if (datum.depth < 2) {
    return datum.name
  } else {
    return findParent(datum.parent)
  }
}

function findParentLinks(datum) {
  if (datum.target.depth < 2) {
    return datum.target.name
  } else {
    return findParent(datum.target.parent)
  }
}

// Toggle children on click.
function click(d) {
  if (d.hasOwnProperty('pageNumber')) {
    d.parent.children = d.parent.pages[d.pageNumber];
  } else if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}

Here working example

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related