Giter Club home page Giter Club logo

d3-dag's Introduction

d3-dag

npm build docs

Often data sets are hierarchical, but are not in a tree structure, such as genetic data. In these instances d3-hierarchy may not suit your needs, which is why d3-dag (Directed Acyclic Graph) exists. This module implements a data structure for manipulating DAGs. Old versions were designed to mimic d3-hierarchy's api as much as possible, newer versions have opted to use modern javascript conventions while breaking from the standard set by d3.

Examples

Status

⚠️ tl;dr this is effectively in light maintanence mode: simple feature requests may still be implemented, but I won't be trying to expand to new use cases

This project started years ago with the intention of providing a rough framework for implementing or extending a sugiyama-style layout for small to medium sized static DAGs. At the time, this was one of the only libraries to support layered graph layouts in javascript. Since then many more libraries exist, and since I no longer use it, it's been hard to actively develop.

In addition, I started this mostly for experimentation purposes, but most people just want something reasonable out of the box, that works for most inputs. Fully supporting that would take a different library, but fortunately there are several: (Note this list may not be up to date, but PRs are welcome)

  • graphology - a general javascript graph library that's similar to the graph implementation provided as part of this library.
  • sigma - a graph layout library specifically targeted at large graphs.

Installing

If you use node, npm i d3-dag or yarn add d3-dag. Otherwise you can load it using unpkg:

<script src="https://unpkg.com/[email protected]"></script>
<script>
const dag = d3.graphStratify(...);
const layout = d3.sugiyama();
layout(dag);

// ... actually render here ...
for (const node of dag.nodes()) {
  console.log(node.x, node.y);
}
for (const { points } of dag.links()) {
  console.log(points);
}

</script>

General Usage Notes

This library is built around the concept of operators. Operators are functions with a fluent interface to modify their behavior. Every function that modifies behavior returns a copy, and will not modify the original operator. For example, the stratify operator creates dags from id-based parent data, can be used like so:

// note initial function call with no arguments to create default operator
const stratify = graphStratify();
const dag = stratify([{ id: "parent" }, { id: "child", parentIds: ["parent"] }]);

stratify.id(({ myid }: { myid: string }) => myid);
// doesn't work, stratify was not modified
const dag = stratify([{ myid: "parent" }, { myid: "child", parentIds: ["parent"] }]);

const myStratify = stratify.id(({ myid }: { myid: string }) => myid);
// works!
const dag = myStratify([{ myid: "parent" }, { myid: "child", parentIds: ["parent"] }]);

Updating

For information about changes between releases see the changelog.

Contributing

Contributions, issues, and PRs are all welcome!

d3-dag's People

Contributors

arquintl avatar bumbeishvili avatar dependabot[bot] avatar erikbrinkman avatar inokawa avatar kangaechigai avatar skalt avatar travishaby avatar trias avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

d3-dag's Issues

Webpack support

Hi, I am using your lib in a website built by Webpack. However the latest functional version is 0.4.1 on a specific commit hash. Otherwise the webpack build fails on Module not found: Error: Can't resolve 'child_process' and Module not found: Error: Can't resolve 'fs'. Should I try to fix it and send a Pull request or should I just fork the repo and maintain my paralel version of this project?

TypeScript support?

Hi,
This library is great. Are there any plans to release Typescript types for d3-dag?
Thanks.

Support for nodes and links metadata

Hi! Is it possible to attach metadata to nodes and links?
Something like

{
  "id": 1,
  "data": {
    "name": "john",
    "age": 28
  },
  "children": [
    {
      "linkData": {
        "text": "marriage"
      },
      "node": {
        "id": 2,
        "data": {
          "name": "jane",
          "age": 32
        }
      }
    },
    {
      "linkData": {
        "text": "son"
      },
      "node": {
        "id": 3,
        "data": {
          "name": "jack",
          "age": 3
        }
      }
    }
  ]
}

or analogous for stratify?

how to render

as dag is generated using sugiyama how are we going to render it in HTML?

Constraint rank of a node

Hi ! Very impressive library, thanks for sharing it !

