Skip to content

Instantly share code, notes, and snippets.

@daimpad
Created March 27, 2016 23:24
Show Gist options
  • Save daimpad/49368a58c9970905f9f1 to your computer and use it in GitHub Desktop.
Save daimpad/49368a58c9970905f9f1 to your computer and use it in GitHub Desktop.
Last.fm API x D3 x AngularJS
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.0/d3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
</head>
<body ng-app="viz" ng-controller="lastfmCtrl" scrolling="yes">
<div class="topBar">
<span class="banner">
ANGULARJS + D3.JS + LAST.FM API
</span>
<span class="nextThing">
Click Artist To Set Background
</span>
</div>
<div class="container">
<div class="top-container">
<span class="reach" ng-click="tagsize = 'reach'">REACH</span>
<span class="tags" ng-click="tagsize = 'taggings'">TAGS</span>
<toptag-chart></toptag-chart>
</div>
<div class="chart2">
<div class="active-tag">
<p>ACTIVE TAG: {{ currtag || 'click a bubble above.' }} </p>
<artists-chart artists="artists"></artists-chart>
</div>
</div>
</div>
</div>
</body>

Last.fm API x D3 x AngularJS

A restyled mod of Steven Hall's superb and, for me, ultra-timely 2014 tutorial, “Creating Custom D3 Directives in AngularJS.” Hall’s repo was just what my Angular studies needed in 2015: a practical breakdown of factories, services, and directives around a music API, a lively and ng-integrated D3 visualization of that selfsame data, he even tossed in a cool responsive squish move for the charts–one particularly well-suited to CodePen’s E-layout, by the by. What a guy!

Besides cloning it here as an EZ-forkable CodePen, I've clipped the design, plugged artist images into the Active Tag cloud (note the handy clipPath maneuver), and swapped a full-screen background loader in place of external artist links. Last's zoomed thumbnails become full-page-plausible thanks to Transparent Textures, whose "squairy" overlay is applied here by way of pseudo-element.

Forked from Joey Anuff's Pen Last.fm API x D3 x AngularJS.

A Pen by Daimpad on CodePen.

License.

