diff --git a/ras/api/static/books2.csv b/ras/api/static/books2.csv new file mode 100644 index 0000000..0c51108 --- /dev/null +++ b/ras/api/static/books2.csv @@ -0,0 +1,65 @@ +book;author;genre;pages;readed;rating +Omnia in Omnibus;Lisa Pennings;Sciencefiction;166;05-2013;4 +Het leven van Pi;Yann Martel;Roman;317;10-2020;4 +Ze komt nooit meer terug;Hans Koppel;Thriller;220;10-2020;5 +Vriendschapsverzoek;Laura Marshall;Thriller;368;10-2020;4 +De analist;John Katzenbach;Thriller;518;11-2020;4 +Komt een vrouw bij de hacker;Maria Genova;Thriller;224;11-2020;4 +Niemand hoort het;Linwood Barclay;Thriller;304;11-2020;5 +Lift;Linwood Barclay;Thriller;480;11-2020;3 +Kijk niet weg;Linwood Barclay;Thriller;400;12-2020;5 +Het duistere net;Carmen Mola;Thriller;415;12-2020;5 +De 50/50-moorden;Steve Mosby;Thriller;344;12-2020;3 +Zonder een woord;Linwood Barclay;Thriller;368;01-2021;5 +Geen veilige plek;Linwood Barclay;Thriller;368;01-2021;3 +Op slot;Chris McGeorge;Thriller;320;01-2021;5 +Alles is eventueel / 1408;Stephen King;Thriller;462;02-2021;3 +Waarom je niet zomaar moet stemmen waar je ouders op stemmen;Titia Hoogendoorn&Nienke Schuitemaker;Non-fictie;125;02-2021;4 +Geloof je ogen;Linwood Barclay;Thriller;384;02-2021;4 +Voor ik ga slapen;SJ Watson;Thriller;333;03-2021;5 +Echo;Tamara Geraeds;Thriller;280;03-2021;5 +Niemand vertellen;Harlan Coben;Thriller;302;03-2021;4 +Ik zie je;Michael Berg;Thriller;320;04-2021;4 +Toevluchtsoord;Jerome Loubry;Thriller;355;04-2021;5 +In een donker donker bos;Ruth Ware;Thriller;320;04-2021;3 +Een voor een;Ruth Ware;Thriller;368;04-2021;4 +Vrees het ergste;Linwood Barclay;Thriller;400;05-2021;4 +Het poppenhuis;W.R. Dantz;Thriller;270;05-2021;3 +Malorie;Josh Malerman;Thriller;320;05-2021;4 +Opgejaagd;Gabriel Bergmoser;Thriller;256;06-2021;4 +Reset;Blake Crouch;Thriller;352;06-2021;4 +Wacht maar af;Loes den Hollander;Thriller;300;06-2021;4 +De Russische connectie;Gerrit Barendrecht;Thriller;320;06-2021;4 +Wat jij niet ziet;M.J. Arlidge;Thriller;95;07-2021;3 +Het ongeluk;Linwood Barclay;Thriller;382;07-2021;4 +Slapen in een zee van sterren;Christopher Paolini;Sciencefiction;880;07-2021;2 +Tik Tak;Chris McGeorge;Thriller;320;07-2021;4 +Gegijzeld;Clare Mackintosh;Thriller;397;08-2021;5 +Eindbestemming;Marjolein van der Gaag&Marcella Kleine;Thriller;158;08-2021;4 +Een kille rilling;Bernard Minier;Thriller;582;09-2021;4 +De nachtdienst;Esther Verhoef;Thriller;365;09-2021;5 +Kiekeboe;Chris McGeorge;Thriller;352;10-2021;3 +Noorderlicht;Mariska Overman;Thriller;313;10-2021;4 +De kleine getuige;Jilliane Hoffman;Thriller;368;11-2021;4 +De marathon;Stephen King;Thriller;261;11-2021;4 +Het appartement;Tatiana de Rosnay;Roman;215;11-2021;4 +De intrigant;Patricia Snel;Thriller;189;12-2021;4 +Een schuldig huis;Robert Goddard;Thriller;384;12-2021;5 +Te koop;Eva Monte;Thriller;350;12-2021;3 +Prison Break S1B1;Paul T. Scheuring;Thriller;221;01-2022;5 +Prison Break S1B2;Ed van Eeden;Thriller;272;01-2022;4 +Prison Break S1B3;Paul T. Scheuring;Thriller;285;01-2022;4 +Herfstlied;Simone van der Vlugt;Thriller;277;02-2022;5 +De gastenlijst;Lucy Foley;Thriller;352;02-2022;2 +Anomalie;Herve le Tellier;Thriller;302;02-2022;2 +Prison Break S2B1;Paul T. Scheuring;Thriller;207;03-2022;4 +Prison Break S2B2;Paul T. Scheuring;Thriller;205;03-2022;4 +Prison Break S2B3;Paul T. Scheuring;Thriller;245;03-2022;4 +Ik weet je wachtwoord;Daniel Verlaan;Non-fictie;347;04-2022;5 +De vrouw die een jaar in bed ging liggen;Sue Townsend;Roman;368;04-2022;3 +Moord in de bibliotheek;Agatha Christie;Thriller;256;04-2022;3 +Geestdrift;Daniel Hecht;Thriller;509;05-2022;1 +Moord in de Orient Expres;Agatha Christie;Thriller;222;05-2022;4 +Crash;Jack Bowman;Thriller;344;05-2022;3 +Erken mij;Esther Verhoef;Thriller;91;06-2022;4 +Gestrand;Sarah Goodwin;Thriller;352;06-2022;5 diff --git a/ras/api/urls.py b/ras/api/urls.py index f948546..6982d8e 100644 --- a/ras/api/urls.py +++ b/ras/api/urls.py @@ -3,5 +3,5 @@ from .views import * urlpatterns = [ path('books', api_all_books), - path('books/', api_get_book), + path('books/genres', books_per_genre_per_month) ] \ No newline at end of file diff --git a/ras/api/views.py b/ras/api/views.py index 1c03a09..3cfee69 100644 --- a/ras/api/views.py +++ b/ras/api/views.py @@ -1,26 +1,55 @@ from rest_framework.decorators import api_view from rest_framework.response import Response from .models import Books +import pandas as pd from .serializers import BooksSerializer from django.db.models import Q - +from django.templatetags.static import static +import json @api_view(['GET']) def api_all_books(request): - all_books = Books.objects.all() - if all_books: - serializer = BooksSerializer(all_books, many=True) - return Response(serializer.data) - else: - return Response({"Message": 'Books Not Found'}) + df = pd.read_csv("api/static/books2.csv", encoding = "utf-8") + books = [] + for book in df['Books']: + info = book.split(';') + books.append({ + "name": info[0], + "author": info[1], + "genre": info[2], + "pages": info[3], + "readed": info[4], + "rating": info[5] + }) + return Response(books) @api_view(['GET']) -def api_get_book(request, _id): +def books_per_genre_per_month(request): - obj = Books.objects.filter(id = _id)[0] - if obj: - serializer = BooksSerializer(obj) - return Response(serializer.data) + datayear = request.META.get('HTTP_YEAR') + + if datayear: + + data = [] + + # Get CSV file with book data + df = pd.read_csv("api/static/books2.csv", encoding = "utf-8", header = 0, sep=';') + + # Filter data on year + df = df.where(df['readed'].str.contains(datayear)) + + # Filter array on genre and date + booksPerMonth = df.groupby(['genre','readed'])['genre'].count().reset_index(name="count") + booksPerMonth = booksPerMonth.sort_values(by='readed') + + for index, row in booksPerMonth.iterrows(): + data.append({ + "genre": row['genre'], + "readed": row['readed'], + "count": row['count'] + }) + + return Response(data) else: - return Response({"Message": 'Book Not Found'}) \ No newline at end of file + return Response("No year header included") \ No newline at end of file diff --git a/ras/frontend/src/App.js b/ras/frontend/src/App.js index 5779a29..22b4c0f 100644 --- a/ras/frontend/src/App.js +++ b/ras/frontend/src/App.js @@ -1,15 +1,119 @@ -import React, { Component } from "react" -import {render} from "react-dom" +import React, { Component } from "react"; -export default class App extends Component{ +export default class App extends Component { constructor(props) { super(props); } - render(){ - return