I was wondering whether there is a way to specify the rank of a node. Sometimes, you want to constraint your graph so that certain nodes are laid out on the same level.

Thanks a lot !

node.count() counts leaves, not nodes

The doc of node.count() in the readme says:

Set the value of each node to be the number of descendants including itself.

However, it looks to me, from the behaviour and the code, that it counts the number of leaves among the descendants of the node.

Really thrilled about this repo!

I was just trying to implement dag graphs in my project with dagre and at the moment,
realized that it isn't maintained anymore and concerned with using it in my project.

This currently wraps dagre, but I'd prefer to reimplement a DAG layout to not require it as a dependency, and to allow more options about the different phases of DAG layout. This would be a good place to start. Ideally each phase will just be a callback, similar to the way other d3 layouts are done.

And really thrilled it'll be reimplemented without darge project.
Looking forward to heavily using in my project!

Cheers! 👍

Infinite layout

Adding this issue as I can (seemingly) cause infinite layout cycle in d3-dag using the following test data and configuration:

    const layout = d3.sugiyama()
      .layering(d3.layeringSimplex())
      .decross(d3.decrossOpt())
      .coord(d3.coordVert())
      .nodeSize((node) => {
        return [130, 130];
      })

I plan on attempting to debug further but thought, if nothing else, it might be useful to have the data for a future test. While I cannot confirm that this isn't some awful mistake on my part (made worse by the fact that this makes chrome go to the bad place really fast so it's hard to test), I did confirm that the data is not infinitely recursive and is render-able through other DAG renders (e.g. dagitty)

Will post more here if I determine a root cause (including if it is me)

dagData.txt

What are the size limits on graphs? Can't layout 39-node graph

I forked the first example:
https://beta.observablehq.com/@bumbeishvili/d3-dag-vert

And just changed the data to my graph with 39 nodes.
https://beta.observablehq.com/@zfrenchee/d3-dag-vert

Which basically freezes -- eats up all my CPU and can't layout the nodes.

There are no cycles in my graph, and I can d3.dratify()(graph) without a problem. Why does the layout struggle so much with my graph?

Here's the graph for reference:

[{"id":"GO:0005575"},
 {"id":"GO:1903561","parentIds":["GO:0031982","GO:0043230"]},
 {"id":"GO:0005737","parentIds":["GO:0044424"]},
 {"id":"GO:0030666","parentIds":["GO:0030139","GO:0030659","GO:0098588","GO:0098805"]},
 {"id":"GO:0043230","parentIds":["GO:0043226","GO:0044421"]},
 {"id":"GO:0005623","parentIds":["GO:0005575"]},
 {"id":"GO:0005938","parentIds":["GO:0071944","GO:0099568"]},
 {"id":"GO:0031982","parentIds":["GO:0043227"]},
 {"id":"GO:0099568","parentIds":["GO:0005737","GO:0044444"]},
 {"id":"GO:0097708","parentIds":["GO:0031982","GO:0043229"]},
 {"id":"GO:0005622","parentIds":["GO:0044464"]},
 {"id":"GO:0016020","parentIds":["GO:0005575"]},
 {"id":"GO:0043226","parentIds":["GO:0005575"]},
 {"id":"GO:0030055","parentIds":["GO:0030054"]},
 {"id":"GO:0005576","parentIds":["GO:0005575"]},
 {"id":"GO:0030054","parentIds":["GO:0005575"]},
 {"id":"GO:0031410","parentIds":["GO:0044444","GO:0097708"]},
 {"id":"GO:0031090","parentIds":["GO:0016020","GO:0043227","GO:0044422"]},
 {"id":"GO:0030139","parentIds":["GO:0031410"]},
 {"id":"GO:0070062","parentIds":["GO:0005615","GO:1903561"]},
 {"id":"GO:0044464","parentIds":["GO:0005575","GO:0005623"]},
 {"id":"GO:0005925","parentIds":["GO:0005924"]},
 {"id":"GO:0044444","parentIds":["GO:0005737","GO:0044424"]},
 {"id":"GO:0044421","parentIds":["GO:0005575","GO:0005576"]},
 {"id":"GO:0012506","parentIds":["GO:0031090","GO:0031982"]},
 {"id":"GO:0098805","parentIds":["GO:0016020"]},
 {"id":"GO:0005615","parentIds":["GO:0044421"]},
 {"id":"GO:0098588","parentIds":["GO:0031090"]},
 {"id":"GO:0044433","parentIds":["GO:0031410","GO:0044444","GO:0044446"]},
 {"id":"GO:0043227","parentIds":["GO:0043226"]},
 {"id":"GO:0005912","parentIds":["GO:0070161"]},
 {"id":"GO:0044422","parentIds":["GO:0005575","GO:0043226"]},
 {"id":"GO:0070161","parentIds":["GO:0030054"]},
 {"id":"GO:0030659","parentIds":["GO:0012506","GO:0044433"]},
 {"id":"GO:0043229","parentIds":["GO:0043226","GO:0044424"]},
 {"id":"GO:0044424","parentIds":["GO:0005622","GO:0044464"]},
 {"id":"GO:0044446","parentIds":["GO:0043229","GO:0044422","GO:0044424"]},
 {"id":"GO:0071944","parentIds":["GO:0044464"]},
 {"id":"GO:0005924","parentIds":["GO:0005912","GO:0030055"]}
];

