Recharts Implementation

Removed nvio and added recharts to fix graph falling below 0

Other Fixes
This commit is contained in:
Thegan Govender
2023-04-14 13:37:08 +02:00
parent 2c4d305e2f
commit 0a948f8eda
9 changed files with 449 additions and 331 deletions

View File

@@ -280,6 +280,7 @@ router.post("/getLibraryLastPlayed", async (req, res) => {
}
});
router.post("/getViewsOverTime", async (req, res) => {
try {
const { days } = req.body;
@@ -287,32 +288,36 @@ router.post("/getViewsOverTime", async (req, res) => {
if (days=== undefined) {
_days = 30;
}
const { rows } = await db.query(
const { rows:stats } = await db.query(
`select * from fs_watch_stats_over_time('${_days}')`
);
const { rows:libraries } = await db.query(
`select distinct "Id","Name" from jf_libraries`
);
const reorganizedData = {};
rows.forEach((item) => {
const id = item.Library;
stats.forEach((item) => {
const library = item.Library;
const count = item.Count;
const date = new Date(item.Date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
month: 'short',
day: '2-digit'
});
if (!reorganizedData[id]) {
reorganizedData[id] = {
id,
data: []
if (!reorganizedData[date]) {
reorganizedData[date] = {
Key:date
};
}
reorganizedData[id].data.push({ x: date, y: count });
reorganizedData[date]= { ...reorganizedData[date], [library]: count};
});
const finalData = Object.values(reorganizedData);
const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
res.send(finalData);
} catch (error) {
console.log(error);
@@ -327,29 +332,32 @@ router.post("/getViewsByDays", async (req, res) => {
if (days=== undefined) {
_days = 30;
}
const { rows } = await db.query(
const { rows:stats } = await db.query(
`select * from fs_watch_stats_popular_days_of_week('${_days}')`
);
const { rows:libraries } = await db.query(
`select distinct "Id","Name" from jf_libraries`
);
const reorganizedData = {};
rows.forEach((item) => {
const id = item.Library;
stats.forEach((item) => {
const library = item.Library;
const count = item.Count;
const day = item.Day;
if (!reorganizedData[id]) {
reorganizedData[id] = {
id,
data: []
if (!reorganizedData[day]) {
reorganizedData[day] = {
Key:day
};
}
reorganizedData[id].data.push({ x: day, y: count });
reorganizedData[day]= { ...reorganizedData[day], [library]: count};
});
const finalData = Object.values(reorganizedData);
const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
res.send(finalData);
} catch (error) {
console.log(error);
@@ -365,29 +373,32 @@ router.post("/getViewsByHour", async (req, res) => {
if (days=== undefined) {
_days = 30;
}
const { rows } = await db.query(
const { rows:stats } = await db.query(
`select * from fs_watch_stats_popular_hour_of_day('${_days}')`
);
const { rows:libraries } = await db.query(
`select distinct "Id","Name" from jf_libraries`
);
const reorganizedData = {};
rows.forEach((item) => {
const id = item.Library;
stats.forEach((item) => {
const library = item.Library;
const count = item.Count;
const hour = item.Hour;
if (!reorganizedData[id]) {
reorganizedData[id] = {
id,
data: []
if (!reorganizedData[hour]) {
reorganizedData[hour] = {
Key:hour
};
}
reorganizedData[id].data.push({ x: hour, y: count });
reorganizedData[hour]= { ...reorganizedData[hour], [library]: count};
});
const finalData = Object.values(reorganizedData);
const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
res.send(finalData);
} catch (error) {
console.log(error);
@@ -397,4 +408,5 @@ const finalData = Object.values(reorganizedData);
module.exports = router;

274
package-lock.json generated
View File

@@ -37,6 +37,7 @@
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1",
"recharts": "^2.5.0",
"remixicon-react": "^1.0.0",
"sequelize": "^6.29.0",
"web-vitals": "^2.1.4",
@@ -4991,6 +4992,60 @@
"@types/node": "*"
}
},
"node_modules/@types/d3-array": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.4.tgz",
"integrity": "sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ=="
},
"node_modules/@types/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz",
"integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz",
"integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz",
"integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz",
"integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg=="
},
"node_modules/@types/d3-timer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz",
"integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g=="
},
"node_modules/@types/debug": {
"version": "4.1.7",
"license": "MIT",
@@ -7538,6 +7593,11 @@
"node": ">=0.10.0"
}
},
"node_modules/css-unit-converter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
},
"node_modules/css-what": {
"version": "6.1.0",
"license": "BSD-2-Clause",
@@ -7737,6 +7797,14 @@
"delaunator": "4"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
@@ -7824,6 +7892,14 @@
"d3-time": "1 - 2"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"license": "BSD-2-Clause"
@@ -7874,6 +7950,11 @@
"version": "10.4.3",
"license": "MIT"
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/dedent": {
"version": "0.7.0",
"license": "MIT"
@@ -9298,6 +9379,11 @@
"version": "3.1.3",
"license": "MIT"
},
"node_modules/fast-equals": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz",
"integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg=="
},
"node_modules/fast-glob": {
"version": "3.2.12",
"license": "MIT",
@@ -16201,6 +16287,18 @@
"node": ">=0.10.0"
}
},
"node_modules/react-resize-detector": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz",
"integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==",
"dependencies": {
"lodash": "^4.17.21"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-router": {
"version": "6.8.1",
"license": "MIT",
@@ -16300,6 +16398,43 @@
}
}
},
"node_modules/react-smooth": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz",
"integrity": "sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==",
"dependencies": {
"fast-equals": "^4.0.3",
"react-transition-group": "2.9.0"
},
"peerDependencies": {
"prop-types": "^15.6.0",
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-smooth/node_modules/dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"dependencies": {
"@babel/runtime": "^7.1.2"
}
},
"node_modules/react-smooth/node_modules/react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"dependencies": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0",
"react-dom": ">=15.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"license": "BSD-3-Clause",
@@ -16343,6 +16478,43 @@
"node": ">=8.10.0"
}
},
"node_modules/recharts": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.5.0.tgz",
"integrity": "sha512-0EQYz3iA18r1Uq8VqGZ4dABW52AKBnio37kJgnztIqprELJXpOEsa0SzkqU1vjAhpCXCv52Dx1hiL9119xsqsQ==",
"dependencies": {
"classnames": "^2.2.5",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.19",
"react-is": "^16.10.2",
"react-resize-detector": "^8.0.4",
"react-smooth": "^2.0.2",
"recharts-scale": "^0.4.4",
"reduce-css-calc": "^2.1.8",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"prop-types": "^15.6.0",
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/recharts/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
@@ -16375,6 +16547,20 @@
"node": ">=8"
}
},
"node_modules/reduce-css-calc": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
"integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
"dependencies": {
"css-unit-converter": "^1.1.1",
"postcss-value-parser": "^3.3.0"
}
},
"node_modules/reduce-css-calc/node_modules/postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
},
"node_modules/regenerate": {
"version": "1.4.2",
"license": "MIT"
@@ -18183,6 +18369,94 @@
"node": ">= 0.8"
}
},
"node_modules/victory-vendor": {
"version": "36.6.8",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.8.tgz",
"integrity": "sha512-H3kyQ+2zgjMPvbPqAl7Vwm2FD5dU7/4bCTQakFQnpIsfDljeOMDojRsrmJfwh4oAlNnWhpAf+mbAoLh8u7dwyQ==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/victory-vendor/node_modules/d3-array": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz",
"integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/w3c-hr-time": {
"version": "1.0.2",
"license": "MIT",

View File

@@ -32,6 +32,7 @@
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1",
"recharts": "^2.5.0",
"remixicon-react": "^1.0.0",
"sequelize": "^6.29.0",
"web-vitals": "^2.1.4",

View File

@@ -41,7 +41,7 @@ function Sessions() {
const intervalId = setInterval(fetchData, 1000);
return () => clearInterval(intervalId);
}, [base_url]);
}, [data,base_url]);
if (!data) {
return <Loading />;

View File

@@ -0,0 +1,86 @@
import React from 'react';
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, Legend } from 'recharts';
function Chart({ stats, libraries }) {
const wordToColor = (word) => {
let number = 0;
for (let i = 0; i < word.length; i++) {
const charCode = word.charCodeAt(i);
const letterValue = charCode - 65;
number += letterValue;
}
let hash = number;
const str = number.toString();
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash) + str.charCodeAt(i);
}
// Map the hash to an RGB color, with each channel restricted to a certain range
const red = (hash % 256) * 4;
const green = ((hash >> 8) % 256)*2;
const blue = ((hash >> 16) % 256);
// Return an RGB color string
return `rgb(${red}, ${green}, ${blue})`;
};
const CustomTooltip = ({ payload, label, active }) => {
if (active) {
return (
<div style={{ backgroundColor: "rgba(0,0,0,0.8)", color: "white" }} className="p-2 rounded-2 border-0">
<p className='text-center fs-5'>{label}</p>
{libraries.map((library, index) => (
<p key={library.Id} style={{ color: `${wordToColor(library.Name)}` }}>{`${library.Name} : ${payload[index].value} Views`}</p>
))}
</div>
);
}
return null;
};
const getMaxValue = () => {
let max = 0;
if(stats)
{
stats.forEach(datum => {
Object.keys(datum).forEach(key => {
if (key !== "Key") {
max = Math.max(max, parseInt(datum[key]));
}
});
});
}
return max;
}
const max = getMaxValue()+10;
return (
<ResponsiveContainer width="100%">
<AreaChart data={stats} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<defs>
{libraries.map((library) => (
<linearGradient key={library.Id} id={library.Id} x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={wordToColor(library.Name)} stopOpacity={0.8}/>
<stop offset="95%" stopColor={wordToColor(library.Name)} stopOpacity={0}/>
</linearGradient>
))}
</defs>
<XAxis dataKey="Key" interval={0} angle={-60} textAnchor="end" height={100}/>
<YAxis domain={[0, max]} />
<Tooltip content={<CustomTooltip />} />
<Legend verticalAlign="bottom" />
{libraries.map((library) => (
<Area key={library.Id} type="monotone" dataKey={library.Name} stroke={wordToColor(library.Name)} fillOpacity={1} fill={"url(#" + library.Id + ")"} />
))}
</AreaChart>
</ResponsiveContainer>
);
}
export default Chart;

View File

@@ -1,14 +1,20 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { ResponsiveLine } from "@nivo/line";
import Chart from "./chart";
import "../../css/stats.css";
function DailyPlayStats(props) {
const [data, setData] = useState();
const [days, setDays] = useState(60);
const token = localStorage.getItem("token");
const [stats, setStats] = useState();
const [libraries, setLibraries] = useState();
const [days, setDays] = useState(15);
const token = localStorage.getItem("token");
useEffect(() => {
const fetchLibraries = () => {
const url = `/stats/getViewsOverTime`;
@@ -25,14 +31,15 @@ function DailyPlayStats(props) {
}
)
.then((data) => {
setData(data.data);
setStats(data.data.stats);
setLibraries(data.data.libraries);
})
.catch((error) => {
console.log(error);
});
};
if (!data) {
if (!stats) {
fetchLibraries();
}
if (days !== props.days) {
@@ -42,13 +49,13 @@ function DailyPlayStats(props) {
const intervalId = setInterval(fetchLibraries, 60000 * 5);
return () => clearInterval(intervalId);
}, [data, days, props.days, token]);
}, [stats,libraries, days, props.days, token]);
if (!data) {
if (!stats) {
return <></>;
}
if (data.length === 0) {
if (stats.length === 0) {
return (
<div className="main-widget">
<h1>Daily Play Count Per Library - {days} Days</h1>
@@ -57,101 +64,12 @@ function DailyPlayStats(props) {
</div>
);
}
return (
<div className="main-widget">
<h1>Daily Play Count Per Library - Last {days} Days</h1>
<div className="graph">
<ResponsiveLine
data={data}
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: 0,
max: "auto",
stacked: false,
reverse: false,
}}
enableGridX={false}
enableSlices={"x"}
yFormat=" >-.0f"
curve="cardinal"
enableArea={true}
theme={{
axis: {
ticks: {
line: {
stroke: "white",
},
text: {
fill: "white",
},
},
},
grid: {
line: {
stroke: "rgba(255,255,255,0.2)",
strokeWidth: 2,
strokeDasharray: "1 40",
},
},
}}
axisBottom={{
orient: "bottom",
tickSize: 5,
tickPadding: 10,
tickRotation: -45,
legend: "Days",
legendOffset: 36,
legendPosition: "middle",
}}
axisLeft={{
orient: "left",
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: "Plays",
legendOffset: -40,
legendPosition: "middle",
itemTextColor: "#fff",
theme: "white",
}}
colors={{ scheme: "category10" }}
pointSize={10}
pointColor={{ theme: "background" }}
pointBorderWidth={2}
pointBorderColor={{ from: "serieColor" }}
pointLabelYOffset={-12}
legends={[
{
itemTextColor: "#fff",
anchor: "bottom",
direction: "row",
justify: false,
translateX: 0,
translateY: 100,
itemsSpacing: 0,
itemDirection: "left-to-right",
itemWidth: 100,
itemHeight: 20,
itemOpacity: 0.75,
symbolSize: 12,
symbolShape: "circle",
symbolBorderColor: "rgba(0, 0, 0, .5)",
effects: [
{
on: "hover",
style: {
itemBackground: "rgba(0, 0, 0, .03)",
itemOpacity: 1,
},
},
],
},
]}
/>
<Chart libraries={libraries} stats={stats} />
</div>
</div>
);

View File

@@ -1,11 +1,12 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { ResponsiveLine } from "@nivo/line";
import Chart from "./chart";
import "../../css/stats.css";
function PlayStatsByDay(props) {
const [data, setData] = useState();
const [stats, setStats] = useState();
const [libraries, setLibraries] = useState();
const [days, setDays] = useState(60);
const token = localStorage.getItem("token");
@@ -25,14 +26,15 @@ function PlayStatsByDay(props) {
}
)
.then((data) => {
setData(data.data);
setStats(data.data.stats);
setLibraries(data.data.libraries);
})
.catch((error) => {
console.log(error);
});
};
if (!data) {
if (!stats) {
fetchLibraries();
}
if (days !== props.days) {
@@ -42,13 +44,13 @@ function PlayStatsByDay(props) {
const intervalId = setInterval(fetchLibraries, 60000 * 5);
return () => clearInterval(intervalId);
}, [data, days, props.days, token]);
}, [stats, libraries, days, props.days, token]);
if (!data) {
if (!stats) {
return <></>;
}
if (data.length === 0) {
if (stats.length === 0) {
return (
<div className="statistics-widget small">
<h1>Play Count By Day - Last {days} Days</h1>
@@ -62,95 +64,7 @@ function PlayStatsByDay(props) {
<div className="statistics-widget">
<h1>Play Count By Day - Last {days} Days</h1>
<div className="graph small">
<ResponsiveLine
data={data}
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: 0,
max: "auto",
stacked: false,
reverse: false,
}}
enableGridX={false}
enableSlices={"x"}
yFormat=" >-.0f"
curve="cardinal"
enableArea={true}
theme={{
axis: {
ticks: {
line: {
stroke: "white",
},
text: {
fill: "white",
},
},
},
grid: {
line: {
stroke: "rgba(255,255,255,0.2)",
strokeWidth: 2,
strokeDasharray: "1 40",
},
},
}}
axisBottom={{
orient: "bottom",
tickSize: 5,
tickPadding: 10,
tickRotation: 0,
legend: "Days",
legendOffset: 36,
legendPosition: "middle",
}}
axisLeft={{
orient: "left",
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: "Plays",
legendOffset: -40,
legendPosition: "middle",
itemTextColor: "#fff",
theme: "white",
}}
colors={{ scheme: "category10" }}
pointSize={5}
pointColor={{ theme: "background" }}
pointBorderWidth={2}
pointBorderColor={{ from: "serieColor" }}
pointLabelYOffset={-12}
legends={[
{
itemTextColor: "#fff",
anchor: "bottom",
direction: "row",
justify: false,
translateX: 0,
translateY: 100,
itemsSpacing: 0,
itemDirection: "left-to-right",
itemWidth: 100,
itemHeight: 20,
itemOpacity: 0.75,
symbolSize: 12,
symbolShape: "circle",
symbolBorderColor: "rgba(0, 0, 0, .5)",
effects: [
{
on: "hover",
style: {
itemBackground: "rgba(0, 0, 0, .03)",
itemOpacity: 1,
},
},
],
},
]}
/>
<Chart libraries={libraries} stats={stats} />
</div>
</div>
);

View File

@@ -1,11 +1,11 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { ResponsiveLine } from "@nivo/line";
import Chart from "./chart";
import "../../css/stats.css";
function PlayStatsByHour(props) {
const [data, setData] = useState();
const [stats, setStats] = useState();
const [libraries, setLibraries] = useState();
const [days, setDays] = useState(60);
const token = localStorage.getItem("token");
@@ -25,14 +25,15 @@ function PlayStatsByHour(props) {
}
)
.then((data) => {
setData(data.data);
setStats(data.data.stats);
setLibraries(data.data.libraries);
})
.catch((error) => {
console.log(error);
});
};
if (!data) {
if (!stats) {
fetchLibraries();
}
if (days !== props.days) {
@@ -42,13 +43,13 @@ function PlayStatsByHour(props) {
const intervalId = setInterval(fetchLibraries, 60000 * 5);
return () => clearInterval(intervalId);
}, [data, days, props.days, token]);
}, [stats, libraries, days, props.days, token]);
if (!data) {
if (!stats) {
return <></>;
}
if (data.length === 0) {
if (stats.length === 0) {
return (
<div className="statistics-widget small">
<h1>Play Count By Hour - Last {days} Days</h1>
@@ -58,101 +59,12 @@ function PlayStatsByHour(props) {
);
}
console.log(data);
return (
<div className="statistics-widget">
<h1>Play Count By Hour - Last {days} Days</h1>
<div className="graph small">
<ResponsiveLine
data={data}
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: 0,
max: "auto",
stacked: false,
reverse: false,
}}
enableGridX={false}
enableSlices={"x"}
yFormat=" >-.0f"
curve="cardinal"
enableArea={true}
theme={{
axis: {
ticks: {
line: {
stroke: "white",
},
text: {
fill: "white",
},
},
},
grid: {
line: {
stroke: "rgba(255,255,255,0.2)",
strokeWidth: 2,
strokeDasharray: "1 40",
},
},
}}
axisBottom={{
orient: "bottom",
tickSize: 5,
tickPadding: 10,
tickRotation: 0,
legend: "Days",
legendOffset: 36,
legendPosition: "middle",
}}
axisLeft={{
orient: "left",
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: "Plays",
legendOffset: -40,
legendPosition: "middle",
itemTextColor: "#fff",
theme: "white",
}}
colors={{ scheme: "category10" }}
pointSize={5}
pointColor={{ theme: "background" }}
pointBorderWidth={2}
pointBorderColor={{ from: "serieColor" }}
pointLabelYOffset={-12}
legends={[
{
itemTextColor: "#fff",
anchor: "bottom",
direction: "row",
justify: false,
translateX: 0,
translateY: 100,
itemsSpacing: 0,
itemDirection: "left-to-right",
itemWidth: 100,
itemHeight: 20,
itemOpacity: 0.75,
symbolSize: 12,
symbolShape: "circle",
symbolBorderColor: "rgba(0, 0, 0, .5)",
effects: [
{
on: "hover",
style: {
itemBackground: "rgba(0, 0, 0, .03)",
itemOpacity: 1,
},
},
],
},
]}
/>
<Chart libraries={libraries} stats={stats} />
</div>
</div>
);

View File

@@ -8,7 +8,8 @@ import './css/library/libraries.css';
// import LibraryOverView from './components/libraryOverview';
// import HomeStatisticCards from './components/HomeStatisticCards';
import Sessions from './components/sessions/sessions';
// import Sessions from './components/sessions/sessions';
import DailyPlayStats from './components/statistics/daily-play-count';
@@ -51,7 +52,7 @@ function Testing() {
return (
<div className='Activity'>
<Sessions/>
<DailyPlayStats/>
</div>