Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use dTree in ReactJS? #108

Open
nishanttomar21 opened this issue Jul 16, 2020 · 4 comments
Open

How to use dTree in ReactJS? #108

nishanttomar21 opened this issue Jul 16, 2020 · 4 comments

Comments

@nishanttomar21
Copy link

No description provided.

@souravk-sdei
Copy link

For react

import React, { useEffect } from 'react'
import dTree from './DTreeLib';

function DTree({ treeData,onNode }) {

const tree = React.createRef()

useEffect(()=>{
dTree.init(treeData, {
target: tree.current,
debug: true,
height: 500,
width: 1200,
nodeWidth:150,
callbacks: {
nodeClick: function(name, extra) {
console.log(name);
// alert('name')
onNode()
},
textRenderer: function(name, extra, textClass) {
// THis callback is optinal but can be used to customize
// how the text is rendered without having to rewrite the entire node
// from screatch.
if (extra && extra.nickname)
name = name + " (" + extra.nickname + ")";
let container = "

"+
"
"+
"
"+
<img src=${require('assets/images/crossfill.png')} class='small-icon pointer' />+
""+
"
"+
"

" + name + "

"

          return container;
      },
      nodeRenderer: function(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer) {
        // This callback is optional but can be used to customize the
        // node element using HTML.
        let node = '';
        node += '<div ';
        node += 'style="height:100%;width:100%;" ';
        node += 'class="node" ';
        node += 'id="node' + id + '">\n';
        node += textRenderer(name, extra, textClass);
        node += textRenderer(name, extra, textClass);
        node += '</div>';
        return node;
    },
    styles: {
        node: 'node',
        linage: 'linage',
        marriage: 'marriage',
        text: 'nodeText'
      }
    }
  });

},[])
function addNode() {
alert('adding node')
}
return (


)
}

export default DTree

@souravk-sdei
Copy link

