Skip to content

Instantly share code, notes, and snippets.

@plmrry
Last active July 27, 2016 02:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plmrry/e228d74b03c26084685e5e5e466fcbea to your computer and use it in GitHub Desktop.
Save plmrry/e228d74b03c26084685e5e5e466fcbea to your computer and use it in GitHub Desktop.
Streaming Hierarchy

This example demonstrates how to build a nested hierarchy from a streaming datasource.

Groups in the hierarchy are generated and sorted "on demand" as they are pulled from the data$ Observable.

Rx.Observable.groupBy() produces GroupedObservables which are scanned into arrays and then combined using flatMapLatest(Rx.Observable.combineLatest).

(Note: It's important to shareReplay the GroupedObservables)

This example also uses babel-standalone to enable in-browser ES6 features.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/css/bootstrap.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.7.4/babel.min.js"></script>
<title>Streaming Hierarchy</title>
</head>
<body>
<script>
d3.text('script.js', function(source) {
var compiled = Babel.transform(source, { presets: ['es2015'] }).code;
(new Function(compiled))();
});
/* global Babel, d3 */
</script>
</body>
</html>
'use strict';
const letters = ['a', 'b', 'c'];
const data$ = Rx.Observable
.interval(50)
.map((d) => ({
question: d3.shuffle(letters)[0],
measure: d3.shuffle(letters)[0],
layout: d3.shuffle(letters)[0]
}));
function accumulateGroup(group$) {
return group$
.shareReplay()
.scan((a,response) => a.concat(response), [])
.map(children => ({
key: group$.key,
children
}));
}
function groupAndMap(accessor, map) {
return function(group$) {
return group$
.shareReplay()
.groupBy(accessor)
.map(map)
.scan((array, observable) => array.concat(observable), [])
.flatMapLatest(Rx.Observable.combineLatest)
.map(arr => arr.sort((a,b) => d3.ascending(a.key, b.key)))
.map(children => ({
key: group$.key,
children
}))
}
}
const layoutGrouper = groupAndMap(d => d.layout, accumulateGroup);
const measureGrouper = groupAndMap(d => d.measure, layoutGrouper);
const questionGrouper = groupAndMap(d => d.question, measureGrouper);
data$
.let(questionGrouper)
.subscribe(model => {
const container = d3
.select('body')
.datum(model);
const questions = addChildren(container, 'question');
const measures = addChildren(questions, 'measure');
const layouts = addChildren(measures, 'layout');
});
function addChildren(parent, classed) {
const children = parent
.selectAll(`div.${classed}`)
.data(d => d.children, d => d.key);
children
.enter()
.append('div')
.classed(classed, true)
.style({
border: '1px solid #666',
float: 'left'
})
.append('h5')
.text(d => d.key)
.append('h6')
.text(0);
children
.order()
.select('h6')
.text(d => d.children.length);
return children;
}
/* global Rx, d3 */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment