1
1
Fork 0

Developing UI

This commit is contained in:
Jordy van Zeeland 2022-10-03 17:03:02 +02:00
parent b0008014f6
commit 767ef48e62
5 changed files with 330 additions and 172 deletions

View File

@ -2,7 +2,9 @@ from django.urls import path
from .views import * from .views import *
urlpatterns = [ urlpatterns = [
path('books/years', getYears),
path('books/stats', getStats),
path('books/genres', books_per_genre_per_month), path('books/genres', books_per_genre_per_month),
path('books/genres/count', countGenres), path('books/genres/count', countGenres),
path('ratings', avg_ratings_per_month) path('books/countries', books_per_country),
] ]

View File

@ -17,22 +17,23 @@ def getBooksData():
return df return df
def filterData(df, datayear = None):
df['readed'] = pd.to_datetime(df['readed'], format='%Y-%m-%d')
df['readed'] = df['readed'].dt.strftime('%m-%Y')
# Filter data on year
if datayear and datayear is not None:
df = df.where(df['readed'].str.contains(datayear))
return df
@api_view(['GET']) @api_view(['GET'])
def books_per_genre_per_month(request): def books_per_genre_per_month(request):
if request.META.get('HTTP_YEAR'):
datayear = request.META.get('HTTP_YEAR')
if datayear:
data = [] data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
df = getBooksData()
df['readed'] = pd.to_datetime(df['readed'], format='%Y-%m-%d')
df['readed'] = df['readed'].dt.strftime('%m-%Y')
# Filter data on year
df = df.where(df['readed'].str.contains(datayear))
# Filter array on genre and date # Filter array on genre and date
booksPerMonth = df.groupby(['genre','readed'])['genre'].count().reset_index(name="count") booksPerMonth = df.groupby(['genre','readed'])['genre'].count().reset_index(name="count")
@ -49,49 +50,13 @@ def books_per_genre_per_month(request):
else: else:
return Response("No year header included") return Response("No year header included")
@api_view(['GET'])
def avg_ratings_per_month(request):
datayear = request.META.get('HTTP_YEAR')
if datayear:
data = []
# Get CSV file with book data
df = getBooksData()
df['readed'] = pd.to_datetime(df['readed'], format='%Y-%m-%d')
df['readed'] = df['readed'].dt.strftime('%m-%Y')
# Filter data on year
df = df.where(df['readed'].str.contains(datayear))
avgratingspermonth = df.groupby('readed')['rating'].mean().reset_index(name="rating")
for index, row in avgratingspermonth.iterrows():
data.append({
"date": row['readed'],
"rating": int(row['rating'])
})
return Response(data)
else:
return Response("No year header included")
@api_view(['GET']) @api_view(['GET'])
def countGenres(request): def countGenres(request):
datayear = request.META.get('HTTP_YEAR') if request.META.get('HTTP_YEAR'):
if datayear:
data = [] data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
# Get CSV file with book data
df = getBooksData()
df['readed'] = pd.to_datetime(df['readed'], format='%Y-%m-%d')
df['readed'] = df['readed'].dt.strftime('%m-%Y')
df = df.where(df['readed'].str.contains(datayear))
genres = df.groupby('genre')['genre'].count().reset_index(name="count") genres = df.groupby('genre')['genre'].count().reset_index(name="count")
genres = genres.sort_values(by='count', ascending=False) genres = genres.sort_values(by='count', ascending=False)
@ -106,3 +71,60 @@ def countGenres(request):
else: else:
return Response("No year header included") return Response("No year header included")
@api_view(['GET'])
def books_per_country(request):
if request.META.get('HTTP_YEAR'):
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
countries = df.groupby('country')['country'].count().reset_index(name="count")
countries = countries.sort_values(by='count', ascending=False)
for index, row in countries.iterrows():
data.append({
"country": row['country'],
"count": int(row['count'])
})
return Response(data)
else:
return Response("No year header included")
@api_view(['GET'])
def getStats(request):
if request.META.get('HTTP_YEAR'):
data = []
df = filterData(getBooksData(), request.META.get('HTTP_YEAR'))
df = df.dropna()
statsTotalBooks = df['name'].count()
statsTotalPages = df['pages'].astype(int).sum()
statsTotalWriters = df['author'].nunique()
statsTotalCountries = df['country'].nunique()
statsTotalGenres = df['genre'].nunique()
data.append({
'totalbooks': statsTotalBooks,
'totalpages': statsTotalPages,
'totalauthors': statsTotalWriters,
'totalcountries': statsTotalCountries,
'totalgenres': statsTotalGenres
})
return Response(data[0])
else:
return Response("No year header included")
@api_view(['GET'])
def getYears(request):
df = filterData(getBooksData())
df['readed'] = pd.to_datetime(df['readed'], errors='coerce')
df['year']= df['readed'].dt.year
years = df.groupby('year')['year'].count().reset_index(name="count")
return Response(years['year'])

View File

@ -5,15 +5,95 @@ export default class App extends Component {
super(props); super(props);
this.state = { this.state = {
year: new Date().getFullYear(), year: new Date().getFullYear(),
readingYears: [],
totalbooks: 0,
totalpages: 0,
totalauthors: 0,
totalcountries: 0,
totalgenres: 0
} }
this.yearsArray = []; this.yearsArray = [];
} }
changeYear(event) { changeYear(event) {
this.setState({ this.setState({
year: event.target.value year: event.target.value
}) })
fetch('/api/books/stats', {
"method": "GET",
"headers": {
"year": event.target.value
}
})
.then(response => response.json())
.then(data => {
this.setState({
totalbooks: data.totalbooks,
totalpages: data.totalpages,
totalauthors: data.totalauthors,
totalcountries: data.totalcountries,
totalgenres: data.totalgenres
})
})
}
initHorBar(data){
var countries = [];
var counts = [];
data.forEach((count) => {
if (!countries.includes(count.country)) {
countries.push(count.country)
}
counts.push(count.count)
})
$("canvas#countryChart").remove();
$("div.books-per-country").append('<canvas id="countryChart"></canvas>');
var ctx = document.getElementById("countryChart");
new Chart(ctx, {
type: 'bar',
options: {
indexAxis: 'y',
plugins: {
legend: {
display: false
},
},
scales: {
x: {
ticks: {
beginAtZero: true,
color: "white",
},
stacked: true,
},
y: {
ticks: {
beginAtZero: true,
stepSize: 1,
color: "white",
},
stacked: true
}
},
},
data: {
labels: countries,
datasets: [
{
label: "Boeken",
data: counts,
backgroundColor: '#696ffc'
}]
}
});
} }
initDoughnut(data) { initDoughnut(data) {
@ -50,12 +130,11 @@ export default class App extends Component {
options: { options: {
//cutoutPercentage: 40, //cutoutPercentage: 40,
responsive: true, responsive: true,
} }
}); });
} }
initChart(data, ratings, year) { initChart(data, year) {
/* /*
---------------------------------- ----------------------------------
@ -110,39 +189,6 @@ export default class App extends Component {
}) })
} }
/*
----------------------------------
Avarage ratings per month
----------------------------------
*/
var avgRatings = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var j = 0; j < 12; j++) {
if (j < 9) {
var month = "0" + (j + 1)
} else {
month = (j + 1)
}
for (var i = 0; i < ratings.length; i++) {
if (ratings[i].date == month + '-' + year) {
avgRatings[j] = ratings[i].rating;
}
}
}
dataSet.push({
label: 'Gemiddelde beoordeling',
data: avgRatings,
backgroundColor: '#ffa500',
borderColor: '#ffa500',
tension: 0.4,
type: 'line',
order: 1
})
/* /*
---------------------------------- ----------------------------------
Stacked bar chart Stacked bar chart
@ -196,6 +242,8 @@ export default class App extends Component {
} }
componentDidUpdate() { componentDidUpdate() {
var $this = this;
fetch('/api/books/genres', { fetch('/api/books/genres', {
"method": "GET", "method": "GET",
"headers": { "headers": {
@ -204,17 +252,7 @@ export default class App extends Component {
}) })
.then(response => response.json()) .then(response => response.json())
.then(books => { .then(books => {
fetch('/api/ratings', { this.initChart(books, this.state.year);
"method": "GET",
"headers": {
"year": this.state.year
}
})
.then(response => response.json())
.then(ratings => {
this.initChart(books, ratings, this.state.year);
})
}) })
fetch('/api/books/genres/count', { fetch('/api/books/genres/count', {
@ -227,10 +265,26 @@ export default class App extends Component {
.then(data => { .then(data => {
this.initDoughnut(data); this.initDoughnut(data);
}) })
fetch('/api/books/countries', {
"method": "GET",
"headers": {
"year": this.state.year
}
})
.then(response => response.json())
.then(data => {
this.initHorBar(data);
})
} }
componentDidMount() { componentDidMount() {
var $this = this;
var currentyear = this.state.year ? this.state.year : new Date().getFullYear() var currentyear = this.state.year ? this.state.year : new Date().getFullYear()
fetch('/api/books/genres', { fetch('/api/books/genres', {
@ -241,17 +295,7 @@ export default class App extends Component {
}) })
.then(response => response.json()) .then(response => response.json())
.then(books => { .then(books => {
fetch('/api/ratings', { this.initChart(books, currentyear);
"method": "GET",
"headers": {
"year": currentyear
}
})
.then(response => response.json())
.then(ratings => {
this.initChart(books, ratings, currentyear);
})
}) })
fetch('/api/books/genres/count', { fetch('/api/books/genres/count', {
@ -264,60 +308,106 @@ export default class App extends Component {
.then(data => { .then(data => {
this.initDoughnut(data); this.initDoughnut(data);
}) })
fetch('/api/books/countries', {
"method": "GET",
"headers": {
"year": this.state.year
}
})
.then(response => response.json())
.then(data => {
this.initHorBar(data);
})
fetch('/api/books/stats', {
"method": "GET",
"headers": {
"year": this.state.year
}
})
.then(response => response.json())
.then(data => {
$this.setState({
totalbooks: data.totalbooks,
totalpages: data.totalpages,
totalauthors: data.totalauthors,
totalcountries: data.totalcountries,
totalgenres: data.totalgenres
})
})
fetch('/api/books/years', {
"method": "GET",
})
.then(response => response.json())
.then(data => {
this.setState({
readingYears: data
})
})
} }
render() { render() {
return ( return (
<React.Fragment> <React.Fragment>
<div className="sidebar">
<ul className="sidebar_menu">
<li className="menu-item">
<div className="menu-item-label">
<div className="menu-item-label-name"><i className="fa fa-chart-bar"></i> Dashboard</div>
</div>
</li>
<li>
<div className="menu-item-label">
<div className="menu-item-label-name"><i class="fa fa-book-open"></i> Boeken</div>
</div>
</li>
</ul>
</div>
<div className="content"> <div className="content">
<div className="filter">
<div className="container-fluid">
<div className="row">
<div className="col-md-4">
<span style={{ color: '#ffffff', display: 'inline-block', width: 'auto' }}>Jaar: </span>
<select style={{ display: 'inline-block', width: 'auto' }} defaultValue={this.state.year} onChange={(event) => this.changeYear(event)}>
<option value="2020">2020</option>
<option value="2021">2021</option>
<option value="2022">2022</option>
</select>
</div>
<div className="col-md-4"></div>
<div className="col-md-4"></div>
</div>
</div>
</div>
<div className="books-stats"> <div className="books-stats">
<div className="container-fluid"> <div className="container-fluid">
<div className="row"> <div className="row">
<div className="col-md-4"> <div className="col-md-2">
<div className="stat-block"> <div className="stat-block">
<span>Boeken</span> <i class="fa fa-calendar"></i>
<span>17</span> <span className="stats-number">
<span>-20%</span> <select className="yearselector" defaultValue={this.state.year} onChange={(event) => this.changeYear(event)}>
{this.state.readingYears.map((year) => {
if(year === this.state.year){
var selected = 'selected'
}else{
selected = ''
}
return(<option selected={selected} value={year}>{year}</option>)
})}
</select>
</span>
</div> </div>
</div> </div>
<div className="col-md-4">
<div className="col-md-2">
<div className="stat-block"> <div className="stat-block">
<span>Bladzijdes</span> <i class="fa fa-book"></i>
<span>512</span> <span className="stats-number">{this.state.totalbooks}</span>
<span>+5%</span> <span className="stats-label">Boeken</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-book-open"></i>
<span className="stats-number">{this.state.totalpages}</span>
<span className="stats-label">Bladzijdes</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-pen"></i>
<span className="stats-number">{this.state.totalauthors}</span>
<span className="stats-label">Schrijvers</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-book"></i>
<span className="stats-number">{this.state.totalgenres}</span>
<span className="stats-label">Genres</span>
</div>
</div>
<div className="col-md-2">
<div className="stat-block">
<i class="fa fa-globe"></i>
<span className="stats-number">{this.state.totalcountries}</span>
<span className="stats-label">Landen</span>
</div> </div>
</div> </div>
</div> </div>
@ -334,6 +424,17 @@ export default class App extends Component {
</div> </div>
</div> </div>
</div> </div>
<div className="container-fluid">
<div className="row">
<div className="col-md-6">
<div className="books-per-country"><canvas id="countryChart"></canvas></div>
</div>
<div className="col-md-6">
</div>
</div>
</div>
</div> </div>
</React.Fragment> </React.Fragment>
) )

File diff suppressed because one or more lines are too long

View File

@ -36,30 +36,11 @@
padding: 20px 0; padding: 20px 0;
} }
.books-per-month, .genresPercent{ .books-per-month, .genresPercent, .books-per-country{
background: #1f2940; background: #1f2940;
padding:20px; padding:20px;
} }
.sidebar{
position: fixed;
background-color: #1f2940;
width: 255px;
height: 100%;
box-shadow: 0 0 20px rgb(0 0 0 / 10%);
/* overflow-y: scroll; */
z-index: 999998;
transition: width .25s;
-ms-overflow-style: none;
scrollbar-width: none;
padding-top: 56px;
}
.content{
width: 100%;
padding-left: 255px;
}
.sidebar .menu-item-label-name { .sidebar .menu-item-label-name {
text-align: center; text-align: center;
font-size: 15px; font-size: 15px;
@ -96,6 +77,58 @@
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;
} }
.books-stats{
margin:20px 0;
}
.books-stats .stat-block{
background: #1f2940;
padding: 10px 5px;
color:#ffffff;
text-align: center;
}
.books-stats .stat-block i{
font-weight: 900;
font-size: 25px;
border-radius: 50%;
padding: 20px;
width: 70px;
height: 70px;
line-height: 30px;
text-align: center;
background: #696ffc;
box-shadow: 7px 7px 10px rgba(20,27,45,0.5);
}
.books-stats .stat-block .stats-number{
font-weight: bold;
display: inline-block;
margin-left: 10px;
font-size: 25px;
margin-right: 10px;
}
.books-stats .stat-block .stats-label{
color: rgba(255,255,255,0.5);
font-weight: bold;
font-size: 25px;
}
.yearselector{
display: inline-block;
width: auto;
background: #1f2940;
border: none;
color: #fff;
font-weight: bold;
}
.container-fluid{
margin-bottom:20px !important;
}
</style> </style>
</head> </head>