{"id":22120064,"url":"https://github.com/joshbrew/acyclicgraph.js","last_synced_at":"2025-03-24T06:25:59.613Z","repository":{"id":57172979,"uuid":"453557192","full_name":"joshbrew/AcyclicGraph.js","owner":"joshbrew","description":"Easy node tree graphs for creating DAGs i.e. any arbitrary node tree with forward and backpropagation, repeaters, etc. for chaining scripts and scopes e.g. for game systems.","archived":false,"fork":false,"pushed_at":"2022-05-13T10:06:58.000Z","size":241,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-01T21:45:49.610Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joshbrew.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-30T01:01:06.000Z","updated_at":"2022-09-22T02:50:04.000Z","dependencies_parsed_at":"2022-08-24T14:41:09.064Z","dependency_job_id":null,"html_url":"https://github.com/joshbrew/AcyclicGraph.js","commit_stats":null,"previous_names":["brainsatplay/acyclicgraph.js"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FAcyclicGraph.js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FAcyclicGraph.js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FAcyclicGraph.js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshbrew%2FAcyclicGraph.js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joshbrew","download_url":"https://codeload.github.com/joshbrew/AcyclicGraph.js/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245219863,"owners_count":20579661,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-01T14:20:12.337Z","updated_at":"2025-03-24T06:25:59.588Z","avatar_url":"https://github.com/joshbrew.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Acyclic Graphs\n\n![status](https://img.shields.io/npm/v/acyclicgraph) \n![downloads](https://img.shields.io/npm/dt/acyclicgraph)\n![size](https://img.shields.io/github/size/brainsatplay/acyclicgraph.js/acyclicgraph.js)\n![lic](https://img.shields.io/npm/l/acyclicgraph)\n\n`npm i acyclicgraph`\n\nEasy node tree graph for creating graphs like [DAGs](https://en.wikipedia.org/wiki/Directed_acyclic_graph) i.e. any arbitrary node tree with forward and backpropagation, repeaters, etc. for chaining scripts and scopes e.g. [game systems](https://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/). You can construct any type of graph and run async coroutines etc.\n\nThis is built around the idea of having an operator i.e. a custom i/o handler at each scope. You can easily extend the graphnode class with primitives etc for different systems, just read the code. Otherwise it's a pure javascript implementation with no dependencies.\n\nRunning a node returns a promise that resolves after the tree is finished running, so you can chain complex functions that can mutate e.g. an object returned and passed along by the first operator or a property on a node. See below for a very simple example. \n\nThis can get as complex as you want as each node is essentially just a different local scope and a main() function for each, with some easy piping based on writing a native object hierarchy with optional tagging for important nodes where results need to be subscribed to or chained to other complex nodes.\n\nFor a more basic function sequencer, see [Sequencer.js](https://github.com/moothyknight/Sequencer.js)\n\n### Basic usage\n```js\n\nlet tree = { //top level should be an object, children can be arrays of objects\n    tag:'top',\n    operator:(\n        input, //input, e.g. output from another node\n        node,  //'this' node\n        origin, //origin node\n        cmd    //e.g. 'loop' or 'animate' will be defined if the operator is running on the loop or animate routines, needed something. Can define more commands but you might as well use an object in input for that. \n    )=\u003e{\n        if(typeof input === 'object') {\n            if(input?.x) node.x = input.x; \n            if(input?.y) node.y = input.y;\n            if(input?.z) node.z = input.z;\n            console.log('top node, input:', input);\n        }\n        return input;\n    }, //input is the previous result if passed from another node. node is 'this' node, origin is the previous node if passed\n    forward:true, //forward prop: returned outputs from the operator are passed to children operator(s)\n    //backward:true, //backprop: returned outputs from the operator are passed to the parent operator\n    x:3, //arbitrary properties available on the node variable in the operator \n    y:2,\n    z:1,\n    children:{ //object, array, or tag. Same as the 'next' tag in Sequencer.js\n        tag:'next', //tagged nodes get added to the node map by name, they must be unique! non-tagged nodes are only referenced internally e.g. in call trees\n        operator:(\n            input,\n            node,\n            origin,\n            cmd\n        )=\u003e{\n            if(origin.x) { //copy over the coordinates\n                node.x = origin.x;\n                node.y = origin.y;\n                node.z = origin.z;\n            }\n            console.log('next node \\n parent node:',node,'\\ninput',input);\n        }, // if you use a normal function operator(input,node,origin){} then you can use 'this' reference instead of 'node', while 'node' is more flexible for arrow functions etc.\n        //etc..\n        delay:500,\n        repeat:3\n    },\n    delay:1000//, //can timeout the operation\n    //frame:true //can have the operation run via requestAnimationFrame (throttling)\n    //repeat:3 //can repeat an operator, or use \"recursive\" for the same but passing the node's result back in\n    //loop: 10 //can add a loop subroutine, the node will only run the loop once and can still be called. Specify milliseconds. Stop with node.stopLooping()\n    //animate: true //can add a requestAnimationFrame subroutine. Stop with node.stopAnimating()\n};\n\n\nlet graph = new AcyclicGraph();\ngraph.addNode(tree);\n\nlet res = graph.run(tree.tag,{x:4,y:5,z:6}).then(res =\u003e console.log('promise, after', res));\n\nconsole.log('promise returned:',res);\n\n\n```\n\n### Also try the webcomponents we built to run natively with our AcyclicGraph logic!\n`npm i acyclicgraph-webcomponents`\n\nRun the /example_app for demonstration, it's purely conceptual but you can see a fully implemented example at http://190.92.148.106 using this to do gravitational physics with html elements as planets.\n\n### GraphNode class\n\nThese are the objects created to represent each node in the tree. They can be created without belonging to an acyclic graph. The acyclic graph simply adds sequential tags 'node0, node1' etc (rather than random tags) to all untagged nodes according to the order of the tree provided so it's easier to create self-referencing trees.\n\nGraphNode properties\n```ts\ntype GraphNodeProperties = {\n    tag?:string, //generated if not specified, or use to get another node by tag instead of generating a new one\n    operator:( //can be async\n        input:any, //input, e.g. output from another node\n        node:GraphNode|string,  //'this' node\n        origin?:GraphNode|string, //origin node\n        cmd?:string|number    //e.g. 'loop' or 'animate' will be defined if the operator is running on the loop or animate routines, needed something. Can define more commands but you might as well use an object in input for that. \n    )=\u003eany, //Operator to handle I/O on this node. Returned inputs can propagate according to below settings\n    forward:boolean, //pass output to child nodes\n    backward:boolean, //pass output to parent node\n    children?:string|GraphNodeProperties|GraphNode|(GraphNodeProperties|GraphNode|string)[], //child node(s), can be tags of other nodes, properties objects like this, or graphnodes, or null\n    parent?:GraphNode|undefined, //parent graph node\n    delay?:false|number, //ms delay to fire the node\n    repeat?:false|number, // set repeat as an integer to repeat the input n times, cmd will be the number of times the operation has been repeated\n    recursive?:false|number, //or set recursive with an integer to pass the output back in as the next input n times, cmd will be the number of times the operation has been repeated\n    frame?:boolean, //true or false. If repeating or recursing, execute on requestAnimationFrame? Careful mixing this with animate:true\n    animate?:boolean, //true or false\n    loop?:false|number, //milliseconds or false\n    animation?:( //uses operator by default unless defined otherwise can be async \n        input:any, //input, e.g. output from another node\n        node:GraphNode|string,  //'this' node\n        origin?:GraphNode|string, //origin node\n        cmd?:string|number    //e.g. 'loop' or 'animate' will be defined if the operator is running on the loop or animate routines, needed something. Can define more commands but you might as well use an object in input for that. \n    )=\u003eany | undefined,\n    looper?:( //uses operator by default unless defined otherwise (to separate functions or keep them consolidated) can be async\n        input:any, //input, e.g. output from another node\n        node:GraphNode|string,  //'this' node\n        origin?:GraphNode|string, //origin node\n        cmd?:string|number    //e.g. 'loop' or 'animate' will be defined if the operator is running on the loop or animate routines, needed something. Can define more commands but you might as well use an object in input for that. \n    )=\u003eany | undefined,\n    [key:string]:any //add whatever variables and utilities\n}; //can specify properties of the element which can be subscribed to for changes.\n\n```\n\nGraphNode utilities\n\n```js\n\n    //node properties you can set, create a whole tree using the children\n    let props={\n        operator:(\n            input, //input, e.g. output from another node\n            node,  //'this' node\n            origin, //origin node\n            cmd    //e.g. 'loop' or 'animate' will be defined if the operator is running on the loop or animate routines, needed something. Can define more commands but you might as well use an object in input for that. \n        )=\u003e{ console.log(input); return input; }, //Operator to handle I/O on this node. Returned inputs can propagate according to below settings\n        forward:true, //pass output to child nodes\n        backward:false, //pass output to parent node\n        children:undefined, //child node(s), can be tags of other nodes, properties objects like this, or graphnodes, or null\n        parent:undefined, //parent graph node\n        delay:false, //ms delay to fire the node\n        repeat:false, // set repeat as an integer to repeat the input n times\n        recursive:false, //or set recursive with an integer to pass the output back in as the next input n times\n        frame:false, //true or false. If repeating or recursing, execute on requestAnimationFrame? Careful mixing this with animate:true\n        animate:false, //true or false\n        loop:undefined, //milliseconds or false\n        tag:undefined, //generated if not specified, or use to get another node by tag instead of generating a new one\n      }; //can specify properties of the element which can be subscribed to for changes.\n\n\nlet node = new GraphNode(props, parentNode, graph);\n\nnode\n    .operator(input,node=this,origin,cmd) //\u003c--- runs the operator function\n    \n    .runOp(input, node=this, origin, cmd) //\u003c--- runs the operator and sets state with the result for that tag. Returns a promise if the operator is an async function.\n    \n    .runNode(node,input,origin) //\u003c--- runs the node sequence starting from the given node. If any async or flow logic is being used by the node, it returns a promise which can be awaited to get the final result of the tree. Else it returns a synchronous operation for speed.\n\n    .run(input,node=this,origin) //\u003c--- this is the base sequencing function.  If any async or flow logic is being used by the node, it returns a promise which can be awaited to get the final result of the tree. Else it returns a synchronous operation for speed.\n\n    .runAnimation(input,node=this,origin) //run the operator loop on the animation loop with the given input conditions, the cmd will be 'animate' so you can put an if statement in to run animation logic in the operator\n\n    .runLoop(input,node=this,origin) //runs a setTimeout loop according to the node.loop setting (ms)\n\n    .setOperator(operator) //set the operator functions\n\n    .setParent(parent) //set the parent GraphNode\n\n    .addChildren(children) //add child GraphNodes to this node (operation results passed on forward pass)\n\n    .removeTree(node) //remove a node and all associated nodes\n\n    .addNode(props) //add a node using a properties object\n\n    .appendNode(props, parentNode=this) //append a child node with a properties object or string\n\n    .getNode(tag) //get a child node of this node by tag (in tree)\n\n    .stopLooping() //stop the loop\n\n    .stopAnimating() //stop the animation loop\n\n    .stopNode() //stop both\n\n    .convertChildrenToNodes(node=this) //convert child node properties objects/tags/etc to nodes.\n\n    .callParent(input, origin=this, cmd) //run the parent node operation (no propagation)\n\n    .callChildren(input, origin=this, cmd, idx) //call the children node(s) with the given input, won't run their forward/backward passes. \n\n    .setProps(props) //assign to self\n\n    .subscribe(callback=(res)=\u003e{},tag=this.tag) //subscribe to the tagged node output, returns an int. if you pass a graphnode as a callback it will call subscribeNode\n \n    .unsubscribe(sub,tag=this.tag) //unsubscribe from the tag, no sub = unsubscribe all\n\n    .subscribeNode(node) //subscribe another node sequence (not a direct child) to this node's output via the state\n\n    .print(node=this,printChildren=true) //recursively print a reconstrucible json hierarchy of the graph nodes, including arbitrary keys/functions, if printChildren is set to false it will only print the tags and not the whole object in the .children property of this node\n\n    .reconstruct(json='{}') //reconstruct a jsonified node hierarchy into a functional GraphNode tree and add it to the list\n\n```\n\n\n### Acyclic Graph Utilities\n\n```js\n\n//this is less useful now that the graph nodes are self contained but it can act as an index for your node trees.\nlet graph = new AcyclicGraph();\n\n    graph\n\n        .addNode(node) // add a node with a properties object\n\n        .getNode(tag) // get a node by tag, nodes added in the acyclic graph automatically get their tags set to sequential numbers if not set otherwise\n\n        .create(operator=(input,node,origin,cmd)=\u003e{},parentNode,props) //create a node just using an operator, can pass props for more\n\n        .run(node,input,origin) //\u003c--- runs the node sequence starting from the given node, returns a promise that will spit out the final result from the tree if any\n\n        .runNode(node,input,origin) //same as run\n\n        .removeTree(node) // remove a node tree by head node\n\n        .removeNode(node) // remove a node and any references\n\n        .appendNode(node, parentNode) // append a node to a parent node\n\n        .callParent(node,input,origin=node,cmd) // call a parent ndoe of a given node\n\n        .callChildren(node, input, origin=node, cmd, idx) // call the children of a given node\n\n        .subscribe(tag, callback=(res)=\u003e{}) //subscribe to a node tag, callbacks contain that node's operator output, returns an int sub number\n\n        .unsubscribe(tag, sub) //unsubscribe to a node by tag, \n\n        .subscribeNode(inputNode,outputNode) //subscribe the outputNode to the output of the inputNode\n\n        .print(node,printChildren=true) //recursively print a reconstrucible json hierarchy of the graph nodes, including arbitrary keys/functions, if printChildren is set to false it will only print the tags and not the whole object in the .children property of this node\n\n        .reconstruct(json='{}') //reconstruct a jsonified node hierarchy into a functional GraphNode tree\n\n```\n\nExtra methods:\n```js \nreconstructNode(json='{}') //return a GraphNode tree reconstructed from a jsonified tree\n\n//just provide an operator to make a node\ncreateNode(operator=(input,node,origin,cmd)=\u003e{},parentNode,props,graph)\n\n```\n\n\n### Design Philosophy\n\nGraphs simply are a way to manage operation sequences. These can be directed or undirected, and can have cycles on some nodes (technically not acyclic) with single trees or multiple running concurrently e.g. an animation loop and then event loops for user or server inputs\u003cbr/\u003e\n![DAG-and-not-DAG-768x321](https://user-images.githubusercontent.com/18196383/153975071-34ae096e-2bfa-4564-a1ec-4e21989806ad.png)\n\nAcyclic graphs are trees of operations with arbitrary entry and exit points plus arbitrary propagation of results through the tree.\nEach node is an object with a few required properties and functions and then anything else you want to add as variables, reference, utility functions etc. \n\nNodes added to the graph tree are made into a 'GraphNode' class object with some added utility functions added to allow generic message passing between parent/child/any nodes.\nThere are additional properties to indicate whether to delay (or render on frame), repeat or recurse, and do automatic forward or backprop based on the tree hierarchy.\n\nEach node comes with an 'operator' main function to handle input and output with arbitrary conditions. \n\nTagged nodes are indexed as callable entry points to the tree.\nNode operations return results via a promise as well as propagating up or down-treee (or to other trees) based on available default object settings. \nAll else will be built into the custom main 'operator()' functions you add yourself.\n\nThe 'operator()' function in each node is a program for that node that passes an input, the node, and the origin node if it's passing the input. \nIt can and should return results which can be used for propagation to other nodes automatically or for returning results from a chain of operations \nstarting with the called node. This is like a 'main()' program in a file where the node is the script's scope with local properties\n\nTagged node operation results can also be subscribed to with via an internal state manager from anywhere in your program so you don't need to add more lines to operators to output to certain places.\n\n### Contributors\n\nJoshua Brewster --  AGPLv3.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshbrew%2Facyclicgraph.js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshbrew%2Facyclicgraph.js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshbrew%2Facyclicgraph.js/lists"}