Skip to content

Instantly share code, notes, and snippets.

@katiejots
Last active December 19, 2015 22:18
Show Gist options
  • Save katiejots/6026457 to your computer and use it in GitHub Desktop.
Save katiejots/6026457 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":26},"_.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":26},"data.js":{"default":true,"vim":false,"emacs":false,"fontSize":26},"map.js":{"default":true,"vim":false,"emacs":false,"fontSize":26},"filter.js":{"default":true,"vim":false,"emacs":false,"fontSize":26},"fold.js":{"default":true,"vim":false,"emacs":false,"fontSize":26},"glue.js":{"default":true,"vim":false,"emacs":false,"fontSize":26},"extras.js":{"default":true,"vim":false,"emacs":false,"fontSize":26}},"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","ajax-caching":true}
//imageDir = 'http://localhost/hof/images'
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': 'Grumpy Cat', 'kind': 'cat', 'image': [{'source': imageDir + '/gc.jpg'}], 'anagram': 'My Crap Gut', 'facebook': 1309067, 'twitter': 110074},
{ 'name': 'Rich Hickey', 'kind': 'human', 'image': [{'source': imageDir + '/rh.jpg'}], 'anagram': 'Heck I Cry Hi', 'facebook': 2332, 'twitter': 10927},
{ 'name': 'Lil Bub', 'kind': 'cat', 'image': [{'source': imageDir + '/lb.jpg'}], 'anagram': 'Bub Ill', 'facebook': 198536, 'twitter': 20946},
{ 'name': 'Yukihiro Matsumoto', 'kind': 'human', 'image': [{'source': imageDir + '/ym.jpg'}], 'anagram': 'Imitators You Hokum', 'facebook': 1564, 'twitter': 26501}];
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};
crown = {'source': imageDir + '/crown.png', 'height': 100, 'width': 135, 'xOffset': 13, 'yOffset': 0};
prize = {'source': imageDir + '/winner.png', 'height': 133, 'width': 100, 'xOffset': 90, 'yOffset': 125};
filterVisualise = function() {
// Filter names; consonants only
var noVowels = function(name) {
return _.filter(name, function(l) {
return ! _.contains(['a','e','i','o','u'], l);
}).join('');
};
var nameCons =
makeDisplayText(_.pluck(famousList, 'name'),
noVowels);
// Filter items
var oneKind = function(kind) {
return function(item) { return item.kind === kind };
};
var filtered = _.filter(famousList, oneKind('cat'));
return [extendObjects(famousList, nameCons)];
};
foldVisualise = function() {
// Code we are reusing (repeated here for ease of reference)
var oneKind = function(kind) {
return function(item) {return item.kind === kind};
};
// Use fold to sum data for the list
var total = function(list, getValue) {
return _.foldl(list, function(acc, x) {
return acc + getValue(x);
}, 0);
};
var grabValue = function(item) {
return item.facebook;
};
var catTotal = total(_.filter(famousList,
oneKind('cat')), grabValue);
var humanTotal = total(_.filter(famousList,
oneKind('human')), grabValue);
// Use fold to reverse a list
var reverse = function(list) {
return _.foldl(list, function(acc, x) {
return cons(x, acc);
}, []);
};
var costumeItems = [hat, glasses, mo, beard, mouth];
var applyCostume = function(item) {
return {
'image':deepCloneArr(item.image)
.concat(costumeItems)
};
};
var disguises = _.map(famousList, applyCostume);
var text = "Cat popularity score: " + catTotal
+ ",</br> Human popularity score: " + humanTotal;
var fbTotal =
makeDisplayText(_.pluck(famousList, 'facebook'),
_.identity);
return [extendObjects(famousList, fbTotal), text];
};
// 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
deepCloneArr = 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;
});
};
// Takes a list and a function, and transforms each item in the
// list with that function, returning a list of objects containing
// each result value as the property 'displayText'. Can be used with
// extendObjects() to add a displayText property to each object in a list.
makeDisplayText = function(list, func) {
return _.map(list, function(n) {
return { 'displayText': func(n) };
});
};
// 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)));
};
};
// The Underscore times function is not working properly, so overriding for now
_.times = function(n, iterator) {
var accum = [];
for (var i = 0; i < n; i++) accum.push(iterator(i));
return accum;
};
glueVisualise = function() {
// Code we are reusing (repeated here for ease of reference)
var oneKind = function(kind) {
return function(item) { return item.kind === kind };
};
var grabValue = function(item) {
return item.facebook;
};
var total = function(list, getValue) {
return _.foldl(list, function(acc, x) {
return acc + getValue(x);
}, 0);
};
var catTotal = total(_.filter(famousList, oneKind('cat')), grabValue);
var humanTotal = total(_.filter(famousList, oneKind('human')), grabValue);
var reverse = function(list) {
return _.foldl(list, function(acc, x) {
return cons(x, acc);
}, []);
};
var costumeItems = [crown, prize];
var applyCostume = function(item) {
return {'image':deepCloneArr(item.image).concat(costumeItems)};
};
// Four functions to transform list
var displayScores = function(scorer, list) {
return extendObjects(list,
makeDisplayText(list, scorer));
};
var rank = function(sortFunc, list) {
return reverse(_.sortBy(list, sortFunc));
};
var applyFilter = function(filterFunc, list) {
return _.filter(list, filterFunc);
};
var transformNumOne = function(transformFunc, list) {
var firstItem = _.head(list);
return cons(_.extend({}, firstItem,
transformFunc(firstItem)),
_.tail(list));
};
var transformer = function(transformFunc, filterFunc, scoreFunc) {
return _.compose(_.partial(transformNumOne, transformFunc),
_.partial(applyFilter, filterFunc),
_.partial(rank, scoreFunc),
_.partial(displayScores, scoreFunc));
};
var revealPopularity = transformer(applyCostume,
_.identity,
grabValue);
var text = "Cat popularity score: " + catTotal
+ ",</br> Human popularity score: " + humanTotal;
return [revealPopularity(famousList), text];
};
var initialVisualise = function() {
var anagrams = _.pluck(famousList, 'anagram');
var anagramNames = makeDisplayText(anagrams,
_.identity);
var newItems = extendObjects(famousList,
anagramNames);
return [newItems];
};
visualise.apply(this, initialVisualise());
//visualise.apply(this, mapVisualise());
//visualise.apply(this, filterVisualise());
//visualise.apply(this, foldVisualise());
//visualise.apply(this, glueVisualise());
mapVisualise = function() {
// Translate a string into Pig Latin
var 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
var latNames =
makeDisplayText(_.pluck(famousList, 'name'),
pigify);
// Create list with extra image for each item
var mappedMo = _.map(famousList, function(item) {
return {
'image':deepCloneArr(item.image).concat([mo])
};
});
return [extendObjects(famousList, latNames)];
};
.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: 400px; }
.listComma { color: black; font-family: serif; font-size: 200px; }
.itemText { color: black; font-size: 25px; text-align: center; min-height: 88px; padding: 3px; background-color: white; }
.textOutput { color: #330066; font-size: 30px; font-weight: bold; padding: 5px; line-height: 1em; }
#panel { width:50%; }
// Presentation logic
var listStart = {'char': '[', 'x': -20, 'y': 285};
var listEnd = {'char': ']', 'x': 120, 'y': 285};
var row1 = 23;
var row2 = 365;
var row3 = 707;
var col1 = 80;
var col2 = 300;
var col3 = 520;
var listPositions = [{'x': col1, 'y': row1}, {'x': col2, 'y': row1}, {'x': col3, 'y': row1},
{'x': col1, 'y': row2}, {'x': col2, 'y': row2}, {'x': col3, 'y': row2},
{'x': col1, 'y': row3}, {'x': col2, 'y': row3}];
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 newText = arguments[1];
var svg = d3.select('svg');
if (newText) {
var textDisplay = svg.append('g').classed('textDisplay', true);
var textOutput = textDisplay.selectAll('text').data([newText]).enter().append('foreignObject');
var textDisplayAttr = textOutput.attr('x', 80).attr('y', 1015).attr('width', 800).attr('height', 200)
.append('xhtml:body').html(function(d) { return '<div class="textOutput">' + d + '</div>' });
}
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', 75)
.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 + 138 } else { return d.x + 170 } })
.attr('y', function(d) { if (isLast(d, listPositions, newListItems.length)) { return d.y + 262 } else { return d.y + 288} })
.text(function (d) { if (isLast(d, listPositions, newListItems.length)) { return listEnd.char } else { return ',' }});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment