Skip to content

Visualizing a hierarchy with a configurable indented tree.

License

Notifications You must be signed in to change notification settings

EE2dev/d3-indented-tree

Repository files navigation

d3-indented-tree

Visualizing a hierarchy with a configurable indented tree.

Credits

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.

Examples

More examples demonstrating specific API calls:

Data Format examples

API examples to customize the links

API examples to customize the nodes

Other examples

1. How to use d3-indented-tree

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>  

2. Data format

Hierarchical data can be specified in one of the following three data formats. The examples contain the same data in the supported formats.

2.1. JSON format

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);
      ...

2.2. CSV (hierarchical) format

A csv file format consisting of one row for each node. Each row contains keyas 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);
      ...

2.3. CSV (relational) format

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 separatorproperty 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);
      ...

2.4. Embedding data into a html node

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);
      ...

3.0 API reference

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 for myChart.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 to convertTypes to prevent conversions, then all columns are stored as string. The default is d3.autoType.

3.1 Links

# myChart.alignLeaves() <>

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.

# myChart.linkColor() <>

Transitions to the new color of the links. The horizontal link to is denoted by its color.

  1. argument:

    • to set the link color dynamically, provide the name of a field as a string (default is "value").
  2. 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 case inherit is set to false the default color will be used for the vertical links, if true the color of the parent is used (default is true).
  • with no argument returns the field used for the color.

# myChart.linkHeight() <>

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.

# myChart.linkLabel() <>

Transitions to the new number label on top of the links.

  1. 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.
  2. 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 the format 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 is true. If onTop 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 to false then the link label is not displayed if it is longer than the link width. The default value is false.

      • 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").

  • No argument:
    • with no argument the function returns the name of the numeric field for the link labels.

# myChart.linkStrength() <>

Transitions to the new strength (thickness) of the links. The horizontal link to and the vertical link from a node is denoting its strength.

  1. 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.
  2. 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 is d3.scaleLinear()).
      • range refers to the range of the scale (default is [1,10]).
  • No argument:
    • with no argument the function returns the static strength of the links.

# myChart.linkWidth() <>

Transitions to the new width (horizontal length) of the links. The horizontal link to a node is affected by its corresponding value.

  1. 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.
  2. 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 is d3.scaleLinear()).
      • range refers to the range of the scale (default is [15, 100]).
  • No argument:
    • with no argument the function returns the static width of the links.

3.2 Nodes

# myChart.nodeBar() <>

Displays/ transitions the bar for each node to the new value.

  1. 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.
  2. 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 is false).
      • 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 the format 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 is black).
      • 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 is steelblue, and darkorange, 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 is grey).
      • scale is a function defining the scale used to map the values to the node bar width (default is d3.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 is true).
      • translateX specifies a number property denoting the minimal distance in pixels for the connector between the node label and the node bar. (default is 50).
  • No argument:
    • with no argument the function returns the name of the numeric field for the node bars.

# myChart.nodeCollapse() <>

Collapses the tree at the specified nodes.

  1. 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.
  2. 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 is true.

With no arguments returns the array of the specified nodes to be collapsed.

# myChart.nodeExpand() <>

Expands the tree at the specified nodes.

  1. 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.
  2. 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 is true.

With no arguments returns the array of the specified nodes to be expanded.

# myChart.nodeImageFile() <>

Sets the node images based on an image file.

  1. 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.
  2. argument: (optional):

    • An object with the following properties can be used to further specify the mapping:
      • width specifies the width of the image (default is 10).
      • height specifies the height of the image (default is 10).
      • x specifies the x position of upper left corner of the image relative to the node (default is -1 * width / 2).
      • y specifies the y 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 is false).
      • default determines if the default node image should be used for nodes with no referenced images (default is true).

With no arguments returns the callback function for the node images.

# myChart.nodeImageSelection() <>

Sets the node images based on a selection.

  1. 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.
  2. 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.

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.

# myChart.nodeSort() <>

Sorts the nodes of the tree. The type conversion specified in dataSpec.convertTypes determines how the order is applied.

  1. argument:

    • A string denoting the name of a field based on which the nodes should be sorted.
  2. 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 is false).
      • sortByHeight (boolean) specifies whether the order should be determined by the height of the nodes first (default is false).

With no arguments returns the name of the field based on which the nodes are sorted.

# myChart.nodeTitle() <>

Sets the default tooltip for the nodes.

  1. 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.

3.3 Other API calls

# myChart.debugOn() <>

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.

# myChart.defaultColor() <>

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": ["", " €"]
}

# myChart.margin() <>

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.

# myChart.propagateValue() <>

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.

# myChart.svgDimensions() <>

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.

3.4 CSS styling

  /* 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;
  }

4. License

This code is released under the BSD license.

About

Visualizing a hierarchy with a configurable indented tree.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published