reactive-function
Advanced tools
Comparing version 0.9.0 to 0.10.0
35
index.js
@@ -100,5 +100,15 @@ var ReactiveProperty = require("reactive-property"); | ||
// Remove property nodes with no edges connected. | ||
inputs.concat([output]).forEach(function (property){ | ||
var node = property.id; | ||
if(graph.indegree(node) + graph.outdegree(node) === 0){ | ||
graph.removeNode(property.id); | ||
} | ||
}); | ||
// Remove the reference to the 'evaluate' function. | ||
delete output.evaluate; | ||
// Remove references to everything. | ||
inputs = callback = output = undefined; | ||
} | ||
@@ -156,2 +166,27 @@ }; | ||
ReactiveFunction.serializeGraph = function (){ | ||
var serialized = graph.serialize(); | ||
// Replace ids with names for nodes. | ||
serialized.nodes.forEach(function (node){ | ||
var name = properties[node.id].propertyName; | ||
if(name){ | ||
node.id = name; | ||
} | ||
}); | ||
// Replace ids with names for links. | ||
serialized.links.forEach(function (link){ | ||
var sourceName = properties[link.source].propertyName; | ||
if(sourceName){ | ||
link.source = sourceName; | ||
} | ||
var targetName = properties[link.target].propertyName; | ||
if(targetName){ | ||
link.target = targetName; | ||
} | ||
}); | ||
return serialized; | ||
} | ||
module.exports = ReactiveFunction; |
{ | ||
"name": "reactive-function", | ||
"version": "0.9.0", | ||
"version": "0.10.0", | ||
"description": "A library for managing data flows and changing state.", | ||
@@ -11,3 +11,3 @@ "main": "index.js", | ||
"type": "git", | ||
"url": "git+https://github.com/curran/reactive-function.git" | ||
"url": "git+https://github.com/datavis-tech/reactive-function.git" | ||
}, | ||
@@ -30,12 +30,13 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://github.com/curran/reactive-function/issues" | ||
"url": "https://github.com/datavis-tech/reactive-function/issues" | ||
}, | ||
"homepage": "https://github.com/curran/reactive-function#readme", | ||
"homepage": "https://github.com/datavis-tech/reactive-function#readme", | ||
"dependencies": { | ||
"graph-data-structure": "^0.5.0", | ||
"reactive-property": "^0.8.0" | ||
"graph-data-structure": "^0.8.0", | ||
"reactive-property": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^2.4.5" | ||
"graph-diagrams": "^0.5.0", | ||
"mocha": "^2.5.3" | ||
} | ||
} |
260
README.md
@@ -1,27 +0,32 @@ | ||
# reactive-function [![Build Status](https://travis-ci.org/curran/reactive-function.svg?branch=master)](https://travis-ci.org/curran/reactive-function) | ||
# reactive-function | ||
A library for managing reactive data flows. | ||
[![NPM](https://nodei.co/npm/reactive-function.png)](https://npmjs.org/package/reactive-function) | ||
[![NPM](https://nodei.co/npm-dl/reactive-function.png?months=3)](https://npmjs.org/package/reactive-function) | ||
[![Build Status](https://travis-ci.org/datavis-tech/reactive-function.svg?branch=master)](https://travis-ci.org/datavis-tech/reactive-function) | ||
* A library for managing data flows graphs and changing state. | ||
* Built on [reactive-property](https://github.com/curran/reactive-property). | ||
* The foundation for [reactive-model](https://github.com/curran/reactive-model). | ||
This library provides the ability to define reactive data flows by modeling application state as a directed graph and using [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting) to compute the order in which changes should be propagated. | ||
# Usage | ||
<p align="center"> | ||
<img src="https://cloud.githubusercontent.com/assets/68416/15476439/82b83244-212c-11e6-91d5-d975de8b6b8a.png"> | ||
<br> | ||
This library is built on top of <a href="https://github.com/datavis-tech/reactive-property">reactive-property</a> and <a href="https://github.com/datavis-tech/graph-data-structure">graph-data-structure</a> | ||
</p> | ||
If you are using NPM, install this package with: | ||
**Table of Contents** | ||
`npm install reactive-function` | ||
* [Examples](#examples) | ||
* [Full Name](#full-name) | ||
* [ABC](#abc) | ||
* [Tricky Case](#tricky-case) | ||
* [Installing](#installing) | ||
* [API Reference](#api-reference) | ||
* [Managing Reactive Functions](#managing-reactive-functions) | ||
* [Data Flow Execution](#data-flow-execution) | ||
* [Serialization](#serialization) | ||
Require it in your code like this: | ||
## Examples | ||
```javascript | ||
var ReactiveFunction = require("reactive-function"); | ||
``` | ||
This library is designed to work with [reactive-property](https://github.com/curran/reactive-property), you'll need that too. | ||
```javascript | ||
var ReactiveProperty = require("reactive-property"); | ||
``` | ||
### Full Name | ||
Suppose you have two reactive properties to represent someone's first and last name. | ||
@@ -34,3 +39,3 @@ | ||
Suppose you'd like to have another property that represents the full name of the person. | ||
Another reactive property can represent the full name of the person. | ||
@@ -47,8 +52,6 @@ ```javascript | ||
However, this sets the value of `fullName` only once, and it does not get updated when `firstName` or `lastName` change. | ||
However, the above code sets the value of `fullName` only once. Here's how you can define a **[ReactiveFunction](#constructor)** that automatically updates `fullName` whenever `firstName` or `lastName` change. | ||
Here's how you can define a reactive function that updates the full name whenever the first name or last name changes. | ||
```javascript | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
@@ -62,5 +65,9 @@ output: fullName, | ||
This defines a "reactive function" that will be invoked when its inputs (`firstName` and `lastName`) are both defined and whenever either one changes ([`null` is considered a defined value](https://github.com/curran/reactive-function/issues/1)). The function will be invoked on the next tick of the JavaScript event loop after it is defined and after any dependencies change. | ||
<p align="center"> | ||
<img src="https://cloud.githubusercontent.com/assets/68416/15389922/cf3f24dc-1dd6-11e6-92d6-058051b752ea.png"> | ||
<br> | ||
The data flow graph for the example code above. | ||
</p> | ||
To force a synchronous evaluation of all reactive functions whose dependencies have updated, you can call | ||
Whenever `firstName` or `lastName` change, the callback defined above will be executed on the next animation frame. If you don't want to wait until the next animation frame, you can force a synchronous evaluation of the data flow graph by invoking **[digest](#digest)**. | ||
@@ -71,3 +78,3 @@ ```javascript | ||
Now you can access the computed value of the reactive function by invoking it as a getter. | ||
Now you can access the computed `fullName` value by invoking it as a getter. | ||
@@ -78,6 +85,195 @@ ```javascript | ||
For more detailed example code, have a look at the [tests](https://github.com/curran/reactive-function/blob/master/test.js). | ||
### ABC | ||
Related work: | ||
The output of one reactive function can be used as an input to another. | ||
<p align="center"> | ||
<img src="https://cloud.githubusercontent.com/assets/68416/15385597/44a10522-1dc0-11e6-9054-2150f851db46.png"> | ||
<br> | ||
Here, b is both an output and an input. | ||
</p> | ||
```javascript | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var c = ReactiveProperty(); | ||
ReactiveFunction({ | ||
inputs: [a], | ||
output: b, | ||
callback: function (a){ return a * 2; } | ||
}); | ||
ReactiveFunction({ | ||
inputs: [b], | ||
output: c, | ||
callback: function (b){ return b / 2; } | ||
}); | ||
ReactiveFunction.digest(); | ||
assert.equal(c(), 5); | ||
``` | ||
### Tricky Case | ||
This is the case where [Model.js](https://github.com/curran/model) fails because it uses [Breadth-first Search](https://en.wikipedia.org/wiki/Breadth-first_search) to propagate changes. In this graph, propagation using breadth-first search would cause `e` to be set twice, and the first time it would be set with an *inconsistent state*. This fundamental flaw cropped up as flashes of inconstistent states in some interactive visualizations built on Model.js. For example, it happens when you change the X column in this [Magic Heat Map](http://bl.ocks.org/curran/a54fc3a6578efcdc19f4). This flaw in Model.js is the main inspiration for making this library and using [topological sort](https://en.wikipedia.org/wiki/Topological_sorting), which is the correct algorithm for propagating data flows and avoiding inconsistent states. | ||
<p align="center"> | ||
<img src="https://cloud.githubusercontent.com/assets/68416/15400254/7f779c9a-1e08-11e6-8992-9d2362bfba63.png"> | ||
<br> | ||
The tricky case, where breadth-first propagation fails. | ||
</p> | ||
```javascript | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var c = ReactiveProperty(); | ||
var d = ReactiveProperty(); | ||
var e = ReactiveProperty(); | ||
ReactiveFunction({ inputs: [a], output: b, callback: function (a){ return a * 2; } }); | ||
ReactiveFunction({ inputs: [b], output: c, callback: function (b){ return b + 5; } }); | ||
ReactiveFunction({ inputs: [a], output: d, callback: function (a){ return a * 3; } }); | ||
ReactiveFunction({ inputs: [c, d], output: e, callback: function (c, d){ return c + d; } }); | ||
ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
a(10); | ||
ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
``` | ||
For more detailed example code, have a look at the [tests](https://github.com/datavis-tech/reactive-function/blob/master/test.js). | ||
## Installing | ||
If you are using [NPM](npmjs.com), install this package with: | ||
`npm install reactive-function` | ||
Require it in your code like this: | ||
```javascript | ||
var ReactiveFunction = require("reactive-function"); | ||
``` | ||
This library is designed to work with [reactive-property](https://github.com/datavis-tech/reactive-property), you'll need that too. | ||
`npm install reactive-property` | ||
```javascript | ||
var ReactiveProperty = require("reactive-property"); | ||
``` | ||
## API Reference | ||
* [Managing Reactive Functions](#managing-reactive-functions) | ||
* [Data Flow Execution](#data-flow-execution) | ||
* [Serialization](#serialization) | ||
### Managing Reactive Functions | ||
<a name="constructor" href="#constructor">#</a> <b>ReactiveFunction</b>(<i>options</i>) | ||
Construct a new reactive function. The *options* argument should have the following properties. | ||
* *inputs* - The input properties. An array of **[ReactiveProperty](https://github.com/datavis-tech/reactive-property#constructor)** instances. | ||
* *output* (optional) - The output property. An instance of **[ReactiveProperty](https://github.com/datavis-tech/reactive-property#constructor)**. | ||
* *callback* - The reactive function callback. Arguments are values of *inputs*. The return value will be assigned to *output*. | ||
This constructor sets up a reactive function such that *callback* be invoked | ||
* when all input properties are defined, | ||
* after any input properties change, | ||
* during a **[digest](#digest)**. | ||
An input property is considered "defined" if it has any value other than `undefined`. The special value `null` is considered to be defined. | ||
An input property is considered "changed" when | ||
* the reactive function is initially set up, and | ||
* whenever its value is set. | ||
Input properties for one reactive function may also be outputs of another. | ||
<a name="destroy" href="#destroy">#</a> <i>reactiveFunction</i>.<b>destroy</b>() | ||
Cleans up resources allocated to this reactive function. | ||
More specifically: | ||
* Removes listeners from inputs. | ||
* Removes edges from the data flow graph (from each input). | ||
* Removes property nodes from the data flow graph if they have no incoming or outgoing edges. | ||
You should invoke this function when finished using reactive functions in order to avoid memory leaks. | ||
### Data Flow Execution | ||
<a name="digest" href="#digest">#</a> ReactiveFunction.<b>digest</b>() | ||
Propagates changes from input properties through the data flow graph defined by all reactive properties using [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting). An edge in the data flow graph corresponds to a case where the output of one reactive function is used as an input to another. | ||
Whenever any input properties for any reactive function change, **[digest](#digest)** is debounced (scheduled for invocation) on the **[nextFrame](#next-frame)**. Because it is debounced, multiple synchronous changes to input properties collapse into a single digest invocation. | ||
Digests are debounced to the next animation frame rather than the next tick because browsers will render the page at most every animation frame (approximately 60 frames per second). This means that if DOM manipulations are triggered by reactive functions, and input properties are changed more frequently than 60 times per second (e.g. mouse or keyboard events), the DOM manipulations will only occur at most 60 times per second, not more than that. | ||
<a name="next-frame" href="#next-frame">#</a> ReactiveFunction.<b>nextFrame</b>(<i>callback</i>) | ||
Schedules the given function to execute on the next animation frame or next tick. | ||
This is a simple polyfill for [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) that falls back to [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout). The main reason for having this is for use in the [tests](https://github.com/datavis-tech/reactive-function/blob/master/test.js), which run in a Node.js environment where `requestAnimationFrame` is not available. Automatic digests are debounced against this function. | ||
### Serialization | ||
Data flow graphs can be serialized to JSON, then visualized using [graph-diagrams](https://github.com/datavis-tech/graph-diagrams/). | ||
<a name="serialize" href="#serialize">#</a> ReactiveFunction.<b>serializeGraph</b>() | ||
Returns a serialized form of the graph. Node names are derived from `property.propertyName` for each property. If `propertyName` is not specified, then the automaticelly generated node id (an integer) is used as the node name. | ||
Example: | ||
```javascript | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var fullName = ReactiveProperty(); | ||
// For serialization. | ||
firstName.propertyName = "firstName"; | ||
lastName.propertyName = "lastName"; | ||
fullName.propertyName = "fullName"; | ||
ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
}); | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
``` | ||
The value of `serialized` will be: | ||
```json | ||
{ | ||
"nodes": [ | ||
{ "id": "fullName" }, | ||
{ "id": "firstName" }, | ||
{ "id": "lastName" } | ||
], | ||
"links": [ | ||
{ "source": "firstName", "target": "fullName" }, | ||
{ "source": "lastName", "target": "fullName" } | ||
] | ||
} | ||
``` | ||
See also <a href="https://github.com/datavis-tech/graph-data-structure#serialize"><i>graph</i>.<b>serialize</b>()</a>. | ||
## Related Work | ||
* [Functional Reactive Animation (academic paper)](https://www.eecs.northwestern.edu/~robby/courses/395-495-2009-winter/fran.pdf) | ||
* [ReactiveJS](https://github.com/mattbaker/Reactive.js) | ||
@@ -89,3 +285,9 @@ * [vogievetsky/DVL](https://github.com/vogievetsky/DVL) | ||
* [Model.js](https://github.com/curran/model) | ||
* [RxJS - when](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/when.md) | ||
* [Bacon - When](https://github.com/baconjs/bacon.js/tree/master#bacon-when) | ||
Thanks to Mike Bostock and Vadim Ogievetsky for suggesting to have the ability to digest within a single tick of the event loop (the main difference between this project and the original [model-js](https://github.com/curran/model)). This idea has inspired the construction of this library, which I hope will provide a solid foundation for complex interactive visualization systems. | ||
<p align="center"> | ||
<a href="https://datavis.tech/"> | ||
<img src="https://cloud.githubusercontent.com/assets/68416/15298394/a7a0a66a-1bbc-11e6-9636-367bed9165fc.png"> | ||
</a> | ||
</p> |
249
test.js
@@ -10,2 +10,13 @@ // Unit tests for reactive-function. | ||
var outputGraph = require("graph-diagrams")({ | ||
// If true, writes graph files to ../graph-diagrams for visualization. | ||
outputGraphs: true, | ||
project: "reactive-function" | ||
}); | ||
function output(name){ | ||
outputGraph(ReactiveFunction.serializeGraph(), name); | ||
} | ||
describe("ReactiveFunction", function() { | ||
@@ -20,3 +31,3 @@ | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
@@ -39,2 +50,10 @@ output: fullName, | ||
assert.equal(fullName(), "John Lennon"); | ||
// For serialization. | ||
firstName.propertyName = "firstName"; | ||
lastName.propertyName = "lastName"; | ||
fullName.propertyName = "fullName"; | ||
output("full-name"); | ||
reactiveFunction.destroy(); | ||
}); | ||
@@ -50,3 +69,3 @@ | ||
ReactiveFunction({ | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
@@ -59,3 +78,3 @@ output: d, | ||
ReactiveFunction({ | ||
var rf2 = ReactiveFunction({ | ||
inputs: [a, b, c], | ||
@@ -72,2 +91,13 @@ output: e, | ||
assert.equal(e(), 30); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
e.propertyName = "e"; | ||
output("any-number"); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
@@ -78,7 +108,6 @@ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
var c = ReactiveProperty(); | ||
ReactiveFunction({ | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
@@ -91,3 +120,3 @@ output: b, | ||
ReactiveFunction({ | ||
var rf2 = ReactiveFunction({ | ||
inputs: [b], | ||
@@ -102,2 +131,11 @@ output: c, | ||
assert.equal(c(), 5); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
output("abc"); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
@@ -113,3 +151,3 @@ | ||
ReactiveFunction({ | ||
var rf1 = ReactiveFunction({ | ||
inputs: [a], | ||
@@ -122,3 +160,3 @@ output: c, | ||
ReactiveFunction({ | ||
var rf2 = ReactiveFunction({ | ||
inputs: [b, c], | ||
@@ -133,2 +171,12 @@ output: d, | ||
assert.equal(d(), 20); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
output("abcd"); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
@@ -153,3 +201,2 @@ | ||
var a = ReactiveProperty(5); | ||
var b = ReactiveProperty(); | ||
@@ -160,6 +207,8 @@ var c = ReactiveProperty(); | ||
ReactiveFunction({ inputs: [a], output: b, callback: function (a){ return a * 2; } }); | ||
ReactiveFunction({ inputs: [b], output: c, callback: function (b){ return b + 5; } }); | ||
ReactiveFunction({ inputs: [a], output: d, callback: function (a){ return a * 3; } }); | ||
ReactiveFunction({ inputs: [c, d], output: e, callback: function (c, d){ return c + d; } }); | ||
var rfs = [ | ||
ReactiveFunction({ inputs: [a], output: b, callback: function (a){ return a * 2; } }), | ||
ReactiveFunction({ inputs: [b], output: c, callback: function (b){ return b + 5; } }), | ||
ReactiveFunction({ inputs: [a], output: d, callback: function (a){ return a * 3; } }), | ||
ReactiveFunction({ inputs: [c, d], output: e, callback: function (c, d){ return c + d; } }) | ||
]; | ||
@@ -172,2 +221,14 @@ ReactiveFunction.digest(); | ||
assert.equal(e(), ((a() * 2) + 5) + (a() * 3)); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
c.propertyName = "c"; | ||
d.propertyName = "d"; | ||
e.propertyName = "e"; | ||
output("tricky-case"); | ||
rfs.forEach(function (reactiveFunction){ | ||
reactiveFunction.destroy(); | ||
}); | ||
}); | ||
@@ -180,3 +241,3 @@ | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
@@ -193,2 +254,4 @@ output: b, | ||
assert.equal(numInvocations, 1); | ||
reactiveFunction.destroy(); | ||
}); | ||
@@ -201,3 +264,3 @@ | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
@@ -212,4 +275,6 @@ output: c, | ||
assert.equal(c(), 15); | ||
reactiveFunction.destroy(); | ||
done(); | ||
}, 0); | ||
}); | ||
@@ -280,3 +345,3 @@ | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
@@ -292,2 +357,4 @@ output: c, | ||
assert.equal(numInvocations, 0); | ||
reactiveFunction.destroy(); | ||
}); | ||
@@ -301,3 +368,3 @@ | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a, b], | ||
@@ -313,2 +380,4 @@ output: c, | ||
assert.equal(numInvocations, 1); | ||
reactiveFunction.destroy(); | ||
}); | ||
@@ -320,3 +389,3 @@ | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
@@ -330,2 +399,4 @@ callback: function (a, b){ | ||
assert.equal(numInvocations, 1); | ||
reactiveFunction.destroy(); | ||
}); | ||
@@ -338,3 +409,3 @@ | ||
ReactiveFunction({ | ||
var reactiveFunction = ReactiveFunction({ | ||
inputs: [a], | ||
@@ -350,2 +421,9 @@ output: b, | ||
assert.equal(b(), 5); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
output("ab"); | ||
reactiveFunction.destroy(); | ||
}); | ||
@@ -360,4 +438,4 @@ | ||
ReactiveFunction({ inputs: [a], output: b, callback: identity }); | ||
ReactiveFunction({ inputs: [b], output: a, callback: identity }); | ||
var rf1 = ReactiveFunction({ inputs: [a], output: b, callback: identity }); | ||
var rf2 = ReactiveFunction({ inputs: [b], output: a, callback: identity }); | ||
@@ -377,2 +455,9 @@ ReactiveFunction.digest(); | ||
// For serialization. | ||
a.propertyName = "a"; | ||
b.propertyName = "b"; | ||
output("data-binding"); | ||
rf1.destroy(); | ||
rf2.destroy(); | ||
}); | ||
@@ -388,23 +473,26 @@ | ||
// I = V / R | ||
ReactiveFunction({ | ||
inputs: [V, R], | ||
output: I, | ||
callback: function (v, r){ return v / r; } | ||
}); | ||
var rfs = [ | ||
// V = I * R | ||
ReactiveFunction({ | ||
inputs: [I, R], | ||
output: V, | ||
callback: function (i, r){ return i * r; } | ||
}); | ||
// I = V / R | ||
ReactiveFunction({ | ||
inputs: [V, R], | ||
output: I, | ||
callback: function (v, r){ return v / r; } | ||
}), | ||
// R = V / I | ||
ReactiveFunction({ | ||
inputs: [V, I], | ||
output: R, | ||
callback: function (v, i){ return v / i; } | ||
}); | ||
// V = I * R | ||
ReactiveFunction({ | ||
inputs: [I, R], | ||
output: V, | ||
callback: function (i, r){ return i * r; } | ||
}), | ||
// R = V / I | ||
ReactiveFunction({ | ||
inputs: [V, I], | ||
output: R, | ||
callback: function (v, i){ return v / i; } | ||
}) | ||
]; | ||
V(9) | ||
@@ -430,2 +518,11 @@ I(2) | ||
// For serialization. | ||
V.propertyName = "V"; | ||
I.propertyName = "I"; | ||
R.propertyName = "R"; | ||
output("ohms-law"); | ||
rfs.forEach(function (reactiveFunction){ | ||
reactiveFunction.destroy(); | ||
}); | ||
}); | ||
@@ -467,2 +564,3 @@ | ||
rf.destroy(); | ||
}); | ||
@@ -486,6 +584,77 @@ | ||
assert.equal(value, 15); | ||
rf.destroy(); | ||
done(); | ||
}); | ||
}); | ||
it("Should serialize the data flow graph.", function (){ | ||
var firstName = ReactiveProperty("Jane"); | ||
var lastName = ReactiveProperty("Smith"); | ||
var fullName = ReactiveProperty(); | ||
// For serialization. | ||
firstName.propertyName = "firstName"; | ||
lastName.propertyName = "lastName"; | ||
fullName.propertyName = "fullName"; | ||
var rf = ReactiveFunction({ | ||
inputs: [firstName, lastName], | ||
output: fullName, | ||
callback: function (first, last){ | ||
return first + " " + last; | ||
} | ||
}); | ||
var serialized = ReactiveFunction.serializeGraph(); | ||
//console.log(JSON.stringify(serialized, null, 2)); | ||
assert.equal(serialized.nodes.length, 3); | ||
assert.equal(serialized.links.length, 2); | ||
assert.equal(serialized.nodes[0].id, "fullName"); | ||
assert.equal(serialized.nodes[1].id, "firstName"); | ||
assert.equal(serialized.nodes[2].id, "lastName"); | ||
assert.equal(serialized.links[0].source, "firstName"); | ||
assert.equal(serialized.links[0].target, "fullName"); | ||
assert.equal(serialized.links[1].source, "lastName"); | ||
assert.equal(serialized.links[1].target, "fullName"); | ||
rf.destroy(); | ||
}); | ||
// Holding off until https://github.com/datavis-tech/graph-data-structure/issues/12 | ||
//it("Should serialize the data flow graph, falling back to property ids.", function (){ | ||
// var firstName = ReactiveProperty("Jane"); | ||
// var lastName = ReactiveProperty("Smith"); | ||
// var fullName = ReactiveProperty(); | ||
// ReactiveFunction({ | ||
// inputs: [firstName, lastName], | ||
// output: fullName, | ||
// callback: function (first, last){ | ||
// return first + " " + last; | ||
// } | ||
// }); | ||
// var serialized = ReactiveFunction.serializeGraph(); | ||
// console.log(JSON.stringify(serialized, null, 2)); | ||
// assert.equal(serialized.nodes.length, 3); | ||
// assert.equal(serialized.links.length, 2); | ||
// assert.equal(serialized.nodes[0].id, "fullName"); | ||
// assert.equal(serialized.nodes[1].id, "firstName"); | ||
// assert.equal(serialized.nodes[2].id, "lastName"); | ||
// assert.equal(serialized.links[0].source, "firstName"); | ||
// assert.equal(serialized.links[0].target, "fullName"); | ||
// assert.equal(serialized.links[1].source, "lastName"); | ||
// assert.equal(serialized.links[1].target, "fullName"); | ||
//}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33998
644
288
2
+ Addedgraph-data-structure@0.8.0(transitive)
+ Addedreactive-property@1.0.0(transitive)
- Removedgraph-data-structure@0.5.0(transitive)
- Removedreactive-property@0.8.0(transitive)
Updatedgraph-data-structure@^0.8.0
Updatedreactive-property@^1.0.0