Visualizing a hierarchy with a configurable indented tree.
This approach is based on this bl.ock from Mike Bostock and this codepen by Brendan Dougan and is implemented with d3-template as a reusable d3 chart.
More examples demonstrating specific API calls:
- data format - JSON file (readme example)
- data format - hierarchical csv file (readme example)
- data format - relational csv file (readme example)
- data format - JSON data from variable
- data format - relational csv file
- data format - relational csv file - 2
- data format - relational csv data embedded
- data format - hierarchical csv file
- data format - hierarchical csv file, different key
- data format - hierarchical csv data embedded
- data format - hierarchical csv data embedded, different key
- myChart.linkColor()
- myChart.linkColor() - 2
- myChart.linkHeight()
- myChart.linkLabel()
- myChart.linkLabel() - 2
- myChart.linkLabel() - 3
- myChart.linkLabel() - 4
- myChart.linkLabel() - 5
- myChart.linkStrength()
- myChart.linkStrength() - 2
- myChart.linkStrength() - 3
- myChart.linkWidth()
- myChart.linkWidth() - 2
- myChart.nodeBar()
- myChart.nodeBar() - 2
- myChart.nodeBar() - 3
- myChart.nodeBar() - 4
- myChart.nodeImageFile()
- myChart.nodeImageFile() - 2
- myChart.nodeImageFile() - 3
- myChart.nodeImageFile() - 4
- myChart.nodeImageSelection()
- myChart.nodeImageSelection() - 2
- myChart.nodeLabelPadding()
- myChart.nodeSort()
- myChart.nodeSort() - 2
- myChart.nodeSort() - 3
- custom convertTypes function
- myChart.alignLeaves()
- myChart.defaultColor()
- myChart.formatDefaultLocale()
- myChart.margin()
- myChart.propagateValue()
Here is a minimal template sufficient to call d3-indented-tree. A reference to the data is assigned to the dataSpec.source
object property.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/EE2dev/d3-indented-tree/dist/latest/d3-indented-tree.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/EE2dev/d3-indented-tree/dist/latest/d3-indented-tree.css">
</head>
<body>
<script>
const dataSpec = {source: "path/to/data/data.json"}; // update this path accordingly
const myChart = d3.indentedTree(dataSpec);
showChart(myChart);
function showChart(_chart) {
d3.select("body")
.append("div")
.attr("class", "chart")
.call(_chart);
}
</script>
</body>
</html>
Hierarchical data can be specified in one of the following three data formats. The examples contain the same data in the supported formats.
A JSON file format, containing a key field for each node. By default, the key field for each node should have the property name "key"
. If the key field has a name other than "key"
, the dataSpec object has to reference it (see below for the example which has the field "name"
as a key).
{
"name": "World",
"children": [
{
"name": "Asia",
"population": 4436,
"children": [
{
"name": "China",
"population": 1420
},
{
"name": "India",
"population": 1369
}
]
},
{
"name": "Africa",
"population": 1216
},
{
"name": "Europe",
"population": 739
},
{
"name": "North America",
"population": 579,
"children": [
{
"name": "USA",
"population": 329
}
]
},
{
"name": "South America",
"population": 423
},
{
"name": "Oceania",
"population": 38
}
]
}
Then the javascript part would look like:
...
<script>
const dataSpec = {
source: "../data/data1.json",
key: "name",
};
const myChart = d3.indentedTree(dataSpec);
...
Alternatively, the JSON source can also reference a JSON object which has been created in the javascript part instead of reading it from a file. Then the javascript part would look like:
...
<script>
const myJSON = { // make your own JSON here };
const dataSpec = {
source: myJSON,
key: "name",
};
const myChart = d3.indentedTree(dataSpec);
...
A csv file format consisting of one row for each node. Each row contains key
as the key in the first column, parent
as its parent key in the second column and the remaning data for each node.
key,parent,population
World,,
Asia,World,4436
China,Asia,1420
India,Asia,1369
Africa,World,1216
Europe,World,739
North America,World,579
USA,North America,329
South America,World,423
Oceania,World,38
Then the javascript part would look like:
...
<script>
const dataSpec = {
source: "../data/data3.csv",
};
const myChart = d3.indentedTree(dataSpec);
...
A csv file format consisting of one row for each leaf. Each row contains the keys of each node traversed from the root down to the leaf and the corresponding data for that leaf.
The keys for each level reside in their corresponding columns. The dataSpec object has to reference the columns of each level in its top-down traversal order with the property hierarchyLevels
(see below for the example). The first element contains a string that is used as the root while the other elements reference column names. If the first element is "$"
then the root node will stay without a node label. Note that this representation can assign other data to the root node.
Internally, the separator "$"
is used when the node key is build by concatenating the corresponding columns. If this character is contained in the data, the separator can be changed by specifying the separator
property of the dataSpec object. The column name "__he_name"
is reserved internally for storing the node label.
continent,country,population
Asia,,4436
Asia,China,1420
Asia,India,1369
Africa,,1216
Europe,,739
North America,,579
North America,USA,329
South America,,423
Oceania,,38
...
<script>
const dataSpec = {
source: "../data/data2.csv",
hierarchyLevels: ["World", "continent", "country"],
};
const myChart = d3.indentedTree(dataSpec);
...
In case you want to run d3-indented-tree without a server, you can put your data into a html node. The format of the data can be either CSV (hierarchical) or CSV (relational). If the data is in JSON format you can also include it inline.
The data source is then referenced in the dataSpec
object by assigning the source
property to a string denoting the selector to the node with the data.
An example for embedded hierarchical csv data:
...
<aside id="data">
key,parent,population
World,,
Asia,World,4436
China,Asia,1420
India,Asia,1369
Africa,World,1216
Europe,World,739
North America,World,579
USA,North America,329
South America,World,423
Oceania,World,38
</aside>
...
Then the javascript part would look like:
...
<script>
const dataSpec = {
source: "aside#data",
};
const myChart = d3.indentedTree(dataSpec);
...
An example for embedded relational csv data:
...
<aside id="data">
continent,country,population
Asia,,4436
Asia,China,1420
Asia,India,1369
Africa,,1216
Europe,,739
North America,,579
North America,USA,329
South America,,423
Oceania,,38
</aside>
...
...
<script>
const dataSpec = {
source: "aside#data",
hierarchyLevels: ["World", "continent", "country"],
};
const myChart = d3.indentedTree(dataSpec);
...
The object (named dataSpec above) which is passed to the function d3.indentedTree()
can have the following properties:
source
: string containing the path/URL to the data or the selector referencing the DOM element containing the data.hierarchyLevels
: array containing columns of each level in its top-down traversal order when the refered data is in the csv relational format.separator
: string In case the data comes in the csv relational format, the separator is used internally to concatenate columns. The default is "$". If the data contains a "$", the separator has to be changed to another string/character not contained in the data.delimiter
: string containing the delimiter used in the csv data.convertTypes
: function that converts the data to appropriate types which is relevant formyChart.nodeSort()
. If a conversion function is specified, the specified function is invoked for each row, being passed an object representing the current row (d), the index (i) starting at zero for the first non-header row, and the array of column names. Here is more documentation about this callback function. Alternatively, the string"none"
can be assigned toconvertTypes
to prevent conversions, then all columns are stored as string. The default is d3.autoType.
Transitions the alignment of the leaves of the hierarchy. If leaves are aligned, all leaves start at the same horizontal position (cluster layout). If myChart.linkWidth()
is set dynamically (by referencing a field), this function has no effect.
- the first argument is boolean referencing if all leaves are aligned at the same depth (default is
false
). - with no argument returns if the leaves are aligned at the same depth.
Transitions to the new color of the links. The horizontal link to is denoted by its color.
-
argument:
- to set the link color dynamically, provide the name of a field as a string (default is
"value"
).
- to set the link color dynamically, provide the name of a field as a string (default is
-
argument (optional):
- An object with the following properties can be used to further specify the mapping:
scale
defines the scale used to map the values to the color (default is the identity function(value) => value
assuming the field contains a valid color). This scale callback function is invoked for each instance of the field provided as first argument.inherit
refers to the color of the vertical links. In caseinherit
is set tofalse
the default color will be used for the vertical links, iftrue
the color of the parent is used (default istrue
).
- An object with the following properties can be used to further specify the mapping:
- with no argument returns the field used for the color.
Transitions the height (vertical length) of the links.
- the first argument is an integer referencing link height in pixels (default is
20
). - with no argument returns the height of the links.
Transitions to the new number label on top of the links.
-
argument:
- to set the label dynamically, provide the name of a field as a string. The values of the field can be numeric or string.
- to switch the label on or off provide a boolean.
-
argument (optional):
- An object with the following properties can be used to further specify the mapping:
-
unit
specifies a suffix string for the label (default is""
). -
format
refers to the format string of the label number as the format specifier for d3-format (examples). (default is",.0f"
). -
locale
is an object overriding the default locale format with the specified locale format. The locale is affecting the display of the link label if theformat
property is specified. See also myChart.formatDefaultLocale(). -
onTop
specifies a boolean property denoting whether to place the label on top of (and overlaying) the link (onTop: true
) or to place the label (horizontally) above the label. The default value istrue
. IfonTop
is set to false and the label overlaps with the previous link due to increased font size, the linkHeight can be increased. -
align
specifies a string property denoting how to align the label horizontally and how to set the text-anchor. Passing"start"
,"middle"
or"end"
aligns the label horizontally at the beginning, centered or at the end on the link."aligned"
right-aligns the labels of the siblings (centers the longest label per branch on the shortest link). Default is"aligned"
. -
always
specifies a boolean property denoting whether to to always display the label. If this property is set tofalse
then the link label is not displayed if it is longer than the link width. The default value isfalse
. -
color
is a function that sets the color of the link label. This callback function is called for each link by passing an object with its fields to it. (default is() => "black"
).
-
- An object with the following properties can be used to further specify the mapping:
- No argument:
- with no argument the function returns the name of the numeric field for the link labels.
Transitions to the new strength (thickness) of the links. The horizontal link to and the vertical link from a node is denoting its strength.
-
argument:
- to statically set all the links to the strength, call this function with an integer argument, which denotes the thickness in pixels (default is
1
). - to set the link strength dynamically, provide the name of a numeric field as a string.
- to statically set all the links to the strength, call this function with an integer argument, which denotes the thickness in pixels (default is
-
argument (optional):
- An object with the following properties can be used to further specify the mapping:
scale
defines the scale used to map the values to the strength (default isd3.scaleLinear()
).range
refers to the range of the scale (default is[1,10]
).
- An object with the following properties can be used to further specify the mapping:
- No argument:
- with no argument the function returns the static strength of the links.
Transitions to the new width (horizontal length) of the links. The horizontal link to a node is affected by its corresponding value.
-
argument:
- to statically set all the links to the width, call this function with an integer argument, which denotes the width in pixels (default is
30
). - to set the link width dynamically, provide the name of a numeric field as a string.
- to statically set all the links to the width, call this function with an integer argument, which denotes the width in pixels (default is
-
argument (optional):
- An object with the following properties can be used to further specify the mapping:
scale
defines the scale used to map the values to the width (default isd3.scaleLinear()
).range
refers to the range of the scale (default is[15, 100]
).
- An object with the following properties can be used to further specify the mapping:
- No argument:
- with no argument the function returns the static width of the links.
Displays/ transitions the bar for each node to the new value.
-
argument:
- to set the node bar dynamically, provide the name of a field as a string. The values of the field must be numeric. If node bars should be display for some but not all nodes, e.g. not for internal nodes, simply have a missing value for this field.
- to switch the node bars on or off provide a boolean.
-
argument (optional):
- An object with the following properties can be used to further specify the mapping:
label
specifies the string field name for the node bar label (default is the field which is specified as the first argument and used for drawing the bars).labelInside
specifies a boolean property denoting whether to place the label inside the bars (labelInside: true
) or next to the bar (default isfalse
).unit
specifies a suffix string for the node bar label (default is""
).format
refers to the format string of the node bar label number as the format specifier for d3-format (examples). (default is",.0f"
).locale
is an object overriding the default locale format with the specified locale format. The locale is affecting the display of the link label if theformat
property is specified. See also myChart.formatDefaultLocale().textFill
is a function that sets the color of the node bar label. This callback function is called for each node by passing an object with its fields to it. (default is its (=.node .nodeLabel
) static CSS property which isblack
).rectFill
is a function that sets the fill color of the node bar. This callback function is called for each node by passing an object with its fields to it. (default is its ( for values >= 0:.node .node-bar-positive
, for values <0:.node .node-bar-negative
) static CSS property which issteelblue
, anddarkorange
, respectively).rectStroke
is a function that sets the stroke color of the node bar. This callback function is called for each node by passing an object with its fields to it. (default is its (=.node .node-bar
) static CSS property which isgrey
).scale
is a function defining the scale used to map the values to the node bar width (default isd3.scaleLinear()
).domain
is an array refering to the domain of the scale (default is calculated based on the extent of the corresponding field values).range
is an array refering to the range of the scale (default is[0, 200]
). In case the domain contains positive and negative values, the range is used separately but scaled correspondingly for positive and negative values to compute the width of their node bars.updateScale
specifies a boolean property denoting whether the current scale should be reused for a transition. (default istrue
).translateX
specifies a number property denoting the minimal distance in pixels for the connector between the node label and the node bar. (default is50
).
- An object with the following properties can be used to further specify the mapping:
- No argument:
- with no argument the function returns the name of the numeric field for the node bars.
Collapses the tree at the specified nodes.
-
argument:
- an array of nodes that specifies which nodes should be collapsed.
The domain of the array elements depends on the chosen node property. By default, the corresponding node property is
"key"
. Thus the argument["a", "b", "c"]
would collapse the nodes with the keys"a"
,"b"
and"c"
, respectively. Alternatively, the argument[1, 2]
with"depth"
being the corresponding node property would lead to all node of depth 1 or 2 to be collapsed. - alternatively, a string or number denoting the node(s) to be collapsed.
- an array of nodes that specifies which nodes should be collapsed.
The domain of the array elements depends on the chosen node property. By default, the corresponding node property is
-
argument: (optional):
- An object with the following properties can be used to further specify the mapping:
property
specifies the node property (default is"key"
). Note that"key"
might deviate from the displayed node name. Other options are:"id"
: the node id"depth"
: the depth of the node. The depth is zero for the root node, and increasing by one for each descendant generation."height"
: the height of the node. The height is zero for leaf nodes, and the greatest distance from any descendant leaf for internal nodes.
propagate
specifies a boolean property denoting whether all descendant nodes should be collapsed as well. Default istrue
.
- An object with the following properties can be used to further specify the mapping:
With no arguments returns the array of the specified nodes to be collapsed.
Expands the tree at the specified nodes.
-
argument:
- an array of nodes that specifies which nodes should be expanded.
The domain of the array elements depends on the chosen node property. By default, the corresponding node property is
"key"
. Thus the argument["a", "b", "c"]
would expand the nodes with the keys"a"
,"b"
and"c"
, respectively. Alternatively, the argument[1, 2]
with"depth"
being the corresponding node property would lead to all node of depth 1 or 2 to be expanded. - alternatively, a string or number denoting the node(s) to be expanded.
- an array of nodes that specifies which nodes should be expanded.
The domain of the array elements depends on the chosen node property. By default, the corresponding node property is
-
argument: (optional):
- An object with the following properties can be used to further specify the mapping:
property
specifies the node property (default is"key"
). Note that"key"
might deviate from the displayed node name. Other options are:"id"
: the node id"depth"
: the depth of the node. The depth is zero for the root node, and increasing by one for each descendant generation."height"
: the height of the node. The height is zero for leaf nodes, and the greatest distance from any descendant leaf for internal nodes.
propagate
specifies a boolean property denoting whether all descendant nodes should be expanded as well. Default istrue
.
- An object with the following properties can be used to further specify the mapping:
With no arguments returns the array of the specified nodes to be expanded.
Sets the node images based on an image file.
-
argument:
- a callback function which returns for each selected node a URL to the image to be used for this node. The function is evaluated for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). E.g. with the current datum (d) the URL can be read from a field attached to each node.
- alternatively, a string denoting the URL to an image to be used for each node.
-
argument: (optional):
- An object with the following properties can be used to further specify the mapping:
width
specifies the width of the image (default is10
).height
specifies the height of the image (default is10
).x
specifies thex
position of upper left corner of the image relative to the node (default is-1 * width / 2
).y
specifies they
position of upper left corner of the image relative to the node (default is-1 * height / 2
).preserveAspectRatio
specifies the preserveAspectRatio (default is"xMidYMid meet"
).setBackground
determines if the background under the image should be fill with the background color first (e.g. in case the image has a transparant background) (default isfalse
).default
determines if the default node image should be used for nodes with no referenced images (default istrue
).
- An object with the following properties can be used to further specify the mapping:
With no arguments returns the callback function for the node images.
# myChart.nodeImageSelection() <>
Sets the node images based on a selection.
-
argument:
- a callback function, which is called with a selection as an argument containing all newly entered
g.node
's. The function is evaluated for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). - the callback function is expected to append to each node a graphical element as the node image. To enter a different image based on the node being expandable or not, the attached datum
d._children
can be used. - if the first argument is boolean
false
no node image is shown.
- a callback function, which is called with a selection as an argument containing all newly entered
-
argument: (optional)
- a callback function, which is called with a selection as an argument containing all newly updated
g.node
's. - the callback function is expected to update the graphical element as the node image for each node. The update function should be provided if a different node image based on the node being expandable or not is provided.
- a callback function, which is called with a selection as an argument containing all newly updated
With no arguments returns the callback function for all newly entered g.node
's.
# myChart.nodeLabelLength() <>
Sets the maximum number of characters displayed as node label. All remaining characters are truncated and displayed as ...
.
- the first argument is an integer referencing the maximum number of characters display as node label (default is
50
). - with no argument returns the maximum number of characters displayed as node label.
# myChart.nodeLabelPadding() <>
Adjusts the left-alignment of the node label.
- the first argument is an integer referencing the number of pixels padded left to the start of the node label (default is
10
). - with no argument returns the number of pixels padded left to the start of the node label.
Sorts the nodes of the tree. The type conversion specified in dataSpec.convertTypes
determines how the order is applied.
-
argument:
- A string denoting the name of a field based on which the nodes should be sorted.
-
argument: (optional):
- An object with the following properties can be used to further specify the sorting:
ascending
(boolean) specifies whether the order should be ascending or descending (default isfalse
).sortByHeight
(boolean) specifies whether the order should be determined by the height of the nodes first (default isfalse
).
- An object with the following properties can be used to further specify the sorting:
With no arguments returns the name of the field based on which the nodes are sorted.
Sets the default tooltip for the nodes.
- argument:
- to set the tooltip title dynamically, provide the name of a field as a string. Default is the node name.
- to switch the tooltip title on or off provide a boolean. Default is
true
.
With no arguments returns a boolean indicating if a tooltip title is shown.
Enables/disables debugging info on the console.
- the first argument is boolean and references if the debug option is enabled (default is
false
). - with no argument returns the boolean value indicating if the debug option is enabled.
Sets the default color for the links and nodes.
- the first argument is a string referencing the color (default is
"grey"
). - with no argument returns the default color.
# myChart.formatDefaultLocale() <>
Overrides the default locale format with the specified locale format. The locale is affecting the display of the values (e.g link label) if its format
property is specified.
- the first argument is an object referencing the locale format. E.g. the object for the German Locale would be (as a shortcut you can also pass the string
"DE"
):
{
"decimal": ",",
"thousands": ".",
"grouping": [3],
"currency": ["", " €"]
}
Sets the margins for the SVG.
- the first argument is an object referencing the four dimensions of the margin (default is
{top: 20, right: 10, bottom: 20, left: 10}
). - with no argument returns the default margin.
Propagates a field (which may be just filled in the leaves) throughout all the nodes by summing up the values bottom up.
- the first argument is a string referencing a field to be propagated (default is
"value"
). - with no argument returns if a field is propagated and its name.
Sets the dimensions for the SVG.
- the first argument is an object referencing the dimensions of the SVG (default is
{width: 1400, height: 800}
). - with no argument returns the default SVG dimensions.
# myChart.transitionDuration() <>
Sets the transition duration for the transitions.
- the first argument is an integer referencing the duration of a transition in milliseconds (default is
750
). - with no argument returns the transition duration for the transitions.
/* changing the font size for link labels and node labels */
div.chart {
font-size: 14px;
}
/* changing the font size for node labels */
.node .node-label {
font: 1em sans-serif;
}
/* changing the color of the node labels */
.node .node-label {
fill: black;
}
/* changing the color of the nodeImage (from default selection) */
.node .nodeImage {
stroke: green;
}
/* changing the size of the outline for link labels */
.link text.label.ontop {
stroke-width: 5px;
}
/* setting the stroke of the node bar */
.node .node-bar {
stroke: grey;
}
/* setting the fill of the node bar for positive values */
.node .node-bar.node-bar-positive {
fill: steelblue;
}
/* setting the fill of the node bar for negative values */
.node .node-bar.node-bar-negative {
fill: darkorange;
}
/* setting the style of the node bar labels */
.node .bar-label {
stroke: none;
fill: black;
font: 0.8em sans-serif;
}
This code is released under the BSD license.