From 2f30dec315ea4135d3f5b74f3c0065537b9736de Mon Sep 17 00:00:00 2001 From: Jordy van Zeeland Date: Tue, 4 Oct 2022 16:28:55 +0200 Subject: [PATCH] UI development --- ras/api/views.py | 3 +- ras/frontend/package-lock.json | 61 ++++++++++ ras/frontend/package.json | 4 + ras/frontend/src/App.js | 184 +++++++++++++++++++++--------- ras/frontend/static/js/main.js | 2 +- ras/frontend/templates/index.html | 43 ++++++- 6 files changed, 237 insertions(+), 60 deletions(-) diff --git a/ras/api/views.py b/ras/api/views.py index 4898d05..7a920a4 100644 --- a/ras/api/views.py +++ b/ras/api/views.py @@ -77,12 +77,13 @@ def books_per_country(request): data = [] df = filterData(getBooksData(), request.META.get('HTTP_YEAR')) - countries = df.groupby('country')['country'].count().reset_index(name="count") + countries = df.groupby(['country_code', 'country'])['country'].count().reset_index(name="count") countries = countries.sort_values(by='count', ascending=False) for index, row in countries.iterrows(): data.append({ + "code": row['country_code'], "country": row['country'], "count": int(row['count']) }) diff --git a/ras/frontend/package-lock.json b/ras/frontend/package-lock.json index dbd02c2..5856981 100644 --- a/ras/frontend/package-lock.json +++ b/ras/frontend/package-lock.json @@ -1896,6 +1896,40 @@ } } }, + "chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, + "chartjs-plugin-style": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-style/-/chartjs-plugin-style-0.5.0.tgz", + "integrity": "sha512-QWMWRGpkexgMTHZg2l1OVtnSkElGNAM0MchozG/ucU6NW0xXINS/YsIE2tl7nG1nwZA2FH/myVbI4uMguIAXPA==", + "requires": { + "chart.js": ">= 2.6.0 < 3" + } + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -2010,6 +2044,23 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, + "datatables.net": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.12.1.tgz", + "integrity": "sha512-e6XAMUoV41JdQPS/r9YRfRcmTPcCVvyZbWI+xog1Zg+kjVliMQbEkvWK5XFItmi64Cvwg+IqsZbTUJ1KSY3umA==", + "requires": { + "jquery": ">=1.7" + } + }, + "datatables.net-dt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/datatables.net-dt/-/datatables.net-dt-1.12.1.tgz", + "integrity": "sha512-HYsHbSYqOqlgsgjKMH/kkCB5455t51GmmtXRxgnDMRbjPLEIKo5CZmAlUe5mdD/RVPRtAUaj5K3SDlkEZ1bUmw==", + "requires": { + "datatables.net": ">=1.11.3", + "jquery": ">=1.7" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2340,6 +2391,11 @@ "supports-color": "^8.0.0" } }, + "jquery": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2448,6 +2504,11 @@ "mime-db": "1.52.0" } }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/ras/frontend/package.json b/ras/frontend/package.json index 8fbe68f..c52d42b 100644 --- a/ras/frontend/package.json +++ b/ras/frontend/package.json @@ -26,6 +26,10 @@ "@emotion/styled": "^11.10.4", "@material-ui/icons": "^4.11.3", "@mui/material": "^5.10.3", + "chartjs-plugin-style": "^0.5.0", + "datatables.net": "^1.12.1", + "datatables.net-dt": "^1.12.1", + "jquery": "^3.6.1", "react-router-dom": "^6.3.0" } } diff --git a/ras/frontend/src/App.js b/ras/frontend/src/App.js index 1a928d2..07182db 100644 --- a/ras/frontend/src/App.js +++ b/ras/frontend/src/App.js @@ -10,12 +10,50 @@ export default class App extends Component { totalpages: 0, totalauthors: 0, totalcountries: 0, - totalgenres: 0 + totalgenres: 0, + countries: [] } this.yearsArray = []; } + getGenres(){ + fetch('/api/books/genres', { + "method": "GET", + "headers": { + "year": this.state.year + } + }) + .then(response => response.json()) + .then(books => { + this.initChart(books, this.state.year); + }) + } + + getCountries(init){ + fetch('/api/books/countries', { + "method": "GET", + "headers": { + "year": this.state.year + } + }) + .then(response => response.json()) + .then(data => { + this.setState({ + countries: data + }) + + if(init == true){ + $('#DataTable').DataTable({ + paging: false, + ordering: false, + info: false, + searching: false + }) + } + }) + } + changeYear(event) { this.setState({ @@ -38,6 +76,34 @@ export default class App extends Component { totalgenres: data.totalgenres }) }) + + fetch('/api/books/countries', { + "method": "GET", + "headers": { + "year": this.state.year + } + }) + .then(response => response.json()) + .then(data => { + this.setState({ + countries: data + }) + + this.getCountries(false); + }) + + var $this = this; + + fetch('/api/books/genres/count', { + "method": "GET", + "headers": { + "year": this.state.year + } + }) + .then(response => response.json()) + .then(data => { + this.initDoughnut(data); + }) } initHorBar(data){ @@ -90,7 +156,7 @@ export default class App extends Component { { label: "Boeken", data: counts, - backgroundColor: '#696ffc' + backgroundColor: '#696ffc', }] } }); @@ -123,13 +189,25 @@ export default class App extends Component { backgroundColor: [ '#696ffc', '#7596fa', '#92adfe', '#abc0ff' ], - borderWidth: 3, + borderWidth: 0, borderColor: '#1f2940' }] }, options: { - //cutoutPercentage: 40, + cutout: '70%', responsive: true, + plugins: { + legend: { + labels: { + // This more specific font property overrides the global property + color: "white", + font: { + size: 14, + family: 'Source Sans Pro' + } + } + } + } } }); } @@ -218,6 +296,7 @@ export default class App extends Component { ticks: { beginAtZero: true, color: "white", + fontFamily: "Source Sans Pro", }, stacked: true, }, @@ -226,6 +305,7 @@ export default class App extends Component { beginAtZero: true, stepSize: 1, color: "white", + fontFamily: "Source Sans Pro", }, stacked: true } @@ -233,52 +313,23 @@ export default class App extends Component { plugins: { legend: { labels: { - color: "white" + color: "white", + font: { + size: 14, + family: 'Source Sans Pro' + } } } + }, + tooltips: { + bodyFont: 'Source Sans Pro' } } }); } componentDidUpdate() { - var $this = this; - - fetch('/api/books/genres', { - "method": "GET", - "headers": { - "year": this.state.year - } - }) - .then(response => response.json()) - .then(books => { - this.initChart(books, this.state.year); - }) - - fetch('/api/books/genres/count', { - "method": "GET", - "headers": { - "year": this.state.year - } - }) - .then(response => response.json()) - .then(data => { - this.initDoughnut(data); - }) - - fetch('/api/books/countries', { - "method": "GET", - "headers": { - "year": this.state.year - } - }) - .then(response => response.json()) - .then(data => { - this.initHorBar(data); - }) - - - + this.getGenres(); } componentDidMount() { @@ -309,16 +360,7 @@ export default class App extends Component { this.initDoughnut(data); }) - fetch('/api/books/countries', { - "method": "GET", - "headers": { - "year": this.state.year - } - }) - .then(response => response.json()) - .then(data => { - this.initHorBar(data); - }) + this.getCountries(true); fetch('/api/books/stats', { "method": "GET", @@ -346,9 +388,11 @@ export default class App extends Component { readingYears: data }) }) + } render() { + console.log(this.state); return (
@@ -417,18 +461,46 @@ export default class App extends Component {
-
+
Boeken per maand per genre
-
+
Genres
-
-
+
+ {/*
*/} +
+ Landen + + + + + + + + + + {this.state.countries.map((country, i) => { + + var code = country.code.toLowerCase(); + return( + + + + + + + + ) + + })} + +
#LandBoeken
{i+1} {country.country}{country.count}
+
diff --git a/ras/frontend/static/js/main.js b/ras/frontend/static/js/main.js index ff44f6e..2ec4410 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\nclass App extends react__WEBPACK_IMPORTED_MODULE_0__.Component {\n constructor(props) {\n super(props);\n this.state = {\n year: new Date().getFullYear(),\n readingYears: [],\n totalbooks: 0,\n totalpages: 0,\n totalauthors: 0,\n totalcountries: 0,\n totalgenres: 0\n };\n this.yearsArray = [];\n }\n\n changeYear(event) {\n this.setState({\n year: event.target.value\n });\n fetch(\'/api/books/stats\', {\n "method": "GET",\n "headers": {\n "year": event.target.value\n }\n }).then(response => response.json()).then(data => {\n this.setState({\n totalbooks: data.totalbooks,\n totalpages: data.totalpages,\n totalauthors: data.totalauthors,\n totalcountries: data.totalcountries,\n totalgenres: data.totalgenres\n });\n });\n }\n\n initHorBar(data) {\n var countries = [];\n var counts = [];\n data.forEach(count => {\n if (!countries.includes(count.country)) {\n countries.push(count.country);\n }\n\n counts.push(count.count);\n });\n $("canvas#countryChart").remove();\n $("div.books-per-country").append(\'\');\n var ctx = document.getElementById("countryChart");\n new Chart(ctx, {\n type: \'bar\',\n options: {\n indexAxis: \'y\',\n plugins: {\n legend: {\n display: false\n }\n },\n scales: {\n x: {\n ticks: {\n beginAtZero: true,\n color: "white"\n },\n stacked: true\n },\n y: {\n ticks: {\n beginAtZero: true,\n stepSize: 1,\n color: "white"\n },\n stacked: true\n }\n }\n },\n data: {\n labels: countries,\n datasets: [{\n label: "Boeken",\n data: counts,\n backgroundColor: \'#696ffc\'\n }]\n }\n });\n }\n\n initDoughnut(data) {\n var labels = [];\n var counts = [];\n data.forEach(count => {\n if (!labels.includes(count.genre)) {\n labels.push(count.genre);\n }\n\n counts.push(count.count);\n });\n $("canvas#chartGenres").remove();\n $("div.genresPercent").append(\'\');\n var ctx = document.getElementById("chartGenres");\n var myChart = new Chart(ctx, {\n type: \'doughnut\',\n data: {\n labels: labels,\n datasets: [{\n label: \'# of Tomatoes\',\n data: counts,\n backgroundColor: [\'#696ffc\', \'#7596fa\', \'#92adfe\', \'#abc0ff\'],\n borderWidth: 3,\n borderColor: \'#1f2940\'\n }]\n },\n options: {\n //cutoutPercentage: 40,\n responsive: true\n }\n });\n }\n\n initChart(data, year) {\n /*\r\n ----------------------------------\r\n Books per month per genre\r\n ----------------------------------\r\n */\n var genres = [];\n var colors = [\'#696ffc\', \'#7596fa\', \'#92adfe\', \'#abc0ff\'];\n var dataSet = [];\n data.forEach(book => {\n if (!genres.includes(book.genre)) {\n genres.push(book.genre);\n }\n });\n\n if (genres && genres.length > 0) {\n genres.forEach((genre, index) => {\n var genreData = [];\n\n for (var i = 0; i < 12; i++) {\n genreData[i] = 0;\n\n if (i + 1 < 10) {\n var month = "0" + (i + 1);\n } else {\n month = i + 1;\n }\n\n for (var j = 0; j < data.length; j++) {\n if (data && data[j] && data[j].readed == month + \'-\' + year) {\n if (data[j].genre == genre) {\n genreData[i] = data[j].count;\n }\n }\n }\n }\n\n dataSet.push({\n label: genre,\n data: genreData,\n backgroundColor: colors[index],\n order: 2\n });\n });\n }\n /*\r\n ----------------------------------\r\n Stacked bar chart\r\n ----------------------------------\r\n */\n\n\n $("canvas#chart").remove();\n $("div.books-per-month").append(\'\');\n new Chart(document.getElementById(\'chart\'), {\n type: \'bar\',\n data: {\n labels: ["Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"],\n datasets: dataSet\n },\n options: {\n responsive: true,\n showTooltips: true,\n legend: {\n display: true\n },\n interaction: {\n mode: \'index\'\n },\n scales: {\n x: {\n ticks: {\n beginAtZero: true,\n color: "white"\n },\n stacked: true\n },\n y: {\n ticks: {\n beginAtZero: true,\n stepSize: 1,\n color: "white"\n },\n stacked: true\n }\n },\n plugins: {\n legend: {\n labels: {\n color: "white"\n }\n }\n }\n }\n });\n }\n\n componentDidUpdate() {\n var $this = this;\n fetch(\'/api/books/genres\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(books => {\n this.initChart(books, this.state.year);\n });\n fetch(\'/api/books/genres/count\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(data => {\n this.initDoughnut(data);\n });\n fetch(\'/api/books/countries\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(data => {\n this.initHorBar(data);\n });\n }\n\n componentDidMount() {\n var $this = this;\n var currentyear = this.state.year ? this.state.year : new Date().getFullYear();\n fetch(\'/api/books/genres\', {\n "method": "GET",\n "headers": {\n "year": currentyear\n }\n }).then(response => response.json()).then(books => {\n this.initChart(books, currentyear);\n });\n fetch(\'/api/books/genres/count\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(data => {\n this.initDoughnut(data);\n });\n fetch(\'/api/books/countries\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(data => {\n this.initHorBar(data);\n });\n fetch(\'/api/books/stats\', {\n "method": "GET",\n "headers": {\n "year": this.state.year\n }\n }).then(response => response.json()).then(data => {\n $this.setState({\n totalbooks: data.totalbooks,\n totalpages: data.totalpages,\n totalauthors: data.totalauthors,\n totalcountries: data.totalcountries,\n totalgenres: data.totalgenres\n });\n });\n fetch(\'/api/books/years\', {\n "method": "GET"\n }).then(response => response.json()).then(data => {\n this.setState({\n readingYears: data\n });\n });\n }\n\n render() {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "content"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "books-stats"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "container-fluid"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "row"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n class: "fa fa-calendar"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", {\n className: "yearselector",\n defaultValue: this.state.year,\n onChange: event => this.changeYear(event)\n }, this.state.readingYears.map(year => {\n if (year === this.state.year) {\n var selected = \'selected\';\n } else {\n selected = \'\';\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", {\n selected: selected,\n value: year\n }, year);\n }))))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n class: "fa fa-book"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalbooks), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Boeken"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n class: "fa fa-book-open"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalpages), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Bladzijdes"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n class: "fa fa-pen"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalauthors), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Schrijvers"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n class: "fa fa-book"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalgenres), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Genres"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-2"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "stat-block"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {\n class: "fa fa-globe"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-number"\n }, this.state.totalcountries), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {\n className: "stats-label"\n }, "Landen")))))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "container-fluid"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "row"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-8"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "books-per-month"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {\n id: "chart"\n }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-4"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "genresPercent"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {\n id: "chartGenres"\n }))))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "container-fluid"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "row"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-6"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "books-per-country"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {\n id: "countryChart"\n }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {\n className: "col-md-6"\n })))));\n }\n\n}\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 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/* harmony import */ var _App__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./App */ "./src/App.js");\n\n\n\nreact_dom__WEBPACK_IMPORTED_MODULE_1__.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_App__WEBPACK_IMPORTED_MODULE_2__["default"], null), document.getElementById(\'app\'));\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