angular.module('viz', []);
angular.module('viz').factory('lastfm', ['$http', function($http) {
// GET YOUR API KEY. IT'S FREE AT http://www.last.fm/api
var apiKey = '250efafc211d1b7d587ac8597b8d210f';
return {
topTags: function() {
var url = 'https://ws.audioscrobbler.com/2.0/';
return $http.get(url, {
params: {
method: 'chart.gettoptags',
api_key: apiKey,
format: 'json'
}
});
},
topArtists: function(tag) {
var url = 'https://ws.audioscrobbler.com/2.0/';
return $http.get(url, {
params: {
method: 'tag.gettopartists',
api_key: apiKey,
tag: tag,
format: 'json'
}
});
}
};
}]);
angular.module('viz').controller('lastfmCtrl', ['$scope', '$window', 'lastfm',
function($scope, $window, lastfm) {
$scope.tagsize = 'reach';
$scope.toptags = [];
$scope.currtag = '';
$scope.artists = [];
$window.addEventListener('resize', function() {
$scope.$broadcast('windowResize');
});
lastfm.topTags()
.success(function(res) {
if (res.error) {
throw new Error(res.message);
} else {
$scope.toptags = res.tags.tag.map(function(t) {
t.reach = +t.reach;
t.taggings = +t.taggings;
return t;
});
}
});
}
]);
angular.module('viz').directive('toptagChart', ['lastfm',
function(lastfm) {
var link = function($scope, $el, $attrs) {
var diameter = 500;
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(2.5);
var svg = d3.select($el[0]).append("svg")
.attr({
width: diameter,
height: diameter
})
.attr("viewBox", "0 0 " + diameter + " " + diameter);
var chart = svg.append("g");
chart.append("text").attr("id", "loading")
.text("Loading...")
.attr("transform", "translate(200,250)");
var update = function() {
var data = $scope.toptags.map(function(d) {
d.value = d[$scope.tagsize];
return d;
});
bubble.nodes({
children: data
});
if (data.length) chart.select("#loading").remove();
var selection = chart.selectAll(".node")
.data(data);
var enter = selection.enter()
.append("g").attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
enter.append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", 'dimgray')
.on("click", function(d) {
svg.selectAll("circle").style("fill", 'black');
d3.select(this).style("fill", "deepskyblue");
lastfm.topArtists(d.name)
.success(function(res) {
if (res.error) {
throw new Error(res.message);
} else {
$scope.currtag = d.name;
var artists = res.topartists.artist.map(function(a) {
a.genre = d.name;
a.image = a['image'][2]['#text'];
a.arank = +a['@attr'].rank;
return a;
});
$scope.artists = artists;
}
});
});
enter.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) {
return d.name;
});
selection.transition().duration(2000)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
selection.selectAll("circle").transition().duration(3000)
.attr("r", function(d) {
return d.r;
});
resize();
};
function resize() {
svg.attr("width", $el[0].clientWidth);
svg.attr("height", $el[0].clientWidth); //It's a square
}
$scope.$on('windowResize', resize);
$scope.$watch('tagsize', update);
$scope.$watch('toptags', update);
};
return {
template: '<div class="chart"></div>',
replace: true,
link: link,
restrict: 'E'
};
}
]);
angular.module('viz').directive('artistsChart', ['$window',
function($window) {
var link = function($scope, $el, $attrs) {
var csize = [500, 500],
radius = 50;
var svg = d3.select($el[0]).append("svg")
.attr({
width: csize[0],
height: csize[1]
})
.attr("viewBox", "0 0 " + csize[0] + " " + csize[1]);
var chart = svg.append("g");
var coords = function(position) {
var x, y;
x = ((position - 1) % 5) * 100 - 30;
y = (Math.ceil(position / 5)) * 100 - 50;
return {
x: x,
y: y
};
}
var transform = function(d) {
var c = coords(d.arank);
return "translate(" + (c.x + radius + 30) + "," + c.y + ")";
};
chart.selectAll(".number")
.data(d3.range(1, 51)).enter()
.append("text")
.attr("class", "number")
.style("text-anchor", "middle")
.text(function(d) {
return d;
})
.attr("transform", function(d) {
var c = coords(d);
return "translate(" + (c.x + radius + 30) + "," + (c.y + 12) + ")";
});
var update = function() {
var data = $scope.artists.map(function(d) {
d.value = 10;
return d;
});
var selection = chart.selectAll(".node")
.data(data, function(d) {
return d.name;
});
selection.style("opacity", 1)
selection.transition().duration(2000)
.attr("transform", transform);
selection.selectAll("circle")
.style("fill", "deepskyblue")
var defs = svg.append("defs").attr("id", "imgdefs");
var clipPath = defs.append('clipPath')
.attr('id', 'clip-circle')
.append("circle")
.attr("r", 50)
.attr("cy", 50)
.attr("cx", 50);
var enter = selection.enter()
.append("g")
.attr("class", "node")
.style("opacity", 0)
.attr("transform", transform);
enter.append("svg:image")
.attr("xlink:href", function(d) {
return d.image;
})
.attr("clip-path", "url(#clip-circle)")
.attr('width', 100)
.attr('height', 100)
.attr("transform", "translate(-50,-50)").style('z-index', 2000);
enter.append("circle")
.attr("r", radius)
.on("click", function(d) {
$("body,html").css("background", "url(" + d.image + ") no-repeat fixed center center");
console.log(d);
});
enter.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) {
return d.name.slice(0, 21);
});
enter.transition().duration(2000)
.style("opacity", 1)
selection.exit().transition().duration(1000)
.attr("transform", function(d) {
return "translate(" + 1000 + "," + 1000 + ")";
}).remove();
resize();
};
function resize() {
svg.attr("width", $el[0].clientWidth);
svg.attr("height", $el[0].clientWidth); //It's a square
}
$scope.$on('windowResize', resize);
$scope.$watch('artists', update);
};
return {
template: '<div class="chart"></div>',
replace: true,
scope: {
artists: '='
},
link: link,
restrict: 'E'
};
}
]);
@font-face {font-family: "TrashHand";
src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/TrashHand.eot");
src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/TrashHand.eot?#iefix") format("embedded-opentype"),
url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/TrashHand.woff") format("woff"),
url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/TrashHand.ttf") format("truetype"),
url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/TrashHand.svg#TrashHand") format("svg");
}
@font-face {font-family: "hardcore_pen";
src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/hardcore_pen.eot");
src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/hardcore_pen.eot?#iefix") format("embedded-opentype"),
url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/hardcore_pen.woff") format("woff"),
url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/hardcore_pen.ttf") format("truetype"),
url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/hardcore_pen.svg#hardcore_pen") format("svg");
}
@font-face {
font-family: "PixelSix14";
src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/PixelSix14.eot");
src: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/PixelSix14.eot?#iefix") format("embedded-opentype"), url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/PixelSix14.ttf") format("truetype"), url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/PixelSix14.svg#PixelSix14") format("svg");
}
body,
html {
background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/143273/badbrains.jpg) no-repeat fixed center top;
-webkit-background-size: cover !important;
-moz-background-size: cover !important;
-o-background-size: cover !important;
background-size: cover !important;
overflow: scroll;
overflow-x: hidden;
-webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
}
::-webkit-scrollbar {
width: 0px;
background: transparent;
}
.container {
margin: auto;
width: 100%;
}
.container:before {
content: ' ';
display: block;
position: fixed;
left: 0;
top: 0;
width: 100%;
min-height: 100vh;
z-index: 0;
pointer-events: none;
opacity: 0.4;
background-repeat: no-repeat;
background-position: 50% 0;
-ms-background-size: cover;
-o-background-size: cover;
-moz-background-size: cover;
-webkit-background-size: cover;
background-size: cover;
background: url(http://www.transparenttextures.com/patterns/squairy.png) repeat;
}
text {
font: 1.4em "TrashHand";
pointer-events: none;
letter-spacing: 0.0em;
fill: deeppink;
}
.top-container {
position: relative;
text-align: right;
padding: 0px 30px 0px 20px;
}
.reach {
position: absolute;
color: deepskyblue;
font-family: 'hardcore_pen';
font-size: 5em;
left: 0;
}
.tags {
position: absolute;
color: deepskyblue;
font-family: 'hardcore_pen';
font-size: 5em;
right: 0;
}
.tag-cloud,
.active-tag > p {
position: absolute;
color: deepskyblue;
font-family: 'hardcore_pen';
font-size: 4.5em;
line-height: .5em !important;
text-align: left;
margin-top: -20px !important;
}
#loading {
font-size: 50px;
fill: deeppink;
}
.number {
font-size: 30px;
fill: deeppink;
opacity: 1;
}
circle {
opacity: .6;
stroke: whitesmoke;
stroke-width: 1px;
}
circle:hover, .active-tag circle:hover {
opacity: .4;
cursor: pointer;
}
.active-tag circle {
opacity: 0;
}
.bottomBar {
font-family: 'PixelSix14';
color: white;
font-size: 12px;
position: fixed;
left: 0;
width: 100vw;
height: 16px;
background: black;
bottom: 0;
z-index: 10;
}
.moreJoey a {
right: 3px;
margin-bottom: 2px;
text-decoration: none;
text-align: right;
float: right;
margin-right: 3px;
color: yellow;
}
.topBar {
font-family: 'PixelSix14';
color: white;
font-size: 12px;
position: fixed;
left: 0;
width: 100vw;
height: 16px;
background: black;
top: 0;
z-index: 10;
}
.miniLoad, .loadingInfo, .banner {
margin-left: 3px;
}
.moreJoey a, .nextThing {
right: 3px;
margin-bottom: 2px;
}
.loadingInfo, .miniLoad, .moreJoey a, .nextThing {
text-decoration: none;
text-align: right;
}
.moreJoey a, .nextThing {
float: right;
margin-right: 3px;
}
.miniLoad, .moreJoey a {
color: yellow;
}
.nextThing {
padding-top: 1px !important;
color: white;
}
.topBar .banner {
position: absolute;
margin-top: 1px !important;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment