Code Monkey home page Code Monkey logo

react-dag's Introduction

React-dag

Imgur

React wrapper for jsplumb. Helps in rendering a Directed Acyclic Graph.

A demo of this module being used can be found here Demo.

Install

npm install react-dag

Usage

  import ReactDAG from "react-dag";
  class MyComponent extends Component {
    ...
    render() {
      return (
        <ReactDAG
          nodes={cloneDeep(this.state.nodes)}
          connections={cloneDeep(this.state.connections)}
          jsPlumbSettings={defaultSettings}
          connectionDecoders={[
            transformConnectionDecoder,
            conditionConnectionDecoder,
          ]}
          connectionEncoders={[
            transformConnectionEncoder,
            conditionConnectionEncoder,
          ]}
          onChange={({
            nodes: n,
            connections: c,
          }: {
            nodes: INode[];
            connections: IConnectionParams[];
          }) => {
            this.setState({ nodes: n, connections: c });
          }}
          eventListeners={eventListeners}
          registerTypes={registerTypes}
          zoom={this.state.zoom}
        >
          {this.state.nodes.map((node, i) => {
            const Component = getComponent(node.config ? node.config.type : "");
            return <Component key={i} id={node.id} />;
          })}
        </ReactDAG>
      );
    }
  }

Older version docs:

For older version (version 1.x) please follow the readme here README. Since this is a re-write the API is completely different and includes for a lot of additional functionality not available in the previous version

Props

  • settings - Settings to be used for JsPlumb. Check out dag-settings.ts for base settings that are available.

    Note

    The settings prop is used as argument while creating a jsplumb instance. For individual nodes to use specific settings please import the same in each node module.

  • nodes: Set of nodes to be rendered in the DAG. A node in the nodes array takes the type of INode - type.

    • Every node has to have an id to be uniquely identified
    • And an optional config that is needed for individual nodes
  • connections: The set of connections are the rules for connecting the above nodes. A connection in the connections array takes the type of IConnectionParams type

    • Every connection MUST have a sourceId and a targetId.
    • Optionally it can have other information too. The extra information should be evident when using encoders and decoders for connections.
  • connectionDecoders: A connection decoder is nothing but a pure function that accepts a connection parameter and the set of nodes. This decoder helps in decoding a connection that has information about, say its endoint, to something more generic.

    An example would be,

    • Lets say we have two nodes. NodeA with two endpoints (left and right) and NodeB with only one endpoint (a source) and the entire node acts as a target (meaning it can accept incoming connections anywhere on its left side).
    • Now the right end point from NodeA connects to NodeB.

    When that happens jsPlumb returns a connection that is of the form,

      {
        sourceId: "NodeA-right-endpoint",
        targetId: "NodeB"
      }

    (if we chose to name the end points of NodeA as NodeA-right-endpoint and NodeA-left-endpoint)

    In order to decode this to be,

      {
        sourceId: "NodeA",
        targetId: "NodeB"
      }

    a decoder function is used. The list of decoders are passed a connection object (current connection), jsplumb instance and the list of nodes. Each function has logic to convert the incoming connection to what is desired. And it returns the modified connection object. The same is passed to subsequent decoders to decode the connection objects for different nodes.

    The reduce logic of decoders looks like this,

    const newConnObj = encoders.reduce(
      (prev, curr) => curr(prev, this.state.jsPlumbInstance, this.props.nodes),
      { ...connObj }
    );
  • connectionEncoders: As the name mentions a connection decoder converts a connection object from jsplumb to the one desired.

    An example of a encoder and a decoder can be found here

  • eventListeners: Is a key-value map where key represents the event and the value represents the callback to call when the event occurs. This is useful if you want to bind to specific events in jsplumb instance. For a list of possible events please check jsplumb docs here

  • registerTypes: Is a key-value map where the key represents the type name and the value represents the paint style object. This is useful if you want to set types in jsplumb instance that you can use to style nodes/connections while drawing the graph. jsplumb docs for registerConnection/registerEndpoint types can be found here

  • zoom: Is an integer that represents the zoom level of the entire graph.

  • onChange: Every time there is a change in the state of the graph (nodes/connections), the onChange callback is called back. This is similar to onChange in any input type html elements.

  • children: The children should be an array of different types of nodes that needs to be rendered. Each child needs to have a unique id to be identified.

    ReactDAG accepts the children and wraps with extra properties that it needs to initialize and render itself. In addition to the id prop provided as part of the node, ReactDAG also provides the following props,

    • config: a config object if provided as part of the node in the nodes array.

    • initNode: a initializing function. Every node that needs to be rendered in the DAG MUST call this.props.initNode function. The function accepts 4 parameters in this order,

      • nodeId: the unique node identifier passed initially.
      • endPointParams: A list of endpoints to be added to the node. This endpointParam type reference can be found here. The endpoint param is the same object that you would pass to addEndpoint function in jsplumb. It accepts an element, params and referenceParams
      • makeSourceParams: is the same object passed to makeSource function in jsplumb. This is to make the entire node a source (or part of it based on the paramters). A doc reference for makeSource can be found here
      • makeTargetParams: Similar to makeSource. A doc reference for makeTarget in jsplumb can be found here
  • onDelete: A callback function when deleting a node. This function has to be called if the DAG has to be re-rendered.

react-dag's People

Contributors

ajainarayanan avatar dependabot[bot] 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

react-dag's Issues

Deleting a node with connections

I am using ReactDAG for creating a workflow management tool.

Everytime I delete a node and pass the updated nodes and connections to reactDAG, the UI gets all messed up. I am not sure where am I going wrong. Any help would be greatly appreciated.

UI with nodes and connections
Screen Shot 2019-03-27 at 11 59 35 AM

UI after deleting notification node
Screen Shot 2019-03-27 at 12 00 02 PM

Endpoint Events

Hi Ajainarayanan,

By reading your code implementation, understand your are setting event listeners for the JSplumb flow but there use cases we need to set the Event bind for EndPoints, we tried to bind the events but custom endpoint / node wont have JSPlumb endpoint reference object.
......
this.addEndpoint(element, params, referenceParams);
....

Can you please check possibility of referencing the JSPlumb Endpoint reference in the DefaultNode or provide option add event listener for the EndPoint.

Thanks,
Rathish

onDelete not working with 2.0.0-alpha-5 version

onDelete is available as props to a nodetype as this.onDeleteNode=(t=>{this.removeNode(t)})} // from dist
which only accepts node id as t.

But the same method in source code is

public onDeleteNode = (nodeId: string, endpoints: endpointUUID[]) => { this.removeNode(nodeId, endpoints); };

it expects endpoints as well. @ajainarayanan

IE11 is not supported

Jsplumb supports IE9+. But Demo of this library is broken in IE11. Do you have plan to fix IE11 issues?

Add static type system

This includes,

  • Using Flow for static types
  • Adding eslint for better development experience

Error in custom functional node component

Bug: When I try to use custom nodes without extending DefaultNode, I get these errors in console on every connection/disconnection:

"react-dag.js:1 .each iteration failed : TypeError: Cannot read property 'force' of undefined"

"jsPlumb function failed : TypeError: Cannot read property '1' of null"

And the behavior of the app became wrong.

Steps to reproduce:

  1. Download reaact-dag-app (example)
  2. Rewrite NodeType1 in functional style (Check out my GIST )
    It is the only part I changed in whole project;
  3. npm run start:dev-js

Flowchart connector is overlapping with node

image

Hi, @ajainarayanan, I have a problem like this, the connector is overlapping with the node。

the full settings is:

import { extend, clone } from 'lodash'

// globalTextColor
const globalGrayColor = '#90949c'
const whiteColor = '#fff'

// 基础设置
export const defaultSettings = {
  Connector: ['Flowchart', {
    alwaysRespectStubs: true,
    gap: 4,
    cornerRadius: 10,
    midpoint: 0.2,
    stub: 40
  }],
  ConnectionsDetachable: true
}

// 是否可 drop
export const dropNotAllowedStyle = {
  stroke: 'red',
  fill: 'red'
}

// 连接线样式
export const connectorStyle = {
  stroke: globalGrayColor,
  strokeWidth: 2
}

export const connectorOverlays = {
  connectorOverlays: [
    ['Arrow', { location: 1, length: 6, width: 12, height: 10, foldback: 1 }],
    ['Label', {
      location: 0.5,
      id: 'label',
      cssClass: 'aLabel',
      events: {
        click: (info) => console.log(info.label)
      }
    }]
  ]
}

export const disabledConnectorOverlays = {
  connectorOverlays: [
    [ 'Arrow', { location: 1, length: 6, width: 12, height: 10, foldback: 1 } ]
  ]
}

export const commonSettings = {
  endpoint: 'Dot',
  maxConnections: -1, // -1 means unlimited connections
  allowLoopback: false,
  paintStyle: {
    stroke: globalGrayColor,
    fill: whiteColor,
    radius: 4,
    lineWidth: 1
  },
  anchors: ['Static']
}

export const sourceSettings = extend({
  isSource: true,
  connectorStyle: connectorStyle,
  anchor: [1, 0.5, 0, 0, 0, 0]
}, commonSettings, {maxConnections: -1})

export const sinkSettings = extend({
  isTarget: true,
  anchor: [0, 0.5, 0, 0, 0, 0],
  connectorStyle: connectorStyle,
  onMaxConnections: function (info, e) {
    console.error('Maximum connections (' + info.maxConnections + ') reached') // eslint-disable-line
  },
  dropOptions: {
    // todo: dynamic检测
    // hoverClass: 'drop-hover',
    activeClass: 'drag-active'
  }
}, commonSettings, {maxConnections: -1})

export function getSettings (isDisabled = false) {
  let settings = {
    transformSource: {},
    transformSink: {}
  }
  if (isDisabled) {
    settings = {
      default: defaultSettings,
      commonSettings: extend(commonSettings, disabledConnectorOverlays),
      source: extend(sourceSettings, disabledConnectorOverlays),
      sink: extend(sinkSettings, disabledConnectorOverlays),
      transformSource: {},
      transformSink: {}
    }
  } else {
    settings = {
      default: defaultSettings,
      commonSettings: extend(commonSettings, connectorOverlays),
      source: extend(sourceSettings, connectorOverlays),
      sink: extend(sinkSettings, connectorOverlays),
      transformSource: {},
      transformSink: {}
    }
  }

  settings.transformSource = clone(settings.source)
  settings.transformSink = clone(settings.sink)

  return settings
}

do you have any advice on this?

How to bind node click event?

I want to bind the click event for the nodes, from the jsplumb doc I don't find any click event for nodes

How do I implement the click event on nodes?

opensource the demo source code?

i want 2 draw the same canvas, but the code of the demo has been mix up.
would you please opensource the demo source code
3q very much!

Looks like circular dependencies are allowed ?

Wondering if its a bug or an expected behavior where DAG allows circular workflows ? I was expecting it would not be allowed.

image

BTW: Great work, just discovered this and playing with it.

Connections can get frozen on removal

When I was working locally I noticed that on occasion when I disconnected two nodes, the connection would become frozen and I could no longer interact with it. I was able to reproduce this on the demo example as well. See below:

whythis4

It appears to happen when you release the connection very close to where it was connected.

Deleting a Middle Node leads to all other connectors getting removed from view

I was using the example from dev folder. Delete option was added to NodeType2 in this demo. Instead of adding NodeType2 as the last node ( as given in example ), I added it as a middle node by changing the connections source and target in data.ts file. On deleting the node, the connections are not removed from view or not deleted in few scenarios. @ajainarayanan

Steps to Reproduce issue: Replace the data.ts with the data below. Delete node ( Node Type 2 (action 1) ), add a new node of any type. All the existing connections are removed from view.

export const data = {
  connections: [
    {
      sourceId: "1",
      targetId: "2",
    },
    {
      sourceId: "2",
      targetId: "3",
    },
    {
      sourceId: "2",
      targetId: "4",
    },
    {
      sourceId: "3",
      targetId: "4",
    },
    {
      sourceId: "3",
      targetId: "5",
    },
    {
      sourceId: "4",
      targetId: "5",
    },
  ],
  nodes: [
    {
      config: {
        label: "Default Node",
        type: "source",
      },
      id: "1",
    },
    {
      config: {
        label: "Node Type 1 (action)",
        type: "action",
      },
      id: "2",
    },
    {
      config: {
        label: "Node Type 1 (action)",
        type: "action",
      },
      id: "3",
    },
    {
      config: {
        label: "Node Type 2",
        type: "action",
      },
      id: "4",
    },
    {
      config: {
        label: "Node Type 2",
        type: "action",
      },
      id: "5",
    },
  ],
};

Are there plans to upgrade to jsplumb 2.15.x+?

I took a look at what version of jsplumb is being used by react-dag.

It seems the version is 2.5.12, which seems to be 3 years old.

Here is a diff between the jsplumb 2.5.12 vs 2.15.4 (latest 2.x version):
jsplumb/jsplumb@2.5.12...2.15.4

Note that most changes are about doc/demo/dist until the package.json file (about half of the changes aren't really interesting to look at)

I don't know if upgrading would provide any benefits, I assume so. (the API seems to have changed a lot). I noticed a react-dag v2 is in progress, but I'm not sure what its purpose is. 😃

Are there plans to upgrade to jsplumb 4?

Jsplumb 4 is still in beta, but I wanted to know if you have any plan to upgrade the current implementation?

I don't know jsplumb at all, but the major improvement I can see is that it uses TypeScript, meaning a much better Developer Experience overall.

onDelete() typings not in dist 2.0.0-alpha-5

The changes made to the interface INodeProps made in f3258b3#diff-87b8af5581ac9180d1633b288fd10975R10 don't appear to be reflected in the release 2.0.0-alpha-5 on npm.

Having cloned "react-dag": "^2.0.0-alpha-5" the typings in index.d.ts still appear as follows.

export interface INodeProps {
  id: string;
  config?: INodeConfig;
  initNode?: (initConfig: IInitNodeProps) => void;
  onDelete?: () => void;
}

Which obviously results in TS complaining.

Expected 0 arguments, but got 2. [Error/2554]

Fix testing

This includes,

  • Removing karma + associated npm packages
  • Adding jest for testing
  • Fixing jsplumb for code usage + testing

Readme update

Hello, thank you for that wrapper for jsPlumb. Can you update Readme file? Especially hyperlinks inside

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.