From 670693203eaf4aeb73a5c6ebc05f793c84b32e7b Mon Sep 17 00:00:00 2001 From: Kevin Mok Date: Thu, 7 Mar 2019 05:57:30 -0500 Subject: [PATCH] Fix genre graph not displaying - take top genres/artists for graphs - improve graphs' appearance - update pip packages --- api/utils.py | 10 +- api/views.py | 29 +-- graphs/static/graphs/scripts/genre_graph.js | 251 ++++++++++--------- graphs/templates/graphs/features_graphs.html | 50 ++-- graphs/templates/graphs/genre_graph.html | 50 ++-- requirements.txt | 49 ++-- update-history.sh | 2 + 7 files changed, 226 insertions(+), 215 deletions(-) diff --git a/api/utils.py b/api/utils.py index 2cb0110..7538c01 100644 --- a/api/utils.py +++ b/api/utils.py @@ -222,9 +222,13 @@ def get_artists_in_genre(user, genre): processed_artist_counts = {} for artist in user_artists: - processed_artist_counts[artist.name] = round(artist.track_set - .filter(genre=genre_obj, users=user) - .count() * track_count / total_artist_counts, 2) + # TODO: figure out collab problem # + # artist_count = math.floor(artist.track_set.filter(genre=genre_obj, + # users=user).count() * track_count / total_artist_counts) + artist_count = artist.track_set.filter(genre=genre_obj, + users=user).count() + if artist_count > 0: + processed_artist_counts[artist.name] = artist_count return processed_artist_counts # }}} get_artists_in_genre # diff --git a/api/views.py b/api/views.py index 4a326ac..e84dba2 100644 --- a/api/views.py +++ b/api/views.py @@ -141,7 +141,7 @@ def get_artist_data(request, user_secret): artist_counts = Artist.objects.annotate(num_songs=Count('track', filter=Q(track__users=user))) processed_artist_counts = [{'name': artist.name, 'num_songs': artist.num_songs} - for artist in artist_counts] + for artist in artist_counts if artist.num_songs > 1] if CONSOLE_LOGGING: pprint(processed_artist_counts) return JsonResponse(data=processed_artist_counts, safe=False) @@ -175,7 +175,6 @@ def get_audio_feature_data(request, audio_feature, user_secret): # get_genre_data {{{ # - def get_genre_data(request, user_secret): """Return genre data needed to create the graph TODO @@ -187,27 +186,17 @@ def get_genre_data(request, user_secret): # annotates each genre and not each Track, due to the earlier values() call .annotate(num_songs=Count('genre')) ) + genre_counts = [genre_dict for genre_dict in genre_counts if + genre_dict['num_songs'] > 3] + # genre_counts is a QuerySet with the format - # [{'genre': 'classical', 'num_songs': 100}, {'genre': 'pop', 'num_songs': 50}...] - for genre_dict in genre_counts: - genre_dict['artists'] = get_artists_in_genre(user, genre_dict['genre']) ''' - Now genre_counts has the format - [ - {'genre': 'classical', - 'num_songs': 100, - 'artists': { - 'Helene Grimaud': 40.5, - 'Beethoven': 31.2, - 'Mozart': 22... - } - }, - {'genre': 'pop', - 'num_songs': 150, - 'artists': {...} - },... - ] + Now genre_counts has the format [ {'genre': 'classical', 'num_songs': 100, + 'artists': { 'Helene Grimaud': 40.5, 'Beethoven': 31.2, ... }},... ] ''' + for genre_dict in genre_counts: + genre_dict['artists'] = get_artists_in_genre(user, genre_dict['genre']) + if CONSOLE_LOGGING: print("*** Genre Breakdown ***") pprint(list(genre_counts)) diff --git a/graphs/static/graphs/scripts/genre_graph.js b/graphs/static/graphs/scripts/genre_graph.js index 2d107ea..1e9380f 100644 --- a/graphs/static/graphs/scripts/genre_graph.js +++ b/graphs/static/graphs/scripts/genre_graph.js @@ -1,137 +1,154 @@ 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); - let 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 {{{ // + + data.forEach(function(d) { + d.num_songs = +d.num_songs; + console.log(d.genre, d.num_songs); + let 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 // + }); + + // }}} 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; // returns the maximum number of songs in the genre - })]).nice(); - - // }}} domains // + // 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; // returns the maximum number of songs in the genre + }) * 1.6]).nice(); + + // }}} domains // - // setup bar colors {{{ // + // setup bar colors {{{ // + + let max_artists = d3.max(data, function(d) { + return Object.keys(d.artists).length; + }); + let colorScale = d3.scaleOrdinal().range(randomColor({ + count: max_artists, + luminosity: 'light', + // hue: '#3399FF', + // hue: '#00ced1', + hue: '#0099CC', + })); + + // }}} setup bar colors // + + for (let genre_dict of data) { + + // process artist breakdown {{{ // - let max_artists = d3.max(data, function(d) { - return Object.keys(d.artists).length; - }); - let colorScale = d3.scaleOrdinal().range(randomColor({ - count: max_artists, - luminosity: 'light', - })); + let keys = Object.keys(genre_dict.artists); + let stack = d3.stack() + .order(d3.stackOrderDescending) + .keys(keys)([genre_dict.artists]) + // unpack the column + .map((d, i) => { + return { + key: keys[i], + data: d[0] + } + }); - // }}} setup bar colors // + // }}} process artist breakdown // - for (let genre_dict of data) { - - // process artist breakdown {{{ // - - let keys = Object.keys(genre_dict.artists); - let stack = d3.stack() - .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) => colorScale(i)) + .style('font-size', '1.5em') + .append('title').text(d => d.key + ': ' + (d.data[1] - d.data[0]).toPrecision(1)); + + // }}} add bars // - // 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) => colorScale(i)) - // keep 3 significant figures in the song count label - .append('title').text(d => d.key + ': ' + (d.data[1] - d.data[0]).toPrecision(3)); - - // }}} add bars // + // x-axis {{{ // + + g.append("g") + .attr("class", "axis") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(x)) + .selectAll(".tick text") + .style('font-size', '1.5em') + .call(wrap, x.bandwidth()); + + // }}} x-axis // - // 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", "white") + .style('font-size', '2em') + .attr("text-anchor", "start") + .text("Songs"); + + // }}} y-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 // + // title {{{ // + + g.append("text") + .attr('x', (width / 2)) + .attr('y', (margin.top / 2)) + .attr('fill', "white") + .attr('text-anchor', 'middle') + .attr("font-weight", "bold") + .style('font-size', '2em') + .text('Genre Graph (With Artists)'); + + // }}} title // - } + } } // wrap text {{{ // // wrapping long labels // https://gist.github.com/guypursey/f47d8cd11a8ff24854305505dbbd8c07#file-index-html function wrap(text, width) { - text.each(function() { - let 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); - } - } - }) + text.each(function() { + let 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/graphs/templates/graphs/features_graphs.html b/graphs/templates/graphs/features_graphs.html index 477145d..61017e1 100644 --- a/graphs/templates/graphs/features_graphs.html +++ b/graphs/templates/graphs/features_graphs.html @@ -1,28 +1,28 @@ {% load static %} {% load sass_tags %} - - - + + + - - - - Features Graphs - - + + + + Features Graphs + + - - + + - - + +
@@ -38,10 +38,10 @@
- - - + + - + + diff --git a/graphs/templates/graphs/genre_graph.html b/graphs/templates/graphs/genre_graph.html index ae86a8b..fb75842 100644 --- a/graphs/templates/graphs/genre_graph.html +++ b/graphs/templates/graphs/genre_graph.html @@ -1,9 +1,9 @@ - - + + + {% load static %} {% load sass_tags %} @@ -14,44 +14,46 @@ Genre Graph - + + {% load static %}
-
-
-
+
+
- - +
+ {% comment %} {% endcomment %} + + diff --git a/requirements.txt b/requirements.txt index 58da672..23301ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,40 +1,37 @@ -astroid==2.0.4 -certifi==2018.10.15 +astroid==2.2.4 +certifi==2018.11.29 chardet==3.0.4 -Django==2.1.3 -django-appconf==1.0.2 +defusedxml==0.5.0 +Django==2.1.7 +django-appconf==1.0.3 django-compressor==2.2 django-crispy-forms==1.7.2 -django-filter==2.0.0 +django-filter==2.1.0 django-sass-processor==0.7.2 -django-tables2==2.0.3 -djangorestframework==3.9.0 -docopt==0.6.2 +django-tables2==2.0.5 +djangorestframework==3.9.2 et-xmlfile==1.0.1 -idna==2.7 -isort==4.3.4 +idna==2.8 +isort==4.3.12 jdcal==1.4 lazy-object-proxy==1.3.1 -libsass==0.16.0 +libsass==0.17.0 mccabe==0.6.1 -odfpy==1.3.6 -openpyxl==2.5.10 -pkg-resources==0.0.0 -psycopg2==2.7.6.1 -psycopg2-binary==2.7.6.1 -pylint==2.1.1 -python-dateutil==2.7.5 -pytz==2018.7 +odfpy==1.4.0 +openpyxl==2.6.1 +psycopg2-binary==2.7.7 +pylint==2.3.1 +python-dateutil==2.8.0 +pytz==2018.9 PyYAML==3.13 rcssmin==1.0.6 -requests==2.20.1 -rjsmin==1.0.12 -six==1.11.0 +requests==2.21.0 +rjsmin==1.1.0 +six==1.12.0 tablib==0.12.1 -typed-ast==1.1.0 +typed-ast==1.3.1 unicodecsv==0.14.1 urllib3==1.24.1 -wrapt==1.10.11 -xlrd==1.1.0 +wrapt==1.11.1 +xlrd==1.2.0 xlwt==1.3.0 -yarg==0.1.9 diff --git a/update-history.sh b/update-history.sh index 5f2c10d..3e92f41 100755 --- a/update-history.sh +++ b/update-history.sh @@ -1 +1,3 @@ +#!/bin/bash + /home/kevin/coding/spotify-lib-vis/bin/python /home/kevin/coding/spotify-lib-vis/src/manage.py update-history >> /home/kevin/coding/spotify-lib-vis/src/api/management/commands/update-history.log