Fix genre graph not displaying

- take top genres/artists for graphs
- improve graphs' appearance
- update pip packages
This commit is contained in:
2019-03-07 05:57:30 -05:00
parent 15c07a208a
commit 670693203e
7 changed files with 228 additions and 217 deletions

View File

@@ -222,9 +222,13 @@ def get_artists_in_genre(user, genre):
processed_artist_counts = {} processed_artist_counts = {}
for artist in user_artists: for artist in user_artists:
processed_artist_counts[artist.name] = round(artist.track_set # TODO: figure out collab problem #
.filter(genre=genre_obj, users=user) # artist_count = math.floor(artist.track_set.filter(genre=genre_obj,
.count() * track_count / total_artist_counts, 2) # 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 return processed_artist_counts
# }}} get_artists_in_genre # # }}} get_artists_in_genre #

View File

@@ -141,7 +141,7 @@ def get_artist_data(request, user_secret):
artist_counts = Artist.objects.annotate(num_songs=Count('track', artist_counts = Artist.objects.annotate(num_songs=Count('track',
filter=Q(track__users=user))) filter=Q(track__users=user)))
processed_artist_counts = [{'name': artist.name, 'num_songs': artist.num_songs} 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: if CONSOLE_LOGGING:
pprint(processed_artist_counts) pprint(processed_artist_counts)
return JsonResponse(data=processed_artist_counts, safe=False) 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 {{{ # # get_genre_data {{{ #
def get_genre_data(request, user_secret): def get_genre_data(request, user_secret):
"""Return genre data needed to create the graph """Return genre data needed to create the graph
TODO 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 # annotates each genre and not each Track, due to the earlier values() call
.annotate(num_songs=Count('genre')) .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_counts is a QuerySet with the format
# [{'genre': 'classical', 'num_songs': 100}, {'genre': 'pop', 'num_songs': 50}...] '''
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: for genre_dict in genre_counts:
genre_dict['artists'] = get_artists_in_genre(user, genre_dict['genre']) 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': {...}
},...
]
'''
if CONSOLE_LOGGING: if CONSOLE_LOGGING:
print("*** Genre Breakdown ***") print("*** Genre Breakdown ***")
pprint(list(genre_counts)) pprint(list(genre_counts))

View File

@@ -1,137 +1,154 @@
function create_genre_graph(data) { function create_genre_graph(data) {
// convert strings to nums {{{ // // convert strings to nums {{{ //
data.forEach(function(d) { data.forEach(function(d) {
d.num_songs = +d.num_songs; d.num_songs = +d.num_songs;
console.log(d.genre, d.num_songs); console.log(d.genre, d.num_songs);
let artist_names = Object.keys(d.artists); let artist_names = Object.keys(d.artists);
artist_names.forEach(function(e) { artist_names.forEach(function(e) {
d.artists[e] = +d.artists[e]; d.artists[e] = +d.artists[e];
console.log(e, d.artists[e]); console.log(e, d.artists[e]);
//console.log(e, d.artists[e], d.artists[e] + 1); //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; // returns the maximum number of songs in the genre
}) * 1.6]).nice();
// }}} domains //
// 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 {{{ //
// }}} convert strings to nums // let keys = Object.keys(genre_dict.artists);
let stack = d3.stack()
// domains {{{ // .order(d3.stackOrderDescending)
.keys(keys)([genre_dict.artists])
data.sort(function(a, b) { // unpack the column
return b.num_songs - a.num_songs; .map((d, i) => {
}); return {
x.domain(data.map(function(d) { key: keys[i],
return d.genre; data: d[0]
})); }
// 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 //
// setup bar colors {{{ //
let max_artists = d3.max(data, function(d) { // }}} process artist breakdown //
return Object.keys(d.artists).length;
}); // add bars {{{ //
let colorScale = d3.scaleOrdinal().range(randomColor({
count: max_artists,
luminosity: 'light',
}));
// }}} setup bar colors // 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 //
for (let genre_dict of data) { // x-axis {{{ //
// process artist breakdown {{{ // g.append("g")
.attr("class", "axis")
let keys = Object.keys(genre_dict.artists); .attr("transform", "translate(0," + height + ")")
let stack = d3.stack() .call(d3.axisBottom(x))
.order(d3.stackOrderDescending) .selectAll(".tick text")
.keys(keys)([genre_dict.artists]) .style('font-size', '1.5em')
// unpack the column .call(wrap, x.bandwidth());
.map((d, i) => {
return { // }}} x-axis //
key: keys[i],
data: d[0]
}
});
// }}} process artist breakdown //
// add bars {{{ // // y-axis {{{ //
g.append("g") g.append("g")
.selectAll("rect") .attr("class", "axis")
.data(stack) .call(d3.axisLeft(y).ticks(null, "s"))
.enter().append("rect") .append("text")
.attr("x", x(genre_dict.genre)) .attr("x", 2)
.attr("y", function(d) { .attr("y", y(y.ticks().pop()) + 0.5)
return y(d.data[1]); .attr("dy", "0.32em")
}) .attr("fill", "white")
.attr("height", d => y(d.data[0]) - y(d.data[1])) .style('font-size', '2em')
.attr("width", x.bandwidth()) .attr("text-anchor", "start")
.attr('fill', (d, i) => colorScale(i)) .text("Songs");
// keep 3 significant figures in the song count label
.append('title').text(d => d.key + ': ' + (d.data[1] - d.data[0]).toPrecision(3)); // }}} y-axis //
// }}} add bars //
// x-axis {{{ // // title {{{ //
g.append("g") g.append("text")
.attr("class", "axis") .attr('x', (width / 2))
.attr("transform", "translate(0," + height + ")") .attr('y', (margin.top / 2))
.call(d3.axisBottom(x)) .attr('fill', "white")
.selectAll(".tick text") .attr('text-anchor', 'middle')
.call(wrap, x.bandwidth()); .attr("font-weight", "bold")
.style('font-size', '2em')
// }}} x-axis // .text('Genre Graph (With Artists)');
// }}} title //
// 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 {{{ // // wrap text {{{ //
// wrapping long labels // wrapping long labels
// https://gist.github.com/guypursey/f47d8cd11a8ff24854305505dbbd8c07#file-index-html // https://gist.github.com/guypursey/f47d8cd11a8ff24854305505dbbd8c07#file-index-html
function wrap(text, width) { function wrap(text, width) {
text.each(function() { text.each(function() {
let text = d3.select(this), let text = d3.select(this),
words = text.text().split(/\s+/).reverse(), words = text.text().split(/\s+/).reverse(),
word, word,
line = [], line = [],
lineNumber = 0, lineNumber = 0,
lineHeight = 1.1, // ems lineHeight = 1.1, // ems
y = text.attr("y"), y = text.attr("y"),
dy = parseFloat(text.attr("dy")), dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em") tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em")
while (word = words.pop()) { while (word = words.pop()) {
line.push(word); line.push(word);
tspan.text(line.join(" ")); tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) { if (tspan.node().getComputedTextLength() > width) {
line.pop(); line.pop();
tspan.text(line.join(" ")); tspan.text(line.join(" "));
line = [word]; line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", `${++lineNumber * lineHeight + dy}em`).text(word); tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", `${++lineNumber * lineHeight + dy}em`).text(word);
} }
} }
}) })
} }
// }}} wrap text // // }}} wrap text //

View File

@@ -1,28 +1,28 @@
{% load static %} {% load static %}
{% load sass_tags %} {% load sass_tags %}
<!DOCTYPE html> <!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Features Graphs</title> <title>Features Graphs</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% sass_src 'scss/custom.scss' %}"> <link rel="stylesheet" href="{% sass_src 'scss/custom.scss' %}">
<style> <style>
.tick { .tick {
font-size: 15px; font-size: 15px;
} }
</style> </style>
</head> </head>
<body> <body>
<!--[if lt IE 7]> <!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p> <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
<![endif]--> <![endif]-->
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
@@ -38,10 +38,10 @@
</div> </div>
</div> </div>
<script src="https://d3js.org/d3.v5.js"></script> <script src="https://d3js.org/d3.v5.js"></script>
<script src="{% static "graphs/scripts/audio_feat_graph.js" %}"></script> <script src="{% static "graphs/scripts/audio_feat_graph.js" %}"></script>
<script type="text/javascript"> <script type="text/javascript">
let userSecret = "{{ user_secret }}"; let userSecret = "{{ user_secret }}";
let graphParams = { let graphParams = {
"acousticness": { "acousticness": {
intervalEndPoints: {begin: 0, end: 1.0, step: 0.20}, intervalEndPoints: {begin: 0, end: 1.0, step: 0.20},
@@ -74,6 +74,6 @@
drawAudioFeatGraph(featureKey, params.intervalEndPoints, drawAudioFeatGraph(featureKey, params.intervalEndPoints,
params.colId, userSecret); params.colId, userSecret);
} }
</script> </script>
</body> </body>
</html> </html>

View File

@@ -1,9 +1,9 @@
<!-- header {{{ --> <!-- header {{{ -->
<!DOC <!DOC
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <!--[if gt IE 8]><!-->
{% load static %} {% load static %}
{% load sass_tags %} {% load sass_tags %}
@@ -14,44 +14,46 @@
<title>Genre Graph</title> <title>Genre Graph</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- <link rel="stylesheet" href="{% sass_src 'scss/custom.scss' %}"> --> <link rel="stylesheet" href="{% sass_src 'scss/custom.scss' %}">
</head> </head>
<!-- }}} header --> <!-- }}} header -->
<body> <body>
<script src="https://d3js.org/d3.v5.min.js"></script> <script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/randomcolor/0.5.2/randomColor.min.js"></script>
{% load static %} {% load static %}
<script src="{% static "graphs/scripts/genre_graph.js" %}"></script> <script src="{% static "graphs/scripts/genre_graph.js" %}"></script>
<!-- <div class="row"> <!-- <div class="row">
<div class="col-" id="genre-graph"></div> <div class="col-" id="genre-graph"></div>
</div> --> </div> -->
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12" id="genre-column"> <div class="col-md-12" id="genre-column">
</div>
</div> </div>
</div> </div>
<svg id="genre-graph" width="600" height="400" </div>
viewBox="0 0 600 400" {% comment %} <svg id="genre-graph" width="600" height="400"
perserveAspectRatio="xMinYMid"> viewBox="0 0 600 400"
</svg> perserveAspectRatio="xMinYMid"> {% endcomment %}
<svg id="genre-graph" width="1500" height="900">
</svg>
<script> <script>
let svg = d3.select("svg"), let svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40}, margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right, width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom, height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let x = d3.scaleBand() let x = d3.scaleBand()
.rangeRound([0, width]) .rangeRound([0, width])
.paddingInner(0.1) .paddingInner(0.1)
.paddingOuter(0.7) .paddingOuter(0.7)
.align(0.1); .align(0.1);
let y = d3.scaleLinear() let y = d3.scaleLinear()
.rangeRound([height, 0]); .rangeRound([height, 0]);
d3.json("{% url "api:get_genre_data" user_secret %}").then(create_genre_graph); d3.json("{% url "api:get_genre_data" user_secret %}").then(create_genre_graph);
</script> </script>

View File

@@ -1,40 +1,37 @@
astroid==2.0.4 astroid==2.2.4
certifi==2018.10.15 certifi==2018.11.29
chardet==3.0.4 chardet==3.0.4
Django==2.1.3 defusedxml==0.5.0
django-appconf==1.0.2 Django==2.1.7
django-appconf==1.0.3
django-compressor==2.2 django-compressor==2.2
django-crispy-forms==1.7.2 django-crispy-forms==1.7.2
django-filter==2.0.0 django-filter==2.1.0
django-sass-processor==0.7.2 django-sass-processor==0.7.2
django-tables2==2.0.3 django-tables2==2.0.5
djangorestframework==3.9.0 djangorestframework==3.9.2
docopt==0.6.2
et-xmlfile==1.0.1 et-xmlfile==1.0.1
idna==2.7 idna==2.8
isort==4.3.4 isort==4.3.12
jdcal==1.4 jdcal==1.4
lazy-object-proxy==1.3.1 lazy-object-proxy==1.3.1
libsass==0.16.0 libsass==0.17.0
mccabe==0.6.1 mccabe==0.6.1
odfpy==1.3.6 odfpy==1.4.0
openpyxl==2.5.10 openpyxl==2.6.1
pkg-resources==0.0.0 psycopg2-binary==2.7.7
psycopg2==2.7.6.1 pylint==2.3.1
psycopg2-binary==2.7.6.1 python-dateutil==2.8.0
pylint==2.1.1 pytz==2018.9
python-dateutil==2.7.5
pytz==2018.7
PyYAML==3.13 PyYAML==3.13
rcssmin==1.0.6 rcssmin==1.0.6
requests==2.20.1 requests==2.21.0
rjsmin==1.0.12 rjsmin==1.1.0
six==1.11.0 six==1.12.0
tablib==0.12.1 tablib==0.12.1
typed-ast==1.1.0 typed-ast==1.3.1
unicodecsv==0.14.1 unicodecsv==0.14.1
urllib3==1.24.1 urllib3==1.24.1
wrapt==1.10.11 wrapt==1.11.1
xlrd==1.1.0 xlrd==1.2.0
xlwt==1.3.0 xlwt==1.3.0
yarg==0.1.9

View File

@@ -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 /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