Serious Lag + Out-Of-Memory Issues

Hello, thanks so much for the excellent library!

I'm working on using d3-dag to develop a software for the generation of pedigrees, which requires live regeneration of my graph as data is inputted. I'm noticing serious slowdowns of the plotting functions as I add more nodes to the graph. Here is an example of a fairly simple graph that is causing serious slowdowns (around 10 seconds to render):
image

Any idea what might be causing these performance issues? Below is the performance profile of the above rendering operation if it is at all helpful:
image

Module parse failed: Unexpected token (2257:6)

App is unable to compile when using d3-dag version 0.8.1 with Create React App.
image

This can be replicated by

  • Creating a brand new Create React App "e.g. npx create-react-app my-app --template typescript"
  • Import d3-dag into the App.tsx and call dagStratify()

Attached example project
my-app.zip

Using d3-dag in angular

Getting the below error when i use d3-dag in Angular

ERROR in ./node_modules/d3-dag/src/index.ts
Module build failed (from ./node_modules/@ngtools/webpack/src/index.js):
Error: /home/projects/ETL/newdrawnode/node_modules/d3-dag/src/index.ts is missing from the TypeScript compilation. Please make sure it is in your tsconfig via the 'files' or 'include' property.
The missing file seems to be part of a third party library. TS files in published libraries are often a sign of a badly packaged library. Please open an issue in the library repository to alert its author and ask them to package the library using the Angular Package Format (https://goo.gl/jB3GVv).
at AngularCompilerPlugin.getCompiledFile (/home/projects/ETL/newdrawnode/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:949:23)
at plugin.done.then (/home/projects/ETL/newdrawnode/node_modules/@ngtools/webpack/src/loader.js:43:31)
at process._tickCallback (internal/process/next_tick.js:68:7)


Tried this in tsconfig.ts, still the same error
"files": ["/home/projects/ETL/newdrawnode/node_modules/d3-dag/src/index.ts"]

Any idea on how to resolve this?

custom position of nodes

Hi. First of all, thanks for this amazing library!

I am wondering if there is a way to define custom x and y position of single node and then calculate all the links? I am looking for a way to mark node/s with custom or predefined position to not be included in calculating of their positions but links should be correctly calculated to them. Is that currently possible? Thanks in advance.

Error: no roots

I get "Error: no roots" when I run dagConnect on an array of edges. The edges are JS objects with an id, source and target nodes. I am using accessors to get the ids of the nodes. Upon debugging, it seems to happen when dagConnect calls dagStratify.

The dag I'm building has 3 nodes and 2 edges and looks like this:

(node) (node)
    \    /
    (node)

The array of edges (edgeData) looks something like this in debug:

0: { id: 1572447089979, sourceNode: {id: 1572447087123, …}, targetNode: {id: 1572447087506, ...}}
1: { id: 1572447090797, sourceNode: {id: 1572447087331, …}, targetNode: {id: 1572447087506, ...}}

My call looks like this:
let dag = d3.dagConnect().sourceAccessor((e) => e.id).targetAccessor((e) => e.id)(edgeData);

Not sure if this is a bug or if I am doing something wrong? Am I supposed to pass in a pseudo-edge from the root nodes to an 'undefined' node?

Unconnected DAG: Necessary?

Hi!

Running into an issue where I have a dynamic DAG structure that may be technically two independent trees.

The verify step that tests that a DAG is connected throws an error currently and so doesn't allow rendering to finish. However, removing the throw allows the rendering to continue and I end up with two (or more) really nice, independent trees.

Is there a reason the library should be throwing an error there? Could there perhaps be a console.warn instead, in case of potential overlap issues?

Collapse/minimize subgraphs?

Does d3-dag have any in-built functionality to be able to minimize/collapse subgraphs?

I saw this demo of collapsible trees on d3: https://observablehq.com/@d3/collapsible-tree

Is it possible to do something like this with d3-dag?

Also, can you use a dom node as a graph node? Say I build an elaborate HTML node with input elements and few images, can I use this dom node as my graph node?

Arrow example does not work with 0.2.3

Hi, I updated to v0.2.3 and noticed that the arrow calculation stopped working.
I updated the online example to make it run:

  • import grafo.json from examples folder
  • use dagStratify instead of dratify

This is the updated link

However the arrow are always placed at the top left corner.
I suspect that the position calculation is somehow changed as links are now provided at creation time.
Can you please update the example to make it actually work as with v0.1.x?
Thanks a lot!

Support for dummy nodes count in a line

I'm having a non-fixed size graph, so I need to compute its width given the dag-layout. To do so I'd need to know how many dummy nodes there are in a given line. Could you consider adding this feature ?

Custom node appearance

I'm not d3 fluent, I figured I should ask: Is there a way to change the appearance of the nodes/vertex?

Influencing node coordinates to achieve desired horizontal ordering

Thanks to a hint in a previous issue from @erikbrinkman, I've tried the following:

  function layering(dag) {
    // Run layering once so that everything has a level.
    layeringSimplex()(dag);

    dag.descendants().forEach(node => {
      // Give everything a rank, but unions have a rank one less than their level.
      node.rank = node.layer;
      if (node.data.isUnion) {
        node.rank -= 1;
      }
    });
    // Re-run layering to allow ranks to take effect.
    layeringSimplex()(dag)
  }

  let treeFn = sugiyama()
    .nodeSize(nodeSize)
    .layering(layering)
    .decross(decrossOpt())
    .coord(coordQuad());
treeFn(dag);

Running this has not altered the layout at all - as if the ranks are invalid and being ignored.

does d3-dag has nodeSize API?

I need to draw a dynamic DAG. I have to use sugiyama layout first, so while nodes increases, the space become tight. So is there a nodeSize API like d3 that could solve this problem

Support for adding arrows

The links of a DAG have directions.
Are there any options in d3-dag to display arrows in link header or body, to show directions?

@erikbrinkman d3-dag is a great project and I would love to contribute this part if not supported yet :)