Soon here comes the Reading Analytics System!

- } -} + initChart(data, year) { -const appDiv = document.getElementById("app"); -render(, appDiv); \ No newline at end of file + var genres = []; + + var colors = [ + '#d0b2cf', '#b66f2b', '#003C72', '#ecdb0e' + ] + + var dataSet = []; + + data.forEach(book => { + if (!genres.includes(book.genre)) { + genres.push(book.genre) + } + }); + + if (genres && genres.length > 0) { + genres.forEach((genre, index) => { + var genreData = []; + + for (var i = 0; i < 12; i++) { + + genreData[i] = 0; + + if ((i + 1) < 10) { + var month = "0" + (i + 1); + } else { + month = (i + 1); + } + + for (var j = 0; j < data.length; j++) { + if (data && data[j] && data[j].readed == month + '-' + year) { + if (data[j].genre == genre) { + genreData[i] = data[j].count; + } + + } + } + } + + dataSet.push({ + label: genre, + data: genreData, + backgroundColor: colors[index] + }) + }) + } + + new Chart(document.getElementById('chart'), { + type: 'bar', + data: { + labels: ["Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"], + datasets: dataSet + }, + options: { + legend: { + display: true, + }, + tooltips: { + mode: 'index', + intersect: true, + axis: 'y' + }, + scales: { + x: { + ticks: { + beginAtZero: true, + + }, + stacked: true, + }, + y: { + ticks: { + beginAtZero: true, + stepSize: 1 + }, + stacked: true + } + } + } + }); + } + + componentDidMount() { + + var currentyear = new Date().getFullYear() + + console.log(currentyear); + + fetch('/api/books/genres', { + "method": "GET", + "headers": { + "year": currentyear + } + }) + .then(response => response.json()) + .then(data => { + this.initChart(data, currentyear); + console.log(data); + }) + } + + render() { + return ( + + +

Soon here comes the Reading Analytics System! test

+
+ + ) + } +} \ No newline at end of file diff --git a/ras/frontend/src/index.js b/ras/frontend/src/index.js index 43a4f0e..dbf0b20 100644 --- a/ras/frontend/src/index.js +++ b/ras/frontend/src/index.js @@ -1 +1,5 @@ -import App from "./App"; \ No newline at end of file +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from "./App"; + +ReactDOM.render(, document.getElementById('app')); \ No newline at end of file diff --git a/ras/frontend/static/js/main.js b/ras/frontend/static/js/main.js index 60ef133..0d564f8 100644 --- a/ras/frontend/static/js/main.js +++ b/ras/frontend/static/js/main.js @@ -1,2 +1,2 @@ /*! For license information please see main.js.LICENSE.txt */ -(()=>{"use strict";var __webpack_modules__={"./src/App.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => (/* binding */ App)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js");\n\n\nclass App extends react__WEBPACK_IMPORTED_MODULE_0__.Component {\n constructor(props) {\n super(props);\n }\n\n render() {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("p", null, "Soon here comes the Reading Analytics System!");\n }\n\n}\nconst appDiv = document.getElementById("app");\n(0,react_dom__WEBPACK_IMPORTED_MODULE_1__.render)( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(App, null), appDiv);\n\n//# sourceURL=webpack://frontend/./src/App.js?')},"./src/index.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{eval('__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _App__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./App */ "./src/App.js");\n\n\n//# sourceURL=webpack://frontend/./src/index.js?')},"./node_modules/react-dom/cjs/react-dom.development.js":(__unused_webpack_module,exports,__webpack_require__)=>{eval("/**\n * @license React\n * react-dom.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nif (true) {\n (function() {\n\n 'use strict';\n\n/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */\nif (\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart ===\n 'function'\n) {\n __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());\n}\n var React = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\nvar Scheduler = __webpack_require__(/*! scheduler */ \"./node_modules/scheduler/index.js\");\n\nvar ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n\nvar suppressWarning = false;\nfunction setSuppressWarning(newSuppressWarning) {\n {\n suppressWarning = newSuppressWarning;\n }\n} // In DEV, calls to console.warn and console.error get replaced\n// by calls to these methods by a Babel plugin.\n//\n// In PROD (or in packages without access to React internals),\n// they are left as they are instead.\n\nfunction warn(format) {\n {\n if (!suppressWarning) {\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n printWarning('warn', format, args);\n }\n }\n}\nfunction error(format) {\n {\n if (!suppressWarning) {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n\n printWarning('error', format, args);\n }\n }\n}\n\nfunction printWarning(level, format, args) {\n // When changing this logic, you might want to also\n // update consoleWithStackDev.www.js as well.\n {\n var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;\n var stack = ReactDebugCurrentFrame.getStackAddendum();\n\n if (stack !== '') {\n format += '%s';\n args = args.concat([stack]);\n } // eslint-disable-next-line react-internal/safe-string-coercion\n\n\n var argsWithFormat = args.map(function (item) {\n return String(item);\n }); // Careful: RN currently depends on this prefix\n\n argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it\n // breaks IE9: https://github.com/facebook/react/issues/13610\n // eslint-disable-next-line react-internal/no-production-logging\n\n Function.prototype.apply.call(console[level], console, argsWithFormat);\n }\n}\n\nvar FunctionComponent = 0;\nvar ClassComponent = 1;\nvar IndeterminateComponent = 2; // Before we know whether it is function or class\n\nvar HostRoot = 3; // Root of a host tree. Could be nested inside another node.\n\nvar HostPortal = 4; // A subtree. Could be an entry point to a different renderer.\n\nvar HostComponent = 5;\nvar HostText = 6;\nvar Fragment = 7;\nvar Mode = 8;\nvar ContextConsumer = 9;\nvar ContextProvider = 10;\nvar ForwardRef = 11;\nvar Profiler = 12;\nvar SuspenseComponent = 13;\nvar MemoComponent = 14;\nvar SimpleMemoComponent = 15;\nvar LazyComponent = 16;\nvar IncompleteClassComponent = 17;\nvar DehydratedFragment = 18;\nvar SuspenseListComponent = 19;\nvar ScopeComponent = 21;\nvar OffscreenComponent = 22;\nvar LegacyHiddenComponent = 23;\nvar CacheComponent = 24;\nvar TracingMarkerComponent = 25;\n\n// -----------------------------------------------------------------------------\n\nvar enableClientRenderFallbackOnTextMismatch = true; // TODO: Need to review this code one more time before landing\n// the react-reconciler package.\n\nvar enableNewReconciler = false; // Support legacy Primer support on internal FB www\n\nvar enableLazyContextPropagation = false; // FB-only usage. The new API has different semantics.\n\nvar enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber\n\nvar enableSuspenseAvoidThisFallback = false; // Enables unstable_avoidThisFallback feature in Fizz\n// React DOM Chopping Block\n//\n// Similar to main Chopping Block but only flags related to React DOM. These are\n// grouped because we will likely batch all of them into a single major release.\n// -----------------------------------------------------------------------------\n// Disable support for comment nodes as React DOM containers. Already disabled\n// in open source, but www codebase still relies on it. Need to remove.\n\nvar disableCommentsAsDOMContainers = true; // Disable javascript: URL strings in href for XSS protection.\n// and client rendering, mostly to allow JSX attributes to apply to the custom\n// element's object properties instead of only HTML attributes.\n// https://github.com/facebook/react/issues/11347\n\nvar enableCustomElementPropertySupport = false; // Disables children for