d3-sankey-diagram
Version:
Sankey diagram d3 plugin
285 lines (251 loc) • 7.94 kB
JavaScript
import tape from 'tape'
import sankey from '../src/sankey.js'
tape('sankey() has the expected defaults', test => {
const s = sankey()
test.equal(s.nodeId()({ id: 'foo' }), 'foo')
// test.equal(s.nodeBackwards()({direction: 'l'}), true)
test.deepEqual(s.sourceId()({ source: 'bar', sourcePort: 'a' }), { id: 'bar', port: 'a' })
test.deepEqual(s.targetId()({ target: 'baz', targetPort: 'b' }), { id: 'baz', port: 'b' })
test.equal(s.linkType()({ type: 'x' }), 'x')
test.end()
})
tape('sankey(graph) builds the graph structure', test => {
const s = sankey()
const graph = s({
nodes: [
{ id: 'a' },
{ id: 'b' }
],
links: [
{ source: 'a', target: 'b', type: 'c' }
]
})
test.deepEqual(graph.nodes[0].incoming, [], 'node a incoming')
test.deepEqual(graph.nodes[1].outgoing, [], 'node b outgoing')
test.deepEqual(graph.nodes[0].outgoing, [graph.links[0]], 'node a outgoing')
test.deepEqual(graph.nodes[1].incoming, [graph.links[0]], 'node b incoming')
test.equal(graph.links[0].source, graph.nodes[0], 'link source')
test.equal(graph.links[0].target, graph.nodes[1], 'link target')
// original objects modified?
// test.equal(l.source, 'a')
test.end()
})
// tape('sankey(graph) sets port locations', test => {
// test.deepEqual(graph.nodes[0].ports, [
// ], 'node a ports')
// {
// id: '',
// y: 0,
// dy: 0,
// incoming: [],
// outgoing: [graph.links[0]]
// }])
// test.deepEqual(graph.nodes[1].subdivisions, [{
// id: '',
// y: 0,
// dy: 0,
// incoming: [graph.links[0]],
// outgoing: []
// }])
// test.deepEqual(graph.nodes[0].subdivisions, [{
// id: '',
// y: 0,
// dy: 0,
// incoming: [],
// outgoing: [graph.links[0]]
// }])
// test.deepEqual(graph.nodes[1].subdivisions, [{
// id: '',
// y: 0,
// dy: 0,
// incoming: [graph.links[0]],
// outgoing: []
// }])
// })
// tape('sankey(graph) can be called again', test => {
// var input1 = {
// nodes: [
// {id: 'a'},
// {id: 'b'}
// ],
// links: [
// {source: 'a', target: 'b', type: 'c', value: 1}
// ]
// }
// var input2 = {
// nodes: [
// {id: 'a'},
// {id: 'b'}
// ],
// links: [
// {source: 'a', target: 'b', type: 'c', value: 1}
// ]
// }
// var graph1 = sankey()(input1)
// var graph2 = sankey()(input2)
// graph2 = sankey()(input2)
// // console.log('links', graph1.links)
// test.deepEqual(graph1.nodes[0], graph2.nodes[0])
// test.end()
// })
tape('sankey(graph) sets node and link values and link type', test => {
// Custom accessors so we can check .value and .type are really being set
const s = sankey()
.linkValue(function (d) { return d.val })
.linkType(function (d) { return d.typ })
const graph = s({
nodes: [
{ id: 'a' },
{ id: 'b' },
{ id: 'c' }
],
links: [
{ source: 'a', target: 'b', typ: 'x', val: 7 },
{ source: 'a', target: 'b', typ: 'y', val: 2 },
{ source: 'b', target: 'c', typ: 'x', val: 3 }
]
})
test.equal(graph.links[0].type, 'x')
test.equal(graph.links[1].type, 'y')
test.equal(graph.links[2].type, 'x')
test.equal(graph.links[0].value, 7)
test.equal(graph.links[1].value, 2)
test.equal(graph.links[2].value, 3)
test.equal(graph.nodes[0].value, 9)
test.equal(graph.nodes[1].value, 9)
test.equal(graph.nodes[2].value, 3)
test.end()
})
tape('sankey(nodes, edges) observes the specified id accessor functions', test => {
const s = sankey()
.nodeId(function (d) { return d.foo })
.sourceId(function (d) { return d.bar })
.targetId(function (d) { return d.baz })
.linkType(function (d) { return d.fred })
const graph = s({
nodes: [
{ foo: 'a' },
{ foo: 'b' }
],
links: [
{ bar: 'a', baz: 'b', fred: 'c' }
]
})
// test.equal(graph.nodes[0].id, 'a')
// test.equal(graph.nodes[1].id, 'b')
test.equal(graph.links[0].source, graph.nodes[0])
test.equal(graph.links[0].target, graph.nodes[1])
// test.equal(graph.links[0].type, 'c')
test.end()
})
tape('nodeId() is given the node and its index', test => {
const s = sankey().nodeId(function (d, i) { return i })
const graph = s({ nodes: [{ id: 'a' }, { id: 'b' }], links: [{ source: 0, target: 1 }] })
test.equal(graph.links[0].source.id, 'a')
test.equal(graph.links[0].target.id, 'b')
test.end()
})
tape('sankey.nodeId(id) tests that nodeId is a function', test => {
const s = sankey()
test.throws(function () { s.nodeId(42) })
test.throws(function () { s.nodeId(null) })
test.end()
})
tape('sankey.nodeBackwards(id) tests that nodeBackwards is a function', test => {
const s = sankey()
test.throws(function () { s.nodeBackwards(42) })
test.throws(function () { s.nodeBackwards(null) })
test.end()
})
tape('sankey.sourceId(id) tests that id is a function', test => {
const s = sankey()
test.throws(function () { s.sourceId(42) })
test.throws(function () { s.sourceId(null) })
test.end()
})
tape('sankey.targetId(id) tests that id is a function', test => {
const s = sankey()
test.throws(function () { s.targetId(42) })
test.throws(function () { s.targetId(null) })
test.end()
})
tape('sankey.linkType(id) tests that id is a function', test => {
const s = sankey()
test.throws(function () { s.linkType(42) })
test.throws(function () { s.linkType(null) })
test.end()
})
tape('sankey(graph) includes toElsewhere links in node value', test => {
const s = sankey()
const graph = s({
nodes: [
{ id: 'a', toElsewhere: [{ value: 5 }] },
{ id: 'b' }
],
links: [
{ source: 'a', target: 'b', value: 3 }
]
})
// Node 'a' has 3 outgoing via regular link plus 5 via toElsewhere → value should be 8
test.equal(graph.nodes[0].value, 8, 'node a value includes toElsewhere')
test.end()
})
tape('sankey(graph) includes fromElsewhere links in node value', test => {
const s = sankey()
const graph = s({
nodes: [
{ id: 'a' },
{ id: 'b', fromElsewhere: [{ value: 5 }] }
],
links: [
{ source: 'a', target: 'b', value: 3 }
]
})
// Node 'b' has 3 incoming via regular link plus 5 via fromElsewhere → value should be 8
test.equal(graph.nodes[1].value, 8, 'node b value includes fromElsewhere')
test.end()
})
tape('sankey(graph) throws an error if a node is missing', test => {
const s = sankey()
test.throws(function () {
s({ nodes: [], links: [{ source: 'a', target: 'b' }] })
}, /\bmissing\b/)
test.end()
})
tape('sankey(graph) throws an error if multiple nodes have the same id', test => {
const s = sankey()
test.throws(function () {
s({ nodes: [{ id: 'a' }, { id: 'a' }], links: [] })
}, /\bduplicate\b/)
test.end()
})
// tape('sankey(graph) works with multiple links between same nodes', test => {
// var s = sankey()
// var graph = s({
// nodes: [
// {id: 'a'},
// {id: 'b'}
// ],
// links: [
// {source: 'a', target: 'b', type: 'c'},
// {source: 'a', target: 'b', type: 'c'}
// ]
// })
// test.deepEqual(graph.nodes[0].incoming, [])
// test.deepEqual(graph.nodes[0].outgoing, [graph.links[0], graph.links[1]])
// test.deepEqual(graph.nodes[1].incoming, [graph.links[0], graph.links[1]])
// test.deepEqual(graph.nodes[1].outgoing, [])
// test.equal(graph.links[0].source, graph.nodes[0])
// test.equal(graph.links[0].target, graph.nodes[1])
// test.equal(graph.links[1].source, graph.nodes[0])
// test.equal(graph.links[1].target, graph.nodes[1])
// test.end()
// })
// tape('sankey(graph) adds a new node if source or target id does not exist', test => {
// var s = sankey()
// var graph = s({nodes: [{id: 'a'}], links: [{source: 'a', target: 'b'}]})
// test.equal(graph.nodes.length, 2)
// test.equal(graph.nodes[0].id, 'a')
// test.equal(graph.nodes[1].id, 'b')
// test.end()
// })