Howto: in Javascript?

Hi,

Is there a tutorial/reference for how to use this library in Javascript? I poked around on Observable HQ and put together this little script. However, when I run it on the browser, I get an error.

function run()
{
    var data = [
        {
          "id": "0",
          "parentIds": []
        },
        {
          "id": "1",
          "parentIds": ["0"]
        },
        {
          "id": "2",
          "parentIds": ["0"]
        },
        {
            "id": "3",
            "parentIds": ["1", "2"]
        },
        {
            "id": "4",
            "parentIds": ["0"]
        },
        {
            "id": "5",
            "parentIds": ["4"]
        }
      ];

      var layout = d3.sugiyama()
                .size([400, 400])
                .layering(d3.layeringSimplex())
                .decross(d3.decrossOpt())
                .coord(d3.coordCenter());

      layout(d3.dagConnect(data));
}

The error:

d3-dag.min.js:16 Uncaught Error: got arguments to dagConnect([object Object],[object Object],[object Object],[object Object],[object Object],[object Object]), but constructor takes no aruguments. These were probably meant as data which should be called as dagConnect()(...)
    at Object.t.dagConnect (d3-dag.min.js:16)
    at run (dag.js:36)

Thanks

no exported class LayoutDagRoot, LayoutDagNode, LayoutChildLink

