Skip to content

Instantly share code, notes, and snippets.

@davidmason
Created September 12, 2013 01:48
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 davidmason/6532277 to your computer and use it in GitHub Desktop.
Save davidmason/6532277 to your computer and use it in GitHub Desktop.
Higher-order fun
{"description":"Higher-order fun","endpoint":"","display":"svg","public":true,"require":[],"fileconfigs":{"inlet.js":{"default":true,"vim":false,"emacs":false,"fontSize":24},"_.md":{"default":true,"vim":false,"emacs":false,"fontSize":16},"config.json":{"default":true,"vim":false,"emacs":false,"fontSize":16},"style.css":{"default":true,"vim":false,"emacs":false,"fontSize":16},"visualise.js":{"default":true,"vim":false,"emacs":false,"fontSize":16},"func.js":{"default":true,"vim":false,"emacs":false,"fontSize":24},"data.js":{"default":true,"vim":false,"emacs":false,"fontSize":24},"map.js":{"default":true,"vim":false,"emacs":false,"fontSize":24},"filter.js":{"default":true,"vim":false,"emacs":false,"fontSize":24},"fold.js":{"default":true,"vim":false,"emacs":false,"fontSize":24},"glue.js":{"default":true,"vim":false,"emacs":false,"fontSize":24},"extras.js":{"default":true,"vim":false,"emacs":false,"fontSize":24}},"fullscreen":false,"play":false,"loop":false,"restart":false,"autoinit":true,"pause":true,"loop_type":"period","bv":false,"nclones":15,"clone_opacity":0.4,"duration":3000,"ease":"linear","dt":0.01,"thumbnail":"http://i.imgur.com/FY2NBW8.png"}
// Data
imageDir = 'https://raw.github.com/codemiller/higher-order-fun/gh-pages/images';
famousList = [{ 'name': 'Colonel Meow', 'kind': 'cat', 'image': [{'source': imageDir + '/cm.jpg'}], 'anagram': 'Welcome Loon', 'facebook': 198145, 'twitter': 4573},
{ 'name': 'Guido van Rossum', 'kind': 'human', 'image': [{'source': imageDir + '/gv.jpg'}], 'anagram': 'Gun Sumo Advisor', 'facebook': 2693, 'twitter': 35528},
{ 'name': 'Business Cat', 'kind': 'cat', 'image': [{'source': imageDir + '/bc.jpg'}], 'anagram': 'Bans Cuss Tie', 'facebook': 21767, 'twitter': 93},
{ 'name': 'Brendan Eich', 'kind': 'human', 'image': [{'source': imageDir + '/be.jpg'}], 'anagram': 'Bare Chinned', 'facebook': 441, 'twitter': 24648},
{ 'name': 'Rich Hickey', 'kind': 'human', 'image': [{'source': imageDir + '/rh.jpg'}], 'anagram': 'Heck I Cry Hi', 'facebook': 2332, 'twitter': 10927},
{ 'name': 'Grumpy Cat', 'kind': 'cat', 'image': [{'source': imageDir + '/gc.jpg'}], 'anagram': 'My Crap Gut', 'facebook': 1309067, 'twitter': 110074},
{ 'name': 'Yukihiro Matsumoto', 'kind': 'human', 'image': [{'source': imageDir + '/ym.jpg'}], 'anagram': 'Imitators You Hokum', 'facebook': 1564, 'twitter': 26501},
{ 'name': 'Lil Bub', 'kind': 'cat', 'image': [{'source': imageDir + '/lb.jpg'}], 'anagram': 'Bub Ill', 'facebook': 198536, 'twitter': 20946}];
// Extra data
mo = {'source': imageDir + '/mo.png', 'height': 50, 'width': 100, 'xOffset': 37, 'yOffset': 110};
mouth = {'source': imageDir + '/lips.png', 'height': 30, 'width': 80, 'xOffset': 45, 'yOffset': 132};
beard = {'source': imageDir + '/beard.png', 'height': 105, 'width': 115, 'xOffset': 25, 'yOffset': 114};
glasses = {'source': imageDir + '/glasses.png', 'height': 60, 'width': 150, 'xOffset': 10, 'yOffset': 70};
hat = {'source': imageDir + '/fedora.png', 'height': 93, 'width': 200, 'xOffset': -8, 'yOffset': -17};
// Filter names; consonants only
noVowels = function(name) {
return _.filter(name, function(l) {
return ! _.contains(['a','e','i','o','u'], l)
}).join('');
}
nameConsonants = makeDisplayText(_.pluck(famousList, 'name'),
noVowels);
// Filter items
oneKind = function(kind) {
return function(item) { return item.kind === kind }
}
filtered = _.filter(famousList, oneKind('cat'));
filterDemo = function() {
newItems = extendObjects(famousList, pigLatinNames);
visualise(filtered);
}
// Use fold to sum data for the list
twitFollowers = makeDisplayText(_.pluck(famousList, 'twitter'),
_.identity)
grabValue = function(item) {
return item.twitter
}
total = function(list, getValue) {
return _.foldl(list, function(acc, x) {
return acc + getValue(x);
}, 0);
}
catTotal = total(_.filter(famousList, oneKind('cat')), grabValue);
humanTotal = total(_.filter(famousList, oneKind('human')), grabValue);
/*
visualiseText("Cat popularity score: " + catTotal +
",</br> Human popularity score: " + humanTotal);
*/
// Use fold to reverse a list
reverse = function(list) {
return _.foldl(list, function(acc, x) {
return cons(x, acc);
}, []);
}
costumeItems = [hat, glasses, mo, beard, mouth];
applyDisguise = function(item) {
return {'image':deepCloneArray(item.image).concat(costumeItems)}
};
disguises = _.map(famousList, applyDisguise);
// Useful functions
// Takes an element and a list
// Returns a new list with that element on the head of the list
cons = function(element, list) {
return [element].concat(list);
}
// Takes a binary function and two arrays of arguments
// Returns the result of applying the function to each pair of zipped arguments
zipWith = function(func, arr0, arr1) {
return _.zip(arr0, arr1).map(function(params) {
return func(params[0], params[1])
});
}
// Takes a list of objects and a list of the same length containing objects to merge with those in the first list
// Returns a new list of objects, each of which contains the key/value pairs of both objects at corresponding indices in the input lists
// Key/value pairs of objects in the extension list will override those in the original list
extendObjects = function(objectList, extensionObjectList) {
return zipWith(function(obj, ext) {
return _.extend({}, obj, ext)
},
objectList, extensionObjectList);
}
// To clone an object
deepClone = function(item) {
return _.extend({}, item);
}
// To clone an array
deepCloneArray = function(array) {
return _.map(array, function(item) {
if (_.isString(item) || _.isNumber(item)) { return item }
if (_.isArray(item)) { return deepCloneArray(item) }
if (_.isObject(item)) { return deepClone(item) }
return item;
});
}
// Partial function from Underscore.js 1.4.4 and higher
// Reproduced here as Tributary is on Underscore 1.4.0
_.partial = function(func) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
return func.apply(this, args.concat(Array.prototype.slice.call(arguments)));
};
};
// Gluing it all together
displayScores = function(scorer, list) {
return extendObjects(list, makeDisplayText(list, scorer));
}
rank = function(sortFunc, list) {
return reverse(_.sortBy(list, sortFunc));
}
applyFilter = function(filterFunc, list) {
return _.filter(list, filterFunc)
}
transformFirst = function(transformFunc, list) {
var firstItem = _.head(list);
return cons(_.extend({}, firstItem, transformFunc(firstItem)),
_.tail(list));
}
transformer = function(transformFunc, filterFunc, scoreFunc) {
return _.compose(_.partial(transformFirst, transformFunc),
_.partial(applyFilter, filterFunc),
_.partial(rank, scoreFunc),
_.partial(displayScores, scoreFunc));
}
listTransformer = transformer(applyDisguise,
_.identity,
grabValue);
// mapDemo();
filterDemo();
// To create display text
makeDisplayText = function(list, func) {
return _.map(list, function(n) {
return { 'displayText': func(n) }
});
}
// Create anagram names
anagrams = _.pluck(famousList, 'anagram');
anagramNames = makeDisplayText(anagrams, _.identity);
// Translate a string into Pig Latin
pigify = function(str) {
return _.map(str.split(' '), function(word) {
if (word.length < 2) return word
return word.charAt(1).toUpperCase()
+ word.slice(2)
+ word.charAt(0).toLowerCase() + 'ay';
}).join(' ')
};
// Create Pig Latin name for each list item
pigLatinNames = makeDisplayText(_.pluck(famousList, 'name'),
pigify);
// Create list with extra image for each item
mappedMo = _.map(famousList, function(item) {
return { 'image': deepCloneArray(item.image).concat([mo]) }
});
mapDemo = function () {
newItems = extendObjects(famousList, pigLatinNames);
visualise(newItems);
}
.cm-s-lesser-dark.CodeMirror { background: #1E2426; color: #696969; }
.cm-s-lesser-dark div.CodeMirror-selected {background: #064968 !important; }
.cm-s-lesser-dark span.cm-variable { color:#22EFFF; }
.cm-s-lesser-dark span.cm-variable-2 { color: #FFCCB4; }
.cm-s-lesser-dark span.cm-variable-3 { color: #FFF; }
.cm-s-lesser-dark span.cm-string { color: #76EE00; }
.cm-s-lesser-dark span.cm-string-2 {color: #76EE00; }
.cm-s-lesser-dark span.cm-def {color: #FFCCB4; opacity: 1.0 }
.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; }
.cm-s-lesser-dark pre { color:#FFF; }
.cm-s-lesser-dark span.cm-comment { color: #AFB4B4; }
.cm-s-lesser-dark span.cm-property {color: #FDA676; }
.cm-s-lesser-dark span.cm-number { color: #FF92EE; }
.cm-s-lesser-dark span.cm-keyword { color: #FFFF18; }
.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; }
body { background-color: rgb(200,185,235); }
.listEnd { color: black; font-family: serif; font-size: 440px; }
.listComma { color: black; font-family: serif; font-size: 250px; }
.itemText { color: black; font-size: 25px; text-align: center; min-height: 88px; padding: 3px; background-color: white; }
.textOutput { color: #330066; font-size: 40px; font-weight: bold; padding: 5px; line-height: 1em; }
// Presentation logic
var listStart = {'char': '[', 'x': -10, 'y': 325};
var listEnd = {'char': ']', 'x': 120, 'y': 325};
var listPositions = [{'x': 100, 'y': 50}, {'x': 325, 'y': 50}, {'x': 550, 'y': 50}, {'x': 775, 'y': 50},
{'x': 100, 'y': 440}, {'x': 325, 'y': 440}, {'x': 550, 'y': 440}, {'x': 775, 'y': 440}];
var isLast = function(d, listPos, numItems) {
return d.x === _.last(_.take(listPos, numItems)).x && d.y === _.last(_.take(listPos, numItems)).y;
}
visualise = function(newListItems) {
var svg = d3.select('svg');
var listDecoration = svg.append('g').classed('listEnd', true);
var listData = svg.append('g').classed('listData', true);
var textNode = listDecoration.selectAll('text');
var listDecoData = newListItems.length > 0 ? textNode.data([listStart]) : textNode.data([listStart, listEnd]);
var listDeco = listDecoData.enter()
.append('text');
var listDecoratorAttributes = listDeco.attr('x', function (d) { return d.x })
.attr('y', function (d) { return d.y })
.text(function (d) { return d.char });
var listItems = extendObjects(newListItems, _.take(listPositions, newListItems.length));
var itemImages = listData.selectAll('g').data(listItems).enter().append('g').classed('imageGroup', true);
var items = itemImages.append('image')
.classed('baseImage', true)
.attr('xlink:href', function(d) { return _.first(d.image).source } )
.attr('x', function(d) { return d.x })
.attr('y', function(d) { return d.y })
.attr('height', 225)
.attr('width', 175);
var extraImages = itemImages.selectAll('g').data(function(d) { return extendObjects(_.rest(d.image), _.times(d.image.length-1, function() { return {'px': d.x, 'py': d.y} })) })
.enter().append('g').classed('extraImageGroup', true);
var images = extraImages.append('image')
.classed('extraImage', true)
.attr('xlink:href', function(d) { return d.source })
.attr('x', function(d) { return d.px + d.xOffset })
.attr('y', function(d) { return d.py + d.yOffset })
.attr('height', function(d) { return d.height})
.attr('width', function(d) {return d.width});
var namesText = listData.selectAll('.listData')
.data(listItems)
.enter()
.append('foreignObject');
var textLabels = namesText.attr('x', function(d) { return d.x })
.attr('y', function(d) { return d.y + 223 })
.attr('width', 175)
.attr('height', 88)
.append('xhtml:body')
.html(function(d) {
return '<div class="itemText">'
+ (typeof d.displayText === 'undefined' ? d.name : d.displayText)
+ '</div>'
});
var listDecoText = listData.selectAll('.listData')
.data(listItems)
.enter()
.append('text')
.classed('listComma', function(d) { return ! isLast(d, listPositions, newListItems.length) })
.classed('listEnd', function(d) { return isLast(d, listPositions, newListItems.length) });
var listDecos = listDecoText.attr('x', function(d) { if (isLast(d, listPositions, newListItems.length)) { return d.x + 135 } else { return d.x + 165 } })
.attr('y', function(d) { if (isLast(d, listPositions, newListItems.length)) { return d.y + 275 } else { return d.y + 305 } })
.text(function (d) { if (isLast(d, listPositions, newListItems.length)) { return listEnd.char } else { return ',' }});
return listItems;
}
visualiseText = function(string) {
var svg = d3.select('svg');
var textDisplay = svg.append('g').classed('textDisplay', true);
var textOutput = textDisplay.selectAll('text').data([string]).enter().append('foreignObject');
var textDisplayAttr = textOutput.attr('x', 30).attr('y', 800).attr('width', 800).attr('height', 200)
.append('xhtml:body').html(function(d) { return '<div class="textOutput">' + d + '</div>' });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment