diff --git a/react-ui/package-lock.json b/react-ui/package-lock.json
index b45093d67..a31343d07 100644
--- a/react-ui/package-lock.json
+++ b/react-ui/package-lock.json
@@ -4113,6 +4113,73 @@
"type": "^1.0.1"
}
},
+ "d3-array": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+ "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+ },
+ "d3-collection": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
+ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
+ },
+ "d3-color": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz",
+ "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg=="
+ },
+ "d3-format": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz",
+ "integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g=="
+ },
+ "d3-interpolate": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
+ "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
+ "requires": {
+ "d3-color": "1"
+ }
+ },
+ "d3-path": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz",
+ "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg=="
+ },
+ "d3-scale": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
+ "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
+ "requires": {
+ "d3-array": "^1.2.0",
+ "d3-collection": "1",
+ "d3-format": "1",
+ "d3-interpolate": "1",
+ "d3-time": "1",
+ "d3-time-format": "2"
+ }
+ },
+ "d3-shape": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz",
+ "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==",
+ "requires": {
+ "d3-path": "1"
+ }
+ },
+ "d3-time": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
+ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
+ },
+ "d3-time-format": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
+ "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
+ "requires": {
+ "d3-time": "1"
+ }
+ },
"damerau-levenshtein": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz",
@@ -4166,6 +4233,11 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
+ "decimal.js-light": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.0.tgz",
+ "integrity": "sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg=="
+ },
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@@ -4402,6 +4474,14 @@
"utila": "~0.4"
}
},
+ "dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
"dom-serializer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
@@ -8137,6 +8217,11 @@
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
},
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
+ },
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -8164,6 +8249,11 @@
"lodash._reinterpolate": "^3.0.0"
}
},
+ "lodash.throttle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+ "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+ },
"lodash.unescape": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
@@ -8260,6 +8350,11 @@
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
+ "math-expression-evaluator": {
+ "version": "1.2.17",
+ "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
+ "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw="
+ },
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -10618,6 +10713,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.1.tgz",
"integrity": "sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw=="
},
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
"react-popper": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.4.tgz",
@@ -10631,6 +10731,17 @@
"warning": "^4.0.2"
}
},
+ "react-resize-detector": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-2.3.0.tgz",
+ "integrity": "sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ==",
+ "requires": {
+ "lodash.debounce": "^4.0.8",
+ "lodash.throttle": "^4.1.1",
+ "prop-types": "^15.6.0",
+ "resize-observer-polyfill": "^1.5.0"
+ }
+ },
"react-router": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz",
@@ -10748,6 +10859,28 @@
"react-dom": "^16.6.1"
}
},
+ "react-smooth": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-1.0.2.tgz",
+ "integrity": "sha512-pIGzL1g9VGAsRsdZQokIK0vrCkcdKtnOnS1gyB2rrowdLy69lNSWoIjCTWAfgbiYvria8tm5hEZqj+jwXMkV4A==",
+ "requires": {
+ "lodash": "~4.17.4",
+ "prop-types": "^15.6.0",
+ "raf": "^3.4.0",
+ "react-transition-group": "^2.5.0"
+ }
+ },
+ "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==",
+ "requires": {
+ "dom-helpers": "^3.4.0",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
"reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
@@ -10827,6 +10960,39 @@
"util.promisify": "^1.0.0"
}
},
+ "recharts": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-1.7.1.tgz",
+ "integrity": "sha512-i4vK/ZSICr+dXGmaijuNybc+xhctiX0464xnqauY+OvE6WvU5v+0GYciQvD/HJSObkKG4wY8aRtiuUL9YtXnHQ==",
+ "requires": {
+ "classnames": "^2.2.5",
+ "core-js": "^2.5.1",
+ "d3-interpolate": "^1.3.0",
+ "d3-scale": "^2.1.0",
+ "d3-shape": "^1.2.0",
+ "lodash": "^4.17.5",
+ "prop-types": "^15.6.0",
+ "react-resize-detector": "^2.3.0",
+ "react-smooth": "^1.0.0",
+ "recharts-scale": "^0.4.2",
+ "reduce-css-calc": "^1.3.0"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
+ "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
+ }
+ }
+ },
+ "recharts-scale": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.2.tgz",
+ "integrity": "sha512-p/cKt7j17D1CImLgX2f5+6IXLbRHGUQkogIp06VUoci/XkhOQiGSzUrsD1uRmiI7jha4u8XNFOjkHkzzBPivMg==",
+ "requires": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
"recursive-readdir": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
@@ -10835,6 +11001,31 @@
"minimatch": "3.0.4"
}
},
+ "reduce-css-calc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
+ "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=",
+ "requires": {
+ "balanced-match": "^0.4.2",
+ "math-expression-evaluator": "^1.2.14",
+ "reduce-function-call": "^1.0.1"
+ },
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+ "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+ }
+ }
+ },
+ "reduce-function-call": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz",
+ "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==",
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@@ -11064,6 +11255,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
+ "resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
"resolve": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
diff --git a/react-ui/package.json b/react-ui/package.json
index 8e8b8767c..a38f63f52 100644
--- a/react-ui/package.json
+++ b/react-ui/package.json
@@ -13,6 +13,7 @@
"react-router-dom": "^5.1.2",
"react-scripts": "3.1.2",
"react-sketch": "^0.5.1",
+ "recharts": "^1.7.1",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^0.88.1"
},
diff --git a/react-ui/src/components/common/WeaponDropdown.js b/react-ui/src/components/common/WeaponDropdown.js
index 74c02269f..884dd1d0a 100644
--- a/react-ui/src/components/common/WeaponDropdown.js
+++ b/react-ui/src/components/common/WeaponDropdown.js
@@ -5,7 +5,7 @@ import { weapons } from "../../utils/lists"
import { wpnSmall } from "../../assets/imageImports"
import weaponDict from "../../utils/english_internal.json"
-const WeaponDropdown = ({ onChange, value, showImages = true }) => {
+const WeaponDropdown = ({ value, onChange, showImages = true }) => {
return (
{
- Twitter
- Twitch
- YouTube
- Discord
+
+ Twitter
+
+
+ Twitch
+
+
+ YouTube
+
+
+ Discord
+
- Splatoonwiki
- Some random
+
+ Inkipedia
+
+
+ stat.ink
+
Links
More
diff --git a/react-ui/src/components/root/MainMenu.js b/react-ui/src/components/root/MainMenu.js
index 834b6446d..01a0e7adf 100644
--- a/react-ui/src/components/root/MainMenu.js
+++ b/react-ui/src/components/root/MainMenu.js
@@ -1,6 +1,6 @@
import React from "react"
-import { Menu, Container, Image, Dropdown, Button } from "semantic-ui-react"
-import { NavLink } from "react-router-dom"
+import { Menu, Container, Image, Dropdown, Icon } from "semantic-ui-react"
+import { Link, NavLink } from "react-router-dom"
import sink_logo from "../../assets/sink_logo.png"
const dropdownStyle = {
@@ -11,7 +11,7 @@ const dropdownStyle = {
const MainMenu = () => {
return (
-
+
@@ -53,8 +53,10 @@ const MainMenu = () => {
-
- Log in via Discord
+
+
+
+ Log in via Discord
diff --git a/react-ui/src/components/root/RollSim.js b/react-ui/src/components/root/RollSim.js
index 9146e807d..507add723 100644
--- a/react-ui/src/components/root/RollSim.js
+++ b/react-ui/src/components/root/RollSim.js
@@ -1,10 +1,8 @@
import React, { useState } from "react"
-import { Comment } from "semantic-ui-react"
import { clothingGear, shoesGear, headGear } from "../../utils/lists"
import { choose } from "../../utils/helperFunctions"
import { abilityIcons } from "../../assets/imageImports"
-import murchpfp from "../../assets/murchpfp.png"
import head from "../../utils/head.json"
import clothes from "../../utils/clothes.json"
import shoes from "../../utils/shoes.json"
@@ -37,6 +35,8 @@ const subAbilityStyle = {
}
const gearStyle = { maxWidth: "50px", height: "auto" }
+const paddingStyle = { paddingLeft: "0.7em" }
+
const subAbilities = [
abilityIcons.ISM,
abilityIcons.ISS,
@@ -167,7 +167,6 @@ const RollSim = () => {
choose(subAbilities)
])
const [rolling, setRolling] = useState(false)
- const [rollCount, setRollCount] = useState(0)
const setSubs = (json, gear) => {
let gearName = gear.split("_")[1]
@@ -239,7 +238,6 @@ const RollSim = () => {
}
setSubsState(setSubs(json, gear)) //only using this method in the last roll that matters for optimization purposes
setRolling(false)
- setRollCount(rollCount + 1)
}
return (
@@ -250,7 +248,9 @@ const RollSim = () => {
style={gearStyle}
alt=""
/>
-
+
+
+
{
style={gearStyle}
alt=""
/>
-
+
+
+
{
style={gearStyle}
alt=""
/>
-
+
+
+
{
onClick={() => roll("SHOES")}
/>
- {rollCount > 0 && (
-
-
-
-
- Murch
-
- You have rolled {rollCount} {rollCount === 1 ? "time" : "times"}
- , chum.
-
-
-
-
- )}
)
}
diff --git a/react-ui/src/components/root/Routes.js b/react-ui/src/components/root/Routes.js
index d0a3b434e..33533347a 100644
--- a/react-ui/src/components/root/Routes.js
+++ b/react-ui/src/components/root/Routes.js
@@ -7,6 +7,8 @@ const Rotations = lazy(() => import("../rotation/Rotations"))
const MapPlanner = lazy(() => import("../plans/MapPlanner"))
const Calendar = lazy(() => import("../calendar/Calendar"))
const XLeaderboard = lazy(() => import("../xleaderboard/XLeaderboard"))
+const PlayerXRankStats = lazy(() => import("../xsearch/PlayerXRankStats"))
+const XTrends = lazy(() => import("../xtrends/XTrends"))
const Routes = () => {
return (
@@ -27,6 +29,12 @@ const Routes = () => {
+
+
+
+
+
+
)
diff --git a/react-ui/src/components/rotation/Rotations.js b/react-ui/src/components/rotation/Rotations.js
index f10f29023..2535f6f81 100644
--- a/react-ui/src/components/rotation/Rotations.js
+++ b/react-ui/src/components/rotation/Rotations.js
@@ -39,7 +39,7 @@ const Rotations = ({ setMenuSelection }) => {
}, [data, loading, monthly.loading])
if (loading || monthly.loading || rotation.length === 0) return
- if (error || monthly.error) return
+ if (error || monthly.error) return
const monthlyMaps = monthly.data.maplists[0]
diff --git a/react-ui/src/components/xleaderboard/FlexLeaderboard.js b/react-ui/src/components/xleaderboard/FlexLeaderboard.js
index 8ec7b1746..0eaa4fdd3 100644
--- a/react-ui/src/components/xleaderboard/FlexLeaderboard.js
+++ b/react-ui/src/components/xleaderboard/FlexLeaderboard.js
@@ -1,5 +1,5 @@
import React, { useEffect } from "react"
-import { Table, Icon, Popup, Segment } from "semantic-ui-react"
+import { Table, Icon, Popup } from "semantic-ui-react"
import { useQuery } from "@apollo/react-hooks"
import { Link } from "react-router-dom"
@@ -21,7 +21,7 @@ const FlexLeaderboard = () => {
)
}
if (result.error) {
- return
+ return
}
const leaderboard = result.data["topFlex"]
diff --git a/react-ui/src/components/xleaderboard/WeaponLeaderboard.js b/react-ui/src/components/xleaderboard/WeaponLeaderboard.js
index 4e25cd6ca..ec435dd86 100644
--- a/react-ui/src/components/xleaderboard/WeaponLeaderboard.js
+++ b/react-ui/src/components/xleaderboard/WeaponLeaderboard.js
@@ -22,7 +22,7 @@ const WeaponLeaderboard = ({ query, queryName, scoreField, weaponsField }) => {
)
}
if (result.error) {
- return
+ return
}
const leaderboard = result.data[queryName]
diff --git a/react-ui/src/components/xsearch/MonthsTable.js b/react-ui/src/components/xsearch/MonthsTable.js
new file mode 100644
index 000000000..b803d5f21
--- /dev/null
+++ b/react-ui/src/components/xsearch/MonthsTable.js
@@ -0,0 +1,61 @@
+import React from "react"
+import { Table, Header, Image } from "semantic-ui-react"
+
+import { wpnSmall } from "../../assets/imageImports"
+import szIcon from "../../assets/sz.png"
+import tcIcon from "../../assets/tc.png"
+import rmIcon from "../../assets/rm.png"
+import cbIcon from "../../assets/cb.png"
+import { months } from "../../utils/lists"
+import weaponDict from "../../utils/english_internal.json"
+
+const MonthsTable = ({ placements }) => {
+ //data received is ordered chronologically and sz->tc->rm->cb
+ const modeIcons = [null, szIcon, tcIcon, rmIcon, cbIcon]
+ let lastMonth = placements[0].month
+ let toggle = false
+
+ return (
+
+
+
+
+ Name
+ X Power
+ Placement
+ Weapon
+
+
+
+
+ {placements.map(p => {
+ if (lastMonth !== p.month) {
+ lastMonth = p.month
+ toggle = !toggle
+ }
+ return (
+
+
+
+
+
+ {months[p.month]}
+ {p.year}
+
+
+
+ {p.name}
+ {p.x_power}
+ {p.rank}
+
+
+
+
+ )
+ })}
+
+
+ )
+}
+
+export default MonthsTable
diff --git a/react-ui/src/components/xsearch/PlayerXRankStats.js b/react-ui/src/components/xsearch/PlayerXRankStats.js
new file mode 100644
index 000000000..ec7940b0a
--- /dev/null
+++ b/react-ui/src/components/xsearch/PlayerXRankStats.js
@@ -0,0 +1,146 @@
+import React, { useEffect, useState } from "react"
+import { useQuery } from "@apollo/react-hooks"
+import { Header, Image, Icon, List, Segment } from "semantic-ui-react"
+import { Link, Redirect, useParams } from "react-router-dom"
+
+import { playerInfo } from "../../graphql/queries/playerInfo"
+import TopPlacementsTable from "./TopPlacementsTable"
+import WpnPlayedTable from "./WpnPlayedTable"
+import MonthsTable from "./MonthsTable"
+import Loading from "../common/Loading"
+import Error from "../common/Error"
+
+const addXRankHelp = (
+
+ This user has no X Rank profile set up. If you are the owner of this user
+ page here is how you can set it up:
+
+
+
+ Finish an X rank season in the Top 500 in at least one mode.
+
+ Send your Twitter handle to Sendou via DM.
+
+ Add your Twitter account to your profile on Discord, verify it and
+ make sure it's set to appear publicly.
+
+ Log in to sendou.ink
+
+
+
+)
+
+const PlayerXRankStats = ({ twitter, tabMode = false }) => {
+ let searchVariables = {}
+ const { uid } = useParams()
+ if (uid) searchVariables = { uid }
+ if (twitter) searchVariables = { twitter }
+ const { data, error, loading } = useQuery(playerInfo, {
+ variables: searchVariables
+ })
+ const [top, setTop] = useState([])
+
+ useEffect(() => {
+ if (loading) {
+ return
+ }
+ if (data && data.playerInfo) {
+ document.title = `${data.playerInfo.player.name} - X Rank - sendou.ink`
+ const placements = data.playerInfo.placements
+
+ //reducing placements to top sz, tc etc. rank and x power
+ const tops = ["", "szTop", "tcTop", "rmTop", "cbTop"]
+ const exs = ["", "szX", "tcX", "rmX", "cbX"]
+ setTop(
+ placements.reduce(
+ (acc, cur) => {
+ const topKey = tops[cur.mode]
+ const xKey = exs[cur.mode]
+ if (!acc[xKey]) {
+ acc[xKey] = cur
+ acc[topKey] = cur
+ return acc
+ }
+ if (acc[xKey].x_power < cur.x_power) {
+ acc[xKey] = cur
+ }
+ if (acc[topKey].rank > cur.rank) {
+ acc[topKey] = cur
+ }
+
+ return acc
+ },
+ {
+ szX: null,
+ szTop: null,
+ tcX: null,
+ tcTop: null,
+ rmX: null,
+ rmTop: null,
+ cbX: null,
+ cbTop: null
+ }
+ )
+ )
+ }
+ }, [data, loading])
+
+ if (!uid && !twitter) return addXRankHelp
+ if (loading) return
+ if (error) {
+ if (error.message === "GraphQL error: player not found") {
+ if (tabMode) return addXRankHelp
+ else return
+ }
+ return
+ }
+ const playerData = data.playerInfo.player
+ return (
+ <>
+ {!tabMode ? (
+
+ {playerData.twitter ? (
+
+ ) : null}{" "}
+ {playerData.alias ? playerData.alias : playerData.name}
+ {playerData.twitter ? (
+
+
+
+ ) : null}
+
+ ) : null}
+ {tabMode && playerData.discord_id ? (
+ <>
+ User page
+
+ >
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Weapons reached Top 500 with
+
+
+
+ >
+ )
+}
+export default PlayerXRankStats
diff --git a/react-ui/src/components/xsearch/TopPlacementsTable.js b/react-ui/src/components/xsearch/TopPlacementsTable.js
new file mode 100644
index 000000000..248e545ff
--- /dev/null
+++ b/react-ui/src/components/xsearch/TopPlacementsTable.js
@@ -0,0 +1,118 @@
+import React from "react"
+import { Header, Image, Table } from "semantic-ui-react"
+
+import { wpnSmall } from "../../assets/imageImports"
+import weaponDict from "../../utils/english_internal.json"
+import { months } from "../../utils/lists"
+import szIcon from "../../assets/sz.png"
+import tcIcon from "../../assets/tc.png"
+import rmIcon from "../../assets/rm.png"
+import cbIcon from "../../assets/cb.png"
+
+const TopPlacementsTable = ({ top }) => {
+ const returnRow = (x, placement, mode) => {
+ if (!x) {
+ return null
+ }
+
+ const modeIcon = ["", szIcon, tcIcon, rmIcon, cbIcon][mode]
+ const modeName = [
+ "",
+ "Splat Zones",
+ "Tower Control",
+ "Rainmaker",
+ "Clam Blitz"
+ ][mode]
+
+ if (x === placement) {
+ return (
+
+
+
+
+
+
+ {modeName}
+
+ Highest X Power & Placement
+
+
+
+
+ {x.x_power}
+ {x.rank}
+
+
+
+ {months[x.month]}
+ {x.year}
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+ {modeName}
+ Highest X Power
+
+
+
+ {x.x_power}
+ {x.rank}
+
+
+
+ {months[x.month]}
+ {x.year}
+
+
+
+
+
+
+ {modeName}
+ Highest Placement
+
+
+
+ {placement.x_power}
+ {placement.rank}
+
+
+
+ {months[placement.month]}
+ {placement.year}
+
+
+ )
+ }
+ return (
+
+
+
+
+ X Power
+ Placement
+ Weapon
+ Month
+ Year
+
+
+ {returnRow(top.szX, top.szTop, 1)}
+ {returnRow(top.tcX, top.tcTop, 2)}
+ {returnRow(top.rmX, top.rmTop, 3)}
+ {returnRow(top.cbX, top.cbTop, 4)}
+
+ )
+}
+
+export default TopPlacementsTable
diff --git a/react-ui/src/components/xsearch/WpnPlayedTable.js b/react-ui/src/components/xsearch/WpnPlayedTable.js
new file mode 100644
index 000000000..8887a9877
--- /dev/null
+++ b/react-ui/src/components/xsearch/WpnPlayedTable.js
@@ -0,0 +1,63 @@
+import React from "react"
+import { weaponsByCategory } from "../../utils/lists"
+import { categoryKeys } from "../../utils/lists"
+import weaponDict from "../../utils/english_internal.json"
+import { wpnSmall } from "../../assets/imageImports"
+import { Table, Header, Popup } from "semantic-ui-react"
+
+const WpnPlayedTable = ({ weapons }) => {
+ const weaponStyle = wpnName => {
+ const activeStyle = {}
+ const inactiveStyle = { filter: "grayscale(1)", opacity: "0.3" }
+
+ return weapons.includes(wpnName) ? activeStyle : inactiveStyle
+ }
+ return (
+
+ {categoryKeys.map(c => {
+ return (
+
+
+
+
+
+
+ {weaponsByCategory[c].map(w => {
+ return (
+
+ }
+ content={w}
+ />
+ )
+ })}
+
+
+
+ )
+ })}
+
+
+
+
+
+
+ {weapons.length} / 129
+
+
+
+ )
+}
+
+export default WpnPlayedTable
diff --git a/react-ui/src/components/xtrends/XTrends.js b/react-ui/src/components/xtrends/XTrends.js
new file mode 100644
index 000000000..e3bd31484
--- /dev/null
+++ b/react-ui/src/components/xtrends/XTrends.js
@@ -0,0 +1,350 @@
+import React, { useState, useEffect } from "react"
+import {
+ Button,
+ Form,
+ Radio,
+ List,
+ Image,
+ Grid,
+ Dropdown,
+ Message
+} from "semantic-ui-react"
+import { wpnSmall } from "../../assets/imageImports"
+import useTrends from "../../utils/useTrends"
+import useWindowDimensions from "../../utils/useWindowDimensions"
+import english_internal from "../../utils/english_internal.json"
+import { months } from "../../utils/lists"
+import WeaponDropdown from "../common/WeaponDropdown"
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ Brush
+} from "recharts"
+
+const customToolTipStyle = {
+ width: "200px",
+ margin: "0",
+ lineHeight: "24px",
+ border: "1px solid #f5f5f5",
+ backgroundColor: "hsla(0,0%,100%,.8)",
+ padding: "10px"
+}
+
+const labelStyle = {
+ margin: "0",
+ color: "#666",
+ fontWeight: "700"
+}
+
+const introStyle = {
+ borderTop: "1px solid #f5f5f5",
+ margin: "0"
+}
+
+const descStyle = {
+ margin: 0,
+ color: "#999"
+}
+
+const XTrends = () => {
+ const [weaponForm, setWeaponForm] = useState(null)
+ const [combineFormLeft, setCombineFormLeft] = useState(null)
+ const [combineFormRight, setCombineFormRight] = useState(null)
+ const [weaponForDispatch, setWeaponForDispatch] = useState(null)
+ const [mode, setMode] = useState("ALL")
+ const [modeForDispatch, setModeForDispatch] = useState(null)
+ const { loading, error, plotData, dispatch } = useTrends(
+ weaponForDispatch,
+ modeForDispatch
+ )
+ const { containerWidth } = useWindowDimensions()
+
+ useEffect(() => {
+ document.title = "X Rank Trends - sendou.ink"
+ }, [])
+
+ const CustomTooltip = ({ active, payload }) => {
+ if (active) {
+ if (payload.length === 0) return null
+ const monthNumber = payload[0].payload.name
+ const yearNumber = payload[0].payload.year
+ let patchDescription = null
+ if (payload[0].payload.hasOwnProperty("patch")) {
+ if (monthNumber === 4 && yearNumber === 2019) {
+ patchDescription = "Patches 4.6 and 4.7 were released."
+ } else {
+ patchDescription = `Patch ${payload[0].payload.patch} was released.`
+ }
+ }
+ return (
+
+
{`${months[monthNumber]} (${yearNumber})`}
+ {payload.map(p => {
+ return (
+
+ {p.payload[p.dataKey]} - {p.dataKey}
+
+ )
+ })}
+ {patchDescription &&
{patchDescription}
}
+
+ )
+ }
+
+ return null
+ }
+
+ if (error) {
+ return {error.message}
+ }
+
+ return (
+ <>
+ setWeaponForm(value)}
+ />
+
+
+ setMode("ALL")}
+ />
+
+
+ setMode("SZ")}
+ />
+
+
+ setMode("TC")}
+ />
+
+
+ setMode("RM")}
+ />
+
+
+ setMode("CB")}
+ />
+
+
+
+
+
{
+ setWeaponForDispatch(weaponForm)
+ setModeForDispatch(mode)
+ const wpnForCombine =
+ mode === "ALL" ? weaponForm : `${weaponForm} (${mode})`
+ setCombineFormLeft(wpnForCombine)
+ if (plotData.keys.length >= 1)
+ setCombineFormRight(
+ plotData.keys[plotData.keys.length - 1].weapon
+ )
+ }}
+ disabled={!weaponForm}
+ >
+ Add to plot as new
+
+ {plotData.keys.length >= 2 && (
+
+ {
+ dispatch({
+ type: "combine",
+ left: combineFormLeft,
+ right: combineFormRight
+ })
+ }}
+ >
+ Combine...
+
+
+ {" "}
+ {
+ return { text: k.weapon, value: k.weapon }
+ })
+ .filter(k => k.text !== combineFormRight)}
+ onChange={(event, { value }) => {
+ setCombineFormLeft(value)
+ }}
+ value={combineFormLeft}
+ />{" "}
+ with{" "}
+ {
+ return { text: k.weapon, value: k.weapon }
+ })
+ .filter(k => k.text !== combineFormLeft)}
+ onChange={(event, { value }) => {
+ setCombineFormRight(value)
+ }}
+ value={combineFormRight}
+ />
+
+
+ )}
+
+ {plotData.keys.length > 0 && (
+
+
+
+
+
+
+ } />
+
+
+ {plotData.keys.map(keyObj => {
+ const w = keyObj.weapon
+ const color = keyObj.color
+ return
+ })}
+
+
+
+
+ {plotData.keys.map(keyObj => {
+ const w = keyObj.weapon
+ const color = keyObj.color
+ return (
+
+
+
+ dispatch({ type: "delete", weapon: w })
+ }
+ />
+
+ {w.indexOf("+") === -1 && (
+
+ )}
+
+
+ dispatch({ type: "randomizeColor", weapon: w })
+ }
+ >
+ {w}
+
+
+
+ )
+ })}
+
+
+
+
+
+ )}
+
+
+
+ You can check out all the patch notes{" "}
+
+ here
+
+
+
+ For alternative take on X Rank trends check out{" "}
+ Splat Meta
+
+
+
+ >
+ )
+}
+
+export default XTrends
diff --git a/react-ui/src/index.css b/react-ui/src/index.css
index 81e613d27..c57360d16 100644
--- a/react-ui/src/index.css
+++ b/react-ui/src/index.css
@@ -6,7 +6,7 @@ body {
height: 100%;
margin: 0;
background: #79f1a4;
- background-image: linear-gradient(135deg, #79f1a4 10%, #0e5cad 100%);
+ background: linear-gradient(to right, #0f2027, #203a43, #2c5364);
background-repeat: no-repeat;
background-attachment: fixed;
}
diff --git a/react-ui/src/utils/lists.js b/react-ui/src/utils/lists.js
index 9ed6e434d..dbb8e5d04 100644
--- a/react-ui/src/utils/lists.js
+++ b/react-ui/src/utils/lists.js
@@ -154,6 +154,191 @@ export const weapons = [
"Kensa Undercover Brella"
]
+export const shooters = [
+ "Sploosh-o-matic",
+ "Neo Sploosh-o-matic",
+ "Sploosh-o-matic 7",
+ "Splattershot Jr.",
+ "Custom Splattershot Jr.",
+ "Kensa Splattershot Jr.",
+ "Splash-o-matic",
+ "Neo Splash-o-matic",
+ "Aerospray MG",
+ "Aerospray RG",
+ "Aerospray PG",
+ "Splattershot",
+ "Tentatek Splattershot",
+ "Kensa Splattershot",
+ ".52 Gal",
+ ".52 Gal Deco",
+ "Kensa .52 Gal",
+ "N-ZAP '85",
+ "N-ZAP '89",
+ "N-ZAP '83",
+ "Splattershot Pro",
+ "Forge Splattershot Pro",
+ "Kensa Splattershot Pro",
+ ".96 Gal",
+ ".96 Gal Deco",
+ "Jet Squelcher",
+ "Custom Jet Squelcher"
+]
+
+export const semiauto = [
+ "L-3 Nozzlenose",
+ "L-3 Nozzlenose D",
+ "Kensa L-3 Nozzlenose",
+ "H-3 Nozzlenose",
+ "H-3 Nozzlenose D",
+ "Cherry H-3 Nozzlenose",
+ "Squeezer",
+ "Foil Squeezer"
+]
+
+export const blasters = [
+ "Luna Blaster",
+ "Luna Blaster Neo",
+ "Kensa Luna Blaster",
+ "Blaster",
+ "Custom Blaster",
+ "Range Blaster",
+ "Custom Range Blaster",
+ "Grim Range Blaster",
+ "Rapid Blaster",
+ "Rapid Blaster Deco",
+ "Kensa Rapid Blaster",
+ "Rapid Blaster Pro",
+ "Rapid Blaster Pro Deco",
+ "Clash Blaster",
+ "Clash Blaster Neo"
+]
+
+export const rollers = [
+ "Carbon Roller",
+ "Carbon Roller Deco",
+ "Splat Roller",
+ "Krak-On Splat Roller",
+ "Kensa Splat Roller",
+ "Dynamo Roller",
+ "Gold Dynamo Roller",
+ "Kensa Dynamo Roller",
+ "Flingza Roller",
+ "Foil Flingza Roller"
+]
+
+export const brushes = [
+ "Inkbrush",
+ "Inkbrush Nouveau",
+ "Permanent Inkbrush",
+ "Octobrush",
+ "Octobrush Nouveau",
+ "Kensa Octobrush"
+]
+
+export const chargers = [
+ "Classic Squiffer",
+ "New Squiffer",
+ "Fresh Squiffer",
+ "Splat Charger",
+ "Firefin Splat Charger",
+ "Kensa Charger",
+ "Splatterscope",
+ "Firefin Splatterscope",
+ "Kensa Splatterscope",
+ "E-liter 4K",
+ "Custom E-liter 4K",
+ "E-liter 4K Scope",
+ "Custom E-liter 4K Scope",
+ "Bamboozler 14 Mk I",
+ "Bamboozler 14 Mk II",
+ "Bamboozler 14 Mk III",
+ "Goo Tuber",
+ "Custom Goo Tuber"
+]
+
+export const sloshers = [
+ "Slosher",
+ "Slosher Deco",
+ "Soda Slosher",
+ "Tri-Slosher",
+ "Tri-Slosher Nouveau",
+ "Sloshing Machine",
+ "Sloshing Machine Neo",
+ "Kensa Sloshing Machine",
+ "Bloblobber",
+ "Bloblobber Deco",
+ "Explosher",
+ "Custom Explosher"
+]
+
+export const splatlings = [
+ "Mini Splatling",
+ "Zink Mini Splatling",
+ "Kensa Mini Splatling",
+ "Heavy Splatling",
+ "Heavy Splatling Deco",
+ "Heavy Splatling Remix",
+ "Hydra Splatling",
+ "Custom Hydra Splatling",
+ "Ballpoint Splatling",
+ "Ballpoint Splatling Nouveau",
+ "Nautilus 47",
+ "Nautilus 79"
+]
+
+export const dualies = [
+ "Dapple Dualies",
+ "Dapple Dualies Nouveau",
+ "Clear Dapple Dualies",
+ "Splat Dualies",
+ "Enperry Splat Dualies",
+ "Kensa Splat Dualies",
+ "Glooga Dualies",
+ "Glooga Dualies Deco",
+ "Kensa Glooga Dualies",
+ "Dualie Squelchers",
+ "Custom Dualie Squelchers",
+ "Dark Tetra Dualies",
+ "Light Tetra Dualies"
+]
+
+export const brellas = [
+ "Splat Brella",
+ "Sorella Brella",
+ "Tenta Brella",
+ "Tenta Sorella Brella",
+ "Tenta Camo Brella",
+ "Undercover Brella",
+ "Undercover Sorella Brella",
+ "Kensa Undercover Brella"
+]
+
+export const weaponsByCategory = {
+ Shooters: shooters,
+ "Semi-automatic Shooters": semiauto,
+ Blasters: blasters,
+ Rollers: rollers,
+ Brushes: brushes,
+ Chargers: chargers,
+ Sloshers: sloshers,
+ Splatlings: splatlings,
+ Dualies: dualies,
+ Brellas: brellas
+}
+
+export const categoryKeys = [
+ "Shooters",
+ "Semi-automatic Shooters",
+ "Blasters",
+ "Rollers",
+ "Brushes",
+ "Chargers",
+ "Sloshers",
+ "Splatlings",
+ "Dualies",
+ "Brellas"
+]
+
export const clothingGear = [
"Clt_AMB000",
"Clt_AMB001",
@@ -442,7 +627,6 @@ export const headGear = [
"Hed_CAP011",
"Hed_CAP012",
"Hed_CAP014",
- "Hed_CAP015",
"Hed_CAP018",
"Hed_CAP019",
"Hed_CAP020",
@@ -569,7 +753,6 @@ export const headGear = [
"Hed_NCP004",
"Hed_NCP005",
"Hed_NCP006",
- "Hed_NCP007",
"Hed_NCP008",
"Hed_NCP009",
"Hed_NCP010",
diff --git a/react-ui/src/utils/useTrends.js b/react-ui/src/utils/useTrends.js
new file mode 100644
index 000000000..7a3328ca5
--- /dev/null
+++ b/react-ui/src/utils/useTrends.js
@@ -0,0 +1,278 @@
+import { useReducer, useEffect } from "react"
+import { useQuery } from "@apollo/react-hooks"
+import { searchForTrend } from "../graphql/queries/seachForTrend"
+
+const month = []
+month[0] = null
+month[1] = "Jan"
+month[2] = "Feb"
+month[3] = "Mar"
+month[4] = "Apr"
+month[5] = "May"
+month[6] = "Jun"
+month[7] = "Jul"
+month[8] = "Aug"
+month[9] = "Sep"
+month[10] = "Oct"
+month[11] = "Nov"
+month[12] = "Dec"
+
+const patches = {
+ 5: {
+ 2018: {
+ name: "3.0",
+ link: "https://splatoonwiki.org/wiki/Version_3.0.0_(Splatoon_2)"
+ },
+ 2019: {
+ name: "4.8",
+ link: "https://splatoonwiki.org/wiki/Version_4.8.0_(Splatoon_2)"
+ }
+ },
+ 6: {
+ 2018: {
+ name: "3.1",
+ link: "https://splatoonwiki.org/wiki/Version_3.1.0_(Splatoon_2)"
+ },
+ 2019: {
+ name: "4.9",
+ link: "https://splatoonwiki.org/wiki/Version_4.9.0_(Splatoon_2)"
+ }
+ },
+ 7: {
+ 2018: {
+ name: "3.2",
+ link: "https://splatoonwiki.org/wiki/Version_3.2.0_(Splatoon_2)"
+ },
+ 2019: {
+ name: "5.0",
+ link: "https://splatoonwiki.org/wiki/Version_5.0.0_(Splatoon_2)"
+ }
+ },
+ 9: {
+ 2018: {
+ name: "4.0",
+ link: "https://splatoonwiki.org/wiki/Version_4.0.0_(Splatoon_2)"
+ }
+ },
+ 10: {
+ 2018: {
+ name: "4.1",
+ link: "https://splatoonwiki.org/wiki/Version_4.1.0_(Splatoon_2)"
+ }
+ },
+ 11: {
+ 2018: {
+ name: "4.2",
+ link: "https://splatoonwiki.org/wiki/Version_4.2.0_(Splatoon_2)"
+ }
+ },
+ 12: {
+ 2018: {
+ name: "4.3",
+ link: "https://splatoonwiki.org/wiki/Version_4.3.0_(Splatoon_2)"
+ }
+ },
+ 1: {
+ 2019: {
+ name: "4.4",
+ link: "https://splatoonwiki.org/wiki/Version_4.4.0_(Splatoon_2)"
+ }
+ },
+ 3: {
+ 2019: {
+ name: "4.5",
+ link: "https://splatoonwiki.org/wiki/Version_4.5.0_(Splatoon_2)"
+ }
+ },
+ 4: {
+ 2019: {
+ name: "4.6+4.7",
+ link: "https://splatoonwiki.org/wiki/List_of_updates_in_Splatoon_2"
+ }
+ }
+}
+
+const presetColors = [
+ "#FF00FF",
+ "#008000",
+ "#FF0000",
+ "#0000FF",
+ "#FFA500",
+ "#800080",
+ "#A52A2A",
+ "#1BC5CD",
+ "#000080",
+ "#5BCCA0"
+]
+
+const setPlotDataInitial = () => {
+ const arr_to_return = []
+ for (let i = 5; i < 13; i++) {
+ if (patches.hasOwnProperty(i) && patches[i].hasOwnProperty(2018)) {
+ arr_to_return.push({
+ name: i,
+ year: 2018,
+ xLabel: month[i],
+ patch: patches[i][2018].name
+ })
+ } else {
+ arr_to_return.push({ name: i, year: 2018, xLabel: month[i] })
+ }
+ }
+ const d = new Date()
+ const year = d.getFullYear()
+ const currentMonth = d.getMonth() + 1
+ for (let i = 2019; i < year + 1; i++) {
+ for (let j = 1; j < 13; j++) {
+ // break the loop when we reach the future
+ if (i === year && j === currentMonth) break
+ const xLabel = j === 1 ? `Jan (${year})` : month[j]
+ if (patches.hasOwnProperty(j) && patches[j].hasOwnProperty(i)) {
+ arr_to_return.push({
+ name: j,
+ year: i,
+ xLabel,
+ patch: patches[j][i].name
+ })
+ } else {
+ arr_to_return.push({ name: j, year: i, xLabel })
+ }
+ }
+ }
+
+ return arr_to_return
+}
+
+const mergeModeArrays = countObj => {
+ const sz_arr = countObj["SZ"]
+ const tc_arr = countObj["TC"]
+ const rm_arr = countObj["RM"]
+ const cb_arr = countObj["CB"]
+ const arr_to_return = new Array(12).fill(0)
+ for (let i = 1; i < 13; i++) {
+ arr_to_return[i] = sz_arr[i] + tc_arr[i] + rm_arr[i] + cb_arr[i]
+ }
+ return arr_to_return
+}
+
+// monthIndex is between 1 and 12 (inclusive)
+const resolveStartIndex = (monthIndex, year) => {
+ // resolves start index for an array where index 0 is always May 2018
+ if (year === 2018) {
+ return monthIndex - 5
+ } else if (year === 2019) {
+ return 7 + monthIndex
+ } else {
+ return 7 + monthIndex + (year - 2019) * 12
+ }
+}
+
+const getColor = state => {
+ if (state.keys.length < 9) {
+ return presetColors[state.keys.length]
+ }
+
+ return "#000000".replace(/0/g, function() {
+ return (~~(Math.random() * 16)).toString(16)
+ })
+}
+
+const reducer = (state, action) => {
+ switch (action.type) {
+ case "add":
+ const trend = action.trendDocument
+ const mode = action.mode
+ const modeLabel = mode === "ALL" ? "" : ` (${mode})`
+ const weapon = `${action.trendDocument.weapon}${modeLabel}`
+ // don't add duplicate plots
+ if (state.keys.indexOf(weapon) !== -1) return state
+ const toPlotData = [...state.data]
+ for (let index = 0; index < trend.counts.length; index++) {
+ let year = trend.counts[index].year
+ let year_arr = null
+ if (mode === "ALL") {
+ year_arr = mergeModeArrays(trend.counts[index])
+ } else {
+ year_arr = trend.counts[index][mode]
+ }
+
+ for (let i = 1; i < 13; i++) {
+ // if year is 2018 skipping to the index where values are found
+ if (year === 2018 && i < 5) {
+ i = 4
+ continue
+ }
+ const plotIndex = resolveStartIndex(i, year)
+ if (plotIndex === toPlotData.length) break
+ toPlotData[plotIndex][weapon] = year_arr[i]
+ }
+ }
+
+ const keyObj = {
+ weapon,
+ color: getColor(state)
+ }
+ return { data: toPlotData, keys: [...state.keys, keyObj] }
+ case "delete":
+ const weaponToDelete = action.weapon
+ const newKeys = [...state.keys].filter(k => k.weapon !== weaponToDelete)
+ return { data: state.data, keys: newKeys }
+ case "combine":
+ const weaponLeft = action.left
+ const weaponRight = action.right
+ const newKey = `${weaponLeft} + ${weaponRight}`
+ const newKeysWithCombined = [
+ ...state.keys,
+ { weapon: newKey, color: getColor(state) }
+ ].filter(k => k.weapon !== weaponLeft && k.weapon !== weaponRight)
+ const newDataWithCombined = [...state.data].map(d => {
+ const dataObj = { ...d }
+ let weaponLeftCount = 0
+ let weaponRightCount = 0
+ if (dataObj.hasOwnProperty(weaponLeft))
+ weaponLeftCount = dataObj[weaponLeft]
+ if (dataObj.hasOwnProperty(weaponRight))
+ weaponRightCount = dataObj[weaponRight]
+ dataObj[newKey] = weaponLeftCount + weaponRightCount
+ delete dataObj[weaponLeft]
+ delete dataObj[weaponRight]
+ return dataObj
+ })
+
+ return { data: newDataWithCombined, keys: newKeysWithCombined }
+ case "randomizeColor":
+ const keysWithNewColor = [...state.keys].map(k => {
+ if (k.weapon !== action.weapon) {
+ return k
+ }
+ return {
+ ...k,
+ color: "#000000".replace(/0/g, function() {
+ return (~~(Math.random() * 16)).toString(16)
+ })
+ }
+ })
+ return { data: state.data, keys: keysWithNewColor }
+ default:
+ throw new Error()
+ }
+}
+
+export default function useTrends(weapon, mode) {
+ const [plotData, dispatch] = useReducer(reducer, {
+ data: setPlotDataInitial(),
+ keys: []
+ })
+ // Skip query if there is no weapon provided
+ const { data, loading, error } = useQuery(searchForTrend, {
+ skip: !weapon,
+ variables: { weapon }
+ })
+
+ useEffect(() => {
+ if (loading || !data) return
+ dispatch({ type: "add", trendDocument: data.searchForTrend, mode })
+ }, [loading, data, mode])
+
+ return { loading, error, plotData, dispatch }
+}