Hello,

the following classes are not exported: LayoutDagRoot, LayoutDagNode, LayoutChildLink.
I tryed to import theses classe, and use them directly to build with custom code the DAG graph.

import { LayoutDagRoot, LayoutDagNode, LayoutChildLink } from 'd3-dag'; /// does not compile!!
import { LayoutDagRoot, LayoutDagNode, LayoutChildLink } from 'd3-dag/dist/dag/node'; // compile in IDE, but does not work at runtime !!

..
let fooObjs: Fooj[] = ...  with child dependencies to bars
let barObjs: Fooj[] = ...  with child dependencies to some baz, and parent dependencies from some baz (typically, read/write relation between data and process)
let bazObjs: Fooj[] = ...  other dependencies ...

// create node objects
let dagNodeMap = new Map<string,LayoutDagNode>();
fooObjs.forEach(obj => {
    let fooId = 'foo:' + obj.id;
    let nodeData = { type:'foo', foo };
    dagNodeMap.set(fooId, new LayoutDagNode(fooId, nodeData));
});
barObjs.forEach(barObj =>  ... similar create  LayoutDagNode ..
bazObjs.forEach(bazObj =>  ... similar create  LayoutDagNode ..
// create child dependency links between objects..
// dependency between foo-[dep1]->bar(s)
fooObjs.forEach(fooObj => {
   let fooNode = dagNodeMap.get('foo:' + fooObj.id);
   fooObj.dep1Bars.forEach(barObj => {
      let childBarNode = dagNodeMap.get('bar:' + barObj.id);
      let linkData = { linkType:'foo_dep1_bar' };
      fooNode.dataChildren.push( new LayoutChildLink(barObj, linkData));
   });
 // dependency between bar-[dep2]->foo(s) 
 fooObj.dep2Bars.forEach(barObj => {
      let  = dagNodeMap.get('bar:' + barObj.id);
      let linkData = { linkType:'bar_dep2_foo' };
      parentBarNode.dataChildren.push( new LayoutChildLink(fooNode, linkData));
   });
   // other dendencies between foo,bar,baz...   
});

Maybe I could use directly some other graph builder operator?
I did not understand the doc, and what is effectively exported/private from the doc

Thanks for your answer, or fix

Switch layout spacing to support node Sizes

The current method of setting a size or nodeSize and separation while trying to mimic d3-hierarchy doesn't seem to be doing a good job for users who know roughly the sizes of nodes they want to layout.

The proposal is this:
Instead of supplying a separation, dag size or nodeSize, users can specify a node width and height accessor (or nodeSize accessor) and a dagSize. The node with accessor will be used in place of the separation accessor and will return left.width + right.width / 2 thus enforcing this constraint. For height, we'll find the maximum height of each layer, and similarly set the spacing between heights to be bottom.height + top.height / 2. If an optional dag size is specified than the coordinates will be rescaled to fit within the specified size, centering if all coordinates are 0. (special care might need to be taken for graphs that are disconnected across layers as you'll get some really strange layouts, although handling that is not a top priority as reasonable layerings won't produce that). The node height accessor can default to 1 for every node. The node width accessor will default to 1 for normal nodes and 0 for dummy nodes, thus keeping in line with the new default separation function.

I believe this will make it easier for people who have dags with known sizes to set appropriate layouts. It should also make it fairly reasonable to take nodes that are attached to dom objects to measure the objects in advance and then set node sizes accordingly.

Comma Dangle eslint rule

Hi,

I am using your library in an angular project and the eslint comma dangle rule is causing issues with the angular build.

The following issue is
ERROR in main.81d528981f082d6185f9.js from Terser
Unexpected token: punc ()) [main.81d528981f082d6185f9.js:188447,4]

One of the places the issue is found is in src/sugiyama/coord/center.js line 12

I have a commit that applies this rule across the code base. Would you allow me to become a contributor so that I can push my changes to a branch

Regards
Matthew

Hey-- thank you!

Hey, just wanted to say, thank you. i wanted this years ago and never got around to making it. thanks!

Can't resolve 'javascript-lp-solver'

Hi @erikbrinkman,

Thank you for this library!
I'm trying to build components based on it for nivo, however I had an error during build phase, javascript-lp-solver cannot be resolved as it's not defined as a dependency but as a dev dependency.

Constant separation?

Hi Erik,

I'm trying to figure out how to get constant spacing between neighboring nodes. When I have two nodes, they are positioned such that one is on the far left and the other is one the far right. As I add more nodes, the they move closer and closer together. At some point, they overlap. I want to prevent this. I would like to have the spacing independent of the number of nodes in a layer. I'd rather have the nodes move out of the screen when there are too many (it's not a problem because I have panning activated).

I'm using sugiyama layout like this:

var tree = d3.sugiyama()
.size([height, width])
.layering(d3.layeringSimplex())
.decross(d3.decrossTwoLayer())
.coord(d3.coordVert())
.separation( (a, b) => { return 1 } );
`

I figured out that I can get constant separation between layers by setting

nodes.forEach(d => d.x = d.depth().layer * 140);

Is there something similar for the y-position?

Best regards
Ben

Preventing node overlap on complex graphs

I'm having trouble preventing node overlap on complex graphs and I'm not sure what the best avenue to correct it is.

I am generating DAGs and using the layout's layers + nodes per layer to dynamically compute width and height based on node size and a separation value. This works well with smaller graphs but as complexity increases my nodes don't seem to be utilizing space well:

2019-04-10-125051_192x305_scrot
2019-04-10-125105_644x309_scrot

There's more than enough space to separate those tightly packed clusters of nodes but I'm not having much luck figuring out how to do it well over a variety of graph complexities.

Should I explore the coord accessors or is this something that the separation function takes care of? How do I specify that the separation between nodes should be at least the node width + a separation value that I specify?

These layouts are being generated by the react hook below:

import { useMemo, useEffect, useState } from 'react';
import * as d3dag from 'd3-dag';
import { groupBy, map } from 'lodash';

const dagStratify = d3dag.dagStratify();

export default function useSugiyamaDag<DT>(
  data: NodeDef<DT>[],
  {
    nodeWidth,
    nodeHeight,
    nodeSeparationX,
    nodeSeparationY,
  }: {
    nodeWidth: number;
    nodeHeight: number;
    nodeSeparationX: number;
    nodeSeparationY: number;
  }
): UseSugiyamaDagOut<DT> {
  // Start with width and height of 0. This gets overwritten below
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);

  // Re-stratify data + remake layout fn whenever changes are detected
  const dag = useMemo(() => dagStratify(data), [data]);
  const layout = useMemo(() => {
    console.warn('(Re)creating DAG layout function');
    return (
      d3dag
        .sugiyama()
        // .debug(true)
        .size([width, height])
        // .layering(d3dag.layeringLongestPath())
        // .coord(d3dag.coordCenter())
        // not sure if this is working
        .separation((_a: {}, _b: {}) => nodeWidth + nodeSeparationX)
    );
  }, [width, height, nodeWidth, nodeSeparationX]);

  // Layout the tree
  const { tree, descendants, descendantsByLayer } = useMemo<{
    tree: UseSugiyamaDagOut<DT>['tree'];
    descendants: {}[];
    descendantsByLayer: Record<number, {}[]>;
  }>(() => {
    console.warn('Laying out DAG');
    const t = layout(dag);
    const d = t.descendants();
    const dBL = groupBy(d, n => n.layer);
    return { tree: t, descendants: d, descendantsByLayer: dBL };
  }, [layout, dag]);

  // Calculate and set height and width of the graph dynamically
  useEffect(() => {
    // Start with width and height of 0. This gets overwritten below once layout has been computed
    const newHeight =
      Object.keys(descendantsByLayer).length * (nodeHeight + nodeSeparationY);
    // Find each layers width (number of nodes per layer * width of each node) and
    // set the overall width to the max computed layer width
    const newWidth = Math.max(
      ...map(
        descendantsByLayer,
        layer => layer.length * (nodeWidth + nodeSeparationX)
      )
    );

    // Set height/width if values don't match
    if (height !== newHeight || width !== newWidth) {
      setHeight(newHeight);
      setWidth(newWidth);
    }
  }, [
    descendants,
    descendantsByLayer,
    height,
    nodeHeight,
    nodeSeparationY,
    nodeWidth,
    width,
    nodeSeparationX,
  ]);

  return { height, width, tree, descendants };
}

export interface NodeDef<Data extends {}> {
  id: string;
  parentIds?: string[];
  data: Data;
}

export interface LayoutNodeDef<DT> {
  id: string;
  layer: number;
  x: number;
  y: number;
  children: LayoutNodeDef<DT>[];
  data: NodeDef<DT>;
}

interface UseSugiyamaDagOut<DT> {
  height: number;
  width: number;
  tree: {
    links: () => {
      source: LayoutNodeDef<DT>;
      target: LayoutNodeDef<DT>;
    }[];
    descendants: () => LayoutNodeDef<DT>[];
  };
  descendants: {}[];
}

twolayerOpt can cause the browser tab to crash

Here is a minimal repro. I can reproduce this on a recent model of MacBook Pro in both Safari and Chrome:

<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
  <title>Crash browser tab on Mac Chrome with D3 Dag</title>
   <script src="https://d3js.org/d3.v5.min.js"></script>
   <script src="https://unpkg.com/[email protected]"></script>
   <script>
      let sourceData2 = []
     // creating a lesser amount of nodes will no longer crash
      for (i = 0; i < 100; i++) {
        sourceData2.push({"id":`${i}`, parentIds:[]});
      }
      for (i = 0; i < 100; i++) {
        sourceData2.push({"id":`${i+100}`, parentIds:[`${i}`]});
      }

      layout = d3
        .sugiyama()
        .decross(
          d3
           .decrossTwoLayer()
           .order(
               // changing twolayerOpt to twolayerMedian will no longer crash
              //d3.twolayerMedian()
              d3.twolayerOpt()
           )
         )
      var result = layout(d3.dagStratify()(sourceData2))
      console.log(result)
   </script>
</head>
<body>
</body>
</html>

Descendants not determined correctly

Hi @erikbrinkman!

A user filed a bug in js_family_tree, which uses d3-dag for display. After digging down, I realized that in his particular case, the dag does not return the correct number of descendants. I prepared a minimum working example, which reproduces the bug:

<!DOCTYPE html>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.15.0/d3.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/d3-dag.min.js"></script>
    <script>
        links = [
            ["id1", "id3"],
            // ["id2", "id3"],
            ["id3", "id4"],
        ]
        dag = d3.dagConnect()(links);
        nodes = dag.descendants();
        root = nodes.find(n => n.id == "id3");
        dag.children = [root];
        children_of_root = dag.descendants(); // expected: id3, id4, returned: id1, id3, id4
    </script>
</body>
</html>

Interestingly, the error does not occur, if id3has another parent (you can test this by uncommenting the link).

I know it seems a bit weird to use dag.descendants here instead of root.descendants but that's because it is a minimum example. In the working code, have to get the descendants of the whole dag because I am expecting multiple root nodes.

Cheers,
Ben

Spaces in ID field

At least using stratify, spaces in the id are unsupported and cause some confusion at the moment.

There may be more than one place but guessing this is part of the culprit

stratify.id = id;

I would be happy to adjust and create a PR but, before I did, I wanted to make sure that this wasn't an intentional issue or something you would prefer left alone.

Support for vertical Zherebko

Hello there.

Would you consider adding a vertical zherebko render method? My use-case involves a topological sort and a >100 node graph.

It's great that zherebko renders them sorted, but I feel it would be a pain to horizontal scroll through everything.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.