├── example ├── index.css ├── example.png └── index.js ├── .gitignore ├── test.js ├── .travis.yml ├── LICENSE ├── package.json ├── index.js └── README.md /example/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | tmp/ 4 | npm-debug.log* 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | // const test = require('tape') 2 | // const virtualStreamGraph = require('./') 3 | -------------------------------------------------------------------------------- /example/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoshuawuyts/virtual-streamgraph/HEAD/example/example.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - "0.12" 3 | - "4" 4 | sudo: false 5 | language: node_js 6 | script: "npm run test:cov" 7 | after_script: "npm i -g codecov.io && cat ./coverage/lcov.info | codecov" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yoshua Wuyts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "virtual-streamgraph", 3 | "version": "1.0.3", 4 | "description": "Create a virtual-dom stream graph", 5 | "main": "index.js", 6 | "scripts": { 7 | "deps": "dependency-check . && dependency-check . --extra --no-dev", 8 | "test": "standard && npm run deps && NODE_ENV=test node test", 9 | "test:cov": "standard && npm run deps && NODE_ENV=test istanbul cover test.js", 10 | "example": "budo ./example/index.js --css ./example/index.css", 11 | "example:size": "browserify --full-paths -t hyperxify -g uglifyify ./example/index.js | discify --open" 12 | }, 13 | "repository": "yoshuawuyts/virtual-streamgraph", 14 | "keywords": [ 15 | "stream", 16 | "graph", 17 | "virtual-dom", 18 | "vdom" 19 | ], 20 | "license": "MIT", 21 | "dependencies": { 22 | "d3": "^3.5.13", 23 | "virtual-widget": "^1.2.0" 24 | }, 25 | "devDependencies": { 26 | "budo": "^8.0.0", 27 | "dependency-check": "^2.5.1", 28 | "disc": "^1.3.2", 29 | "hyperx": "^1.2.0", 30 | "hyperxify": "^1.1.0", 31 | "istanbul": "^0.4.2", 32 | "standard": "^5.4.1", 33 | "tape": "^4.4.0", 34 | "uglifyify": "^3.0.1", 35 | "virtual-app": "^2.2.0", 36 | "virtual-dom": "^2.1.1", 37 | "xtend": "^4.0.1" 38 | }, 39 | "files": [ 40 | "index.js", 41 | "bin/*" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | const createApp = require('virtual-app') 2 | const vdom = require('virtual-dom') 3 | const hyperx = require('hyperx') 4 | const xtend = require('xtend') 5 | 6 | const createGraph = require('..') 7 | 8 | const hx = hyperx(vdom.h) 9 | 10 | const body = document.querySelector('body') 11 | if (body.parentNode) body.parentNode.removeChild(body) 12 | const app = createApp(document.querySelector('html'), vdom) 13 | const render = app.start(modify, { 14 | data: updateData(), 15 | height: 500, 16 | width: 960 17 | }) 18 | 19 | // render application 20 | render(function (state) { 21 | return hx` 22 |
23 | 28 | ${createGraph({ 29 | height: state.height, 30 | width: state.width, 31 | data: state.data 32 | })} 33 | 34 | ` 35 | }) 36 | 37 | // modify state 38 | // (obj, obj) -> obj 39 | function modify (action, state) { 40 | if (action.type === 'transition') { 41 | return xtend(state, { data: updateData() }) 42 | } 43 | } 44 | 45 | // generate graph data 46 | // null -> obj 47 | function updateData () { 48 | return [ 49 | { name: 'apples', values: bumpLayer(400) }, 50 | { name: 'bananas', values: bumpLayer(400) }, 51 | { name: 'cherries', values: bumpLayer(400) }, 52 | { name: 'dates', values: bumpLayer(400) }, 53 | { name: 'plums', values: bumpLayer(400) }, 54 | { name: 'pears', values: bumpLayer(400) } 55 | ] 56 | 57 | function bumpLayer (n) { 58 | var a = [] 59 | var i = null 60 | for (i = 0; i < n; ++i) a[i] = 0 61 | for (i = 0; i < 5; ++i) bump(a) 62 | return a.map(function (d, i) { 63 | return {x: i, y: Math.max(0, d)} 64 | }) 65 | 66 | function bump (a) { 67 | const x = 1 / (0.1 + Math.random()) 68 | const y = 2 * Math.random() - 0.5 69 | const z = 10 / (0.1 + Math.random()) 70 | for (var i = 0; i < n; i++) { 71 | var w = (i / n - y) * z 72 | a[i] += x * Math.exp(-w * w) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const virtualWidget = require('virtual-widget') 2 | const assert = require('assert') 3 | const d3 = require('d3') 4 | 5 | module.exports = virtualWidget({ 6 | init: function () { 7 | const el = document.createElementNS(d3.ns.prefix.svg, 'svg') 8 | render(el, this.state) 9 | return el 10 | }, 11 | update: function (prev, el) { 12 | render(el, this.state) 13 | } 14 | }) 15 | 16 | // Create a virtual-dom stream graph 17 | // (obj, obj) -> null 18 | function render (el, state) { 19 | const height = state.height 20 | const width = state.width 21 | const data = state.data 22 | 23 | assert.equal(typeof height, 'number', 'height should be a number') 24 | assert.equal(typeof width, 'number', 'width should be a number') 25 | assert.equal(typeof data, 'object', 'data must be an object') 26 | 27 | const stack = d3.layout.stack() 28 | .offset('wiggle') 29 | 30 | const values = d3.range(data.length).map(function (i) { 31 | return data[i].values 32 | }) 33 | const layers = stack(values) 34 | const area = createArea(layers, height, width) 35 | const color = d3.scale.linear().range([ '#aad', '#556' ]) 36 | 37 | const svg = d3.select(el) 38 | .attr('width', width) 39 | .attr('height', height) 40 | 41 | svg.selectAll('path') 42 | .data(layers) 43 | .enter() 44 | .append('path') 45 | .attr('d', area) 46 | .style('fill', function (d, i) { 47 | return color(hashString(data[i].name)) 48 | }) 49 | } 50 | 51 | // create svg area 52 | // (obj, obj) -> obj 53 | function createArea (layers, height, width) { 54 | var m = 200 55 | var x = d3.scale.linear() 56 | .domain([0, m - 1]) 57 | .range([0, width]) 58 | 59 | var y = d3.scale.linear() 60 | .domain([0, d3.max(layers, createLayer)]) 61 | .range([height, 0]) 62 | 63 | return d3.svg.area() 64 | .x(function (d) { return x(d.x) }) 65 | .y0(function (d) { return y(d.y0) }) 66 | .y1(function (d) { return y(d.y0 + d.y) }) 67 | 68 | function createLayer (layer) { 69 | return d3.max(layer, function (d) { 70 | return d.y0 + d.y 71 | }) 72 | } 73 | } 74 | 75 | // hash a string to a number 0.0 - 1.0 76 | // str -> num 77 | function hashString (str) { 78 | var hash = 0 79 | var i = null 80 | var len = null 81 | if (!str.length) return hash 82 | for (i = 0, len = str.length; i < len; i++) { 83 | hash += str.charCodeAt(i) / 100 84 | } 85 | return hash % 1 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # virtual-streamgraph [![stability][0]][1] 2 | [![npm version][2]][3] [![build status][4]][5] [![test coverage][6]][7] 3 | [![downloads][8]][9] [![js-standard-style][10]][11] 4 | 5 | Create a [virtual-dom][14] [streamgraph][12]. Useful to visualize 6 | [time series data][13]. 7 | 8 |  9 | 10 | ## Usage 11 | ```js 12 | const vgraph = require('virtual-streamgraph') 13 | const vdom = require('virtual-dom') 14 | const hyperx = require('hyperx') 15 | const hx = hyperx(vdom.h) 16 | 17 | const data = [ 18 | { name: 'apples', values: [ { x: 1, y: 55 }, { x: 2, y: 65 }, { x: 3, y: 67 }] }, 19 | { name: 'bananas', values: [ { x: 1, y: 55 }, { x: 2, y: 65 }, { x: 3, y: 67 }] }, 20 | { name: 'cherries', values: [ { x: 1, y: 55 }, { x: 2, y: 65 }, { x: 3, y: 67 }] }, 21 | { name: 'dates', values: [ { x: 1, y: 55 }, { x: 2, y: 65 }, { x: 3, y: 67 }] } 22 | ] 23 | 24 | const tree = hx` 25 |