From 9875cbeb6843ab8051a2d5e2e86a73eb9d5b931a Mon Sep 17 00:00:00 2001 From: Kevin Mok Date: Thu, 14 Jun 2018 07:49:27 -0400 Subject: [PATCH] Moved script to create genre graph to static file - sorted artists in bar by most to least, bottom to top - wrap x-axis labels by bar width - increased track name length (program crashed on a track name) --- spotifyvis/models.py | 2 +- .../static/spotifyvis/scripts/genre_graph.js | 137 ++++++++++++++++++ spotifyvis/templates/spotifyvis/test_db.html | 80 +--------- spotifyvis/utils.py | 2 +- spotifyvis/views.py | 2 +- 5 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 spotifyvis/static/spotifyvis/scripts/genre_graph.js diff --git a/spotifyvis/models.py b/spotifyvis/models.py index 0cc8879..99f93d8 100644 --- a/spotifyvis/models.py +++ b/spotifyvis/models.py @@ -49,7 +49,7 @@ class Track(models.Model): year = models.PositiveSmallIntegerField() popularity = models.PositiveSmallIntegerField() runtime = models.PositiveSmallIntegerField() - name = models.CharField(max_length=150) + name = models.CharField(max_length=200) users = models.ManyToManyField(User, blank=True) genre = models.CharField(max_length=30) diff --git a/spotifyvis/static/spotifyvis/scripts/genre_graph.js b/spotifyvis/static/spotifyvis/scripts/genre_graph.js new file mode 100644 index 0000000..6422d22 --- /dev/null +++ b/spotifyvis/static/spotifyvis/scripts/genre_graph.js @@ -0,0 +1,137 @@ +function create_genre_graph(data) { + // convert strings to nums {{{ // + + data.forEach(function(d) { + d.num_songs = +d.num_songs; + console.log(d.genre, d.num_songs); + var artist_names = Object.keys(d.artists); + artist_names.forEach(function(e) { + d.artists[e] = +d.artists[e]; + console.log(e, d.artists[e]); + //console.log(e, d.artists[e], d.artists[e] + 1); + }); + }); + + // }}} convert strings to nums // + + // domains {{{ // + + data.sort(function(a, b) { + return b.num_songs - a.num_songs; + }); + x.domain(data.map(function(d) { + return d.genre; + })); + //y.domain([0, d3.max(data, function(d) { return d.num_songs; }) * 1.25]).nice(); + y.domain([0, d3.max(data, function(d) { + return d.num_songs; + })]).nice(); + + // }}} domains // + + // setup bar colors {{{ // + + var max_artists = d3.max(data, function(d) { + return Object.keys(d.artists).length; + }); + var z = d3.scaleOrdinal().range(randomColor({ + count: max_artists, + luminosity: 'light', + })); + + // }}} setup bar colors // + + for (var genre_dict of data) { + + // process artist breakdown {{{ // + + var keys = Object.keys(genre_dict.artists); + var stack = d3.stack() + //.order(d3.stackOrderAscending) + .order(d3.stackOrderDescending) + .keys(keys)([genre_dict.artists]) + //unpack the column + .map((d, i) => { + return { + key: keys[i], + data: d[0] + } + }); + + // }}} process artist breakdown // + + // add bars {{{ // + + g.append("g") + .selectAll("rect") + .data(stack) + .enter().append("rect") + .attr("x", x(genre_dict.genre)) + .attr("y", function(d) { + return y(d.data[1]); + }) + .attr("height", d => y(d.data[0]) - y(d.data[1])) + .attr("width", x.bandwidth()) + .attr('fill', (d, i) => z(i)) + .append('title').text(d => d.key + ': ' + (d.data[1] - d.data[0])); + + // }}} add bars // + + // x-axis {{{ // + + g.append("g") + .attr("class", "axis") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(x)) + .selectAll(".tick text") + .call(wrap, x.bandwidth()); + + // }}} x-axis // + + // y-axis {{{ // + + g.append("g") + .attr("class", "axis") + .call(d3.axisLeft(y).ticks(null, "s")) + .append("text") + .attr("x", 2) + .attr("y", y(y.ticks().pop()) + 0.5) + .attr("dy", "0.32em") + .attr("fill", "#000") + .attr("font-weight", "bold") + .attr("text-anchor", "start") + .text("Songs"); + + // }}} y-axis // + + } +} + +// wrap text {{{ // + +// https://gist.github.com/guypursey/f47d8cd11a8ff24854305505dbbd8c07#file-index-html +function wrap(text, width) { + text.each(function() { + var text = d3.select(this), + words = text.text().split(/\s+/).reverse(), + word, + line = [], + lineNumber = 0, + lineHeight = 1.1, // ems + y = text.attr("y"), + dy = parseFloat(text.attr("dy")), + tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em") + while (word = words.pop()) { + line.push(word) + tspan.text(line.join(" ")) + if (tspan.node().getComputedTextLength() > width) { + line.pop() + tspan.text(line.join(" ")) + line = [word] + tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", `${++lineNumber * lineHeight + dy}em`).text(word) + } + } + }) +} + +// }}} wrap text // diff --git a/spotifyvis/templates/spotifyvis/test_db.html b/spotifyvis/templates/spotifyvis/test_db.html index 6024e87..74ecd70 100644 --- a/spotifyvis/templates/spotifyvis/test_db.html +++ b/spotifyvis/templates/spotifyvis/test_db.html @@ -19,9 +19,11 @@ - - + + diff --git a/spotifyvis/utils.py b/spotifyvis/utils.py index ede11c1..b7a95c7 100644 --- a/spotifyvis/utils.py +++ b/spotifyvis/utils.py @@ -26,7 +26,7 @@ def parse_library(headers, tracks, user): """ # TODO: implement importing entire library with 0 as tracks param # number of tracks to get with each call - limit = 5 + limit = 50 # keeps track of point to get songs from offset = 0 payload = {'limit': str(limit)} diff --git a/spotifyvis/views.py b/spotifyvis/views.py index 878ace0..8b9e3b8 100644 --- a/spotifyvis/views.py +++ b/spotifyvis/views.py @@ -19,7 +19,7 @@ from .models import User, Track, AudioFeatures, Artist # }}} imports # TIME_FORMAT = '%Y-%m-%d-%H-%M-%S' -TRACKS_TO_QUERY = 15 +TRACKS_TO_QUERY = 100 # generate_random_string {{{ #