var _ = require('lodash')
var d3 = require('d3v4')
var define = require('define')
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.dTree = factory();
})(this, function () {
'use strict';

var TreeBuilder = (function () {
function TreeBuilder(root, siblings, opts) {
_classCallCheck(this, TreeBuilder);

  TreeBuilder.DEBUG_LEVEL = opts.debug ? 1 : 0;

  this.root = root;
  this.siblings = siblings;
  this.opts = opts;

  // flatten nodes
  this.allNodes = this._flatten(this.root);

  // calculate node sizes
  this.nodeSize = opts.callbacks.nodeSize.call(this,
  // filter hidden and marriage nodes
  _.filter(this.allNodes, function (node) {
    return !(node.hidden || _.get(node, 'data.isMarriage'));
  }), opts.nodeWidth, opts.callbacks.textRenderer);
  this.marriageSize = opts.callbacks.marriageSize.call(this,
  // filter hidden and non marriage nodes
  _.filter(this.allNodes, function (node) {
    return !node.hidden && _.get(node, 'data.isMarriage');
  }), this.opts.marriageNodeSize);
}

_createClass(TreeBuilder, [{
  key: 'create',
  value: function create() {

    var opts = this.opts;
    var allNodes = this.allNodes;
    var nodeSize = this.nodeSize;

    var width = opts.width + opts.margin.left + opts.margin.right;
    var height = opts.height + opts.margin.top + opts.margin.bottom;

    // create zoom handler
    var zoom = this.zoom = d3.zoom().scaleExtent([0.1, 10]).on('zoom', function () {
      g.attr('transform', d3.event.transform);
    });

    // make a svg
    var svg = this.svg = d3.select(opts.target).append('svg').attr('viewBox', [0, 0, width, height]).call(zoom);

    // create svg group that holds all nodes
    var g = this.g = svg.append('g');

    // set zoom identity
    svg.call(zoom.transform, d3.zoomIdentity.translate(width / 2, opts.margin.top).scale(1));

    // Compute the layout.
    this.tree = d3.tree().nodeSize([nodeSize[0] * 2, opts.callbacks.nodeHeightSeperation.call(this, nodeSize[0], nodeSize[1])]);

    this.tree.separation(function separation(a, b) {
      if (a.data.hidden || b.data.hidden) {
        return 0.3;
      } else {
        return 0.6;
      }
    });

    this._update(this.root);
  }
}, {
  key: '_update',
  value: function _update(source) {

    var opts = this.opts;
    var allNodes = this.allNodes;
    var nodeSize = this.nodeSize;
    var marriageSize = this.marriageSize;

    var treenodes = this.tree(source);
    var links = treenodes.links();

    // Create the link lines.
    this.g.selectAll('.link').data(links).enter()
    // filter links with no parents to prevent empty nodes
    .filter(function (l) {
      return !l.target.data.noParent;
    }).append('path').attr('class', opts.styles.linage).attr('d', this._elbow);

    var nodes = this.g.selectAll('.node').data(treenodes.descendants()).enter();

    this._linkSiblings();

    // Draw siblings (marriage)
    this.g.selectAll('.sibling').data(this.siblings).enter().append('path').attr('class', opts.styles.marriage).attr('d', _.bind(this._siblingLine, this));

    // Create the node rectangles.
    nodes.append('foreignObject').filter(function (d) {
      return d.data.hidden ? false : true;
    }).attr('x', function (d) {
      return Math.round(d.x - d.cWidth / 2) + 'px';
    }).attr('y', function (d) {
      return Math.round(d.y - d.cHeight / 2) + 'px';
    }).attr('width', function (d) {
      return d.cWidth + 'px';
    }).attr('height', function (d) {
      return d.cHeight + 'px';
    }).attr('id', function (d) {
      return d.id;
    }).html(function (d) {
      if (d.data.isMarriage) {
        return opts.callbacks.marriageRenderer.call(this, d.x, d.y, marriageSize[0], marriageSize[1], d.data.extra, d.data.id, d.data['class']);
      } else {
        return opts.callbacks.nodeRenderer.call(this, d.data.name, d.x, d.y, nodeSize[0], nodeSize[1], d.data.extra, d.data.id, d.data['class'], d.data.textClass, opts.callbacks.textRenderer);
      }
    }).on('dblclick', function () {
      // do not propagate a double click on a node
      // to prevent the zoom from being triggered
      d3.event.stopPropagation();
    }).on('click', function (d) {
      // ignore double-clicks and clicks on hidden nodes
      if (d3.event.detail === 2 || d.data.hidden) {
        return;
      }
      if (d.data.isMarriage) {
        opts.callbacks.marriageClick.call(this, d.data.extra, d.data.id);
      } else {
        opts.callbacks.nodeClick.call(this, d.data.name, d.data.extra, d.data.id);
      }
    }).on('contextmenu', function (d) {
      if (d.data.hidden) {
        return;
      }
      d3.event.preventDefault();
      if (d.data.isMarriage) {
        opts.callbacks.marriageRightClick.call(this, d.data.extra, d.data.id);
      } else {
        opts.callbacks.nodeRightClick.call(this, d.data.name, d.data.extra, d.data.id);
      }
    });
  }
}, {
  key: '_flatten',
  value: function _flatten(root) {
    var n = [];
    var i = 0;

    function recurse(node) {
      if (node.children) {
        node.children.forEach(recurse);
      }
      if (!node.id) {
        node.id = ++i;
      }
      n.push(node);
    }
    recurse(root);
    return n;
  }
}, {
  key: '_elbow',
  value: function _elbow(d, i) {
    if (d.target.data.noParent) {
      return 'M0,0L0,0';
    }
    var ny = Math.round(d.target.y + (d.source.y - d.target.y) * 0.50);

    var linedata = [{
      x: d.target.x,
      y: d.target.y
    }, {
      x: d.target.x,
      y: ny
    }, {
      x: d.source.x,
      y: d.source.y
    }];

    var fun = d3.line().curve(d3.curveStepAfter).x(function (d) {
      return d.x;
    }).y(function (d) {
      return d.y;
    });
    return fun(linedata);
  }
}, {
  key: '_linkSiblings',
  value: function _linkSiblings() {

    var allNodes = this.allNodes;

    _.forEach(this.siblings, function (d) {
      var start = allNodes.filter(function (v) {
        return d.source.id == v.data.id;
      });
      var end = allNodes.filter(function (v) {
        return d.target.id == v.data.id;
      });
      d.source.x = start[0].x;
      d.source.y = start[0].y;
      d.target.x = end[0].x;
      d.target.y = end[0].y;

      var marriageId = start[0].data.marriageNode != null ? start[0].data.marriageNode.id : end[0].data.marriageNode.id;
      var marriageNode = allNodes.find(function (n) {
        return n.data.id == marriageId;
      });
      d.source.marriageNode = marriageNode;
      d.target.marriageNode = marriageNode;
    });
  }
}, {
  key: '_siblingLine',
  value: function _siblingLine(d, i) {

    var ny = Math.round(d.target.y + (d.source.y - d.target.y) * 0.50);
    var nodeWidth = this.nodeSize[0];
    var nodeHeight = this.nodeSize[1];

    // Not first marriage
    if (d.number > 0) {
      ny -= Math.round(nodeHeight * 8 / 10);
    }

    var linedata = [{
      x: d.source.x,
      y: d.source.y
    }, {
      x: Math.round(d.source.x + nodeWidth * 6 / 10),
      y: d.source.y
    }, {
      x: Math.round(d.source.x + nodeWidth * 6 / 10),
      y: ny
    }, {
      x: d.target.marriageNode.x,
      y: ny
    }, {
      x: d.target.marriageNode.x,
      y: d.target.y
    }, {
      x: d.target.x,
      y: d.target.y
    }];

    var fun = d3.line().curve(d3.curveStepAfter).x(function (d) {
      return d.x;
    }).y(function (d) {
      return d.y;
    });
    return fun(linedata);
  }
}], [{
  key: '_nodeHeightSeperation',
  value: function _nodeHeightSeperation(nodeWidth, nodeMaxHeight) {
    return nodeMaxHeight + 25;
  }
}, {
  key: '_nodeSize',
  value: function _nodeSize(nodes, width, textRenderer) {
    var maxWidth = 0;
    var maxHeight = 0;
    var tmpSvg = document.createElement('svg');
    document.body.appendChild(tmpSvg);

    _.map(nodes, function (n) {
      var container = document.createElement('div');
      container.setAttribute('class', n.data['class']);
      container.style.visibility = 'hidden';
      container.style.maxWidth = width + 'px';

      var text = textRenderer(n.data.name, n.data.extra, n.data.textClass);
      container.innerHTML = text;

      tmpSvg.appendChild(container);
      var height = container.offsetHeight;
      tmpSvg.removeChild(container);

      maxHeight = Math.max(maxHeight, height);
      n.cHeight = height;
      if (n.data.hidden) {
        n.cWidth = 0;
      } else {
        n.cWidth = width;
      }
    });
    document.body.removeChild(tmpSvg);

    return [width, maxHeight];
  }
}, {
  key: '_marriageSize',
  value: function _marriageSize(nodes, size) {
    _.map(nodes, function (n) {
      if (!n.data.hidden) {
        n.cHeight = size;
        n.cWidth = size;
      }
    });

    return [size, size];
  }
}, {
  key: '_nodeRenderer',
  value: function _nodeRenderer(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer) {
    var node = '';
    node += '<div ';
    node += 'style="height:100%;width:100%;" ';
    node += 'class="' + nodeClass + '" ';
    node += 'id="node' + id + '">\n';
    node += textRenderer(name, extra, textClass);
    node += '</div>';
    return node;
  }
}, {
  key: '_textRenderer',
  value: function _textRenderer(name, extra, textClass) {
    var node = '';
    node += '<p ';
    node += 'align="center" ';
    node += 'class="' + textClass + '">\n';
    node += name;
    node += '</p>\n';
    return node;
  }
}, {
  key: '_marriageRenderer',
  value: function _marriageRenderer(x, y, height, width, extra, id, nodeClass) {
    return '<div style="height:100%" class="' + nodeClass + '" id="node' + id + '"></div>';
  }
}, {
  key: '_debug',
  value: function _debug(msg) {
    if (TreeBuilder.DEBUG_LEVEL > 0) {
      console.log(msg);
    }
  }
}]);

return TreeBuilder;

})();

var dTree = {

VERSION: '2.4.1',

init: function init(data) {
  var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

  var opts = _.defaultsDeep(options || {}, {
    target: '#graph',
    debug: false,
    width: 600,
    height: 600,
    hideMarriageNodes: true,
    callbacks: {
      nodeClick: function nodeClick(name, extra, id) {},
      nodeRightClick: function nodeRightClick(name, extra, id) {},
      marriageClick: function marriageClick(extra, id) {},
      marriageRightClick: function marriageRightClick(extra, id) {},
      nodeHeightSeperation: function nodeHeightSeperation(nodeWidth, nodeMaxHeight) {
        return TreeBuilder._nodeHeightSeperation(nodeWidth, nodeMaxHeight);
      },
      nodeRenderer: function nodeRenderer(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer) {
        return TreeBuilder._nodeRenderer(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer);
      },
      nodeSize: function nodeSize(nodes, width, textRenderer) {
        return TreeBuilder._nodeSize(nodes, width, textRenderer);
      },
      nodeSorter: function nodeSorter(aName, aExtra, bName, bExtra) {
        return 0;
      },
      textRenderer: function textRenderer(name, extra, textClass) {
        return TreeBuilder._textRenderer(name, extra, textClass);
      },
      marriageRenderer: function marriageRenderer(x, y, height, width, extra, id, nodeClass) {
        return TreeBuilder._marriageRenderer(x, y, height, width, extra, id, nodeClass);
      },
      marriageSize: function marriageSize(nodes, size) {
        return TreeBuilder._marriageSize(nodes, size);
      }
    },
    margin: {
      top: 0,
      right: 0,
      bottom: 0,
      left: 0
    },
    nodeWidth: 100,
    nodeHeight:100,
    marriageNodeSize: 10,
    styles: {
      node: 'node',
      marriageNode: 'marriageNode',
      linage: 'linage',
      marriage: 'marriage',
      text: 'nodeText'
    }
  });

  var data = this._preprocess(data, opts);
  var treeBuilder = new TreeBuilder(data.root, data.siblings, opts);
  treeBuilder.create();

  function _zoomTo(x, y) {
    var zoom = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2];
    var duration = arguments.length <= 3 || arguments[3] === undefined ? 500 : arguments[3];

    treeBuilder.svg.transition().duration(duration).call(treeBuilder.zoom.transform, d3.zoomIdentity.translate(opts.width / 2, opts.height / 2).scale(zoom).translate(-x, -y));
  }

  return {
    resetZoom: function resetZoom() {
      var duration = arguments.length <= 0 || arguments[0] === undefined ? 500 : arguments[0];

      treeBuilder.svg.transition().duration(duration).call(treeBuilder.zoom.transform, d3.zoomIdentity.translate(opts.width / 2, opts.margin.top).scale(1));
    },
    zoomTo: _zoomTo,
    zoomToNode: function zoomToNode(nodeId) {
      var zoom = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1];
      var duration = arguments.length <= 2 || arguments[2] === undefined ? 500 : arguments[2];

      var node = _.find(treeBuilder.allNodes, { data: { id: nodeId } });
      if (node) {
        _zoomTo(node.x, node.y, zoom, duration);
      }
    },
    zoomToFit: function zoomToFit() {
      var duration = arguments.length <= 0 || arguments[0] === undefined ? 500 : arguments[0];

      var groupBounds = treeBuilder.g.node().getBBox();
      var width = groupBounds.width;
      var height = groupBounds.height;
      var fullWidth = treeBuilder.svg.node().clientWidth;
      var fullHeight = treeBuilder.svg.node().clientHeight;
      var scale = 0.95 / Math.max(width / fullWidth, height / fullHeight);

      treeBuilder.svg.transition().duration(duration).call(treeBuilder.zoom.transform, d3.zoomIdentity.translate(fullWidth / 2 - scale * (groupBounds.x + width / 2), fullHeight / 2 - scale * (groupBounds.y + height / 2)).scale(scale));
    }
  };
},

_preprocess: function _preprocess(data, opts) {

  var siblings = [];
  var id = 0;

  var root = {
    name: '',
    id: id++,
    hidden: true,
    children: []
  };

  var reconstructTree = function reconstructTree(person, parent) {

    // convert to person to d3 node
    var node = {
      name: person.name,
      id: id++,
      hidden: false,
      children: [],
      extra: person.extra,
      textClass: person.textClass ? person.textClass : opts.styles.text,
      'class': person['class'] ? person['class'] : opts.styles.node
    };

    // hide linages to the hidden root node
    if (parent == root) {
      node.noParent = true;
    }

    // apply depth offset
    for (var i = 0; i < person.depthOffset; i++) {
      var pushNode = {
        name: '',
        id: id++,
        hidden: true,
        children: [],
        noParent: node.noParent
      };
      parent.children.push(pushNode);
      parent = pushNode;
    }

    // sort children
    dTree._sortPersons(person.children, opts);

    // add "direct" children
    _.forEach(person.children, function (child) {
      reconstructTree(child, node);
    });

    parent.children.push(node);

    //sort marriages
    dTree._sortMarriages(person.marriages, opts);

    // go through marriage
    _.forEach(person.marriages, function (marriage, index) {
      var m = {
        name: '',
        id: id++,
        hidden: opts.hideMarriageNodes,
        noParent: true,
        children: [],
        isMarriage: true,
        extra: marriage.extra,
        'class': marriage['class'] ? marriage['class'] : opts.styles.marriageNode
      };

      var sp = marriage.spouse;

      var spouse = {
        name: sp.name,
        id: id++,
        hidden: false,
        noParent: true,
        children: [],
        textClass: sp.textClass ? sp.textClass : opts.styles.text,
        'class': sp['class'] ? sp['class'] : opts.styles.node,
        extra: sp.extra,
        marriageNode: m
      };

      parent.children.push(m, spouse);

      dTree._sortPersons(marriage.children, opts);
      _.forEach(marriage.children, function (child) {
        reconstructTree(child, m);
      });

      siblings.push({
        source: {
          id: node.id
        },
        target: {
          id: spouse.id
        },
        number: index
      });
    });
  };

  _.forEach(data, function (person) {
    reconstructTree(person, root);
  });

  return {
    root: d3.hierarchy(root),
    siblings: siblings
  };
},

_sortPersons: function _sortPersons(persons, opts) {
  if (persons != undefined) {
    persons.sort(function (a, b) {
      return opts.callbacks.nodeSorter.call(this, a.name, a.extra, b.name, b.extra);
    });
  }
  return persons;
},

_sortMarriages: function _sortMarriages(marriages, opts) {
  if (marriages != undefined && Array.isArray(marriages)) {
    marriages.sort(function (marriageA, marriageB) {
      var a = marriageA.spouse;
      var b = marriageB.spouse;
      return opts.callbacks.nodeSorter.call(this, a.name, a.extra, b.name, b.extra);
    });
  }
  return marriages;
}

};

return dTree;
});
//# sourceMappingURL=dTree.js.map

@PhpGuyZim
Copy link

PhpGuyZim commented Jan 22, 2022

Simple, minimalist solution that separates concerns:

//in src/App.js
import React, {useEffect} from 'react';
import dTree from 'd3-dtree';
const FamTree =() => {
useEffect(() => {
let treeData = [{
name: "Father",
depthOffset: 1,
marriages: [{
spouse: {
name: "Mother",
},
children: [{
name: "Child",
}]
}],
extra: {}
}];

dTree.init(treeData, {target: "#graph", height:800, width: 1200, debug:true});

}, []);
return (




);
}

export default FamTree;

THEN

//in your public/index.html add this:

<script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script> <style> body { font: 10px sans-serif; } .linage { fill: none; stroke: #000; }
.marriage {
    fill: none;
    stroke: black;
}

.man {
    background-color: lightblue;
    border-style: solid;
    border-width: 1px;
    box-sizing: border-box;
}

.woman {
    background-color: pink;
    border-style: solid;
    border-width: 1px;
    box-sizing: border-box;
}

.emphasis {
    font-style: italic;
}

p {
    padding: 0;
    margin: 0;
}

svg {
    border-style: solid;
    border-width: 1px;
}
</style>

.... rest of the React defaults here

Tested - works.

@noszone
Copy link

noszone commented Jul 22, 2022

Simple, minimalist solution that separates concerns:

//in src/App.js import React, {useEffect} from 'react'; import dTree from 'd3-dtree'; const FamTree =() => { useEffect(() => { let treeData = [{ name: "Father", depthOffset: 1, marriages: [{ spouse: { name: "Mother", }, children: [{ name: "Child", }] }], extra: {} }];

dTree.init(treeData, {target: "#graph", height:800, width: 1200, debug:true});

}, []); return (

);
}
export default FamTree;

THEN

//in your public/index.html add this:

<script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script> <style> body { font: 10px sans-serif; } .linage { fill: none; stroke: #000; }
.marriage {
    fill: none;
    stroke: black;
}

.man {
    background-color: lightblue;
    border-style: solid;
    border-width: 1px;
    box-sizing: border-box;
}

.woman {
    background-color: pink;
    border-style: solid;
    border-width: 1px;
    box-sizing: border-box;
}

.emphasis {
    font-style: italic;
}

p {
    padding: 0;
    margin: 0;
}

svg {
    border-style: solid;
    border-width: 1px;
}
</style>

.... rest of the React defaults here

Tested - works.

Hello, can you please advice, what to return in html? I followed your example, no errors, but no output of tree on page. Guess the component should return somethig like:

return (
<Tree/>
);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants