[
  {
    "path": ".babelrc",
    "content": "{\n  \"blacklist\": [\"useStrict\"]\n}"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: http://EditorConfig.org\n\nroot = true;\n\n[*]\n#  Ensure there's no lingering whitespace\ntrim_trailing_whitespace = true\n# Ensure a newline at the end of each file\ninsert_final_newline = true\n\n[*.js]\n# Unix-style newlines\nend_of_line = lf\ncharset = utf-8\nindent_style = space\nindent_size = 2"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"rules\": {\n    \"strict\": 0,\n    \"quotes\": [2, \"single\"]\n  },\n  \"env\": {\n    \"browser\": true,\n    \"node\": true\n  }\n}"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\n# Commenting this out is preferred by some people, see\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-\nnode_modules\nbower_components\ncoverage\ntmp\n\n# Users Environment Variables\n.lock-wscript\n\n.env\n\n### OSX ###\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\r\r\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n"
  },
  {
    "path": ".jscsrc",
    "content": "{\n  \"preset\": \"google\",\n  \"maximumLineLength\": null\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<a name=\"2.4.1\"></a>\n## [2.4.1](https://github.com/ErikGartner/dtree/compare/2.4.0...v2.4.1) (2020-04-24)\n\n\n\n\n<a name=\"2.4.0\"></a>\n# [2.4.0](https://github.com/ErikGartner/dtree/compare/2.3.0...v2.4.0) (2020-04-24)\n\n\n\n\n<a name=\"2.3.0\"></a>\n# [2.3.0](https://github.com/ErikGartner/dtree/compare/2.2.2...v2.3.0) (2020-04-24)\n\n\n### Features\n\n* **#20:** add feature to zoom/pan to position/node ([eb1bb87](https://github.com/ErikGartner/dtree/commit/eb1bb87))\n* **#20:** add zoomToFit() function ([f57859c](https://github.com/ErikGartner/dtree/commit/f57859c))\n* **#65:** add custom click event for marriage nodes ([b4111d6](https://github.com/ErikGartner/dtree/commit/b4111d6))\n* **#65:** add custom renderer for marriages ([112a375](https://github.com/ErikGartner/dtree/commit/112a375))\n\n\n<a name=\"2.2.2\"></a>\n## [2.2.2](https://github.com/ErikGartner/dtree/compare/2.2.1...v2.2.2) (2019-07-07)\n\n\n### Bug Fixes\n\n* Lower rectangle border thinner than the upper one in Firefox ([748a0c4](https://github.com/ErikGartner/dtree/commit/748a0c4))\n* Rounding issues #92 ([9b70e04](https://github.com/ErikGartner/dtree/commit/9b70e04))\n\n\n\n<a name=\"2.2.1\"></a>\n## [2.2.1](https://github.com/ErikGartner/dtree/compare/2.1.0...v2.2.1) (2019-05-07)\n\n\n\n\n<a name=\"2.1.0\"></a>\n# [2.1.0](https://github.com/ErikGartner/dtree/compare/2.0.2...v2.1.0) (2019-02-23)\n\n\n### Bug Fixes\n\n* Bug in build script to update CDN link ([2525601](https://github.com/ErikGartner/dtree/commit/2525601))\n* Missing borders in dTree demo ([5926c15](https://github.com/ErikGartner/dtree/commit/5926c15))\n\n### Features\n\n* Add border to SVG in demo ([7876de6](https://github.com/ErikGartner/dtree/commit/7876de6))\n* Add callback for node width separation #27 ([027de86](https://github.com/ErikGartner/dtree/commit/027de86))\n* Remove unused test stub code ([938395a](https://github.com/ErikGartner/dtree/commit/938395a))\n* Update dependencies with Yarn ([6a207c6](https://github.com/ErikGartner/dtree/commit/6a207c6))\n\n\n\n<a name=\"2.0.2\"></a>\n## [2.0.2](https://github.com/ErikGartner/dtree/compare/2.0.1...v2.0.2) (2017-05-02)\n\n\n### Bug Fixes\n\n* Incorrect marriages line. #53 ([25fd62e](https://github.com/ErikGartner/dtree/commit/25fd62e))\n\n\n\n<a name=\"2.0.1\"></a>\n## [2.0.1](https://github.com/ErikGartner/dtree/compare/2.0.0...v2.0.1) (2017-04-10)\n\n\n### Bug Fixes\n\n* Missing parameters to click callback #50 ([fb00d8f](https://github.com/ErikGartner/dtree/commit/fb00d8f))\n\n\n\n<a name=\"2.0.0\"></a>\n# [2.0.0](https://github.com/ErikGartner/dtree/compare/1.3.2...v2.0.0) (2017-03-09)\n\n\n### Bug Fixes\n\n* upgrade to d3 v4 ([d63d003](https://github.com/ErikGartner/dtree/commit/d63d003))\n\n### Features\n\n* Deprecated marriage attribute. Use marriages instead now. #26 ([096b90f](https://github.com/ErikGartner/dtree/commit/096b90f))\n\n\n\n<a name=\"1.3.2\"></a>\n## [1.3.2](https://github.com/ErikGartner/dtree/compare/1.3.1...v1.3.2) (2016-07-21)\n\n\n\n\n<a name=\"1.3.1\"></a>\n## [1.3.1](https://github.com/ErikGartner/dtree/compare/1.3.0...v1.3.1) (2016-03-08)\n\n\n\n\n<a name=\"1.3.0\"></a>\n# [1.3.0](https://github.com/ErikGartner/dtree/compare/1.2.1...v1.3.0) (2016-03-08)\n\n\n### Features\n\n* Further improvement to height calculation ([2171995](https://github.com/ErikGartner/dtree/commit/2171995))\n\n\n\n<a name=\"1.2.1\"></a>\n## [1.2.1](https://github.com/ErikGartner/dtree/compare/1.2.0...v1.2.1) (2016-03-08)\n\n\n### Bug Fixes\n\n* Proper height calculation #29 ([282fae5](https://github.com/ErikGartner/dtree/commit/282fae5))\n\n\n\n<a name=\"1.2.0\"></a>\n# [1.2.0](https://github.com/ErikGartner/dtree/compare/1.1.0...v1.2.0) (2016-01-14)\n\n\n### Features\n\n* Update to lodash 4.0.0 ([bd8451d](https://github.com/ErikGartner/dtree/commit/bd8451d))\n\n\n\n<a name=\"1.1.0\"></a>\n# [1.1.0](https://github.com/ErikGartner/dtree/compare/1.0.0...v1.1.0) (2016-01-11)\n\n\n### Features\n\n* Add support for multiple marriages #25 ([8795a5e](https://github.com/ErikGartner/dtree/commit/8795a5e))\n\n\n\n<a name=\"1.0.0\"></a>\n# [1.0.0](https://github.com/ErikGartner/dtree/compare/0.8.1...v1.0.0) (2016-01-10)\n\n\n\n\n<a name=\"0.8.1\"></a>\n## [0.8.1](https://github.com/ErikGartner/dtree/compare/0.8.0...v0.8.1) (2016-01-05)\n\n\n### Bug Fixes\n\n* Bug due improper skipping of hidden nodes ([9739472](https://github.com/ErikGartner/dtree/commit/9739472))\n\n\n\n<a name=\"0.8.0\"></a>\n# [0.8.0](https://github.com/ErikGartner/dtree/compare/0.7.6...v0.8.0) (2016-01-05)\n\n\n### Features\n\n* Individual height of nodes depending of content ([6d9aa8a](https://github.com/ErikGartner/dtree/commit/6d9aa8a))\n\n\n\n<a name=\"0.7.6\"></a>\n## [0.7.6](https://github.com/ErikGartner/dtree/compare/0.7.5...v0.7.6) (2016-01-04)\n\n\n\n\n<a name=\"0.7.5\"></a>\n## [0.7.5](https://github.com/ErikGartner/dtree/compare/0.7.4...v0.7.5) (2016-01-04)\n\n\n\n\n<a name=\"0.7.4\"></a>\n## [0.7.4](https://github.com/ErikGartner/dtree/compare/0.7.3...v0.7.4) (2016-01-04)\n\n\n\n\n<a name=\"0.7.3\"></a>\n## [0.7.3](https://github.com/ErikGartner/dtree/compare/0.7.2...v0.7.3) (2016-01-04)\n\n\n\n\n<a name=\"0.7.2\"></a>\n## [0.7.2](https://github.com/ErikGartner/dtree/compare/0.7.1...v0.7.2) (2016-01-04)\n\n\n\n\n<a name=\"0.7.1\"></a>\n## [0.7.1](https://github.com/ErikGartner/dtree/compare/0.7.0...v0.7.1) (2016-01-04)\n\n\n\n\n<a name=\"0.7.0\"></a>\n# [0.7.0](https://github.com/ErikGartner/dtree/compare/0.6.1...v0.7.0) (2016-01-04)\n\n\n### Features\n\n* Resizing nodes ([2454086](https://github.com/ErikGartner/dtree/commit/2454086))\n\n\n\n<a name=\"0.6.1\"></a>\n## [0.6.1](https://github.com/ErikGartner/dtree/compare/0.6.0...v0.6.1) (2016-01-02)\n\n\n\n\n<a name=\"0.6.0\"></a>\n# [0.6.0](https://github.com/ErikGartner/dtree/compare/0.5.0...v0.6.0) (2016-01-02)\n\n\n### Bug Fixes\n\n* Bug in demo ([b1092a4](https://github.com/ErikGartner/dtree/commit/b1092a4))\n\n### Features\n\n* Add depthOffset attribute ([7037257](https://github.com/ErikGartner/dtree/commit/7037257))\n\n\n\n<a name=\"0.5.0\"></a>\n# [0.5.0](https://github.com/ErikGartner/dtree/compare/0.4.0...v0.5.0) (2016-01-01)\n\n\n### Features\n\n* Add custom sorting #7 ([9923c66](https://github.com/ErikGartner/dtree/commit/9923c66))\n\n\n\n<a name=\"0.4.0\"></a>\n# [0.4.0](https://github.com/ErikGartner/dtree/compare/0.3.4...v0.4.0) (2016-01-01)\n\n\n### Features\n\n* Add direct children nodes #19 ([2a37451](https://github.com/ErikGartner/dtree/commit/2a37451))\n\n\n\n<a name=\"0.3.4\"></a>\n## [0.3.4](https://github.com/ErikGartner/dtree/compare/0.3.3...v0.3.4) (2016-01-01)\n\n\n### Bug Fixes\n\n* Another gulp bug ([000165c](https://github.com/ErikGartner/dtree/commit/000165c))\n\n\n\n<a name=\"0.3.3\"></a>\n## [0.3.3](https://github.com/ErikGartner/dtree/compare/0.3.2...v0.3.3) (2016-01-01)\n\n\n### Bug Fixes\n\n* Bug in automatic release ([97cdd26](https://github.com/ErikGartner/dtree/commit/97cdd26))\n\n\n\n<a name=\"0.3.2\"></a>\n## [0.3.2](https://github.com/ErikGartner/dtree/compare/0.3.1...v0.3.2) (2016-01-01)\n\n\n### Features\n\n* Add automatic releasing #10 ([1e3f4b8](https://github.com/ErikGartner/dtree/commit/1e3f4b8))\n\n\n\n<a name=\"0.3.1\"></a>\n## [0.3.1](https://github.com/ErikGartner/dtree/compare/0.3.0...v0.3.1) (2015-12-31)\n\n\n### Bug Fixes\n\n* Build including old dist ([5ca3f2a](https://github.com/ErikGartner/dtree/commit/5ca3f2a))\n\n\n\n<a name=\"0.3.0\"></a>\n# [0.3.0](https://github.com/ErikGartner/dtree/compare/0.2.5...v0.3.0) (2015-12-31)\n\n\n### Bug Fixes\n\n* Center graph on load #11 ([1d5e71c](https://github.com/ErikGartner/dtree/commit/1d5e71c))\n* Jerky zoom start ([ea184b5](https://github.com/ErikGartner/dtree/commit/ea184b5))\n\n### Features\n\n* Add custom html renderers and wrapping #16 #5 #3 ([5f71c8c](https://github.com/ErikGartner/dtree/commit/5f71c8c))\n* Add debug variable ([51ec554](https://github.com/ErikGartner/dtree/commit/51ec554))\n\n\n\n<a name=\"0.2.5\"></a>\n## [0.2.5](https://github.com/ErikGartner/dtree/compare/0.2.4...v0.2.5) (2015-12-30)\n\n\n### Bug Fixes\n\n* Invalid buildstep ([f593bce](https://github.com/ErikGartner/dtree/commit/f593bce))\n\n\n\n<a name=\"0.2.4\"></a>\n## [0.2.4](https://github.com/ErikGartner/dtree/compare/0.2.3...v0.2.4) (2015-12-30)\n\n\n### Features\n\n* Add dist/ directory ([98c3d10](https://github.com/ErikGartner/dtree/commit/98c3d10))\n\n\n\n<a name=\"0.2.3\"></a>\n## [0.2.3](https://github.com/ErikGartner/dtree/compare/0.2.2...v0.2.3) (2015-12-30)\n\n\n\n\n<a name=\"0.2.2\"></a>\n## [0.2.2](https://github.com/ErikGartner/dtree/compare/0.2.1...v0.2.2) (2015-12-30)\n\n\n### Features\n\n* Add custom text function #6 ([2384b01](https://github.com/ErikGartner/dtree/commit/2384b01))\n* Add version to dTree #14 ([ffd9737](https://github.com/ErikGartner/dtree/commit/ffd9737))\n\n\n\n<a name=\"0.2.1\"></a>\n## [0.2.1](https://github.com/ErikGartner/dtree/compare/0.2.0...v0.2.1) (2015-12-29)\n\n\n### Features\n\n* Decrease node seperation ([5ca1cd0](https://github.com/ErikGartner/dtree/commit/5ca1cd0))\n\n\n\n<a name=\"0.2.0\"></a>\n# [0.2.0](https://github.com/ErikGartner/dtree/compare/0.1.4...v0.2.0) (2015-12-27)\n\n\n\n\n<a name=\"0.1.4\"></a>\n## [0.1.4](https://github.com/ErikGartner/dtree/compare/0.1.3...v0.1.4) (2015-12-27)\n\n\n### Bug Fixes\n\n* Bug when setting opts defaults ([d06bc19](https://github.com/ErikGartner/dtree/commit/d06bc19))\n\n### Features\n\n* Add click handler support #8 ([6908bc9](https://github.com/ErikGartner/dtree/commit/6908bc9))\n* Add gulp demo command ([7b01f6d](https://github.com/ErikGartner/dtree/commit/7b01f6d))\n* Improved separation between nodes ([c001db3](https://github.com/ErikGartner/dtree/commit/c001db3))\n\n\n\n<a name=\"0.1.3\"></a>\n## [0.1.3](https://github.com/ErikGartner/dtree/compare/0.1.2...v0.1.3) (2015-12-27)\n\n\n### Bug Fixes\n\n* Bug affecting textClass ([bdfe318](https://github.com/ErikGartner/dtree/commit/bdfe318))\n\n\n\n<a name=\"0.1.2\"></a>\n## [0.1.2](https://github.com/ErikGartner/dtree/compare/0.1.1...v0.1.2) (2015-12-27)\n\n\n### Features\n\n* Add css class override for node text ([21b8677](https://github.com/ErikGartner/dtree/commit/21b8677))\n\n\n\n<a name=\"0.1.1\"></a>\n## [0.1.1](https://github.com/ErikGartner/dtree/compare/0.1.0...v0.1.1) (2015-12-26)\n\n\n### Bug Fixes\n\n* Typo in badge url ([e318bb4](https://github.com/ErikGartner/dtree/commit/e318bb4))\n\n### Features\n\n* Add release tasks to gulp ([027968c](https://github.com/ErikGartner/dtree/commit/027968c))\n\n\n\n<a name=\"0.1.0\"></a>\n# 0.1.0 (2015-12-26)\n\n\n### Bug Fixes\n\n* Duplicates in package.json ([09be9a3](https://github.com/ErikGartner/dtree/commit/09be9a3))\n* Invalid text attribute ([90f6528](https://github.com/ErikGartner/dtree/commit/90f6528))\n* README.md typo ([4851fad](https://github.com/ErikGartner/dtree/commit/4851fad))\n\n### Features\n\n* Add \"extra\" field to nodes ([618b1d7](https://github.com/ErikGartner/dtree/commit/618b1d7))\n* Add zooming ([f4d8aeb](https://github.com/ErikGartner/dtree/commit/f4d8aeb))\n* Automatic box size ([ff38f3f](https://github.com/ErikGartner/dtree/commit/ff38f3f))\n* Changed data format ([1591875](https://github.com/ErikGartner/dtree/commit/1591875))\n* Iteration zero ([30899eb](https://github.com/ErikGartner/dtree/commit/30899eb))\n* Switched core foundation ([ac23c28](https://github.com/ErikGartner/dtree/commit/ac23c28))\n* Update devDeps ([c653f93](https://github.com/ErikGartner/dtree/commit/c653f93))\n* Update options and align text ([9346c2d](https://github.com/ErikGartner/dtree/commit/9346c2d))\n* Use class in data ([085ed25](https://github.com/ErikGartner/dtree/commit/085ed25))\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to dTree\n\n## Commiting\nPlease follow these guidelines.\n\n### Versioning\ndTree uses [SemVer 2.0.0](http://semver.org/spec/v2.0.0.html). That is the versioning is \\<MAJOR\\>.\\<MINOR\\>.\\<PATCH\\>.\n\n### Commit Message Format\nEach commit message consists of a **header**, a **body** and a **footer**.  The header has a special\nformat that includes a **type**, a **scope** and a **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\nAny line of the commit message cannot be longer 100 characters! This allows the message to be easier\nto read on GitHub as well as in various git tools.\n\n### Revert\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\nMust be one of the following:\n\n* **feat**: A new feature\n* **fix**: A bug fix\n* **docs**: Documentation only changes\n* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing\n  semi-colons, etc)\n* **refactor**: A code change that neither fixes a bug nor adds a feature\n* **perf**: A code change that improves performance\n* **test**: Adding missing tests\n* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation\n  generation\n\n### Scope\nThe scope could be anything specifying place of the commit change. For example `$location`,\n`$browser`, `$compile`, `$rootScope`, `ngHref`, `ngClick`, `ngView`, etc...\n\n### Subject\nThe subject contains succinct description of the change:\n\n* use the imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n* don't capitalize first letter\n* no dot (.) at the end\n\n### Body\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\nThe body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\nThe footer should contain any information about **Breaking Changes** and is also the place to\nreference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n\n## Recognitions\nThese guidelines are forked from Angular.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Erik Gärtner\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# dTree\n*A library for visualizing data trees with multiple parents built on top of [D3](https://github.com/mbostock/d3).*\n\n[![npm](https://img.shields.io/npm/v/d3-dtree.svg)](https://www.npmjs.com/package/d3-dtree) [![Bower](https://img.shields.io/bower/v/d3-dtree.svg)](https://github.com/ErikGartner/dTree) [![](https://data.jsdelivr.com/v1/package/npm/d3-dtree/badge?style=rounded)](https://www.jsdelivr.com/package/npm/d3-dtree)\n\n**Using dTree? Send me a message with a link to your website to be listed below.**\n\n## The Online Viewer\nThere exists an online viewer for dTree graphs called [Treehouse](https://treehouse.gartner.io), similar to [https://bl.ocks.org/](https://bl.ocks.org/) for D3. Treehouse allows anybody to host a dTree graph without having to create a website or interact directly with the library. It fetches data from Github's gists and displays it in a nice format. All graphs are unlisted so without your Gist ID nobody else can view them. Checkout the *demo* graph for dTree:\n\nhttps://treehouse.gartner.io/ErikGartner/58e58be650453b6d49d7\n\nThe same demo is also available on [JSFiddle](https://jsfiddle.net/rha8sg79/).\n\n## Installation\nThere are several ways to use dTree. One way is to simply include the compiled file ```dTree.js``` that then exposes a ```dTree``` variable. dTree is available on both NPM and Bower as *d3-dtree*.\n\n```bash\nnpm install d3-dtree\nbower install d3-dtree\nyarn add d3-dtree\n```\n\nLastly dTree is also available through several CDNs such as [jsDelivr](https://www.jsdelivr.com/package/npm/d3-dtree):\n```\nhttps://cdn.jsdelivr.net/npm/d3-dtree@2.4.1/dist/dTree.min.js\n```\n\n## Requirements\nTo use the library the follow dependencies must be loaded:\n\n - [D3](https://github.com/mbostock/d3) v4.x\n - [lodash](https://github.com/lodash/lodash) v4.x\n\n## Usage\nTo create a graph from data use the following command:\n```javascript\ntree = dTree.init(data, options);\n```\n\nThe data object should have the following structure:\n```json\n[\n{\n    \"name\": \"Father\", // The name of the node\n    \"class\": \"node\", // The CSS class of the node\n    \"textClass\": \"nodeText\", // The CSS class of the text in the node\n    \"depthOffset\": 1, // Generational height offset\n    \"marriages\": [\n    { // Marriages is a list of nodes\n        \"spouse\":\n        { // Each marriage has one spouse\n            \"name\": \"Mother\",\n        },\n        \"children\": [\n        { // List of children nodes\n            \"name\": \"Child\",\n        }]\n    }],\n    \"extra\":\n    {} // Custom data passed to renderers\n}]\n```\n\nThe following CSS sets some good defaults:\n```css\n.linage {\n    fill: none;\n    stroke: black;\n}\n.marriage {\n    fill: none;\n    stroke: black;\n}\n.node {\n    background-color: lightblue;\n    border-style: solid;\n    border-width: 1px;\n}\n.nodeText{\n    font: 10px sans-serif;\n}\n.marriageNode {\n    background-color: black;\n    border-radius: 50%;\n}\n```\n\nThe options object has the following default values:\n```javascript\n{\n  target: '#graph',\n  debug: false,\n  width: 600,\n  height: 600,\n  hideMarriageNodes: true,\n  marriageNodeSize: 10,\n  callbacks: {\n    /*\n      Callbacks should only be overwritten on a need to basis.\n      See the section about callbacks below.\n    */\n  },\n  margin: {\n    top: 0,\n    right: 0,\n    bottom: 0,\n    left: 0\n  },\n  nodeWidth: 100,\n  styles: {\n    node: 'node',\n    linage: 'linage',\n    marriage: 'marriage',\n    text: 'nodeText'\n  }\n}\n```\n\n### Zooming\nThe returned object, `tree = dTree.init(data, options)`,  contains functions to control the viewport.\n\n- `tree.resetZoom(duration = 500)` - Reset zoom and position to initial state\n- `zoomTo(x, y, zoom = 1, duration = 500)` - Zoom to a specific position\n- `zoomToNode(nodeId, zoom = 2, duration = 500)` - Zoom to a specific node\n- `zoomToFit(duration = 500)` - Zoom to fit the entire tree into the viewport\n\n### Callbacks\nBelow follows a short descriptions of the available callback functions that may be passed to dTree. See [dtree.js](https://github.com/ErikGartner/dTree/blob/master/src/dtree.js) for the *default implementations*. Information about e.g. mouse cursor position can retrieved by interacting with the `this` object, i.e. `d3.mouse(this)`.\n\n#### nodeClick\n```javascript\nfunction(name, extra, id)\n```\nThe nodeClick function is called by dTree when the node or text is clicked by the user. It shouldn't return any value.\n\n#### nodeRightClick\n```javascript\nfunction(name, extra, id)\n```\nThe nodeRightClick function is called by dTree when the node or text is right-clicked by the user. It shouldn't return any value.\n\n#### nodeRenderer\n```javascript\nfunction(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer)\n```\nThe nodeRenderer is called once for each node and is expected to return a string containing the node. By default the node is rendered using a div containing the text returned from the default textRendeder. See the JSFiddle above for an example on how to set the callback.\n\n#### nodeHeightSeperation\n```javascript\nfunction(nodeWidth, nodeMaxHeight)\n```\nThe nodeHeightSeperation is called during intial layout calculation. It shall return one number representing the distance between the levels in the graph.\n\n#### nodeSize\n```javascript\nfunction(nodes, width, textRenderer)\n```\nThis nodeSize function takes all nodes and a preferred width set by the user. It is then expected to return an array containing the width and height for all nodes (they all share the same width and height during layout though nodes may be rendered as smaller by the nodeRenderer).\n\n#### nodeSorter\n```javascript\nfunction(aName, aExtra, bName, bExtra)\n```\nThe nodeSorterer takes two nodes names and extra data, it then expected to return -1, 0 or 1 depending if A is less, equal or greater than B. This is used for sorting the nodes in the tree during layout.\n\n#### textRenderer\n```javascript\nfunction(name, extra, textClass)\n```\nThe textRenderer function returns the formatted text to the nodeRenderer. This way the user may chose to overwrite only what text is shown but may opt to keep the default nodeRenderer.\n\n#### marriageClick\n```javascript\nfunction(extra, id)\n```\nSame as `nodeClick` but for the marriage nodes (connector).\n\n#### marriageRightClick\n```javascript\nfunction(extra, id)\n```\nSame as `nodeRightClick` but for the marriage nodes (connector).\n\n#### marriageRenderer\n```javascript\nfunction(x, y, height, width, extra, id, nodeClass)\n```\nSame as `nodeRenderer` but for the marriage nodes (connector).\n\n#### marriageSize\n```javascript\nfunction(nodes, size)\n```\nSame as `nodeSize` but for the marriage nodes (connector).\n\n## Built with dTree\n- [🌳 dTree-Seed](https://github.com/JMHeartley/dTree-Seed) - Library to painlessly structure data for dTree, courtesy of [Justin Heartley](https://github.com/JMHeartley)!\n\n## Development\ndTree has the following development environment:\n\n- node v11.x (use Docker [image](https://hub.docker.com/_/node/) `node:11`)\n- gulp 3.x\n- [Yarn](https://yarnpkg.com/) instead of npm.\n\nTo setup and build the library from scratch follow these steps:\n\n1. ```yarn install```\n2. ```yarn run build```\n\nA demo is available by running:\n```\nyarn run demo\n```\nIt hosts a demo on localhost:3000/ by serving [test/demo](test/demo) and using the latest compiled local version of the library.\n\n## Contributing\nContributions are very welcomed! Checkout the [CONTRIBUTING](CONTRIBUTING.md) document for style information.\nA good place to start is to make a pull request to solve an open issue. Feel free to ask questions regarding the issue since most have a sparse description.\n\n## License\nThe MIT License (MIT)\n\nCopyright (c) 2015-2024 Erik Gärtner\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"d3-dtree\",\n  \"description\": \"A library for visualizing data trees built on top of D3.\",\n  \"main\": \"dist/dTree.js\",\n  \"authors\": [\n    \"Erik Gärtner\"\n  ],\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"d3\",\n    \"graphing\",\n    \"tree\",\n    \"graph\",\n    \"genealogy\",\n    \"family\",\n    \"tree\"\n  ],\n  \"homepage\": \"https://github.com/ErikGartner/dTree\",\n  \"moduleType\": [\n    \"amd\",\n    \"globals\",\n    \"node\"\n  ],\n  \"dependencies\": {\n    \"lodash\": \"^4.0.0\",\n    \"d3\": \"^4.5.0\"\n  },\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"test\",\n    \"tests\"\n  ]\n}\n"
  },
  {
    "path": "dist/dTree.js",
    "content": "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; }; })();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.dTree = factory();\n})(this, function () {\n  'use strict';\n\n  var TreeBuilder = (function () {\n    function TreeBuilder(root, siblings, opts) {\n      _classCallCheck(this, TreeBuilder);\n\n      TreeBuilder.DEBUG_LEVEL = opts.debug ? 1 : 0;\n\n      this.root = root;\n      this.siblings = siblings;\n      this.opts = opts;\n\n      // flatten nodes\n      this.allNodes = this._flatten(this.root);\n\n      // calculate node sizes\n      this.nodeSize = opts.callbacks.nodeSize.call(this,\n      // filter hidden and marriage nodes\n      _.filter(this.allNodes, function (node) {\n        return !(node.hidden || _.get(node, 'data.isMarriage'));\n      }), opts.nodeWidth, opts.callbacks.textRenderer);\n      this.marriageSize = opts.callbacks.marriageSize.call(this,\n      // filter hidden and non marriage nodes\n      _.filter(this.allNodes, function (node) {\n        return !node.hidden && _.get(node, 'data.isMarriage');\n      }), this.opts.marriageNodeSize);\n    }\n\n    _createClass(TreeBuilder, [{\n      key: 'create',\n      value: function create() {\n\n        var opts = this.opts;\n        var allNodes = this.allNodes;\n        var nodeSize = this.nodeSize;\n\n        var width = opts.width + opts.margin.left + opts.margin.right;\n        var height = opts.height + opts.margin.top + opts.margin.bottom;\n\n        // create zoom handler\n        var zoom = this.zoom = d3.zoom().scaleExtent([0.1, 10]).on('zoom', function () {\n          g.attr('transform', d3.event.transform);\n        });\n\n        // make a svg\n        var svg = this.svg = d3.select(opts.target).append('svg').attr('viewBox', [0, 0, width, height]).call(zoom);\n\n        // create svg group that holds all nodes\n        var g = this.g = svg.append('g');\n\n        // set zoom identity\n        svg.call(zoom.transform, d3.zoomIdentity.translate(width / 2, opts.margin.top).scale(1));\n\n        // Compute the layout.\n        this.tree = d3.tree().nodeSize([nodeSize[0] * 2, opts.callbacks.nodeHeightSeperation.call(this, nodeSize[0], nodeSize[1])]);\n\n        this.tree.separation(function separation(a, b) {\n          if (a.data.hidden || b.data.hidden) {\n            return 0.3;\n          } else {\n            return 0.6;\n          }\n        });\n\n        this._update(this.root);\n      }\n    }, {\n      key: '_update',\n      value: function _update(source) {\n\n        var opts = this.opts;\n        var allNodes = this.allNodes;\n        var nodeSize = this.nodeSize;\n        var marriageSize = this.marriageSize;\n\n        var treenodes = this.tree(source);\n        var links = treenodes.links();\n\n        // Create the link lines.\n        this.g.selectAll('.link').data(links).enter()\n        // filter links with no parents to prevent empty nodes\n        .filter(function (l) {\n          return !l.target.data.noParent;\n        }).append('path').attr('class', opts.styles.linage).attr('d', this._elbow);\n\n        var nodes = this.g.selectAll('.node').data(treenodes.descendants()).enter();\n\n        this._linkSiblings();\n\n        // Draw siblings (marriage)\n        this.g.selectAll('.sibling').data(this.siblings).enter().append('path').attr('class', opts.styles.marriage).attr('d', _.bind(this._siblingLine, this));\n\n        // Create the node rectangles.\n        nodes.append('foreignObject').filter(function (d) {\n          return d.data.hidden ? false : true;\n        }).attr('x', function (d) {\n          return Math.round(d.x - d.cWidth / 2) + 'px';\n        }).attr('y', function (d) {\n          return Math.round(d.y - d.cHeight / 2) + 'px';\n        }).attr('width', function (d) {\n          return d.cWidth + 'px';\n        }).attr('height', function (d) {\n          return d.cHeight + 'px';\n        }).attr('id', function (d) {\n          return d.id;\n        }).html(function (d) {\n          if (d.data.isMarriage) {\n            return opts.callbacks.marriageRenderer.call(this, d.x, d.y, marriageSize[0], marriageSize[1], d.data.extra, d.data.id, d.data['class']);\n          } else {\n            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);\n          }\n        }).on('dblclick', function () {\n          // do not propagate a double click on a node\n          // to prevent the zoom from being triggered\n          d3.event.stopPropagation();\n        }).on('click', function (d) {\n          // ignore double-clicks and clicks on hidden nodes\n          if (d3.event.detail === 2 || d.data.hidden) {\n            return;\n          }\n          if (d.data.isMarriage) {\n            opts.callbacks.marriageClick.call(this, d.data.extra, d.data.id);\n          } else {\n            opts.callbacks.nodeClick.call(this, d.data.name, d.data.extra, d.data.id);\n          }\n        }).on('contextmenu', function (d) {\n          if (d.data.hidden) {\n            return;\n          }\n          d3.event.preventDefault();\n          if (d.data.isMarriage) {\n            opts.callbacks.marriageRightClick.call(this, d.data.extra, d.data.id);\n          } else {\n            opts.callbacks.nodeRightClick.call(this, d.data.name, d.data.extra, d.data.id);\n          }\n        });\n      }\n    }, {\n      key: '_flatten',\n      value: function _flatten(root) {\n        var n = [];\n        var i = 0;\n\n        function recurse(node) {\n          if (node.children) {\n            node.children.forEach(recurse);\n          }\n          if (!node.id) {\n            node.id = ++i;\n          }\n          n.push(node);\n        }\n        recurse(root);\n        return n;\n      }\n    }, {\n      key: '_elbow',\n      value: function _elbow(d, i) {\n        if (d.target.data.noParent) {\n          return 'M0,0L0,0';\n        }\n        var ny = Math.round(d.target.y + (d.source.y - d.target.y) * 0.50);\n\n        var linedata = [{\n          x: d.target.x,\n          y: d.target.y\n        }, {\n          x: d.target.x,\n          y: ny\n        }, {\n          x: d.source.x,\n          y: d.source.y\n        }];\n\n        var fun = d3.line().curve(d3.curveStepAfter).x(function (d) {\n          return d.x;\n        }).y(function (d) {\n          return d.y;\n        });\n        return fun(linedata);\n      }\n    }, {\n      key: '_linkSiblings',\n      value: function _linkSiblings() {\n\n        var allNodes = this.allNodes;\n\n        _.forEach(this.siblings, function (d) {\n          var start = allNodes.filter(function (v) {\n            return d.source.id == v.data.id;\n          });\n          var end = allNodes.filter(function (v) {\n            return d.target.id == v.data.id;\n          });\n          d.source.x = start[0].x;\n          d.source.y = start[0].y;\n          d.target.x = end[0].x;\n          d.target.y = end[0].y;\n\n          var marriageId = start[0].data.marriageNode != null ? start[0].data.marriageNode.id : end[0].data.marriageNode.id;\n          var marriageNode = allNodes.find(function (n) {\n            return n.data.id == marriageId;\n          });\n          d.source.marriageNode = marriageNode;\n          d.target.marriageNode = marriageNode;\n        });\n      }\n    }, {\n      key: '_siblingLine',\n      value: function _siblingLine(d, i) {\n\n        var ny = Math.round(d.target.y + (d.source.y - d.target.y) * 0.50);\n        var nodeWidth = this.nodeSize[0];\n        var nodeHeight = this.nodeSize[1];\n\n        // Not first marriage\n        if (d.number > 0) {\n          ny -= Math.round(nodeHeight * 8 / 10);\n        }\n\n        var linedata = [{\n          x: d.source.x,\n          y: d.source.y\n        }, {\n          x: Math.round(d.source.x + nodeWidth * 6 / 10),\n          y: d.source.y\n        }, {\n          x: Math.round(d.source.x + nodeWidth * 6 / 10),\n          y: ny\n        }, {\n          x: d.target.marriageNode.x,\n          y: ny\n        }, {\n          x: d.target.marriageNode.x,\n          y: d.target.y\n        }, {\n          x: d.target.x,\n          y: d.target.y\n        }];\n\n        var fun = d3.line().curve(d3.curveStepAfter).x(function (d) {\n          return d.x;\n        }).y(function (d) {\n          return d.y;\n        });\n        return fun(linedata);\n      }\n    }], [{\n      key: '_nodeHeightSeperation',\n      value: function _nodeHeightSeperation(nodeWidth, nodeMaxHeight) {\n        return nodeMaxHeight + 25;\n      }\n    }, {\n      key: '_nodeSize',\n      value: function _nodeSize(nodes, width, textRenderer) {\n        var maxWidth = 0;\n        var maxHeight = 0;\n        var tmpSvg = document.createElement('svg');\n        document.body.appendChild(tmpSvg);\n\n        _.map(nodes, function (n) {\n          var container = document.createElement('div');\n          container.setAttribute('class', n.data['class']);\n          container.style.visibility = 'hidden';\n          container.style.maxWidth = width + 'px';\n\n          var text = textRenderer(n.data.name, n.data.extra, n.data.textClass);\n          container.innerHTML = text;\n\n          tmpSvg.appendChild(container);\n          var height = container.offsetHeight;\n          tmpSvg.removeChild(container);\n\n          maxHeight = Math.max(maxHeight, height);\n          n.cHeight = height;\n          if (n.data.hidden) {\n            n.cWidth = 0;\n          } else {\n            n.cWidth = width;\n          }\n        });\n        document.body.removeChild(tmpSvg);\n\n        return [width, maxHeight];\n      }\n    }, {\n      key: '_marriageSize',\n      value: function _marriageSize(nodes, size) {\n        _.map(nodes, function (n) {\n          if (!n.data.hidden) {\n            n.cHeight = size;\n            n.cWidth = size;\n          }\n        });\n\n        return [size, size];\n      }\n    }, {\n      key: '_nodeRenderer',\n      value: function _nodeRenderer(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer) {\n        var node = '';\n        node += '<div ';\n        node += 'style=\"height:100%;width:100%;\" ';\n        node += 'class=\"' + nodeClass + '\" ';\n        node += 'id=\"node' + id + '\">\\n';\n        node += textRenderer(name, extra, textClass);\n        node += '</div>';\n        return node;\n      }\n    }, {\n      key: '_textRenderer',\n      value: function _textRenderer(name, extra, textClass) {\n        var node = '';\n        node += '<p ';\n        node += 'align=\"center\" ';\n        node += 'class=\"' + textClass + '\">\\n';\n        node += name;\n        node += '</p>\\n';\n        return node;\n      }\n    }, {\n      key: '_marriageRenderer',\n      value: function _marriageRenderer(x, y, height, width, extra, id, nodeClass) {\n        return '<div style=\"height:100%\" class=\"' + nodeClass + '\" id=\"node' + id + '\"></div>';\n      }\n    }, {\n      key: '_debug',\n      value: function _debug(msg) {\n        if (TreeBuilder.DEBUG_LEVEL > 0) {\n          console.log(msg);\n        }\n      }\n    }]);\n\n    return TreeBuilder;\n  })();\n\n  var dTree = {\n\n    VERSION: '2.4.1',\n\n    init: function init(data) {\n      var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];\n\n      var opts = _.defaultsDeep(options || {}, {\n        target: '#graph',\n        debug: false,\n        width: 600,\n        height: 600,\n        hideMarriageNodes: true,\n        callbacks: {\n          nodeClick: function nodeClick(name, extra, id) {},\n          nodeRightClick: function nodeRightClick(name, extra, id) {},\n          marriageClick: function marriageClick(extra, id) {},\n          marriageRightClick: function marriageRightClick(extra, id) {},\n          nodeHeightSeperation: function nodeHeightSeperation(nodeWidth, nodeMaxHeight) {\n            return TreeBuilder._nodeHeightSeperation(nodeWidth, nodeMaxHeight);\n          },\n          nodeRenderer: function nodeRenderer(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer) {\n            return TreeBuilder._nodeRenderer(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer);\n          },\n          nodeSize: function nodeSize(nodes, width, textRenderer) {\n            return TreeBuilder._nodeSize(nodes, width, textRenderer);\n          },\n          nodeSorter: function nodeSorter(aName, aExtra, bName, bExtra) {\n            return 0;\n          },\n          textRenderer: function textRenderer(name, extra, textClass) {\n            return TreeBuilder._textRenderer(name, extra, textClass);\n          },\n          marriageRenderer: function marriageRenderer(x, y, height, width, extra, id, nodeClass) {\n            return TreeBuilder._marriageRenderer(x, y, height, width, extra, id, nodeClass);\n          },\n          marriageSize: function marriageSize(nodes, size) {\n            return TreeBuilder._marriageSize(nodes, size);\n          }\n        },\n        margin: {\n          top: 0,\n          right: 0,\n          bottom: 0,\n          left: 0\n        },\n        nodeWidth: 100,\n        marriageNodeSize: 10,\n        styles: {\n          node: 'node',\n          marriageNode: 'marriageNode',\n          linage: 'linage',\n          marriage: 'marriage',\n          text: 'nodeText'\n        }\n      });\n\n      var data = this._preprocess(data, opts);\n      var treeBuilder = new TreeBuilder(data.root, data.siblings, opts);\n      treeBuilder.create();\n\n      function _zoomTo(x, y) {\n        var zoom = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2];\n        var duration = arguments.length <= 3 || arguments[3] === undefined ? 500 : arguments[3];\n\n        treeBuilder.svg.transition().duration(duration).call(treeBuilder.zoom.transform, d3.zoomIdentity.translate(opts.width / 2, opts.height / 2).scale(zoom).translate(-x, -y));\n      }\n\n      return {\n        resetZoom: function resetZoom() {\n          var duration = arguments.length <= 0 || arguments[0] === undefined ? 500 : arguments[0];\n\n          treeBuilder.svg.transition().duration(duration).call(treeBuilder.zoom.transform, d3.zoomIdentity.translate(opts.width / 2, opts.margin.top).scale(1));\n        },\n        zoomTo: _zoomTo,\n        zoomToNode: function zoomToNode(nodeId) {\n          var zoom = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1];\n          var duration = arguments.length <= 2 || arguments[2] === undefined ? 500 : arguments[2];\n\n          var node = _.find(treeBuilder.allNodes, { data: { id: nodeId } });\n          if (node) {\n            _zoomTo(node.x, node.y, zoom, duration);\n          }\n        },\n        zoomToFit: function zoomToFit() {\n          var duration = arguments.length <= 0 || arguments[0] === undefined ? 500 : arguments[0];\n\n          var groupBounds = treeBuilder.g.node().getBBox();\n          var width = groupBounds.width;\n          var height = groupBounds.height;\n          var fullWidth = treeBuilder.svg.node().clientWidth;\n          var fullHeight = treeBuilder.svg.node().clientHeight;\n          var scale = 0.95 / Math.max(width / fullWidth, height / fullHeight);\n\n          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));\n        }\n      };\n    },\n\n    _preprocess: function _preprocess(data, opts) {\n\n      var siblings = [];\n      var id = 0;\n\n      var root = {\n        name: '',\n        id: id++,\n        hidden: true,\n        children: []\n      };\n\n      var reconstructTree = function reconstructTree(person, parent) {\n\n        // convert to person to d3 node\n        var node = {\n          name: person.name,\n          id: id++,\n          hidden: false,\n          children: [],\n          extra: person.extra,\n          textClass: person.textClass ? person.textClass : opts.styles.text,\n          'class': person['class'] ? person['class'] : opts.styles.node\n        };\n\n        // hide linages to the hidden root node\n        if (parent == root) {\n          node.noParent = true;\n        }\n\n        // apply depth offset\n        for (var i = 0; i < person.depthOffset; i++) {\n          var pushNode = {\n            name: '',\n            id: id++,\n            hidden: true,\n            children: [],\n            noParent: node.noParent\n          };\n          parent.children.push(pushNode);\n          parent = pushNode;\n        }\n\n        // sort children\n        dTree._sortPersons(person.children, opts);\n\n        // add \"direct\" children\n        _.forEach(person.children, function (child) {\n          reconstructTree(child, node);\n        });\n\n        parent.children.push(node);\n\n        //sort marriages\n        dTree._sortMarriages(person.marriages, opts);\n\n        // go through marriage\n        _.forEach(person.marriages, function (marriage, index) {\n          var m = {\n            name: '',\n            id: id++,\n            hidden: opts.hideMarriageNodes,\n            noParent: true,\n            children: [],\n            isMarriage: true,\n            extra: marriage.extra,\n            'class': marriage['class'] ? marriage['class'] : opts.styles.marriageNode\n          };\n\n          var sp = marriage.spouse;\n\n          var spouse = {\n            name: sp.name,\n            id: id++,\n            hidden: false,\n            noParent: true,\n            children: [],\n            textClass: sp.textClass ? sp.textClass : opts.styles.text,\n            'class': sp['class'] ? sp['class'] : opts.styles.node,\n            extra: sp.extra,\n            marriageNode: m\n          };\n\n          parent.children.push(m, spouse);\n\n          dTree._sortPersons(marriage.children, opts);\n          _.forEach(marriage.children, function (child) {\n            reconstructTree(child, m);\n          });\n\n          siblings.push({\n            source: {\n              id: node.id\n            },\n            target: {\n              id: spouse.id\n            },\n            number: index\n          });\n        });\n      };\n\n      _.forEach(data, function (person) {\n        reconstructTree(person, root);\n      });\n\n      return {\n        root: d3.hierarchy(root),\n        siblings: siblings\n      };\n    },\n\n    _sortPersons: function _sortPersons(persons, opts) {\n      if (persons != undefined) {\n        persons.sort(function (a, b) {\n          return opts.callbacks.nodeSorter.call(this, a.name, a.extra, b.name, b.extra);\n        });\n      }\n      return persons;\n    },\n\n    _sortMarriages: function _sortMarriages(marriages, opts) {\n      if (marriages != undefined && Array.isArray(marriages)) {\n        marriages.sort(function (marriageA, marriageB) {\n          var a = marriageA.spouse;\n          var b = marriageB.spouse;\n          return opts.callbacks.nodeSorter.call(this, a.name, a.extra, b.name, b.extra);\n        });\n      }\n      return marriages;\n    }\n\n  };\n\n  return dTree;\n});\n//# sourceMappingURL=dTree.js.map\n"
  },
  {
    "path": "gulpfile.js",
    "content": "// Load Gulp and all of our Gulp plugins\nconst gulp = require('gulp');\nconst $ = require('gulp-load-plugins')();\n\n// Load other npm modules\nconst del = require('del');\nconst glob = require('glob');\nconst path = require('path');\nconst isparta = require('isparta');\nconst babelify = require('babelify');\nconst watchify = require('watchify');\nconst buffer = require('vinyl-buffer');\nconst browserify = require('browserify');\nconst runSequence = require('run-sequence');\nconst source = require('vinyl-source-stream');\nconst rollup = require( 'rollup' );\nconst argv = require('minimist')(process.argv.slice(2));\nconst fs = require('fs');\nconst conventionalGithubReleaser = require('conventional-github-releaser');\n\n// Gather the library data from `package.json`\nconst manifest = require('./package.json');\nconst config = manifest.babelBoilerplateOptions;\nconst mainFile = manifest.main;\nconst demoFolder = manifest.demo;\nconst destinationFolder = path.dirname(mainFile);\nconst exportFileName = path.basename(mainFile, path.extname(mainFile));\n\n// Remove the built files\ngulp.task('clean', function(cb) {\n  del([destinationFolder], cb);\n});\n\n// Remove our temporary files\ngulp.task('clean-tmp', function(cb) {\n  del(['tmp'], cb);\n});\n\nfunction createLintTask(taskName, files) {\n  gulp.task(taskName, function() {\n    return gulp.src(files)\n      .pipe($.plumber())\n      .pipe($.eslint())\n      .pipe($.eslint.format())\n      .pipe($.eslint.failOnError());\n  });\n}\n\n// Lint our source code\ncreateLintTask('lint-src', ['src/**/*.js']);\n\nfunction getPackageJsonVersion () {\n  // We parse the json file instead of using require because require caches\n  // multiple calls so the version number won't be updated\n  return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;\n}\n\n// Build two versions of the library\ngulp.task('build', ['lint-src', 'clean'], function(done) {\n  var version = getPackageJsonVersion();\n  rollup.rollup({\n    entry: 'src/' + config.entryFileName,\n  }).then(function(bundle) {\n    var res = bundle.generate({\n      // use this instead of `toUmd`\n      format: 'umd',\n\n      // this is equivalent to `strict: true` -\n      // optional, will be auto-detected\n      //exports: 'named',\n\n      // `name` -> `moduleName`\n      moduleName: config.mainVarName,\n    });\n\n    $.file(exportFileName + '.js', res.code, { src: true })\n      .pipe($.preprocess({context: {DTREE_VERSION: version}}))\n      .pipe($.plumber())\n      .pipe($.sourcemaps.init({ loadMaps: true }))\n      .pipe($.babel())\n      .pipe($.sourcemaps.write('./'))\n      .pipe(gulp.dest(destinationFolder))\n      .pipe($.filter(['*', '!**/*.js.map']))\n      .pipe($.rename(exportFileName + '.min.js'))\n      .pipe($.sourcemaps.init({ loadMaps: true }))\n      .pipe($.uglify())\n      .pipe($.sourcemaps.write('./'))\n      .pipe(gulp.dest(destinationFolder))\n      .pipe(gulp.dest(demoFolder, {overwrite: true}))\n      .on('end', done);\n  })\n  .catch(done);\n});\n\nfunction bundle(bundler) {\n  return bundler.bundle()\n    .on('error', function(err) {\n      console.log(err.message);\n      this.emit('end');\n    })\n    .pipe($.plumber())\n    .pipe(source('./tmp/__spec-build.js'))\n    .pipe(buffer())\n    .pipe(gulp.dest(''))\n    .pipe($.livereload());\n}\n\n// These are JS files that should be watched by Gulp. When running tests in the browser,\n// watchify is used instead, so these aren't included.\nconst jsWatchFiles = ['src/**/*'];\n// These are files other than JS files which are to be watched. They are always watched.\nconst otherWatchFiles = ['package.json', '**/.eslintrc'];\n\n// Run the headless unit tests as you make changes.\ngulp.task('watch', function() {\n  const watchFiles = jsWatchFiles.concat(otherWatchFiles);\n  gulp.watch(watchFiles, ['build']);\n});\n\ngulp.task('bump', function() {\n  return gulp.src('./package.json')\n  .pipe($.bump({key: 'version', type: argv.bump}))\n  .pipe(gulp.dest('./'));\n});\n\ngulp.task('changelog', function () {\n  return gulp.src('./CHANGELOG.md')\n    .pipe($.conventionalChangelog({\n      preset: 'angular',\n    }))\n    .pipe(gulp.dest('./'));\n});\n\ngulp.task('update-cdn', function() {\n  gulp.src(['./README.md'])\n    .pipe($.replace(/(\\d\\.\\d\\.\\d)\\/dist\\/dTree.min.js/g, getPackageJsonVersion() + '/dist/dTree.min.js'))\n    .pipe(gulp.dest('./'));\n});\n\ngulp.task('tag-release', function (cb) {\n  var version = getPackageJsonVersion();\n  $.git.tag(version, 'Created Tag for version: ' + version, cb);\n});\n\ngulp.task('commit-changes', function () {\n  return gulp.src(['./README.md', './CHANGELOG.md', './dist/*'])\n    .pipe($.git.add())\n    .pipe($.git.commit('chore: Bump version number'));\n});\n\ngulp.task('prepare-release', function (callback) {\n  runSequence(\n    'bump',\n    'changelog',\n    'build',\n    'update-cdn',\n    'commit-changes',\n    'tag-release',\n    function (error) {\n      if (error) {\n        console.log(error.message);\n      } else {\n        console.log('Updated workspace for release!');\n      }\n      callback(error);\n    });\n});\n\ngulp.task('push-changes', function (cb) {\n  $.git.push('origin', 'master', cb);\n});\n\ngulp.task('push-tags', function (cb) {\n  $.git.push('origin', 'master', {args: '--tags'}, cb);\n});\n\ngulp.task('github-release', function (done) {\n  conventionalGithubReleaser({\n    type: 'oauth',\n    token: process.env.GITHUB_TOKEN\n  }, {\n    preset: 'angular'\n  }, done);\n});\n\ngulp.task('npm-publish', $.shell.task([\n  'npm publish'\n]))\n\ngulp.task('push-release', function (callback) {\n  runSequence(\n    'push-changes',\n    'push-tags',\n    'npm-publish',\n    'github-release',\n    function (error) {\n      if (error) {\n        console.log(error.message);\n      } else {\n        console.log('Release Uploaded!');\n      }\n      callback(error);\n    });\n});\n\ngulp.task('release', function (callback) {\n  runSequence(\n    'prepare-release',\n    'push-release',\n    function (error) {\n      if (error) {\n        console.log(error.message);\n      } else {\n        console.log('Release complete!');\n      }\n      callback(error);\n    });\n});\n\ngulp.task('demo', ['build'], $.shell.task([\n  'node test/demo/demo.js'\n]))\n\n// An alias of build\ngulp.task('default', ['build']);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"d3-dtree\",\n  \"version\": \"2.4.1\",\n  \"description\": \"A library for visualizing data trees built on top of D3.\",\n  \"main\": \"dist/dTree.js\",\n  \"demo\": \"test/demo/\",\n  \"scripts\": {\n    \"build\": \"gulp build\",\n    \"coverage\": \"gulp coverage\",\n    \"demo\": \"gulp demo\",\n    \"release\": \"gulp release\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ErikGartner/dtree.git\"\n  },\n  \"keywords\": [\n    \"d3\",\n    \"graphing\",\n    \"tree graph\",\n    \"genealogy\",\n    \"family tree\"\n  ],\n  \"author\": \"Erik Gärtner\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ErikGartner/dtree/issues\"\n  },\n  \"homepage\": \"https://github.com/ErikGartner/dtree\",\n  \"dependencies\": {\n    \"d3\": \"^4.5.0\",\n    \"lodash\": \"^4.0.0\"\n  },\n  \"devDependencies\": {\n    \"babel-core\": \"^5.2.17\",\n    \"babel-eslint\": \"^4.0.5\",\n    \"babelify\": \"^6.0.0\",\n    \"browserify\": \"^11.0.1\",\n    \"connect\": \"^3.4.0\",\n    \"conventional-changelog\": \"^0.5.3\",\n    \"conventional-github-releaser\": \"^0.5.1\",\n    \"del\": \"^1.1.1\",\n    \"glob\": \"^5.0.14\",\n    \"gulp\": \"^3.8.10\",\n    \"gulp-babel\": \"^5.0.0\",\n    \"gulp-bump\": \"^1.0.0\",\n    \"gulp-conventional-changelog\": \"^0.7.0\",\n    \"gulp-eslint\": \"^1.0.0\",\n    \"gulp-file\": \"^0.2.0\",\n    \"gulp-filter\": \"^3.0.0\",\n    \"gulp-git\": \"^2.9.0\",\n    \"gulp-github-release\": \"^1.1.0\",\n    \"gulp-istanbul\": \"^0.10.0\",\n    \"gulp-livereload\": \"^3.4.0\",\n    \"gulp-load-plugins\": \"^0.10.0\",\n    \"gulp-notify\": \"^2.1.0\",\n    \"gulp-plumber\": \"^1.0.1\",\n    \"gulp-preprocess\": \"^2.0.0\",\n    \"gulp-rename\": \"^1.2.0\",\n    \"gulp-replace\": \"^0.5.4\",\n    \"gulp-shell\": \"^0.5.0\",\n    \"gulp-sourcemaps\": \"^1.3.0\",\n    \"gulp-uglify\": \"^1.2.0\",\n    \"isparta\": \"~3.0.3\",\n    \"jquery\": \"^2.1.4\",\n    \"minimist\": \"^1.2.0\",\n    \"rollup\": \"^0.41.4\",\n    \"run-sequence\": \"^1.0.2\",\n    \"serve-static\": \"^1.10.0\",\n    \"vinyl-buffer\": \"^1.0.0\",\n    \"vinyl-source-stream\": \"^1.0.0\",\n    \"watchify\": \"^3.3.1\"\n  },\n  \"babelBoilerplateOptions\": {\n    \"entryFileName\": \"dtree\",\n    \"mainVarName\": \"dTree\",\n    \"mochaGlobals\": [\n      \"stub\",\n      \"spy\",\n      \"expect\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/builder.js",
    "content": "class TreeBuilder {\n\n  constructor(root, siblings, opts) {\n    TreeBuilder.DEBUG_LEVEL = opts.debug ? 1 : 0;\n\n    this.root = root;\n    this.siblings = siblings;\n    this.opts = opts;\n\n    // flatten nodes\n    this.allNodes = this._flatten(this.root);\n\n    // calculate node sizes\n    this.nodeSize = opts.callbacks.nodeSize.call(this,\n      // filter hidden and marriage nodes\n      _.filter(\n        this.allNodes,\n        node => !(node.hidden || _.get(node, 'data.isMarriage'))\n      ),\n      opts.nodeWidth,\n      opts.callbacks.textRenderer\n    )\n    this.marriageSize = opts.callbacks.marriageSize.call(this,\n      // filter hidden and non marriage nodes\n      _.filter(\n        this.allNodes,\n        node => !node.hidden && _.get(node, 'data.isMarriage')\n      ),\n      this.opts.marriageNodeSize\n    )\n  }\n\n  create() {\n\n    let opts = this.opts;\n    let allNodes = this.allNodes;\n    let nodeSize = this.nodeSize;\n\n    let width = opts.width + opts.margin.left + opts.margin.right;\n    let height = opts.height + opts.margin.top + opts.margin.bottom;\n\n    // create zoom handler\n    const zoom = this.zoom = d3.zoom()\n      .scaleExtent([0.1, 10])\n      .on('zoom', function () {\n        g.attr('transform', d3.event.transform)\n      })\n\n    // make a svg\n    const svg = this.svg = d3.select(opts.target)\n      .append('svg')\n      .attr('viewBox', [0, 0, width, height])\n      .call(zoom)\n\n    // create svg group that holds all nodes\n    const g = this.g = svg.append('g')\n\n    // set zoom identity\n    svg.call(zoom.transform, d3.zoomIdentity.translate(width / 2, opts.margin.top).scale(1))\n\n    // Compute the layout.\n    this.tree = d3.tree()\n      .nodeSize([nodeSize[0] * 2,\n                 opts.callbacks.nodeHeightSeperation.call(this, nodeSize[0], nodeSize[1])]);\n\n    this.tree.separation(function separation(a, b) {\n      if (a.data.hidden || b.data.hidden) {\n        return 0.3;\n      } else {\n        return 0.6;\n      }\n    });\n\n    this._update(this.root);\n\n  }\n\n  _update(source) {\n\n    let opts = this.opts;\n    let allNodes = this.allNodes;\n    let nodeSize = this.nodeSize;\n    let marriageSize = this.marriageSize;\n\n    let treenodes = this.tree(source);\n    let links = treenodes.links();\n\n    // Create the link lines.\n    this.g.selectAll('.link')\n      .data(links)\n      .enter()\n      // filter links with no parents to prevent empty nodes\n      .filter(function(l) {\n        return !l.target.data.noParent;\n      })\n      .append('path')\n      .attr('class', opts.styles.linage)\n      .attr('d', this._elbow);\n\n    let nodes = this.g.selectAll('.node')\n      .data(treenodes.descendants())\n      .enter();\n\n    this._linkSiblings();\n\n    // Draw siblings (marriage)\n    this.g.selectAll('.sibling')\n      .data(this.siblings)\n      .enter()\n      .append('path')\n      .attr('class', opts.styles.marriage)\n      .attr('d', _.bind(this._siblingLine, this));\n\n    // Create the node rectangles.\n    nodes.append('foreignObject')\n      .filter(function(d) {\n        return d.data.hidden ? false : true;\n      })\n      .attr('x', function(d) {\n        return Math.round(d.x - d.cWidth / 2) + 'px';\n      })\n      .attr('y', function(d) {\n        return Math.round(d.y - d.cHeight / 2) + 'px';\n      })\n      .attr('width', function(d) {\n        return d.cWidth + 'px';\n      })\n      .attr('height', function(d) {\n        return d.cHeight + 'px';\n      })\n      .attr('id', function(d) {\n        return d.id;\n      })\n      .html(function(d) {\n        if (d.data.isMarriage) {\n          return opts.callbacks.marriageRenderer.call(this,\n            d.x,\n            d.y,\n            marriageSize[0],\n            marriageSize[1],\n            d.data.extra,\n            d.data.id,\n            d.data.class\n          )\n        } else {\n          return opts.callbacks.nodeRenderer.call(this,\n            d.data.name,\n            d.x,\n            d.y,\n            nodeSize[0],\n            nodeSize[1],\n            d.data.extra,\n            d.data.id,\n            d.data.class,\n            d.data.textClass,\n            opts.callbacks.textRenderer\n          )\n        }\n      })\n      .on('dblclick', function () {\n        // do not propagate a double click on a node\n        // to prevent the zoom from being triggered\n        d3.event.stopPropagation()\n      })\n      .on('click', function(d)  {\n        // ignore double-clicks and clicks on hidden nodes\n        if (d3.event.detail === 2 || d.data.hidden) {\n          return;\n        }\n        if (d.data.isMarriage) {\n          opts.callbacks.marriageClick.call(this, d.data.extra, d.data.id)\n        } else {\n          opts.callbacks.nodeClick.call(this, d.data.name, d.data.extra, d.data.id)\n        }\n      })\n      .on('contextmenu', function(d)  {\n        if (d.data.hidden) {\n          return;\n        }\n        d3.event.preventDefault();\n        if (d.data.isMarriage) {\n          opts.callbacks.marriageRightClick.call(this, d.data.extra, d.data.id)\n        } else {\n          opts.callbacks.nodeRightClick.call(this, d.data.name, d.data.extra, d.data.id)\n        }\n      });\n  }\n\n  _flatten(root) {\n    let n = [];\n    let i = 0;\n\n    function recurse(node) {\n      if (node.children) {\n        node.children.forEach(recurse);\n      }\n      if (!node.id) {\n        node.id = ++i;\n      }\n      n.push(node);\n    }\n    recurse(root);\n    return n;\n  }\n\n  _elbow(d, i) {\n    if (d.target.data.noParent) {\n      return 'M0,0L0,0';\n    }\n    let ny = Math.round(d.target.y + (d.source.y - d.target.y) * 0.50);\n\n    let linedata = [{\n      x: d.target.x,\n      y: d.target.y\n    }, {\n      x: d.target.x,\n      y: ny\n    }, {\n      x: d.source.x,\n      y: d.source.y\n    }];\n\n    let fun = d3.line().curve(d3.curveStepAfter)\n      .x(function(d) {\n        return d.x;\n      })\n      .y(function(d) {\n        return d.y;\n      });\n    return fun(linedata);\n  }\n\n  _linkSiblings() {\n\n    let allNodes = this.allNodes;\n\n    _.forEach(this.siblings, function(d) {\n      let start = allNodes.filter(function(v) {\n        return d.source.id == v.data.id;\n      });\n      let end = allNodes.filter(function(v) {\n        return d.target.id == v.data.id;\n      });\n      d.source.x = start[0].x;\n      d.source.y = start[0].y;\n      d.target.x = end[0].x;\n      d.target.y = end[0].y;\n\n      let marriageId = (start[0].data.marriageNode != null ?\n                        start[0].data.marriageNode.id :\n                        end[0].data.marriageNode.id);\n      let marriageNode = allNodes.find(function(n) {\n        return n.data.id == marriageId;\n      });\n      d.source.marriageNode = marriageNode;\n      d.target.marriageNode = marriageNode;\n    });\n\n  }\n\n  _siblingLine(d, i) {\n\n    let ny = Math.round(d.target.y + (d.source.y - d.target.y) * 0.50);\n    let nodeWidth = this.nodeSize[0];\n    let nodeHeight = this.nodeSize[1];\n\n    // Not first marriage\n    if (d.number > 0) {\n      ny -= Math.round(nodeHeight * 8 / 10);\n    }\n\n    let linedata = [{\n      x: d.source.x,\n      y: d.source.y\n    }, {\n      x: Math.round(d.source.x + nodeWidth * 6 / 10),\n      y: d.source.y\n    }, {\n      x: Math.round(d.source.x + nodeWidth * 6 / 10),\n      y: ny\n    }, {\n      x: d.target.marriageNode.x,\n      y: ny\n    }, {\n      x: d.target.marriageNode.x,\n      y: d.target.y\n    }, {\n      x: d.target.x,\n      y: d.target.y\n    }];\n\n    let fun = d3.line().curve(d3.curveStepAfter)\n      .x(function(d) {\n        return d.x;\n      })\n      .y(function(d) {\n        return d.y;\n      });\n    return fun(linedata);\n  }\n\n  static _nodeHeightSeperation(nodeWidth, nodeMaxHeight) {\n    return nodeMaxHeight + 25;\n  }\n\n  static _nodeSize(nodes, width, textRenderer) {\n    let maxWidth = 0;\n    let maxHeight = 0;\n    let tmpSvg = document.createElement('svg');\n    document.body.appendChild(tmpSvg);\n\n    _.map(nodes, function(n) {\n      let container = document.createElement('div');\n      container.setAttribute('class', n.data.class);\n      container.style.visibility = 'hidden';\n      container.style.maxWidth = width + 'px';\n\n      let text = textRenderer(n.data.name, n.data.extra, n.data.textClass);\n      container.innerHTML = text;\n\n      tmpSvg.appendChild(container);\n      let height = container.offsetHeight;\n      tmpSvg.removeChild(container);\n\n      maxHeight = Math.max(maxHeight, height);\n      n.cHeight = height;\n      if (n.data.hidden) {\n        n.cWidth = 0;\n      } else {\n        n.cWidth = width;\n      }\n    });\n    document.body.removeChild(tmpSvg);\n\n    return [width, maxHeight];\n  }\n\n  static _marriageSize (nodes, size) {\n    _.map(nodes, function (n) {\n      if (!n.data.hidden) {\n        n.cHeight = size\n        n.cWidth = size\n      }\n    })\n\n    return [size, size]\n  }\n\n  static _nodeRenderer(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer) {\n    let node = '';\n    node += '<div ';\n    node += 'style=\"height:100%;width:100%;\" ';\n    node += 'class=\"' + nodeClass + '\" ';\n    node += 'id=\"node' + id + '\">\\n';\n    node += textRenderer(name, extra, textClass);\n    node += '</div>';\n    return node;\n  }\n\n  static _textRenderer(name, extra, textClass) {\n    let node = '';\n    node += '<p ';\n    node += 'align=\"center\" ';\n    node += 'class=\"' + textClass + '\">\\n';\n    node += name;\n    node += '</p>\\n';\n    return node;\n  }\n\n  static _marriageRenderer (x, y, height, width, extra, id, nodeClass) {\n    return `<div style=\"height:100%\" class=\"${nodeClass}\" id=\"node${id}\"></div>`\n  }\n\n  static _debug(msg) {\n    if (TreeBuilder.DEBUG_LEVEL > 0)  {\n      console.log(msg);\n    }\n  }\n\n}\n\nexport default TreeBuilder;\n"
  },
  {
    "path": "src/dtree.js",
    "content": "import TreeBuilder from './builder.js';\n\nconst dTree = {\n\n  VERSION: '/* @echo DTREE_VERSION */',\n\n  init: function(data, options = {}) {\n\n    var opts = _.defaultsDeep(options || {}, {\n      target: '#graph',\n      debug: false,\n      width: 600,\n      height: 600,\n      hideMarriageNodes: true,\n      callbacks: {\n        nodeClick: function(name, extra, id) {},\n        nodeRightClick: function(name, extra, id) {},\n        marriageClick: function(extra, id) {},\n        marriageRightClick: function(extra, id) {},\n        nodeHeightSeperation: function(nodeWidth, nodeMaxHeight) {\n          return TreeBuilder._nodeHeightSeperation(nodeWidth, nodeMaxHeight);\n        },\n        nodeRenderer: function(name, x, y, height, width, extra, id, nodeClass, textClass, textRenderer) {\n          return TreeBuilder._nodeRenderer(name, x, y, height, width, extra,\n            id,nodeClass, textClass, textRenderer);\n        },\n        nodeSize: function(nodes, width, textRenderer) {\n          return TreeBuilder._nodeSize(nodes, width, textRenderer);\n        },\n        nodeSorter: function(aName, aExtra, bName, bExtra) {return 0;},\n        textRenderer: function(name, extra, textClass) {\n          return TreeBuilder._textRenderer(name, extra, textClass);\n        },\n        marriageRenderer: function (x, y, height, width, extra, id, nodeClass) {\n          return TreeBuilder._marriageRenderer(x, y, height, width, extra, id, nodeClass)\n        },\n        marriageSize: function (nodes, size) {\n          return TreeBuilder._marriageSize(nodes, size)\n        },\n      },\n      margin: {\n        top: 0,\n        right: 0,\n        bottom: 0,\n        left: 0\n      },\n      nodeWidth: 100,\n      marriageNodeSize: 10,\n      styles: {\n        node: 'node',\n        marriageNode: 'marriageNode',\n        linage: 'linage',\n        marriage: 'marriage',\n        text: 'nodeText'\n      }\n    });\n\n    var data = this._preprocess(data, opts);\n    var treeBuilder = new TreeBuilder(data.root, data.siblings, opts);\n    treeBuilder.create();\n\n    function _zoomTo (x, y, zoom = 1, duration = 500) {\n      treeBuilder.svg\n        .transition()\n        .duration(duration)\n        .call(\n          treeBuilder.zoom.transform,\n          d3.zoomIdentity\n            .translate(opts.width / 2, opts.height / 2)\n            .scale(zoom)\n            .translate(-x, -y)\n        )\n    }\n\n    return {\n      resetZoom: function (duration = 500) {\n        treeBuilder.svg\n          .transition()\n          .duration(duration)\n          .call(\n            treeBuilder.zoom.transform,\n            d3.zoomIdentity.translate(opts.width / 2, opts.margin.top).scale(1)\n          )\n      },\n      zoomTo: _zoomTo,\n      zoomToNode: function (nodeId, zoom = 2, duration = 500) {\n        const node = _.find(treeBuilder.allNodes, {data: {id: nodeId}})\n        if (node) {\n          _zoomTo(node.x, node.y, zoom, duration)\n        }\n      },\n      zoomToFit: function (duration = 500) {\n        const groupBounds = treeBuilder.g.node().getBBox()\n        const width = groupBounds.width\n        const height = groupBounds.height\n        const fullWidth = treeBuilder.svg.node().clientWidth\n        const fullHeight = treeBuilder.svg.node().clientHeight\n        const scale = 0.95 / Math.max(width / fullWidth, height / fullHeight)\n\n        treeBuilder.svg\n          .transition()\n          .duration(duration)\n          .call(\n            treeBuilder.zoom.transform,\n            d3.zoomIdentity\n              .translate(\n                fullWidth / 2 - scale * (groupBounds.x + width / 2),\n                fullHeight / 2 - scale * (groupBounds.y + height / 2)\n              )\n              .scale(scale)\n          )\n      }\n    }\n  },\n\n  _preprocess: function(data, opts) {\n\n    var siblings = [];\n    var id = 0;\n\n    var root = {\n      name: '',\n      id: id++,\n      hidden: true,\n      children: []\n    };\n\n    var reconstructTree = function(person, parent) {\n\n      // convert to person to d3 node\n      var node = {\n        name: person.name,\n        id: id++,\n        hidden: false,\n        children: [],\n        extra: person.extra,\n        textClass: person.textClass ? person.textClass : opts.styles.text,\n        class: person.class ? person.class : opts.styles.node\n      };\n\n      // hide linages to the hidden root node\n      if (parent == root) {\n        node.noParent = true;\n      }\n\n      // apply depth offset\n      for (var i = 0; i < person.depthOffset; i++) {\n        var pushNode = {\n          name: '',\n          id: id++,\n          hidden: true,\n          children: [],\n          noParent: node.noParent\n        };\n        parent.children.push(pushNode);\n        parent = pushNode;\n      }\n\n      // sort children\n      dTree._sortPersons(person.children, opts);\n\n      // add \"direct\" children\n      _.forEach(person.children, function(child) {\n        reconstructTree(child, node);\n      });\n\n      parent.children.push(node);\n\n      //sort marriages\n      dTree._sortMarriages(person.marriages, opts);\n\n      // go through marriage\n      _.forEach(person.marriages, function(marriage, index) {\n        var m = {\n          name: '',\n          id: id++,\n          hidden: opts.hideMarriageNodes,\n          noParent: true,\n          children: [],\n          isMarriage: true,\n          extra: marriage.extra,\n          class: marriage.class ? marriage.class : opts.styles.marriageNode\n        }\n\n        var sp = marriage.spouse;\n\n        var spouse = {\n          name: sp.name,\n          id: id++,\n          hidden: false,\n          noParent: true,\n          children: [],\n          textClass: sp.textClass ? sp.textClass : opts.styles.text,\n          class: sp.class ? sp.class : opts.styles.node,\n          extra: sp.extra,\n          marriageNode: m\n        };\n\n        parent.children.push(m, spouse);\n\n        dTree._sortPersons(marriage.children, opts);\n        _.forEach(marriage.children, function(child) {\n          reconstructTree(child, m);\n        });\n\n        siblings.push({\n          source: {\n            id: node.id\n          },\n          target: {\n            id: spouse.id\n          },\n          number: index\n        });\n      });\n\n    };\n\n    _.forEach(data, function(person) {\n      reconstructTree(person, root);\n    });\n\n    return {\n      root: d3.hierarchy(root),\n      siblings: siblings\n    };\n\n  },\n\n  _sortPersons: function(persons, opts) {\n    if (persons != undefined) {\n      persons.sort(function(a, b) {\n        return opts.callbacks.nodeSorter.call(this, a.name, a.extra, b.name, b.extra);\n      });\n    }\n    return persons;\n  },\n\n  _sortMarriages: function(marriages, opts) {\n    if (marriages != undefined && Array.isArray(marriages)) {\n      marriages.sort(function(marriageA, marriageB) {\n        var a = marriageA.spouse;\n        var b = marriageB.spouse;\n        return opts.callbacks.nodeSorter.call(this, a.name, a.extra, b.name, b.extra);\n      });\n    }\n    return marriages;\n  }\n\n};\n\nexport default dTree;\n"
  },
  {
    "path": "test/demo/data.json",
    "content": "[{\n  \"name\": \"Niclas Superlongsurname\",\n  \"class\": \"man\",\n  \"textClass\": \"emphasis\",\n  \"marriages\": [{\n    \"spouse\": {\n      \"name\": \"Iliana\",\n      \"class\": \"woman\",\n      \"extra\": {\n        \"nickname\": \"Illi\"\n      }\n    },\n    \"children\": [{\n      \"name\": \"James\",\n      \"class\": \"man\",\n      \"marriages\": [{\n        \"spouse\": {\n          \"name\": \"Alexandra\",\n          \"class\": \"woman\"\n        },\n        \"children\": [{\n          \"name\": \"Eric\",\n          \"class\": \"man\",\n          \"marriages\": [{\n            \"spouse\": {\n              \"name\": \"Eva\",\n              \"class\": \"woman\"\n            }\n          }]\n        }, {\n          \"name\": \"Jane\",\n          \"class\": \"woman\"\n        }, {\n          \"name\": \"Jasper\",\n          \"class\": \"man\"\n        }, {\n          \"name\": \"Emma\",\n          \"class\": \"woman\"\n        }, {\n          \"name\": \"Julia\",\n          \"class\": \"woman\"\n        }, {\n          \"name\": \"Jessica\",\n          \"class\": \"woman\"\n        }]\n      }]\n    }]\n  }]\n}]\n"
  },
  {
    "path": "test/demo/demo.js",
    "content": "var connect = require('connect');\nvar serveStatic = require('serve-static');\nconnect().use(serveStatic(__dirname)).listen(3000);\n"
  },
  {
    "path": "test/demo/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<meta charset=\"utf-8\">\n\n<style type=\"text/css\">\n\tbody {\n\t    font: 10px sans-serif;\n\t}\n\t.linage {\n\t    fill: none;\n\t    stroke: #000;\n\t}\n\t.marriage {\n\t    fill: none;\n\t    stroke: black;\n\t}\n\t.marriageNode {\n\t\t\tbackground-color: black;\n\t\t\tborder-radius: 50%;\n\t}\n\t.man {\n\t    background-color: lightblue;\n\t\t\tborder-style: solid;\n\t\t\tborder-width: 1px;\n\t\t\tbox-sizing: border-box;\n\t}\n\t.woman {\n\t\t\tbackground-color: pink;\n\t\t\tborder-style: solid;\n\t\t\tborder-width: 1px;\n\t\t\tbox-sizing: border-box;\n\t}\n\t.emphasis{\n\t\t\tfont-style: italic;\n\t}\n\tp {\n\t\tpadding:0;\n\t\tmargin:0;\n\t}\n\tsvg {\n\t\tborder-style: solid;\n\t\tborder-width: 1px;\n\t}\n</style>\n\n<script src=\"https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js\"></script>\n<script src=\"https://d3js.org/d3.v4.min.js\"></script>\n<script src=\"dTree.min.js\"></script>\n<body>\n\t\t<h1>Demo</h1>\n    <div id=\"graph\"></div>\n\n\t\t<script>\n      treeJson = d3.json(\"data.json\", function(error, treeData) {\n      \tdTree.init(treeData,\n\t\t\t\t\t{\n\t\t\t\t\t\ttarget: \"#graph\",\n\t\t\t\t\t\tdebug: true,\n\t\t\t\t\t\thideMarriageNodes: true,\n\t\t\t\t\t\tmarriageNodeSize: 5,\n\t\t\t\t\t\theight: 800,\n\t\t\t\t\t\twidth: 1200,\n\t\t\t\t\t\tcallbacks: {\n\t\t\t\t\t\t\tnodeClick: function(name, extra) {\n\t\t\t\t\t\t\t\talert('Click: ' + name);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnodeRightClick: function(name, extra) {\n\t\t\t\t\t\t\t\talert('Right-click: ' + name);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttextRenderer: function(name, extra, textClass) {\n\t\t\t\t\t\t\t\tif (extra && extra.nickname)\n\t\t\t\t\t\t\t\t\tname = name + \" (\" + extra.nickname + \")\";\n\t\t\t\t\t\t\t\treturn \"<p align='center' class='\" + textClass + \"'>\" + name + \"</p>\";\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tmarriageClick: function(extra, id) {\n\t\t\t\t\t\t\t\talert('Clicked marriage node' + id);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tmarriageRightClick: function(extra, id) {\n\t\t\t\t\t\t\t\talert('Right-clicked marriage node' + id);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n    \t});\n    </script>\n</body>\n</html>\n"
  }
]