Browse Source

Merge branch 'chris/wip' into vis-page

master
Kevin Mok 6 years ago
parent
commit
a7e4c151cf
  1. 3
      api/admin.py
  2. 4
      api/models.py
  3. 89
      api/tests.py
  4. 55
      api/utils.py
  5. 51
      api/views.py
  6. 45
      graphs/static/graphs/scripts/audio_feat_graph.js
  7. 40
      graphs/static/graphs/scripts/genre_graph.js
  8. 48
      graphs/templates/graphs/features_graphs.html
  9. 11
      graphs/templates/graphs/genre_graph.html
  10. 16
      reset_db.sh

3
api/admin.py

@ -1,8 +1,9 @@
from django.contrib import admin from django.contrib import admin
from .models import Track, Artist, AudioFeatures, User
from .models import Track, Artist, AudioFeatures, User, Genre
# Register your models here. # Register your models here.
admin.site.register(Track) admin.site.register(Track)
admin.site.register(Artist) admin.site.register(Artist)
admin.site.register(AudioFeatures) admin.site.register(AudioFeatures)
admin.site.register(User) admin.site.register(User)
admin.site.register(Genre)

4
api/models.py

@ -30,6 +30,8 @@ class Artist(models.Model):
id = models.CharField(primary_key=True, max_length=MAX_ID) id = models.CharField(primary_key=True, max_length=MAX_ID)
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
genres = models.ManyToManyField(Genre, blank=True) genres = models.ManyToManyField(Genre, blank=True)
# genre = models.ForeignKey(Genre, on_delete=models.CASCADE, blank=True,
# null=True)
def __str__(self): def __str__(self):
return self.name return self.name
@ -52,7 +54,7 @@ class Track(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
users = models.ManyToManyField(User, blank=True) users = models.ManyToManyField(User, blank=True)
genre = models.ForeignKey(Genre, on_delete=models.CASCADE, blank=True, genre = models.ForeignKey(Genre, on_delete=models.CASCADE, blank=True,
null=True)
null=True)
def __str__(self): def __str__(self):
track_str = "{}, genre: {}, artists: [".format(self.name, self.genre) track_str = "{}, genre: {}, artists: [".format(self.name, self.genre)

89
api/tests.py

@ -0,0 +1,89 @@
from django.test import TestCase
from api.models import Track, Genre, Artist
from login.models import User
from api import utils
import math
import pprint
class GenreDataTestCase(TestCase):
@classmethod
def setUpTestData(cls):
test_user = User.objects.create(id="chrisshi", refresh_token="blah", access_token="blah",
access_expires_in=10)
genre = Genre.objects.create(name="classical", num_songs=3)
artist_1 = Artist.objects.create(id='art1', name="Beethoven")
artist_2 = Artist.objects.create(id='art2', name="Mozart")
artist_3 = Artist.objects.create(id='art3', name='Chopin')
track_1 = Track.objects.create(id='track1', year=2013,
popularity=5, runtime=20,
name='concerto1',
genre=genre)
track_1.users.add(test_user)
track_1.artists.add(artist_1)
track_1.artists.add(artist_2)
track_2 = Track.objects.create(id='track2', year=2013,
popularity=5, runtime=20,
name='concerto2',
genre=genre)
track_2.users.add(test_user)
track_2.artists.add(artist_2)
track_2.artists.add(artist_3)
track_2.artists.add(artist_1)
track_3 = Track.objects.create(id='track3', year=2013,
popularity=5, runtime=20,
name='concerto3',
genre=genre)
track_3.users.add(test_user)
track_3.artists.add(artist_1)
track_3.artists.add(artist_3)
pop_genre = Genre.objects.create(name='pop', num_songs=3)
pop_artist1 = Artist.objects.create(id='art4', name="Taylor Swift")
pop_artist2 = Artist.objects.create(id='art5', name="Justin Bieber")
pop_artist3 = Artist.objects.create(id='art6', name="Rihanna")
pop_track_1 = Track.objects.create(id='track4', year=2013,
popularity=5, runtime=20,
name='poptrack1',
genre=pop_genre)
pop_track_1.users.add(test_user)
pop_track_1.artists.add(pop_artist1)
pop_track_1.artists.add(pop_artist2)
pop_track_2 = Track.objects.create(id='track5', year=2013,
popularity=5, runtime=20,
name='poptrack2',
genre=pop_genre)
pop_track_2.users.add(test_user)
pop_track_2.artists.add(pop_artist3)
pop_track_2.artists.add(pop_artist2)
pop_track_2.artists.add(pop_artist1)
pop_track_3 = Track.objects.create(id='track6', year=2013,
popularity=5, runtime=20,
name='poptrack3',
genre=pop_genre)
pop_track_3.users.add(test_user)
pop_track_3.artists.add(pop_artist3)
pop_track_3.artists.add(pop_artist2)
pop_track_3.artists.add(pop_artist1)
def test_get_artist_counts_two_genres(self):
test_user = User.objects.get(id='chrisshi')
artist_counts = utils.get_artists_in_genre(test_user, 'classical')
# pprint.pprint(artist_counts)
self.assertTrue(math.isclose(artist_counts['Beethoven'], 1.3, rel_tol=0.05))
self.assertTrue(math.isclose(artist_counts['Mozart'], 0.85, rel_tol=0.05))
self.assertTrue(math.isclose(artist_counts['Chopin'], 0.85, rel_tol=0.05))
self.assertTrue(math.isclose(sum(artist_counts.values()), 3, rel_tol=0.01))
# test the pop genre
artist_counts = utils.get_artists_in_genre(test_user, 'pop')
self.assertTrue(math.isclose(artist_counts['Taylor Swift'], 1.125, rel_tol=0.05))
self.assertTrue(math.isclose(artist_counts['Justin Bieber'], 1.125, rel_tol=0.05))
self.assertTrue(math.isclose(artist_counts['Rihanna'], 0.75, rel_tol=0.05))
self.assertTrue(math.isclose(sum(artist_counts.values()), 3, rel_tol=0.01))

55
api/utils.py

@ -14,6 +14,8 @@ from login.models import User
from pprint import pprint from pprint import pprint
from dateutil.parser import parse from dateutil.parser import parse
from datetime import datetime from datetime import datetime
from django.db.models import FloatField
from django.db.models.functions import Cast
HISTORY_ENDPOINT = 'https://api.spotify.com/v1/me/player/recently-played' HISTORY_ENDPOINT = 'https://api.spotify.com/v1/me/player/recently-played'
@ -39,16 +41,13 @@ def update_track_genres(user_obj):
user_tracks = Track.objects.filter(users__exact=user_obj) user_tracks = Track.objects.filter(users__exact=user_obj)
for track in user_tracks: for track in user_tracks:
# just using this variable to save another call to db # just using this variable to save another call to db
track_artists = track.artists.all()
# set genres to first artist's genres then find intersection with others
shared_genres = track_artists.first().genres.all()
for artist in track_artists:
shared_genres = shared_genres.intersection(artist.genres.all())
shared_genres = shared_genres.order_by('-num_songs')
track_artists = list(track.artists.all())
# TODO: Use the most popular genre of the first artist as the Track genre
first_artist_genres = track_artists[0].genres.all().order_by('-num_songs')
undefined_genre_obj = Genre.objects.get(name="undefined") undefined_genre_obj = Genre.objects.get(name="undefined")
most_common_genre = shared_genres.first() if shared_genres.first() is \
not undefined_genre_obj else shared_genres[1]
most_common_genre = first_artist_genres.first() if first_artist_genres.first() is \
not undefined_genre_obj else first_artist_genres[1]
track.genre = most_common_genre if most_common_genre is not None \ track.genre = most_common_genre if most_common_genre is not None \
else undefined_genre_obj else undefined_genre_obj
track.save() track.save()
@ -153,16 +152,15 @@ def get_audio_features(headers, track_objs):
# process_artist_genre {{{ # # process_artist_genre {{{ #
def process_artist_genre(genre_name, artist_obj): def process_artist_genre(genre_name, artist_obj):
"""Increase count for correspoding Genre object to genre_name and add that
Genre to artist_obj.
"""Increase count for corresponding Genre object to genre_name and associate that
Genre object with artist_obj.
:genre_name: Name of genre. :genre_name: Name of genre.
:artist_obj: Artist object to add Genre object to.
:artist_obj: Artist object to associate Genre object with
:returns: None :returns: None
""" """
genre_obj, created = Genre.objects.get_or_create(name=genre_name,
defaults={'num_songs':1})
genre_obj, created = Genre.objects.get_or_create(name=genre_name, defaults={'num_songs': 1})
if not created: if not created:
genre_obj.num_songs = F('num_songs') + 1 genre_obj.num_songs = F('num_songs') + 1
genre_obj.save() genre_obj.save()
@ -185,7 +183,6 @@ def add_artist_genres(headers, artist_objs):
""" """
artist_ids = str.join(",", [artist_obj.id for artist_obj in artist_objs]) artist_ids = str.join(",", [artist_obj.id for artist_obj in artist_objs])
params = {'ids': artist_ids}
artists_response = requests.get('https://api.spotify.com/v1/artists/', artists_response = requests.get('https://api.spotify.com/v1/artists/',
headers=headers, headers=headers,
params={'ids': artist_ids}, params={'ids': artist_ids},
@ -208,34 +205,26 @@ def add_artist_genres(headers, artist_objs):
# get_artists_in_genre {{{ # # get_artists_in_genre {{{ #
def get_artists_in_genre(user, genre, max_songs):
def get_artists_in_genre(user, genre):
"""Return count of artists in genre. """Return count of artists in genre.
:user: User object to return data for. :user: User object to return data for.
:genre: genre to count artists for.
:max_songs: max total songs to include to prevent overflow due to having
multiple artists on each track.
:genre: genre to count artists for. (string)
:returns: dict of artists in the genre along with the number of songs they :returns: dict of artists in the genre along with the number of songs they
have. have.
""" """
genre_obj = Genre.objects.get(name=genre) genre_obj = Genre.objects.get(name=genre)
artist_counts = (Artist.objects.filter(track__users=user)
.filter(genres=genre_obj)
.annotate(num_songs=Count('track', distinct=True))
.order_by('-num_songs')
)
tracks_in_genre = Track.objects.filter(genre=genre_obj, users=user)
track_count = tracks_in_genre.count()
user_artists = Artist.objects.filter(track__users=user) # use this variable to save on db queries
total_artist_counts = tracks_in_genre.aggregate(counts=Count('artists'))['counts']
processed_artist_counts = {} processed_artist_counts = {}
songs_added = 0
for artist in artist_counts:
# hacky way to not have total count overflow due to there being multiple
# artists on a track
if songs_added + artist.num_songs <= max_songs:
processed_artist_counts[artist.name] = artist.num_songs
songs_added += artist.num_songs
# processed_artist_counts = [{'name': artist.name, 'num_songs': artist.num_songs} for artist in artist_counts]
# processed_artist_counts = {artist.name: artist.num_songs for artist in artist_counts}
# pprint.pprint(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)
return processed_artist_counts return processed_artist_counts
# }}} get_artists_in_genre # # }}} get_artists_in_genre #

51
api/views.py

@ -33,9 +33,8 @@ FEATURES_LIMIT = 100
# FEATURES_LIMIT = 25 # FEATURES_LIMIT = 25
TRACKS_TO_QUERY = 100 TRACKS_TO_QUERY = 100
TRACKS_ENDPOINT = 'https://api.spotify.com/v1/tracks' TRACKS_ENDPOINT = 'https://api.spotify.com/v1/tracks'
console_logging = True
# console_logging = False
CONSOLE_LOGGING = True
# CONSOLE_LOGGING = False
# }}} constants # # }}} constants #
@ -66,9 +65,7 @@ def parse_library(request, user_secret):
headers=user_headers, headers=user_headers,
params=payload).json()['items'] params=payload).json()['items']
if console_logging:
tracks_processed = 0
tracks_processed = 0
for track_dict in saved_tracks_response: for track_dict in saved_tracks_response:
track_artists = save_track_artists(track_dict['track'], artist_genre_queue, track_artists = save_track_artists(track_dict['track'], artist_genre_queue,
user_headers) user_headers)
@ -76,7 +73,7 @@ def parse_library(request, user_secret):
track_artists, user_obj) track_artists, user_obj)
# add audio features {{{ # # add audio features {{{ #
# if a new track is not created, the associated audio feature does # if a new track is not created, the associated audio feature does
# not need to be created again # not need to be created again
if track_created: if track_created:
@ -87,7 +84,7 @@ def parse_library(request, user_secret):
# }}} add audio features # # }}} add audio features #
if console_logging:
if CONSOLE_LOGGING:
tracks_processed += 1 tracks_processed += 1
print("Added track #{}: {} - {}".format( print("Added track #{}: {} - {}".format(
offset + tracks_processed, offset + tracks_processed,
@ -99,7 +96,7 @@ def parse_library(request, user_secret):
offset += USER_TRACKS_LIMIT offset += USER_TRACKS_LIMIT
# clean-up {{{ # # clean-up {{{ #
# update remaining artists without genres and songs without features if # update remaining artists without genres and songs without features if
# there are any # there are any
if len(artist_genre_queue) > 0: if len(artist_genre_queue) > 0:
@ -177,19 +174,39 @@ 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 user.
"""Return genre data needed to create the graph
TODO TODO
""" """
user = User.objects.get(secret=user_secret) user = User.objects.get(secret=user_secret)
genre_counts = (Track.objects.filter(users__exact=user) genre_counts = (Track.objects.filter(users__exact=user)
.values('genre')
.order_by('genre')
.annotate(num_songs=Count('genre'))
)
.values('genre')
.order_by('genre')
# annotates each genre and not each Track, due to the earlier values() call
.annotate(num_songs=Count('genre'))
)
# genre_counts is a QuerySet with the format
# [{'genre': 'classical', 'num_songs': 100}, {'genre': 'pop', 'num_songs': 50}...]
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['num_songs'])
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': {...}
},...
]
'''
print("*** Genre Breakdown ***") print("*** Genre Breakdown ***")
pprint(list(genre_counts)) pprint(list(genre_counts))
return JsonResponse(data=list(genre_counts), safe=False) return JsonResponse(data=list(genre_counts), safe=False)
@ -258,7 +275,7 @@ def import_history(request, upload_id):
history_obj = save_history_obj(upload_obj.user, timestamp, history_obj = save_history_obj(upload_obj.user, timestamp,
track_obj) track_obj)
if console_logging:
if CONSOLE_LOGGING:
print("Processed row #{}: {}".format( print("Processed row #{}: {}".format(
(rows_read - TRACKS_LIMIT) + responses_processed, history_obj,)) (rows_read - TRACKS_LIMIT) + responses_processed, history_obj,))
responses_processed += 1 responses_processed += 1

45
graphs/static/graphs/scripts/audio_feat_graph.js

@ -3,26 +3,35 @@
* a designated parent element * a designated parent element
* *
* @param audioFeature: the name of the audio feature (string) * @param audioFeature: the name of the audio feature (string)
* @param intervalEndPoints: a sorted array of 5 real numbers defining the intervals (categories) of values,
* @param intervalEndPoints: a object defining the intervals (categories) of values,
* for example: * for example:
* [0, 0.25, 0.5, 0.75, 1.0] for instrumentalness would define ranges
* (0-0.25), (0.25-0.5), (0.5-0.75), (0.75-1.0)
* @param parentElem: the DOM element to append the graph to (a selector string)
* {begin: 0, end: 1.0, step: 0.25} for instrumentalness would define ranges
* [0-0.25), [0.25-0.5), [0.5-0.75), [0.75-1.0]
* @param colId: the DOM element to append the graph to (a selector string)
* @param userSecret: the user secret string for identification * @param userSecret: the user secret string for identification
* @return None * @return None
*/ */
function drawAudioFeatGraph(audioFeature, intervalEndPoints, parentElem, userSecret) {
function drawAudioFeatGraph(audioFeature, intervalEndPoints, colId, userSecret) {
// TODO: Not hard code the dimensions? // TODO: Not hard code the dimensions?
let margin = {top: 20, right: 30, bottom: 30, left: 40}; let margin = {top: 20, right: 30, bottom: 30, left: 40};
let width = 480 - margin.left - margin.right, let width = 480 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom; height = 270 - margin.top - margin.bottom;
let featureData = {}; let featureData = {};
let currentEndPoint = intervalEndPoints.begin; // start at beginning
// Create the keys first in order // Create the keys first in order
for (let index = 0; index < intervalEndPoints.length - 1; index++) {
let key = `${intervalEndPoints[index]} ~ ${intervalEndPoints[index + 1]}`;
while (currentEndPoint < intervalEndPoints.end) {
let startOfRange = currentEndPoint;
let endOfRange = precise(startOfRange + intervalEndPoints.step);
let key = `${startOfRange} ~ ${endOfRange}`;
featureData[key] = 0; featureData[key] = 0;
currentEndPoint = endOfRange;
} }
// for (let index = 0; index < intervalEndPoints.length - 1; index++) {
// let key = `${intervalEndPoints[index]} ~ ${intervalEndPoints[index + 1]}`;
// featureData[key] = 0;
// }
// define the vertical scaling function // define the vertical scaling function
// let vScale = d3.scaleLinear().range([height, 0]); // let vScale = d3.scaleLinear().range([height, 0]);
let padding = 0.5; let padding = 0.5;
@ -33,12 +42,15 @@ function drawAudioFeatGraph(audioFeature, intervalEndPoints, parentElem, userSec
// categorize the data points // categorize the data points
for (let dataPoint of response.data_points) { for (let dataPoint of response.data_points) {
dataPoint = parseFloat(dataPoint); dataPoint = parseFloat(dataPoint);
let index = intervalEndPoints.length - 2;
let currLowerBound = precise(intervalEndPoints.end - intervalEndPoints.step);
let stepSize = intervalEndPoints.step;
// find the index of the first element greater than dataPoint // find the index of the first element greater than dataPoint
while (dataPoint < intervalEndPoints[index]) {
index -= 1;
while (dataPoint < currLowerBound && currLowerBound >= intervalEndPoints.begin) {
currLowerBound = precise(currLowerBound - stepSize);
} }
let key = `${intervalEndPoints[index]} ~ ${intervalEndPoints[index + 1]}`;
let upperBound = precise(currLowerBound + stepSize);
currLowerBound = precise(currLowerBound);
let key = `${currLowerBound} ~ ${upperBound}`;
featureData[key] += 1; featureData[key] += 1;
} }
@ -63,7 +75,7 @@ function drawAudioFeatGraph(audioFeature, intervalEndPoints, parentElem, userSec
let xAxis = d3.axisBottom().scale(hScale); let xAxis = d3.axisBottom().scale(hScale);
let yAxis = d3.axisLeft().scale(vScale); let yAxis = d3.axisLeft().scale(vScale);
let featureSVG = d3.select('#' + parentElem)
let featureSVG = d3.select('#' + colId)
.append('svg').attr('width', width + margin.left + margin.right) .append('svg').attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom); .attr('height', height + margin.top + margin.bottom);
@ -109,3 +121,12 @@ function drawAudioFeatGraph(audioFeature, intervalEndPoints, parentElem, userSec
function capFeatureStr(audioFeature) { function capFeatureStr(audioFeature) {
return audioFeature.charAt(0).toUpperCase() + audioFeature.slice(1); return audioFeature.charAt(0).toUpperCase() + audioFeature.slice(1);
} }
/**
* Converts a number to a floating point value with 2 significant figures
* @param number: the number to be converted
* @returns the input converted to two significant digits
*/
function precise(number) {
return Number.parseFloat(number.toPrecision(2));
}

40
graphs/static/graphs/scripts/genre_graph.js

@ -4,7 +4,7 @@ function create_genre_graph(data) {
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);
var 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]);
@ -22,35 +22,34 @@ function create_genre_graph(data) {
x.domain(data.map(function(d) { x.domain(data.map(function(d) {
return d.genre; 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; }) * 1.25]).nice();
y.domain([0, d3.max(data, function(d) { y.domain([0, d3.max(data, function(d) {
return d.num_songs;
return d.num_songs; // returns the maximum number of songs in the genre
})]).nice(); })]).nice();
// }}} domains // // }}} domains //
// setup bar colors {{{ // // setup bar colors {{{ //
var max_artists = d3.max(data, function(d) {
let max_artists = d3.max(data, function(d) {
return Object.keys(d.artists).length; return Object.keys(d.artists).length;
}); });
var z = d3.scaleOrdinal().range(randomColor({
let colorScale = d3.scaleOrdinal().range(randomColor({
count: max_artists, count: max_artists,
luminosity: 'light', luminosity: 'light',
})); }));
// }}} setup bar colors // // }}} setup bar colors //
for (var genre_dict of data) {
for (let genre_dict of data) {
// process artist breakdown {{{ // // process artist breakdown {{{ //
var keys = Object.keys(genre_dict.artists);
var stack = d3.stack()
//.order(d3.stackOrderAscending)
let keys = Object.keys(genre_dict.artists);
let stack = d3.stack()
.order(d3.stackOrderDescending) .order(d3.stackOrderDescending)
.keys(keys)([genre_dict.artists]) .keys(keys)([genre_dict.artists])
//unpack the column
// unpack the column
.map((d, i) => { .map((d, i) => {
return { return {
key: keys[i], key: keys[i],
@ -72,8 +71,9 @@ function create_genre_graph(data) {
}) })
.attr("height", d => y(d.data[0]) - y(d.data[1])) .attr("height", d => y(d.data[0]) - y(d.data[1]))
.attr("width", x.bandwidth()) .attr("width", x.bandwidth())
.attr('fill', (d, i) => z(i))
.append('title').text(d => d.key + ': ' + (d.data[1] - d.data[0]));
.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 // // }}} add bars //
@ -108,11 +108,11 @@ function create_genre_graph(data) {
} }
// wrap text {{{ // // wrap text {{{ //
// 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() {
var 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 = [],
@ -122,13 +122,13 @@ function wrap(text, width) {
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)
tspan.text(line.join(" "))
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) { 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)
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);
} }
} }
}) })

48
graphs/templates/graphs/features_graphs.html

@ -43,22 +43,38 @@
<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 }}";
drawAudioFeatGraph("acousticness", [0, 0.25, 0.5, 0.75, 1.0],
'acoustic-column', userSecret);
drawAudioFeatGraph("danceability", [0, 0.25, 0.5, 0.75, 1.0],
'dance-column', userSecret);
drawAudioFeatGraph("energy", [0, 0.25, 0.5, 0.75, 1.0],
'energy-column', userSecret);
drawAudioFeatGraph("instrumentalness", [0, 0.25, 0.5, 0.75, 1.0],
'instr-column', userSecret);
drawAudioFeatGraph("loudness", [-60, -45, -30, -15, 0],
'loud-column', userSecret);
drawAudioFeatGraph("speechiness", [0, 0.25, 0.5, 0.75, 1.0],
'speech-column', userSecret);
drawAudioFeatGraph("tempo", [0, 40, 80, 120, 160, 200],
'tempo-column', userSecret);
drawAudioFeatGraph("valence", [0, 0.25, 0.5, 0.75, 1.0],
'valence-column', userSecret);
let graphParams = {
"acousticness": {
intervalEndPoints: {begin: 0, end: 1.0, step: 0.20},
colId: 'acoustic-column'},
"danceability": {
intervalEndPoints: {begin: 0, end: 1.0, step: 0.20},
colId: 'dance-column'},
"energy": {
intervalEndPoints: {begin: 0, end: 1.0, step: 0.20},
colId: 'energy-column'},
"instrumentalness": {
intervalEndPoints: {begin: 0, end: 1.0, step: 0.20},
colId: 'instr-column'},
"loudness": {
intervalEndPoints: {begin: -60, end: 0, step: 12},
colId: 'loud-column'},
"speechiness": {
intervalEndPoints: {begin: 0, end: 1.0, step: 0.20},
colId: 'speech-column'},
"tempo": {
intervalEndPoints: {begin: 0, end: 200, step: 40},
colId: 'tempo-column'},
"valence": {
intervalEndPoints: {begin: 0, end: 1.0, step: 0.20},
colId: 'valence-column'},
};
for(var featureKey in graphParams) {
let params = graphParams[featureKey];
drawAudioFeatGraph(featureKey, params.intervalEndPoints,
params.colId, userSecret);
}
</script> </script>
</body> </body>
</html> </html>

11
graphs/templates/graphs/genre_graph.html

@ -13,7 +13,7 @@
<title>Test DB Page</title> <title>Test DB Page</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="{% static 'css/dark_bg.css' %}">
{# <link rel="stylesheet" href="{% static 'css/dark_bg.css' %}">#}
</head> </head>
<!-- }}} header --> <!-- }}} header -->
@ -34,16 +34,17 @@
perserveAspectRatio="xMinYMid"> perserveAspectRatio="xMinYMid">
</svg> </svg>
<script> <script>
var 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 + ")");
var x = d3.scaleBand()
let x = d3.scaleBand()
.rangeRound([0, width]) .rangeRound([0, width])
.paddingInner(0.05)
.paddingInner(0.1)
.paddingOuter(0.7)
.align(0.1); .align(0.1);
var 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);

16
reset_db.sh

@ -1,15 +1,15 @@
# check if in virtual environment # check if in virtual environment
# https://stackoverflow.com/questions/15454174/how-can-a-shell-function-know-if-it-is-running-within-a-virtualenv/15454916 # https://stackoverflow.com/questions/15454174/how-can-a-shell-function-know-if-it-is-running-within-a-virtualenv/15454916
# python -c 'import sys; print(sys.real_prefix)' 2>/dev/null && INVENV=1 || INVENV=0
python -c 'import sys; print(sys.real_prefix)' 2>/dev/null && INVENV=1 || INVENV=0
# INVENV=$(python -c 'import sys; print ("1" if hasattr(sys, "real_prefix") else "0")') # INVENV=$(python -c 'import sys; print ("1" if hasattr(sys, "real_prefix") else "0")')
# if $INVENV is 1, then in virtualenv # if $INVENV is 1, then in virtualenv
# echo $INVENV # echo $INVENV
# if [ $INVENV -eq 1 ]; then
rm login/migrations/0* api/migrations/0*
sudo -u postgres psql -f reset_db.sql
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
# fi
if [ $INVENV -eq 1 ]; then
rm login/migrations/0* api/migrations/0*
sudo -u postgres psql -f reset_db.sql
python manage.py makemigrations login api
python manage.py migrate
python manage.py runserver
fi
Loading…
Cancel
Save