Skip to content

Instantly share code, notes, and snippets.

@homam
Forked from mbostock/.block
Last active December 1, 2020 22:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save homam/8382622 to your computer and use it in GitHub Desktop.
Save homam/8382622 to your computer and use it in GitHub Desktop.
D3 General Upadte Pattern in LiveScript

By adding transitions, we can more easily follow the elements as they are entered, updated and exited. Separate transitions are defined for each of the three states.

Note that no transition is applied to the merged enter + update selection; this is because it would supersede the transition already scheduled on entering and updating elements. It's possible to schedule concurrent elements by using transition.transition or by setting transition.id, but it's simpler here to only transition the x-position on update; for entering elements, the x-position is assigned statically.

Want to read more? Try these tutorials:


This fork shows how using LiveScript improves readability of D3 enter-update-exit pattern.


See the D3 wiki for even more resources.

Previous: Key Functions

<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font: bold 48px monospace;
}
.enter {
fill: green;
}
.update {
fill: #333;
}
.exit {
fill: brown;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://preludels.com/prelude-browser-min.js"></script>
<script src="index.js"></script>
// Generated by LiveScript 1.2.0
(function(){
var ref$, map, zipAll, sortBy, split, id, take, alphabet, width, height, svg, update, shuffle;
ref$ = require('prelude-ls'), map = ref$.map, zipAll = ref$.zipAll, sortBy = ref$.sortBy, split = ref$.split, id = ref$.id, take = ref$.take;
alphabet = split('', 'abcdefghijklmnopqrstuvwxyz');
width = 960;
height = 500;
svg = d3.select('body').append('svg').attr('width', width).attr('height', height).append('g').attr('transform', "translate(32, " + height / 2 + ")");
update = function(data){
var x$, text, y$, z$;
x$ = text = svg.selectAll('text').data(data, id);
x$.attr('class', 'update').transition().duration(750).attr('x', function(_, i){
return i * 32;
});
y$ = x$.enter().append('text').attr('class', 'enter').attr('dy', '.35em').attr('y', -60).attr('x', function(_, i){
return i * 32;
}).style('fill-opacity', 1e-6).text(id).transition();
y$.duration(750).attr('y', 0).style('fill-opacity', 1);
z$ = x$.exit().attr('class', 'exit').transition();
z$.duration(750).attr('y', 60).style('fill-opacity', 1e-6).remove();
return x$;
};
update(alphabet);
setInterval(function(){
return function(){
return update(function(){
return take(Math.floor.apply(this, arguments));
}(Math.random() * (alphabet.length + 1))(shuffle.apply(this, arguments)));
}(alphabet);
}, 1500);
shuffle = function(arr){
var length, _;
length = arr.length;
return map(function(arg$){
var v, _;
v = arg$[0], _ = arg$[1];
return v;
})(
sortBy(function(arg$){
var _, r;
_ = arg$[0], r = arg$[1];
return r;
})(
zipAll(arr, (function(){
var i$, to$, results$ = [];
for (i$ = 0, to$ = length; i$ <= to$; ++i$) {
_ = i$;
results$.push(Math.random());
}
return results$;
}()))));
};
}).call(this);
{map, zip-all, sort-by, split, id, take} = require 'prelude-ls'
alphabet = split '', 'abcdefghijklmnopqrstuvwxyz'
width = 960
height = 500
svg = d3.select \body .append \svg
.attr \width, width
.attr \height, height
.append \g
.attr \transform, "translate(32, #{height / 2})"
update = (data) ->
text = svg.selectAll \text .data data, id # Join new data with old elements, if any.
..attr \class, \update # Update old elements as needed.
.transition! .duration 750 .attr \x, (_, i) -> i * 32
..enter! .append \text # enter! : Create new elements as needed.
.attr \class \enter
.attr \dy, \.35em
.attr \y, -60
.attr \x, (_, i) -> i * 32
.style \fill-opacity, 1e-6
.text id
.transition!
..duration 750
.attr \y, 0
.style \fill-opacity, 1
..exit! # exit! : Remove old elements as needed.
.attr \class, \exit
.transition!
..duration 750
.attr \y, 60
.style \fill-opacity, 1e-6
.remove!
# The initial display.
update alphabet
# Grab a random sample of letters from the alphabet, in alphabetical order.
setInterval ->
update . (take . Math.floor <| Math.random! * (alphabet.length + 1)) . shuffle <| alphabet
, 1500
# Shuffles the input array.
shuffle = (arr) ->
length = arr.length
arr `zip-all` [Math.random! for _ to length] |> sort-by (([_ ,r]) -> r) |> map ([v, _]) -> v
@maninalift
Copy link

I like your simple shuffle. Note it can be written even more compactly as:

   shuffle = (arr) -> [[Math.random!, el] for el in arr] |> sort-by (.0) |> map (.1)

Now I think about it your code doesn't actually do what is claimed in the comments. The letters are not kept in alphabetical order. To do so quite simply you can use:

  setInterval do 
      -> update <| filter (-> Math.random! < 0.5), alphabet
      1500

or if like me you don't like parameters hanging about after call-backs:

  do
    <- setInterval _, 1500  
    update <| filter (-> Math.random! < 0.5), alphabet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment