{"id":14990200,"url":"https://github.com/glouwa/d3-hypertree","last_synced_at":"2025-04-12T02:05:49.398Z","repository":{"id":26600788,"uuid":"109322593","full_name":"glouwa/d3-hypertree","owner":"glouwa","description":"HTML5 SVG Hyperbolic Tree Implementation.","archived":false,"fork":false,"pushed_at":"2022-12-29T07:03:50.000Z","size":26210,"stargazers_count":83,"open_issues_count":31,"forks_count":18,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-12T02:05:42.381Z","etag":null,"topics":["hierarchy","hierarchy-visualization","hyperbolic-browser","hyperbolic-geometry","hyperbolic-tree","information-visualization","visualisation","webcomponent"],"latest_commit_sha":null,"homepage":"https://hyperbolic-tree-of-life.github.io/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/glouwa.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":null,"patreon":"user?u=11112015","open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["https://www.paypal.com/donate?hosted_button_id=EECL5TU2JAN7E"]}},"created_at":"2017-11-02T21:51:17.000Z","updated_at":"2025-03-06T02:31:30.000Z","dependencies_parsed_at":"2023-01-14T05:00:37.268Z","dependency_job_id":null,"html_url":"https://github.com/glouwa/d3-hypertree","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glouwa%2Fd3-hypertree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glouwa%2Fd3-hypertree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glouwa%2Fd3-hypertree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glouwa%2Fd3-hypertree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glouwa","download_url":"https://codeload.github.com/glouwa/d3-hypertree/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248505862,"owners_count":21115354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hierarchy","hierarchy-visualization","hyperbolic-browser","hyperbolic-geometry","hyperbolic-tree","information-visualization","visualisation","webcomponent"],"created_at":"2024-09-24T14:19:41.840Z","updated_at":"2025-04-12T02:05:49.373Z","avatar_url":"https://github.com/glouwa.png","language":"TypeScript","funding_links":["https://patreon.com/user?u=11112015","https://www.paypal.com/donate?hosted_button_id=EECL5TU2JAN7E"],"categories":[],"sub_categories":[],"readme":"\u003c!--\n\u003cp align=\"justify\"\u003e\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://glouwa.github.io/d3-hypertree/\"\u003e\n  \u003cimg src=\"docs/img/screenshot-light-github.png?raw=true\"\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\u003c/p\u003e\n--\u003e\n\n\u003c!--\n\u003ciframe width=\"590\" height=\"590\" src=\"https://glouwa.github.io/\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"\u003e\u003c/iframe\u003e\n\n\u003ciframe width=\"560\" height=\"315\" src=\"http://www.youtube.com/embed/t6kxOXOJj8E\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"\u003e\u003c/iframe\u003e\n--\u003e\n\n# D3-Hypertree\n\u003c!--\n\u003cp align=\"justify\"\u003e\n\u003cp align=\"center\"\u003e\nA Scalable Intercative Web Component for Hyperbolic Tree Visualisations.\n\u003c/p\u003e\n\u003c/p\u003e\n--\u003e\n\n\u003ca href=\"https://glouwa.github.io/d3-hypertree/\"\u003e\u003cimg \nsrc=\"docs/img/screenshot-light-github.png?raw=true\" width=\"170\" align=\"left\" hspace=\"10\" vspace=\"16\"\u003e\u003c/a\u003e\n\n- Compatible to [d3.hierarchy()](https://github.com/d3/d3-hierarchy#hierarchy) \n- Scalable up to 1000 nodes\n- Scalable up to 50k nodes with weight filter and perimeter culling\n- Mouse and touch interaction  \n- Animation API \n- Compatible with WebKit and Blink browser engines\n\u003cbr\u003e\n\n\n\n## Resources\n- [API Reference](https://github.com/glouwa/d3-hypertree/blob/master/docs/readme.md)\n- [Tree of Life Demo](https://hyperbolic-tree-of-life.github.io/)\n- [API Demo](https://glouwa.github.io/d3-hypertree-examples/examples-html/mouse-events/)\n- [HTML Examples](https://github.com/glouwa/d3-hypertree-examples/)\n- [Observable Examples](https://observablehq.com/collection/@glouwa/d3-hypertree/)\n\n## Installation\n```bash\nnpm install d3-hypertree --save\n```\n`node_modules/d3-hypertree/dist/` will contain all necessary files.\n\n\u003cb\u003eOr\u003c/b\u003e download the [latest release](https://cdn.jsdelivr.net/npm/d3-hypertree@1.1.0/dist/)\nof the prebuilt bundle if npm is not used. \nThe prebuilt bundle declares the global variable `hyt`, \ntherefore an import as in the Webpack example below is not necessary.\n\n### Plain HTML\n\nAnd add the following lines to your page:\n```html\n\u003clink  href=\"index-browser-light.css\" rel=\"stylesheet\"\u003e\n\u003cscript src=\"d3-hypertree.js\"\u003e\u003c/script\u003e\n```\n\n### Webpack\nD3-hypertree is tested with webpack. \nRemember to add one of the hypertree css files to your projects.\nTo make the example snippets compatible to the prebuilt bundle,\nthe following usage examples will assume an import like this:  \n```typescript\nimport * as hyt from 'd3-hypertree'\n```\nExperts might prefer to import specific classes like `d3-hypertree/components/hypertree` to optimize bundle size.\n\n### Oservable\nSee basic example and its forks.\n\n## Usage\nThe following examples will guide you through the most important concepts, \nbeginning with the most simple configuration, followed by more complex configurations.\nFor a complete list of configurations parameters \nSee [API Reference](https://github.com/glouwa/d3-hypertree/blob/master/docs/readme.md) \nor section [Cheat Sheet](#Options-Cheat-Sheet)\n\nThe Hypertree constructor takes all configuration parameters, \nand returns a handle for starting animations or updating the data set.\nd3 like callbacks are supplied to the constructor to create a data driven visualisation, \nsee \"Data Driven Configuration\".\n\nGuess what: The hypertree configuration is structured like a hierarchy.\nThe topmost objects component positioning configuration, \nand the visualisation configuration, \ncontaining configuration groups `layout`, `filter`, `interaction`, `geometry`.\nSee section [Cheat Sheet](#options-cheat-sheet) for the complete structure.\nThe following examples will only show some selected options.\n\n### Constructing a Component\nThis first snippet shows the minimal configuration for creating a Hypertree component.\nParent DOM element and data source are required settings.\n```typescript\nmytree = new hyt.Hypertree(\n    {\n        parent: document.body,        \n    },\n    {        \n        dataloader: hyt.loaders.fromFile('data/LDA128-ward.d3.json'),\n        //dataloader: ok=\u003e ok(d3.hierarchy(...)),\n        //dataloader: ok=\u003e ok(d3.stratify()...(table)),        \n    }\n)\n```\nYou can also use `d3-hierarchy` object as data source, as shown in the comments.\nYou will see a hypertree without any labels or other features, see [Demo 1](). \nWhen a Hypertree is attached to a DOM node, all existing child nodes are removed.\n\n### Data Driven Configuration\nVisualizing node properties is achieved by using callbacks with the node as parameter.\nIt is the same concept as d3 uses, and in fact d3 is behind the scenes. \nHowever, in this case the for d3 typical parameter `d` is a always a node,\nnamed `n` in the following examples.\n\nA typical data driven property configuration looks like this:\n```typescript\n    nodeColor: function(n, i, v) {\n        if (n.data.valueX\u003e30) return 'red'\n        else return 'blue'\n    }\n```\nThe given function is called by the renderer, for each frame, for each visible node.\nJavaScript supports a shorter syntax for functions, called lambda expressions.\nMost code snippets will use this syntax equivalent to the function above.\n```typescript\n    nodeColor: n=\u003e (n.data.valueX\u003e30 ? 'red' : 'blue')\n```\n\n#### The Node objects `n`\nTo calculate colors, or other visual properties, the `n` objects provide \nthe following information: \n-   All properties derived from [d3-hierarchy](https://github.com/d3/d3-hierarchy#hierarchy) like: \n    -   User defined data of the node, accessible by `n.data`.\n    -   Hierarchy structure derived from d3 like `parent`, `children` and more.\n-   Hyperbolic coordinates, euclidean coordinates, layout. \n-   Precalculated properties such as labels, image urls or properties hard to compute.\n    See section [User defined Node Initialization](User-defined-Node-Initialization) for a complete list.\n\nSee [TypeScript interface](https://github.com/glouwa/d3-hypertree/blob/master/src/models/n/n.ts) for a complete list of node properties, \nand [d3-hierarchy]() for base functionality.\nKeep in mind, usually its the most simple way to print the object `n` to the console when working with data driven functions.\n\n#### User defined Node Initialization\n`dataInitBFS` and `langInitBFS` are called at startup in Breath first order.\nUse this functions to calculate static properties. \nSome layers expect specific properties in `n.precalc` like `label`, `icon`, `imageHref`, `clickable`, `cell`.\nLabel dimensions and layout weight will be stored by the hypertree component \nin `n.precalc`.\n```typescript\n    // dataInitBFS is called when data set changes.\n    // node properties which do not change during runtime \n    // should be set in this function.\n    // this way calculations are not necessary for each frame.\n    dataInitBFS: (ht, n)=\u003e {\n        if (n.mergeId == 12)\n            n.precalc.imageHref = 'img/example.png'   \n    }, \n    // is called when data or language is changed, \n    // otherwise similar to dataInitBFS.\n    // typically node labels are calculated in this function.\n    langInitBFS: (ht, n)=\u003e {                        \n        n.precalc.label = `Label ${n.mergeId} / ${n.precalc.layoutWeight}`\n    }\n```\n\n### Layer Configuration \nThis example shows how to add labels and images to nodes by\nenabling the according layers, \nand providing necessary node properties `n.precalc.label` and `n.precalc.imageHref`.\n\nAll Layers have the prperties `invisible` and `hideOnDrag`. \nUse invisible to deactivate a layer, use hideOnDrag to increase framerate \nif necessary. hideOnDrag will hide the layer only when animations or interactions are active.\nLayers might contain additional configuration properties, \nsee [Cheat Sheet](#options-cheat-sheet) for a complete list of options.\n```typescript\nconst mytree = new hyt.Hypertree(\n    { parent: document.body },\n    { \n        dataloader: hyt.loaders.generators.nT1,\n        dataInitBFS: (ht, n)=\u003e {\n            if (n.mergeId == 12)\n                n.precalc.imageHref = 'img/example.png'         \n        }, \n        langInitBFS: (ht, n)=\u003e {                        \n            n.precalc.label = `Label ${n.mergeId} / ${n.precalc.layoutWeight}`\n        },        \n        geometry: {                        \n            layerOptions:       {                \n                'cells':       { invisible: true, hideOnDrag: true },                \n                'images':      { width: .1, height: .1 },\n                'link-arcs':   { \n                    linkColor: n=\u003e {\n                        if (n.mergeId == 12) return 'orange'\n                        return undefined\n                    }\n                }, \n                'nodes': {                     \n                    nodeColor: n=\u003e {\n                        if (n.mergeId == 12) return 'yellow'\n                        if (!n.children) return 'red'\n                        return '#a5d6a7'\n                    }\n                }\n            },                                               \n        }\n    }\n)\n```\nIt is possible to write [custom layer sets](https://github.com/glouwa/d3-hypertree/blob/master/src/models/hypertree/preset-layers.ts), and apply it by setting the `geometry.layers` property. \n\n\n### Non blocking API for Animations and Data updates\nThis example shows how to attach an annimation to the load process.\nThe Hypertree compoenent provides a JavaScript `Promise` for initialisation.\nAttach promises to handle asyncronouse execution.\nTo start animations use the promise returning functions in `mytree.api` whereby `mytree` is your hypertree component variable.\n```typescript\nconst mytree = new hyt.Hypertree(\n    { parent: document.body }, \n    { dataloader: hyt.loaders.generators.nT1 }\n)\n\nvar animationNode1 = mytree.data.children[1]\nvar animationNode2 = mytree.data.children[0].children[1]\n\nmytree.initPromise\n    .then(()=\u003e new Promise((ok, err)=\u003e mytree.animateUp(ok, err)))\n    .then(()=\u003e mytree.api.gotoNode(animationNode1))\n    .then(()=\u003e mytree.api.gotoNode(animationNode2))\n    .then(()=\u003e mytree.api.gotoHome())\n    .then(()=\u003e mytree.api.gotoλ(.25))\n    .then(()=\u003e mytree.api.gotoλ(.5))\n    .then(()=\u003e mytree.api.gotoλ(.4))\n    .then(()=\u003e mytree.drawDetailFrame())\n```\n\n\n### Interaction Event Handling\nBasically some callbacks. \nTypical functions used in them:\n- uer action like open view\n- toggle path \n- ripple\n- update path like root-hover path, or root-centernode path\n- got animaion\n```typescript\nmytree = new hyt.Hypertree(\n    { parent: document.body },\n    { \n        dataloader: hyt.loaders.generators.nT1,\n        interaction: {\n\n            // the node click area is the voronoi cell in euclidean space.\n            // this way, wherever the user clicks, a node can be associated.\n            onNodeClick: (n, m, l)=\u003e { \n                console.log(`#onNodeClick: Node=${n}, click coordinates=${m}, source layer=${l}`)\n\n                mytree.api.goto({ re:-n.layout.z.re, im:-n.layout.z.im }, null)\n                    .then(()=\u003e l.view.hypertree.drawDetailFrame())       \n                /*\n                var s = n.ancestors().find(e=\u003e true)                \n                ud.view.hypertree.api.toggleSelection(s)\n                ud.view.hypertree.args.interaction.onNodeSelect(s)\n                */\n            },\n            \n            // center node is defined as node with minimal distance to the center.\n            onCenterNodeChange: n=\u003e console.log(`#onCenterNodeChange: Node=${n}`)\n        }       \n    }\n)\n```\n\n### Coordinate Systems and Transformations\n\n## Options Cheat Sheet\n\nThis example shows a component instantiation using all configuration options. \nIt uses TypeScript annotations to show parameter types.\n\nFor detailed documentation and a complete list of features see \n[API Reference](https://github.com/glouwa/d3-hypertree/blob/master/docs/readme.md).\n\n```typescript\nnew hyt.Hypertree(\n    {\n        id:                     string\n        classes:                string\n        parent:                 HTMLElement        \n        preserveAspectRatio:    'xMidYMid meet' | ...\n    },\n    {\n        dataloader?:            (ok:(root:N, t0:number, dl:number)=\u003evoid, err:(err)=\u003evoid)=\u003e void   \n        dataInitBFS:            (ht:Hypertree, n:N)=\u003e void\n        langInitBFS:            (ht:Hypertree, n:N)=\u003e void         \n        layout: {\n            type:               (root:N, t?:number, noRecursion?:boolean) =\u003e void\n            weight:             (n:N)=\u003e number\n            linklen:            (n:N)=\u003e number            \n            rootWedge: {\n                orientation:    number\n                angle:          number\n            }\n        }\n        filter: {\n            cullingRadius:      number\n            weightFilter:       null | number | {            \n                weight:         (n)=\u003e number\n                rangeWeight:    { min:number, max:number }\n                rangeNodes:     { min:number, max:number }\n                alpha:          number\n            }\n            focusExtension:     number\n            maxFocusRadius:     number            \n            maxlabels:          number       \n        }       \n        geometry: {\n            layers:             ((v, ls:IUnitDisk)=\u003e ILayer)[]\n            layerOptions: {\n                layerbase: {\n                    invisible:  false\n                    hideOnDrag: false                    \n                },\n                cells: {\n                    invisible:         false\n                    hideOnDrag:        false\n                    fill:              (n:N)=\u003e color\n                    stroke:            (n:N)=\u003e color\n                    strokeWidth:       (n:N)=\u003e number\n                },\n                links: {\n                    stroke:            (n:N)=\u003e color\n                    strokeWidth:       (n:N)=\u003e number\n                    linkCurvature:     '+' | '-' | 'l'\n                },\n                nodes: {\n                    fill:              (n:N)=\u003e color\n                    stroke:            (n:N)=\u003e color\n                    strokeWidth:       (n:N)=\u003e number\n                },\n                labels: {                                        \n                    font:              string                    \n                    delta:             (n:N)=\u003e C\n                    color:             (n:N)=\u003e color\n                    background:        (n:N)=\u003e (undefined | color)\n                    backgroundHeight:  number                    \n                },\n                /*\n                'cells',\n                'culling-r', 'mouse-r', 'focus-r', 'labels-r-𝐖', 'λ', 'zerozero-circle',\n                'center-node', 'path-arcs', 'stem-arc', 'nodes', 'symbols', 'images', 'emojis',  \n                'labels', 'labels2', 'labels-force', \n                'traces',\n                */\n            }\n            nodeRadius:        (ud:IUnitDisk, n:N)=\u003e number\n            nodeScale:         (n:N)=\u003e number\n            nodeFilter:        (n:N)=\u003e boolean\n        }\n        interaction: {            \n            onNodeClick:        (n:N, m:C, l:ILayer)=\u003e void\n            onCenterNodeChange: (n:N)=\u003e void \n            λbounds:            [ number, number ]\n            wheelSensitivity:   number\n            mouseRadius:        number\n        }\n    }\n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglouwa%2Fd3-hypertree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglouwa%2Fd3-hypertree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglouwa%2Fd3-hypertree/lists"}