- Compatible to d3.hierarchy()
- Scalable up to 1000 nodes
- Scalable up to 50k nodes with weight filter and perimeter culling
- Mouse and touch interaction
- Animation API
- Compatible with WebKit and Blink browser engines
npm install d3-hypertree --save
node_modules/d3-hypertree/dist/
will contain all necessary files.
Or download the latest release
of the prebuilt bundle if npm is not used.
The prebuilt bundle declares the global variable hyt
,
therefore an import as in the Webpack example below is not necessary.
And add the following lines to your page:
<link href="index-browser-light.css" rel="stylesheet">
<script src="d3-hypertree.js"></script>
D3-hypertree is tested with webpack. Remember to add one of the hypertree css files to your projects. To make the example snippets compatible to the prebuilt bundle, the following usage examples will assume an import like this:
import * as hyt from 'd3-hypertree'
Experts might prefer to import specific classes like d3-hypertree/components/hypertree
to optimize bundle size.
See basic example and its forks.
The following examples will guide you through the most important concepts, beginning with the most simple configuration, followed by more complex configurations. For a complete list of configurations parameters See API Reference or section Cheat Sheet
The Hypertree constructor takes all configuration parameters, and returns a handle for starting animations or updating the data set. d3 like callbacks are supplied to the constructor to create a data driven visualisation, see "Data Driven Configuration".
Guess what: The hypertree configuration is structured like a hierarchy.
The topmost objects component positioning configuration,
and the visualisation configuration,
containing configuration groups layout
, filter
, interaction
, geometry
.
See section Cheat Sheet for the complete structure.
The following examples will only show some selected options.
This first snippet shows the minimal configuration for creating a Hypertree component. Parent DOM element and data source are required settings.
mytree = new hyt.Hypertree(
{
parent: document.body,
},
{
dataloader: hyt.loaders.fromFile('data/LDA128-ward.d3.json'),
//dataloader: ok=> ok(d3.hierarchy(...)),
//dataloader: ok=> ok(d3.stratify()...(table)),
}
)
You can also use d3-hierarchy
object as data source, as shown in the comments.
You will see a hypertree without any labels or other features, see Demo 1.
When a Hypertree is attached to a DOM node, all existing child nodes are removed.
Visualizing node properties is achieved by using callbacks with the node as parameter.
It is the same concept as d3 uses, and in fact d3 is behind the scenes.
However, in this case the for d3 typical parameter d
is a always a node,
named n
in the following examples.
A typical data driven property configuration looks like this:
nodeColor: function(n, i, v) {
if (n.data.valueX>30) return 'red'
else return 'blue'
}
The given function is called by the renderer, for each frame, for each visible node. JavaScript supports a shorter syntax for functions, called lambda expressions. Most code snippets will use this syntax equivalent to the function above.
nodeColor: n=> (n.data.valueX>30 ? 'red' : 'blue')
To calculate colors, or other visual properties, the n
objects provide
the following information:
- All properties derived from d3-hierarchy like:
- User defined data of the node, accessible by
n.data
. - Hierarchy structure derived from d3 like
parent
,children
and more.
- User defined data of the node, accessible by
- Hyperbolic coordinates, euclidean coordinates, layout.
- Precalculated properties such as labels, image urls or properties hard to compute. See section User defined Node Initialization for a complete list.
See TypeScript interface for a complete list of node properties,
and d3-hierarchy for base functionality.
Keep in mind, usually its the most simple way to print the object n
to the console when working with data driven functions.
dataInitBFS
and langInitBFS
are called at startup in Breath first order.
Use this functions to calculate static properties.
Some layers expect specific properties in n.precalc
like label
, icon
, imageHref
, clickable
, cell
.
Label dimensions and layout weight will be stored by the hypertree component
in n.precalc
.
// dataInitBFS is called when data set changes.
// node properties which do not change during runtime
// should be set in this function.
// this way calculations are not necessary for each frame.
dataInitBFS: (ht, n)=> {
if (n.mergeId == 12)
n.precalc.imageHref = 'img/example.png'
},
// is called when data or language is changed,
// otherwise similar to dataInitBFS.
// typically node labels are calculated in this function.
langInitBFS: (ht, n)=> {
n.precalc.label = `Label ${n.mergeId} / ${n.precalc.layoutWeight}`
}
This example shows how to add labels and images to nodes by
enabling the according layers,
and providing necessary node properties n.precalc.label
and n.precalc.imageHref
.
All Layers have the prperties invisible
and hideOnDrag
.
Use invisible to deactivate a layer, use hideOnDrag to increase framerate
if necessary. hideOnDrag will hide the layer only when animations or interactions are active.
Layers might contain additional configuration properties,
see Cheat Sheet for a complete list of options.
const mytree = new hyt.Hypertree(
{ parent: document.body },
{
dataloader: hyt.loaders.generators.nT1,
dataInitBFS: (ht, n)=> {
if (n.mergeId == 12)
n.precalc.imageHref = 'img/example.png'
},
langInitBFS: (ht, n)=> {
n.precalc.label = `Label ${n.mergeId} / ${n.precalc.layoutWeight}`
},
geometry: {
layerOptions: {
'cells': { invisible: true, hideOnDrag: true },
'images': { width: .1, height: .1 },
'link-arcs': {
linkColor: n=> {
if (n.mergeId == 12) return 'orange'
return undefined
}
},
'nodes': {
nodeColor: n=> {
if (n.mergeId == 12) return 'yellow'
if (!n.children) return 'red'
return '#a5d6a7'
}
}
},
}
}
)
It is possible to write custom layer sets, and apply it by setting the geometry.layers
property.
This example shows how to attach an annimation to the load process.
The Hypertree compoenent provides a JavaScript Promise
for initialisation.
Attach promises to handle asyncronouse execution.
To start animations use the promise returning functions in mytree.api
whereby mytree
is your hypertree component variable.
const mytree = new hyt.Hypertree(
{ parent: document.body },
{ dataloader: hyt.loaders.generators.nT1 }
)
var animationNode1 = mytree.data.children[1]
var animationNode2 = mytree.data.children[0].children[1]
mytree.initPromise
.then(()=> new Promise((ok, err)=> mytree.animateUp(ok, err)))
.then(()=> mytree.api.gotoNode(animationNode1))
.then(()=> mytree.api.gotoNode(animationNode2))
.then(()=> mytree.api.gotoHome())
.then(()=> mytree.api.gotoλ(.25))
.then(()=> mytree.api.gotoλ(.5))
.then(()=> mytree.api.gotoλ(.4))
.then(()=> mytree.drawDetailFrame())
Basically some callbacks. Typical functions used in them:
- uer action like open view
- toggle path
- ripple
- update path like root-hover path, or root-centernode path
- got animaion
mytree = new hyt.Hypertree(
{ parent: document.body },
{
dataloader: hyt.loaders.generators.nT1,
interaction: {
// the node click area is the voronoi cell in euclidean space.
// this way, wherever the user clicks, a node can be associated.
onNodeClick: (n, m, l)=> {
console.log(`#onNodeClick: Node=${n}, click coordinates=${m}, source layer=${l}`)
mytree.api.goto({ re:-n.layout.z.re, im:-n.layout.z.im }, null)
.then(()=> l.view.hypertree.drawDetailFrame())
/*
var s = n.ancestors().find(e=> true)
ud.view.hypertree.api.toggleSelection(s)
ud.view.hypertree.args.interaction.onNodeSelect(s)
*/
},
// center node is defined as node with minimal distance to the center.
onCenterNodeChange: n=> console.log(`#onCenterNodeChange: Node=${n}`)
}
}
)
This example shows a component instantiation using all configuration options. It uses TypeScript annotations to show parameter types.
For detailed documentation and a complete list of features see API Reference.
new hyt.Hypertree(
{
id: string
classes: string
parent: HTMLElement
preserveAspectRatio: 'xMidYMid meet' | ...
},
{
dataloader?: (ok:(root:N, t0:number, dl:number)=>void, err:(err)=>void)=> void
dataInitBFS: (ht:Hypertree, n:N)=> void
langInitBFS: (ht:Hypertree, n:N)=> void
layout: {
type: (root:N, t?:number, noRecursion?:boolean) => void
weight: (n:N)=> number
linklen: (n:N)=> number
rootWedge: {
orientation: number
angle: number
}
}
filter: {
cullingRadius: number
weightFilter: null | number | {
weight: (n)=> number
rangeWeight: { min:number, max:number }
rangeNodes: { min:number, max:number }
alpha: number
}
focusExtension: number
maxFocusRadius: number
maxlabels: number
}
geometry: {
layers: ((v, ls:IUnitDisk)=> ILayer)[]
layerOptions: {
layerbase: {
invisible: false
hideOnDrag: false
},
cells: {
invisible: false
hideOnDrag: false
fill: (n:N)=> color
stroke: (n:N)=> color
strokeWidth: (n:N)=> number
},
links: {
stroke: (n:N)=> color
strokeWidth: (n:N)=> number
linkCurvature: '+' | '-' | 'l'
},
nodes: {
fill: (n:N)=> color
stroke: (n:N)=> color
strokeWidth: (n:N)=> number
},
labels: {
font: string
delta: (n:N)=> C
color: (n:N)=> color
background: (n:N)=> (undefined | color)
backgroundHeight: number
},
/*
'cells',
'culling-r', 'mouse-r', 'focus-r', 'labels-r-𝐖', 'λ', 'zerozero-circle',
'center-node', 'path-arcs', 'stem-arc', 'nodes', 'symbols', 'images', 'emojis',
'labels', 'labels2', 'labels-force',
'traces',
*/
}
nodeRadius: (ud:IUnitDisk, n:N)=> number
nodeScale: (n:N)=> number
nodeFilter: (n:N)=> boolean
}
interaction: {
onNodeClick: (n:N, m:C, l:ILayer)=> void
onCenterNodeChange: (n:N)=> void
λbounds: [ number, number ]
wheelSensitivity: number
mouseRadius: number
}
}
)