├── scripts ├── teams.csv ├── users.csv ├── vips.csv ├── chatters.csv ├── followers.csv ├── moderators.csv ├── streamers.csv ├── streams.csv ├── full_chatters.csv ├── num_of_chatters.csv ├── README.md └── scraper.py ├── frontend ├── src │ ├── semantic-ui │ │ ├── site │ │ │ ├── modules │ │ │ │ ├── embed.variables │ │ │ │ ├── nag.overrides │ │ │ │ ├── tab.overrides │ │ │ │ ├── accordion.overrides │ │ │ │ ├── chatroom.overrides │ │ │ │ ├── checkbox.overrides │ │ │ │ ├── dimmer.overrides │ │ │ │ ├── dropdown.overrides │ │ │ │ ├── embed.overrides │ │ │ │ ├── modal.overrides │ │ │ │ ├── modal.variables │ │ │ │ ├── nag.variables │ │ │ │ ├── popup.overrides │ │ │ │ ├── progress.overrides │ │ │ │ ├── rating.overrides │ │ │ │ ├── rating.variables │ │ │ │ ├── search.overrides │ │ │ │ ├── search.variables │ │ │ │ ├── shape.overrides │ │ │ │ ├── sidebar.overrides │ │ │ │ ├── sidebar.variables │ │ │ │ ├── sticky.overrides │ │ │ │ ├── sticky.variables │ │ │ │ ├── tab.variables │ │ │ │ ├── accordion.variables │ │ │ │ ├── chatroom.variables │ │ │ │ ├── checkbox.variables │ │ │ │ ├── dimmer.variables │ │ │ │ ├── dropdown.variables │ │ │ │ ├── popup.variables │ │ │ │ ├── progress.variables │ │ │ │ ├── shape.variables │ │ │ │ ├── transition.overrides │ │ │ │ └── transition.variables │ │ │ ├── elements │ │ │ │ ├── flag.variables │ │ │ │ ├── button.overrides │ │ │ │ ├── divider.overrides │ │ │ │ ├── flag.overrides │ │ │ │ ├── header.overrides │ │ │ │ ├── icon.overrides │ │ │ │ ├── image.overrides │ │ │ │ ├── input.overrides │ │ │ │ ├── label.overrides │ │ │ │ ├── loader.overrides │ │ │ │ ├── rail.overrides │ │ │ │ ├── reveal.overrides │ │ │ │ ├── segment.overrides │ │ │ │ ├── step.overrides │ │ │ │ ├── button.variables │ │ │ │ ├── container.overrides │ │ │ │ ├── divider.variables │ │ │ │ ├── header.variables │ │ │ │ ├── icon.variables │ │ │ │ ├── image.variables │ │ │ │ ├── input.variables │ │ │ │ ├── label.variables │ │ │ │ ├── list.overrides │ │ │ │ ├── list.variables │ │ │ │ ├── loader.variables │ │ │ │ ├── rail.variables │ │ │ │ ├── reveal.variables │ │ │ │ ├── segment.variables │ │ │ │ ├── step.variables │ │ │ │ └── container.variables │ │ │ ├── collections │ │ │ │ ├── menu.overrides │ │ │ │ ├── form.overrides │ │ │ │ ├── grid.overrides │ │ │ │ ├── form.variables │ │ │ │ ├── grid.variables │ │ │ │ ├── menu.variables │ │ │ │ ├── message.overrides │ │ │ │ ├── table.overrides │ │ │ │ ├── table.variables │ │ │ │ ├── breadcrumb.overrides │ │ │ │ ├── breadcrumb.variables │ │ │ │ └── message.variables │ │ │ ├── globals │ │ │ │ ├── reset.overrides │ │ │ │ ├── reset.variables │ │ │ │ ├── site.overrides │ │ │ │ └── site.variables │ │ │ └── views │ │ │ │ ├── ad.overrides │ │ │ │ ├── ad.variables │ │ │ │ ├── card.overrides │ │ │ │ ├── card.variables │ │ │ │ ├── feed.overrides │ │ │ │ ├── feed.variables │ │ │ │ ├── item.overrides │ │ │ │ ├── item.variables │ │ │ │ ├── comment.overrides │ │ │ │ ├── comment.variables │ │ │ │ ├── statistic.overrides │ │ │ │ └── statistic.variables │ │ └── theme.config │ ├── setupTests.js │ ├── App.test.js │ ├── hooks │ │ └── useD3.js │ ├── index.css │ ├── reportWebVitals.js │ ├── index.js │ ├── App.css │ ├── components │ │ ├── GeneralStats.js │ │ ├── GraphHeader.js │ │ ├── DropdownComp.js │ │ ├── TableComp.js │ │ ├── RadioPick.js │ │ ├── DropdownStreamers.js │ │ ├── Counter.js │ │ ├── Vips.js │ │ ├── Teams.js │ │ ├── Moderators.js │ │ ├── FindStreamer.js │ │ ├── Games.js │ │ ├── FindStreamer2.js │ │ ├── GraphPR.js │ │ ├── HeaderComp.js │ │ ├── GraphBC.js │ │ ├── BC.js │ │ ├── AutoSearch.js │ │ ├── PageRank.js │ │ ├── Graph.js │ │ ├── AutoCompleteGame.js │ │ └── Streamers.js │ ├── logo.svg │ └── App.js ├── .dockerignore ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── mstile-150x150.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── social-logo-round-corners.png │ ├── browserconfig.xml │ ├── site.webmanifest │ ├── manifest.json │ ├── index.html │ └── safari-pinned-tab.svg ├── craco.config.js ├── Dockerfile └── package.json ├── backend ├── requirements.txt ├── Dockerfile ├── import-data │ ├── teams.csv │ ├── vips.csv │ ├── moderators.csv │ └── streamers.csv ├── models.py └── twitch_data.py ├── twitch-stream ├── requirements.txt ├── Dockerfile ├── setup.py ├── dummy.py └── chatters.csv ├── images ├── app_1.png ├── app_2.png ├── app_3.png └── app_4.png ├── memgraph ├── Dockerfile └── query_modules │ └── twitch.py ├── LICENSE ├── README.md ├── docker-compose.yml └── .gitignore /scripts/teams.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/users.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/vips.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/chatters.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/followers.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/moderators.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/streamers.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/streams.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/full_chatters.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/num_of_chatters.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/embed.variables: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.1.0 2 | gqlalchemy==1.1.2 -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .dockerignore 4 | Dockerfile -------------------------------------------------------------------------------- /twitch-stream/requirements.txt: -------------------------------------------------------------------------------- 1 | gqlalchemy==1.1.2 2 | kafka-python==2.0.2 3 | -------------------------------------------------------------------------------- /images/app_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/images/app_1.png -------------------------------------------------------------------------------- /images/app_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/images/app_2.png -------------------------------------------------------------------------------- /images/app_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/images/app_3.png -------------------------------------------------------------------------------- /images/app_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/images/app_4.png -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [{ plugin: require('@semantic-ui-react/craco-less') }], 3 | } -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/flag.variables: -------------------------------------------------------------------------------- 1 | /*------------------- 2 | Flag Variables 3 | --------------------*/ 4 | -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/menu.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/nag.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/tab.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/public/social-logo-round-corners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memgraph/twitch-analytics-demo/HEAD/frontend/public/social-logo-round-corners.png -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/form.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/grid.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/button.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/divider.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/flag.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/header.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/icon.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/image.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/input.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/label.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/loader.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/rail.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/reveal.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/segment.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/step.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/globals/reset.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/globals/reset.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Global Variables 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/globals/site.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/globals/site.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Global Variables 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/accordion.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/chatroom.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/checkbox.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/dimmer.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/dropdown.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/embed.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/modal.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/modal.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/nag.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/popup.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/progress.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/rating.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/rating.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/search.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/search.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/shape.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/sidebar.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/sidebar.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/sticky.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/sticky.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/tab.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/ad.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/ad.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/card.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/card.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/feed.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/feed.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/item.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/item.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/form.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/grid.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/menu.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/message.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/table.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/table.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/button.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/container.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/divider.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/header.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/icon.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/image.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/input.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/label.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/list.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/list.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/loader.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/rail.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/reveal.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/segment.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/step.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/accordion.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/chatroom.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/checkbox.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/dimmer.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/dropdown.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/popup.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/progress.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/shape.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/transition.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/comment.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/comment.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/statistic.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/views/statistic.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/breadcrumb.overrides: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/breadcrumb.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | Site Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/collections/message.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/elements/container.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/site/modules/transition.variables: -------------------------------------------------------------------------------- 1 | /******************************* 2 | User Variable Overrides 3 | *******************************/ 4 | -------------------------------------------------------------------------------- /memgraph/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM memgraph/memgraph-mage:1.3 2 | 3 | USER root 4 | 5 | # Copy the local query modules 6 | COPY query_modules/twitch.py /usr/lib/memgraph/query_modules/twitch.py 7 | 8 | USER memgraph -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/hooks/useD3.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import * as d3 from "d3"; 4 | 5 | export const useD3 = (renderGraph) => { 6 | const ref = React.useRef(); 7 | 8 | React.useEffect(() => { 9 | renderGraph(d3.select(ref.current)); 10 | 11 | return () => {}; 12 | }); 13 | 14 | return ref; 15 | }; 16 | -------------------------------------------------------------------------------- /twitch-stream/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | # Install CMake for gqlalchemy 4 | RUN apt-get update && \ 5 | apt-get --yes install cmake && \ 6 | rm -rf /var/lib/apt/lists/* 7 | 8 | # Install packages 9 | COPY requirements.txt ./ 10 | RUN pip3 install -r requirements.txt 11 | 12 | COPY dummy.py /app/dummy.py 13 | COPY setup.py /app/setup.py 14 | COPY chatters.csv /app/chatters.csv 15 | 16 | WORKDIR /app -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # pull official base image 2 | FROM node:14.17.5-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install app dependencies 11 | COPY package.json ./ 12 | COPY package-lock.json ./ 13 | RUN npm install --silent 14 | RUN npm install react-scripts@3.4.1 -g --silent 15 | 16 | # add app 17 | COPY . ./ 18 | 19 | # start app 20 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | # Install CMake 4 | RUN apt-get update && \ 5 | apt-get --yes install cmake && \ 6 | rm -rf /var/lib/apt/lists/* 7 | 8 | # Install packages 9 | COPY requirements.txt ./ 10 | RUN pip3 install -r requirements.txt 11 | 12 | COPY app.py /app/app.py 13 | COPY import-data /app/import-data 14 | WORKDIR /app 15 | 16 | ENV FLASK_ENV=development 17 | ENV LC_ALL=C.UTF-8 18 | ENV LANG=C.UTF-8 19 | 20 | ENTRYPOINT ["python3", "app.py", "--populate"] -------------------------------------------------------------------------------- /frontend/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/components/GeneralStats.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { Segment, Header } from "semantic-ui-react"; 3 | 4 | class GeneralStats extends Component { 5 | render() { 6 | return ( 7 | 13 |
25 | General statistics 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | export default GeneralStats; 33 | -------------------------------------------------------------------------------- /frontend/src/components/GraphHeader.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { Segment, Header } from "semantic-ui-react"; 3 | 4 | class GraphHeader extends Component { 5 | render() { 6 | return ( 7 | 13 |
25 | Graph visualization 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | export default GraphHeader; 33 | -------------------------------------------------------------------------------- /memgraph/query_modules/twitch.py: -------------------------------------------------------------------------------- 1 | import mgp 2 | import json 3 | 4 | 5 | @mgp.transformation 6 | def chatters(messages: mgp.Messages 7 | ) -> mgp.Record(query=str, parameters=mgp.Nullable[mgp.Map]): 8 | result_queries = [] 9 | 10 | for i in range(messages.total_messages()): 11 | message = messages.message_at(i) 12 | comment_info = json.loads(message.payload().decode('utf8')) 13 | result_queries.append( 14 | mgp.Record( 15 | query=("MERGE (u:User:Stream {id: $user_id}) " 16 | "MERGE (c:User {name: $chatter_login}) " 17 | "CREATE (c)-[:CHATTER]->(u)"), 18 | parameters={ 19 | "user_id": comment_info["user_id"], 20 | "chatter_login": comment_info["chatter_login"]})) 21 | 22 | return result_queries 23 | -------------------------------------------------------------------------------- /frontend/src/components/DropdownComp.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Dropdown, Grid } from "semantic-ui-react"; 3 | 4 | export default class DropdownComp extends Component { 5 | state = {}; 6 | 7 | twoCalls = (e, { value }) => { 8 | this.handleChange(e, { value }); 9 | this.changeStateParent(e, { value }); 10 | }; 11 | 12 | changeStateParent = (e, { value }) => { 13 | this.props.updateStateParent({ value }); 14 | }; 15 | 16 | handleChange = (e, { value }) => { 17 | this.setState({ value }); 18 | }; 19 | 20 | render() { 21 | const { value } = this.state; 22 | 23 | return ( 24 | 25 | 26 | 33 | 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # 💬 Description 2 | 3 | This script is used to generate .csv files from which you can create your graph database. We will use: 4 | 5 | - `streamers.csv` 6 | - `teams.csv` 7 | - `vips.csv` 8 | - `moderators.csv` 9 | - `chatters.csv` 10 | 11 | If you don't want to use already prepared .csv files from [here](https://github.com/memgraph/twitch-analytics-demo/tree/main/memgraph/import-data) which were scraped before, then feel free to scrape new data with this scraper and place the appropriate .csv files into [/memgraph/import-data](https://github.com/memgraph/twitch-analytics-demo/tree/main/memgraph/import-data) folder. 12 | 13 | # 👣 Instructions 14 | 15 | To run this script you have to have your own `client_id` and `client_secret` which you can get by signing up to [Twitch Dev](https://dev.twitch.tv/) and following the instructions from [Twitch API](https://dev.twitch.tv/docs/api/). When you get your `client_id` and `client_secret` then you run `scraper.py` with: 16 | 17 | ``` 18 | python scraper.py 19 | ``` 20 | After this, all scraped data will be written into .csv files. 21 | -------------------------------------------------------------------------------- /frontend/src/components/TableComp.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Table } from "semantic-ui-react"; 3 | 4 | class TableComp extends Component { 5 | renderRows() { 6 | return Object.keys(this.props.column_1).map((game, id) => { 7 | return ( 8 | 9 | 10 | {this.props.column_1[id][this.props.column_1_key]} 11 | 12 | 13 | {this.props.column_2[id][this.props.column_2_key]} 14 | 15 | 16 | ); 17 | }); 18 | } 19 | render() { 20 | return ( 21 | 22 | 23 | 24 | {this.props.headers[0]} 25 | {this.props.headers[1]} 26 | 27 | 28 | {this.renderRows()} 29 |
30 | ); 31 | } 32 | } 33 | 34 | export default TableComp; 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Katarina Supe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/import-data/teams.csv: -------------------------------------------------------------------------------- 1 | user_id,team_name 2 | 569342750,dreamsmp 3 | 124425501,riotgames 4 | 88946548,ader 5 | 88946548,nrg 6 | 156037856,esostreamteam 7 | 146549780,sktt1 8 | 77415295,gemagema 9 | 107305687,rustnchill 10 | 30104304,unitedtalentagency 11 | 38594688,luminosity 12 | 106013742,theteam 13 | 61433001,onmyway 14 | 38746172,otk 15 | 38746172,clique 16 | 54739364,esa 17 | 32053915,lestream 18 | 22253819,unitedtalentagency 19 | 29578325,bts 20 | 29578325,btsstats 21 | 37522866,friendzone 22 | 29722828,wve 23 | 32402794,csgopros 24 | 188890121,monke 25 | 188890121,freak 26 | 188890121,streamersalliance 27 | 68240113,esa 28 | 68240113,germench 29 | 152596920,geng 30 | 86131599,geng 31 | 181077473,tribogaules 32 | 45302947,chrchads 33 | 116885541,gfuel 34 | 46431370,faze 35 | 410965603,ggarmy 36 | 14408894,plantbasedgang 37 | 11897156,friendzone 38 | 11897156,callousrow 39 | 11897156,zoo 40 | 11897156,competitiveoverwatch 41 | 11897156,enemy 42 | 57025612,g2esports 43 | 38779717,ths 44 | 87791915,unique_streamers 45 | 135052907,plus1 46 | 44574567,theclique 47 | 21588571,gfuel 48 | 59635827,nrg 49 | 59635827,sanfranciscoshock 50 | 54121470,appearingoffline 51 | 54121470,tatmanarmy 52 | -------------------------------------------------------------------------------- /frontend/src/components/RadioPick.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Form, Radio } from "semantic-ui-react"; 3 | 4 | export default class RadioPick extends Component { 5 | state = { 6 | value: this.props.defaultValue, 7 | }; 8 | 9 | twoCalls = (e, { value }) => { 10 | this.handleChange(e, { value }); 11 | this.changeStateParent(e, { value }); 12 | }; 13 | 14 | handleChange = (e, { value }) => this.setState({ value }); 15 | changeStateParent = (e, { value }) => { 16 | this.props.updateStateParent({ value }); 17 | }; 18 | 19 | render() { 20 | return ( 21 |
22 | 23 | 30 | 31 | 32 | 39 | 40 |
41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/components/DropdownStreamers.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Dropdown } from "semantic-ui-react"; 3 | 4 | class DropdownStreamers extends Component { 5 | state = { 6 | value: "Followers", 7 | }; 8 | 9 | twoCalls = (e, { value }) => { 10 | this.handleChange(e, { value }); 11 | this.changeStateParent(e, { value }); 12 | }; 13 | 14 | changeStateParent = (e, { value }) => { 15 | this.props.updateStateParent({ value }); 16 | }; 17 | 18 | handleChange = (e, { value }) => { 19 | this.setState({ value }); 20 | }; 21 | 22 | render() { 23 | const { value } = this.state; 24 | return ( 25 | 33 | 34 | 40 | 46 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | export default DropdownStreamers; 53 | -------------------------------------------------------------------------------- /backend/models.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from app import memgraph 3 | from gqlalchemy import Node, Field, Relationship 4 | 5 | 6 | class User(Node): 7 | name: str = Field(index=True, exists=True, unique=True, db=memgraph) 8 | 9 | 10 | class Stream(User): 11 | name: Optional[str] = Field( 12 | index=True, exists=True, unique=True, db=memgraph, label="User" 13 | ) 14 | id: str = Field(index=True, exists=True, unique=True, db=memgraph) 15 | url: Optional[str] = Field() 16 | followers: Optional[int] = Field() 17 | createdAt: Optional[str] = Field() 18 | totalViewCount: Optional[int] = Field() 19 | description: Optional[str] = Field() 20 | 21 | 22 | class Language(Node): 23 | name: str = Field(unique=True, db=memgraph) 24 | 25 | 26 | class Game(Node): 27 | name: str = Field(unique=True, db=memgraph) 28 | 29 | 30 | class Team(Node): 31 | name: str = Field(unique=True, db=memgraph) 32 | 33 | 34 | class Speaks(Relationship, type="SPEAKS"): 35 | pass 36 | 37 | 38 | class Plays(Relationship, type="PLAYS"): 39 | pass 40 | 41 | 42 | class IsPartOf(Relationship, type="IS_PART_OF"): 43 | pass 44 | 45 | 46 | class Vip(Relationship, type="VIP"): 47 | pass 48 | 49 | 50 | class Moderator(Relationship, type="MODERATOR"): 51 | pass 52 | 53 | 54 | class Chatter(Relationship, type="CHATTER"): 55 | pass 56 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@babel/core": "^7.15.0", 7 | "@babel/preset-env": "^7.15.0", 8 | "@testing-library/jest-dom": "^5.11.4", 9 | "@testing-library/react": "^11.1.0", 10 | "@testing-library/user-event": "^12.1.10", 11 | "d3": "^7.0.1", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2", 14 | "react-scripts": "4.0.3", 15 | "react-scroll-to-component": "^1.0.2", 16 | "semantic-ui-css": "^2.4.1", 17 | "semantic-ui-react": "^2.0.3", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "craco start", 22 | "build": "craco build", 23 | "test": "craco test", 24 | "eject": "craco eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "proxy": "http://twitch-app:5000", 45 | "devDependencies": { 46 | "@craco/craco": "^6.2.0", 47 | "@semantic-ui-react/craco-less": "^1.2.3", 48 | "semantic-ui-less": "^2.4.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Twitch Analytics Demo 3 |

4 | 5 | ## :open_file_folder: Dataset 6 | The data was collected using [Twitch API](https://dev.twitch.tv/docs/api/). The files which we'll use are located in `/backend/import-data/` folder and are called: `streamers.csv`, `teams.csv`, `vips.csv`, `moderators.csv` and `chatters.csv`. 7 | 8 | ## :arrow_forward: Starting the app 9 | 10 | You can simply start the app by running: 11 | 12 | ``` 13 | docker-compose build 14 | docker-compose up core 15 | docker-compose up twitch-app 16 | ``` 17 | 18 | If you get the error `mgclient.OperationalError: couldn't connect to host: Connection refused` please try running `docker-compose up twitch-app` again. 19 | When data loading is done, run: 20 | 21 | ``` 22 | docker-compose up react-app 23 | ``` 24 | 25 | Check out the app at `localhost:3000`. 26 | 27 | To start streaming the rest of the data run: 28 | 29 | ``` 30 | docker-compose up twitch-stream 31 | ``` 32 | 33 | Notice how the number of nodes and edges are changing on the navigation bar. Also if you refresh the results of the PageRank below, you'll see the rank difference. 34 | 35 | ## :bar_chart: General statistics 36 | 37 | Choose from top games, teams, vips, moderators or streamers. 38 | 39 | ![](/images/app_2.png) 40 | 41 | ## :eyes: Graph visualization 42 | 43 | Find your favorite streamer or the streamers who stream your favorite game in your language. 44 | 45 | ![](/images/app_3.png) 46 | 47 | Check out the **PageRank** and **Betweenness Centrality** [MAGE](https://memgraph.com/docs/mage) algorithms. 48 | 49 | ![](/images/app_4.png) 50 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 32 | Twitch Demo 33 | 34 | 35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /frontend/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { Button } from "semantic-ui-react"; 3 | 4 | class Counter extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | isLoaded: false, 9 | error: false, 10 | counter: "0", 11 | }; 12 | } 13 | 14 | fetch() { 15 | fetch("/" + this.props.count) 16 | .then((res) => res.json()) 17 | .then( 18 | (result) => { 19 | this.setState({ 20 | isLoaded: true, 21 | counter: result[this.props.count], 22 | }); 23 | }, 24 | (error) => { 25 | this.setState({ 26 | isLoaded: true, 27 | error, 28 | }); 29 | } 30 | ); 31 | } 32 | 33 | componentDidMount() { 34 | this.interval = setInterval(() => this.fetch(), 3000); 35 | } 36 | componentWillUnmount() { 37 | clearInterval(this.interval); 38 | } 39 | 40 | render() { 41 | const { error, isLoaded, counter } = this.state; 42 | if (error) { 43 | return
Error: {error.message}
; 44 | } else if (!isLoaded) { 45 | return ( 46 | 49 | ); 50 | } else { 51 | if (this.props.count === "nodes") { 52 | return ( 53 | 57 | ); 58 | } else { 59 | return ( 60 | 64 | ); 65 | } 66 | } 67 | } 68 | } 69 | 70 | export default Counter; 71 | -------------------------------------------------------------------------------- /twitch-stream/setup.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from gqlalchemy import Memgraph, MemgraphKafkaStream 3 | from kafka.admin import KafkaAdminClient, NewTopic 4 | from kafka.errors import TopicAlreadyExistsError, NoBrokersAvailable 5 | from time import sleep 6 | 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | def connect_to_memgraph(memgraph_ip, memgraph_port): 12 | memgraph = Memgraph(host=memgraph_ip, port=int(memgraph_port)) 13 | while True: 14 | try: 15 | if memgraph._get_cached_connection().is_active(): 16 | return memgraph 17 | except: 18 | log.info("Memgraph probably isn't running.") 19 | sleep(1) 20 | 21 | 22 | def get_admin_client(kafka_ip, kafka_port): 23 | retries = 30 24 | while True: 25 | try: 26 | admin_client = KafkaAdminClient( 27 | bootstrap_servers=kafka_ip + ":" + kafka_port, client_id="twitch-stream" 28 | ) 29 | return admin_client 30 | except NoBrokersAvailable: 31 | retries -= 1 32 | if not retries: 33 | raise 34 | log.info("Failed to connect to Kafka") 35 | sleep(1) 36 | 37 | 38 | def run(memgraph, kafka_ip, kafka_port): 39 | admin_client = get_admin_client(kafka_ip, kafka_port) 40 | log.info("Connected to Kafka") 41 | 42 | topic_list = [ 43 | NewTopic(name="chatters", num_partitions=1, replication_factor=1), 44 | ] 45 | 46 | try: 47 | admin_client.create_topics(new_topics=topic_list, validate_only=False) 48 | except TopicAlreadyExistsError: 49 | pass 50 | log.info("Created topics") 51 | 52 | log.info("Creating stream connections on Memgraph") 53 | stream = MemgraphKafkaStream( 54 | name="chatter_stream", 55 | topics=["chatters"], 56 | transform="twitch.chatters", 57 | bootstrap_servers="'kafka:9092'", 58 | ) 59 | memgraph.create_stream(stream) 60 | memgraph.start_stream(stream) 61 | -------------------------------------------------------------------------------- /twitch-stream/dummy.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from argparse import ArgumentParser 4 | from kafka import KafkaProducer 5 | from kafka.errors import NoBrokersAvailable 6 | from time import sleep 7 | import setup 8 | 9 | 10 | KAFKA_IP = os.getenv('KAFKA_IP', 'kafka') 11 | KAFKA_PORT = os.getenv('KAFKA_PORT', '9092') 12 | MEMGRAPH_IP = os.getenv('MEMGRAPH_IP', 'memgraph-mage') 13 | MEMGRAPH_PORT = os.getenv('MEMGRAPH_PORT', '7687') 14 | 15 | 16 | def parse_args(): 17 | """ 18 | Parse input command line arguments. 19 | """ 20 | parser = ArgumentParser( 21 | description="A Twitch stream machine powered by Memgraph.") 22 | parser.add_argument("--file", help="File with chatter data.") 23 | parser.add_argument( 24 | "--interval", 25 | type=int, 26 | help="Interval for sending data in seconds.") 27 | return parser.parse_args() 28 | 29 | 30 | def create_kafka_producer(): 31 | retries = 30 32 | while True: 33 | try: 34 | producer = KafkaProducer( 35 | bootstrap_servers=KAFKA_IP + ':' + KAFKA_PORT) 36 | return producer 37 | except NoBrokersAvailable: 38 | retries -= 1 39 | if not retries: 40 | raise 41 | print("Failed to connect to Kafka") 42 | sleep(1) 43 | 44 | 45 | def main(): 46 | args = parse_args() 47 | 48 | memgraph = setup.connect_to_memgraph(MEMGRAPH_IP, MEMGRAPH_PORT) 49 | setup.run(memgraph, KAFKA_IP, KAFKA_PORT) 50 | 51 | producer = create_kafka_producer() 52 | with open(args.file) as f: 53 | for line in f.readlines(): 54 | line_list = line.strip().split(",") 55 | line_json = { 56 | 'user_id': line_list[0], 57 | 'chatter_login': line_list[1] 58 | } 59 | 60 | print(f'Sending data to chatters') 61 | producer.send("chatters", json.dumps(line_json).encode('utf8')) 62 | sleep(args.interval) 63 | 64 | 65 | if __name__ == "__main__": 66 | main() 67 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | networks: 3 | app-tier: 4 | driver: bridge 5 | services: 6 | zookeeper: 7 | image: "bitnami/zookeeper:3.7" 8 | ports: 9 | - "2181:2181" 10 | environment: 11 | - ALLOW_ANONYMOUS_LOGIN=yes 12 | networks: 13 | - app-tier 14 | logging: 15 | driver: none 16 | kafka: 17 | image: "bitnami/kafka:2" 18 | logging: 19 | driver: none 20 | ports: 21 | - "9093:9093" 22 | environment: 23 | - KAFKA_BROKER_ID=1 24 | - ALLOW_PLAINTEXT_LISTENER=yes 25 | - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 26 | - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CLIENT:PLAINTEXT,EXTERNAL:PLAINTEXT 27 | - KAFKA_CFG_LISTENERS=CLIENT://:9092,EXTERNAL://:9093 28 | - KAFKA_CFG_ADVERTISED_LISTENERS=CLIENT://kafka:9092,EXTERNAL://localhost:9093 29 | - KAFKA_INTER_BROKER_LISTENER_NAME=CLIENT 30 | depends_on: 31 | - zookeeper 32 | networks: 33 | - app-tier 34 | memgraph-mage: 35 | build: ./memgraph 36 | entrypoint: 37 | [ 38 | "/usr/lib/memgraph/memgraph", 39 | ] 40 | ports: 41 | - "7687:7687" 42 | networks: 43 | - app-tier 44 | core: 45 | image: tianon/true 46 | restart: "no" 47 | depends_on: 48 | - kafka 49 | - memgraph-mage 50 | twitch-app: 51 | build: ./backend 52 | volumes: 53 | - ./backend:/app 54 | ports: 55 | - "5000:5000" 56 | environment: 57 | MG_HOST: memgraph-mage 58 | MG_PORT: 7687 59 | depends_on: 60 | - memgraph-mage 61 | networks: 62 | - app-tier 63 | react-app: 64 | build: ./frontend 65 | volumes: 66 | - ./frontend:/app 67 | - /app/node_modules 68 | ports: 69 | - "3000:3000" 70 | depends_on: 71 | - twitch-app 72 | networks: 73 | - app-tier 74 | twitch-stream: 75 | build: ./twitch-stream 76 | volumes: 77 | - ./twitch-stream:/app 78 | entrypoint: 79 | [ 80 | "python3", 81 | "dummy.py", 82 | "--file=chatters.csv", 83 | "--interval=1" 84 | ] 85 | environment: 86 | KAFKA_IP: kafka 87 | KAFKA_PORT: "9092" 88 | MEMGRAPH_IP: memgraph-mage 89 | MEMGRAPH_PORT: "7687" 90 | depends_on: 91 | - core 92 | networks: 93 | - app-tier 94 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/semantic-ui/theme.config: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ████████╗██╗ ██╗███████╗███╗ ███╗███████╗███████╗ 4 | ╚══██╔══╝██║ ██║██╔════╝████╗ ████║██╔════╝██╔════╝ 5 | ██║ ███████║█████╗ ██╔████╔██║█████╗ ███████╗ 6 | ██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══╝ ╚════██║ 7 | ██║ ██║ ██║███████╗██║ ╚═╝ ██║███████╗███████║ 8 | ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝ 9 | 10 | */ 11 | 12 | /******************************* 13 | Theme Selection 14 | *******************************/ 15 | 16 | /* To override a theme for an individual element 17 | specify theme name below 18 | */ 19 | 20 | /* Global */ 21 | @site : 'default'; 22 | @reset : 'default'; 23 | 24 | /* Elements */ 25 | @button : 'default'; 26 | @container : 'default'; 27 | @divider : 'default'; 28 | @flag : 'default'; 29 | @header : 'default'; 30 | @icon : 'default'; 31 | @image : 'default'; 32 | @input : 'default'; 33 | @label : 'default'; 34 | @list : 'default'; 35 | @loader : 'default'; 36 | @placeholder : 'default'; 37 | @rail : 'default'; 38 | @reveal : 'default'; 39 | @segment : 'default'; 40 | @step : 'default'; 41 | 42 | /* Collections */ 43 | @breadcrumb : 'default'; 44 | @form : 'default'; 45 | @grid : 'default'; 46 | @menu : 'default'; 47 | @message : 'default'; 48 | @table : 'default'; 49 | 50 | /* Modules */ 51 | @accordion : 'default'; 52 | @checkbox : 'default'; 53 | @dimmer : 'default'; 54 | @dropdown : 'default'; 55 | @embed : 'default'; 56 | @modal : 'default'; 57 | @nag : 'default'; 58 | @popup : 'default'; 59 | @progress : 'default'; 60 | @rating : 'default'; 61 | @search : 'default'; 62 | @shape : 'default'; 63 | @sidebar : 'default'; 64 | @sticky : 'default'; 65 | @tab : 'default'; 66 | @transition : 'default'; 67 | 68 | /* Views */ 69 | @ad : 'default'; 70 | @card : 'default'; 71 | @comment : 'default'; 72 | @feed : 'default'; 73 | @item : 'default'; 74 | @statistic : 'default'; 75 | 76 | /******************************* 77 | Folders 78 | *******************************/ 79 | 80 | /* Path to theme packages */ 81 | @themesFolder : 'themes'; 82 | 83 | /* Path to site override folder */ 84 | @siteFolder : '../../src/semantic-ui/site'; 85 | 86 | 87 | /******************************* 88 | Import Theme 89 | *******************************/ 90 | 91 | @import (multiple) "~semantic-ui-less/theme.less"; 92 | @fontPath : '../../../themes/@{theme}/assets/fonts'; 93 | 94 | /* End Config */ 95 | -------------------------------------------------------------------------------- /frontend/src/components/Vips.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | Grid, 4 | Segment, 5 | Header, 6 | Dimmer, 7 | Loader, 8 | } from "semantic-ui-react"; 9 | import TableComp from "./TableComp"; 10 | 11 | class Vips extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | error: null, 16 | isLoaded: false, 17 | vips: [], 18 | streamers: [], 19 | numOfVips: "10", 20 | header: "Top 10 vips", 21 | }; 22 | } 23 | 24 | fetchData(number) { 25 | fetch("/top-vips/" + number) 26 | .then((res) => res.json()) 27 | .then( 28 | (result) => { 29 | this.setState({ 30 | isLoaded: true, 31 | vips: result.vips, 32 | streamers: result.streamers, 33 | }); 34 | }, 35 | (error) => { 36 | this.setState({ 37 | isLoaded: true, 38 | error, 39 | }); 40 | } 41 | ); 42 | this.setState({ 43 | numOfVips: number, 44 | header: "Top " + number + " vips", 45 | }); 46 | } 47 | componentDidMount() { 48 | this.fetchData(this.state.numOfVips); 49 | } 50 | 51 | render() { 52 | const { error, isLoaded, header } = this.state; 53 | const paragraph = 54 | "Find out which user has the most vip badges."; 55 | const headers = ["Vip", "Number of badges"]; 56 | if (error) { 57 | return
Error: {error.message}
; 58 | } else if (!isLoaded) { 59 | return ( 60 | 61 | 62 | 63 | Getting top vips 64 | 65 | 66 | 67 | ); 68 | } else { 69 | return ( 70 | 71 | 72 | 73 | 74 |
75 | {header} 76 |
77 |

{paragraph}

78 |
79 | 80 | 87 | 88 |
89 |
90 |
91 | ); 92 | } 93 | } 94 | } 95 | 96 | export default Vips; 97 | -------------------------------------------------------------------------------- /frontend/src/components/Teams.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | Grid, 4 | Segment, 5 | Header, 6 | Dimmer, 7 | Loader, 8 | } from "semantic-ui-react"; 9 | import TableComp from "./TableComp"; 10 | 11 | class Teams extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | error: null, 16 | isLoaded: false, 17 | teams: [], 18 | members: [], 19 | numOfTeams: "10", 20 | header: "Top 10 teams", 21 | }; 22 | } 23 | 24 | fetchData(number) { 25 | fetch("/top-teams/" + number) 26 | .then((res) => res.json()) 27 | .then( 28 | (result) => { 29 | this.setState({ 30 | isLoaded: true, 31 | teams: result.teams, 32 | members: result.members, 33 | }); 34 | }, 35 | (error) => { 36 | this.setState({ 37 | isLoaded: true, 38 | error, 39 | }); 40 | } 41 | ); 42 | this.setState({ 43 | numOfTeams: number, 44 | header: "Top " + number + " teams", 45 | }); 46 | } 47 | 48 | componentDidMount() { 49 | this.fetchData(this.state.numOfTeams); 50 | } 51 | 52 | render() { 53 | const { error, isLoaded, header } = this.state; 54 | const paragraph = 55 | "Find out which teams are the most popular."; 56 | const headers = ["Team", "Number of members"]; 57 | if (error) { 58 | return
Error: {error.message}
; 59 | } else if (!isLoaded) { 60 | return ( 61 | 62 | 63 | 64 | Getting top teams 65 | 66 | 67 | 68 | ); 69 | } else { 70 | return ( 71 | 72 | 73 | 74 | 75 |
76 | {header} 77 |
78 |

{paragraph}

79 |
80 | 81 | 88 | 89 |
90 |
91 |
92 | ); 93 | } 94 | } 95 | } 96 | 97 | export default Teams; 98 | -------------------------------------------------------------------------------- /frontend/src/components/Moderators.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | Grid, 4 | Segment, 5 | Header, 6 | Dimmer, 7 | Loader, 8 | } from "semantic-ui-react"; 9 | import TableComp from "./TableComp"; 10 | 11 | class Moderators extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | error: null, 16 | isLoaded: false, 17 | moderators: [], 18 | streamers: [], 19 | numOfMods: "10", 20 | header: "Top 10 moderators", 21 | }; 22 | } 23 | 24 | fetchData(number) { 25 | fetch("/top-moderators/" + number) 26 | .then((res) => res.json()) 27 | .then( 28 | (result) => { 29 | this.setState({ 30 | isLoaded: true, 31 | moderators: result.moderators, 32 | streamers: result.streamers, 33 | }); 34 | }, 35 | (error) => { 36 | this.setState({ 37 | isLoaded: true, 38 | error, 39 | }); 40 | } 41 | ); 42 | this.setState({ 43 | numOfMods: number, 44 | header: "Top " + number + " moderators", 45 | }); 46 | } 47 | 48 | componentDidMount() { 49 | this.fetchData(this.state.numOfMods); 50 | } 51 | 52 | render() { 53 | const { error, isLoaded, header } = this.state; 54 | const headers = ["Moderator", "Number of channels"]; 55 | const paragraph = 56 | "Find out which user is the most popular channel moderator."; 57 | if (error) { 58 | return
Error: {error.message}
; 59 | } else if (!isLoaded) { 60 | return ( 61 | 62 | 63 | 64 | Getting top moderators 65 | 66 | 67 | 68 | ); 69 | } else { 70 | return ( 71 | 72 | 73 | 74 | 75 |
76 | {header} 77 |
78 |

{paragraph}

79 |
80 | 81 | 88 | 89 |
90 |
91 |
92 | ); 93 | } 94 | } 95 | } 96 | 97 | export default Moderators; 98 | -------------------------------------------------------------------------------- /frontend/src/components/FindStreamer.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { Grid, Segment, Header, Dimmer, Loader } from "semantic-ui-react"; 3 | import AutoSearch from "./AutoSearch"; 4 | import Graph from "./Graph"; 5 | 6 | class FindStreamer extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | error: null, 11 | isLoaded: false, 12 | nodes: [], 13 | links: [], 14 | streamerName: "Fextralife", 15 | }; 16 | } 17 | 18 | fetchData(name) { 19 | fetch("/streamer/" + name) 20 | .then((res) => res.json()) 21 | .then( 22 | (result) => { 23 | this.setState({ 24 | isLoaded: true, 25 | nodes: result.nodes, 26 | links: result.links, 27 | }); 28 | }, 29 | (error) => { 30 | this.setState({ 31 | isLoaded: true, 32 | error, 33 | }); 34 | } 35 | ); 36 | this.setState({ 37 | streamerName: name, 38 | }); 39 | } 40 | 41 | componentDidMount() { 42 | this.fetchData(this.state.streamerName); 43 | } 44 | 45 | updateStreamerName = (strName) => { 46 | this.fetchData(strName); 47 | }; 48 | 49 | render() { 50 | const { error, isLoaded } = this.state; 51 | if (error) { 52 | return
Error: {error.message}
; 53 | } else if (!isLoaded) { 54 | return ( 55 | 56 | 57 | 58 | Finding your streamer 59 | 60 | 61 | 62 | ); 63 | } else { 64 | const header = "Get to know your favourite streamer"; 65 | const paragraph = 66 | "Games, teams and languages - all you need to know about your favourite streamer."; 67 | const square = { width: 250, height: 250 }; 68 | return ( 69 | 70 | 71 | 72 | 73 |
74 | {header} 75 |
76 |

{paragraph}

77 |

78 | 79 |
80 | 81 | 82 | 83 | 84 | 85 |
86 |
87 |
88 | ); 89 | } 90 | } 91 | } 92 | 93 | export default FindStreamer; 94 | -------------------------------------------------------------------------------- /frontend/src/components/Games.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | Grid, 4 | Segment, 5 | Header, 6 | Dimmer, 7 | Loader, 8 | } from "semantic-ui-react"; 9 | import TableComp from "./TableComp"; 10 | 11 | class Games extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | error: null, 16 | isLoaded: false, 17 | games: [], 18 | players: [], 19 | numOfGames: "10", 20 | header: "Top 10 games", 21 | }; 22 | } 23 | 24 | fetchData(number) { 25 | fetch("/top-games/" + number) 26 | .then((res) => res.json()) 27 | .then( 28 | (result) => { 29 | this.setState({ 30 | isLoaded: true, 31 | games: result.games, 32 | players: result.players, 33 | }); 34 | }, 35 | (error) => { 36 | this.setState({ 37 | isLoaded: true, 38 | error, 39 | }); 40 | } 41 | ); 42 | this.setState({ 43 | numOfGames: number, 44 | header: "Top " + number + " games", 45 | }); 46 | } 47 | 48 | componentDidMount() { 49 | this.fetchData(this.state.numOfGames); 50 | } 51 | 52 | 53 | render() { 54 | 55 | const { error, isLoaded, header } = this.state; 56 | const paragraph = 57 | "Find out which games are played by the largest number of streamers."; 58 | const headers = ["Game", "Number of players"]; 59 | if (error) { 60 | return
Error: {error.message}
; 61 | } else if (!isLoaded) { 62 | return ( 63 | 64 | 65 | 66 | Getting top games 67 | 68 | 69 | 70 | ); 71 | } else { 72 | return ( 73 | 74 | 75 | 76 | 77 |
78 | {header} 79 |
80 |

{paragraph}

81 |
82 | 83 | 90 | 91 |
92 |
93 |
94 | ); 95 | } 96 | } 97 | } 98 | 99 | export default Games; 100 | -------------------------------------------------------------------------------- /frontend/src/components/FindStreamer2.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { Grid, Segment, Header, Dimmer, Loader } from "semantic-ui-react"; 3 | import Graph from "./Graph"; 4 | import AutoCompleteGame from "./AutoCompleteGame"; 5 | 6 | class FindStreamer2 extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | error: null, 11 | isLoaded: false, 12 | nodes: [], 13 | links: [], 14 | streamerName: "Fextralife", 15 | game: "League of Legends", 16 | language: "en", 17 | }; 18 | } 19 | 20 | fetchData(gameName, lang) { 21 | fetch("/streamers/" + lang + "/" + gameName) 22 | .then((res) => res.json()) 23 | .then( 24 | (result) => { 25 | this.setState({ 26 | isLoaded: true, 27 | nodes: result.nodes, 28 | links: result.links, 29 | }); 30 | }, 31 | (error) => { 32 | this.setState({ 33 | isLoaded: true, 34 | error, 35 | }); 36 | } 37 | ); 38 | 39 | this.setState({ 40 | game: gameName, 41 | language: lang, 42 | }); 43 | } 44 | 45 | componentDidMount() { 46 | this.fetchData(this.state.game, this.state.language); 47 | } 48 | 49 | updateGameLang = (gameName, lang) => { 50 | this.fetchData(gameName, lang); 51 | }; 52 | 53 | render() { 54 | const { error, isLoaded } = this.state; 55 | if (error) { 56 | return
Error: {error.message}
; 57 | } else if (!isLoaded) { 58 | return ( 59 | 60 | 61 | 62 | Finding all streamers 63 | 64 | 65 | 66 | ); 67 | } else { 68 | const header = "Find streamers by game and language"; 69 | const paragraph = 70 | "Find all streamers who stream your favourite game in your language."; 71 | const square = { width: 250, height: 250 }; 72 | return ( 73 | 74 | 75 | 76 | 77 |
78 | {header} 79 |
80 |

{paragraph}

81 |

82 | 83 |
84 | 85 | 86 | 87 | 88 | 89 |
90 |
91 |
92 | ); 93 | } 94 | } 95 | } 96 | 97 | export default FindStreamer2; 98 | -------------------------------------------------------------------------------- /frontend/src/components/GraphPR.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useD3 } from "../hooks/useD3"; 3 | import * as d3 from "d3"; 4 | 5 | function GraphPR(props) { 6 | const ref = useD3( 7 | (svg) => { 8 | svg.selectAll("*").remove(); 9 | const height = 500; 10 | const width = 750; 11 | const simulation = d3 12 | .forceSimulation(props.nodes) 13 | .force( 14 | "x", 15 | d3.forceX().x((d) => d.x) 16 | ) 17 | .force( 18 | "y", 19 | d3.forceY().y((d) => d.y) 20 | ) 21 | .force("charge", d3.forceManyBody()) 22 | .force("center", d3.forceCenter(width / 2, height / 2)) 23 | .force( 24 | "collide", 25 | d3.forceCollide().radius((d) => d.rank * 3000) 26 | ); 27 | 28 | const node = svg 29 | .append("g") 30 | .attr("stroke", "#fff") 31 | .attr("stroke-width", 1.5) 32 | .selectAll("circle") 33 | .data(props.nodes) 34 | .join("circle") 35 | .attr("r", function (d) { 36 | return d.rank * 1000; 37 | }) 38 | .attr("class", "node") 39 | .attr("fill", "#ff7701") 40 | .call(drag(simulation)); 41 | 42 | var label = svg 43 | .selectAll(null) 44 | .data(props.nodes) 45 | .enter() 46 | .append("text") 47 | .text(function (d) { 48 | return d.name; 49 | }) 50 | .style("text-anchor", "middle") 51 | .style("fill", "#1b1c1d") 52 | .style("font-family", "Arial") 53 | .attr("font-weight", "700") 54 | .style("font-size", "12px"); 55 | 56 | simulation.on("tick", () => { 57 | node.attr("cx", (d) => d.x).attr("cy", (d) => d.y); 58 | label 59 | .attr("x", function (d) { 60 | return d.x; 61 | }) 62 | .attr("y", function (d) { 63 | return d.y - d.rank * 1000 - 5; 64 | }); 65 | }); 66 | }, 67 | [props.nodes.length] 68 | ); 69 | 70 | const drag = (simulation) => { 71 | function dragstarted(event) { 72 | if (!event.active) simulation.alphaTarget(0.3).restart(); 73 | event.subject.fx = event.subject.x; 74 | event.subject.fy = event.subject.y; 75 | } 76 | 77 | function dragged(event) { 78 | event.subject.fx = event.x; 79 | event.subject.fy = event.y; 80 | } 81 | 82 | function dragended(event) { 83 | if (!event.active) simulation.alphaTarget(0); 84 | event.subject.fx = null; 85 | event.subject.fy = null; 86 | } 87 | 88 | return d3 89 | .drag() 90 | .on("start", dragstarted) 91 | .on("drag", dragged) 92 | .on("end", dragended); 93 | }; 94 | return ( 95 | 104 | ); 105 | } 106 | 107 | export default GraphPR; 108 | -------------------------------------------------------------------------------- /frontend/src/components/HeaderComp.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | Button, 4 | Container, 5 | Menu, 6 | Segment, 7 | Visibility, 8 | Header, 9 | MenuItem, 10 | } from "semantic-ui-react"; 11 | import Counter from "./Counter"; 12 | 13 | function TwitchHeading(props) { 14 | return ( 15 | 16 |
27 |
37 | 38 | ); 39 | } 40 | 41 | class HeaderComp extends Component { 42 | state = {}; 43 | hideFixedMenu = () => this.setState({ fixed: false }); 44 | showFixedMenu = () => this.setState({ fixed: true }); 45 | handleClick = (e) => this.props.scrollTarget(e.target.id); 46 | handleClickFromHeader = (id) => this.props.scrollTarget(id); 47 | render() { 48 | const { fixed } = this.state; 49 | 50 | return ( 51 | 56 | 62 | 69 | 70 | 79 | 80 | 81 | 82 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | ); 103 | } 104 | } 105 | 106 | export default HeaderComp; 107 | -------------------------------------------------------------------------------- /frontend/src/components/GraphBC.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useD3 } from "../hooks/useD3"; 3 | import * as d3 from "d3"; 4 | 5 | function GraphPR(props) { 6 | const ref = useD3( 7 | (svg) => { 8 | svg.selectAll("*").remove(); 9 | const height = 500; 10 | const width = 750; 11 | 12 | const simulation = d3 13 | .forceSimulation(props.nodes) 14 | .force( 15 | "x", 16 | d3.forceX().x((d) => d.x) 17 | ) 18 | .force( 19 | "y", 20 | d3.forceY().y((d) => d.y) 21 | ) 22 | .force("charge", d3.forceManyBody()) 23 | .force("center", d3.forceCenter(width / 2, height / 2)) 24 | .force( 25 | "collide", 26 | d3.forceCollide().radius((d) => d.betweenness_centrality * 7000000) 27 | ); 28 | 29 | const node = svg 30 | .append("g") 31 | .attr("stroke", "#fff") 32 | .attr("stroke-width", 1.5) 33 | .selectAll("circle") 34 | .data(props.nodes) 35 | .join("circle") 36 | .attr("r", function (d) { 37 | return d.betweenness_centrality * 3000000; 38 | }) 39 | .attr("class", "node") 40 | .attr("fill", "#ff7701") 41 | .call(drag(simulation)); 42 | 43 | var label = svg 44 | .selectAll(null) 45 | .data(props.nodes) 46 | .enter() 47 | .append("text") 48 | .text(function (d) { 49 | return d.name; 50 | }) 51 | .style("text-anchor", "middle") 52 | .style("fill", "#1b1c1d") 53 | .style("font-family", "Arial") 54 | .attr("font-weight", "700") 55 | .style("font-size", "12px"); 56 | 57 | simulation.on("tick", () => { 58 | node.attr("cx", (d) => d.x).attr("cy", (d) => d.y); 59 | label 60 | .attr("x", function (d) { 61 | return d.x; 62 | }) 63 | .attr("y", function (d) { 64 | return d.y - d.betweenness_centrality * 3000000 - 5; 65 | }); 66 | }); 67 | }, 68 | [props.nodes.length] 69 | ); 70 | 71 | const drag = (simulation) => { 72 | function dragstarted(event) { 73 | if (!event.active) simulation.alphaTarget(0.5).restart(); 74 | event.subject.fx = event.subject.x; 75 | event.subject.fy = event.subject.y; 76 | } 77 | 78 | function dragged(event) { 79 | event.subject.fx = event.x; 80 | event.subject.fy = event.y; 81 | } 82 | 83 | function dragended(event) { 84 | if (!event.active) simulation.alphaTarget(0); 85 | event.subject.fx = null; 86 | event.subject.fy = null; 87 | } 88 | 89 | return d3 90 | .drag() 91 | .on("start", dragstarted) 92 | .on("drag", dragged) 93 | .on("end", dragended); 94 | }; 95 | return ( 96 | 105 | ); 106 | } 107 | 108 | export default GraphPR; 109 | -------------------------------------------------------------------------------- /frontend/src/components/BC.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { Grid, Segment, Header, Dimmer, Loader } from "semantic-ui-react"; 3 | import GraphBC from "./GraphBC"; 4 | import TableComp from "./TableComp"; 5 | 6 | class BC extends Component { 7 | _isMounted = false; 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | error: null, 12 | isLoaded: false, 13 | nodes: [], 14 | }; 15 | } 16 | 17 | componentDidMount() { 18 | this._isMounted = true; 19 | fetch("/betweenness-centrality") 20 | .then((res) => res.json()) 21 | .then( 22 | (result) => { 23 | this.setState({ 24 | isLoaded: true, 25 | nodes: result.bc, 26 | }); 27 | }, 28 | (error) => { 29 | this.setState({ 30 | isLoaded: true, 31 | error, 32 | }); 33 | } 34 | ); 35 | } 36 | 37 | componentWillUnmount() { 38 | this._isMounted = false; 39 | } 40 | render() { 41 | const { error, isLoaded } = this.state; 42 | if (error) { 43 | return
Error: {error.message}
; 44 | } else if (!isLoaded) { 45 | return ( 46 | 47 | 48 | 49 | Calculating Betweenness centrality 50 | 51 | 52 | 53 | ); 54 | } else { 55 | const { nodes } = this.state; 56 | const header = "Which Twitch user has the most influence?"; 57 | const paragraph = 58 | "Find out which user is most influential using Betweenness centrality algorithm."; 59 | const headers = ["Streamer", "BC"]; 60 | let top10nodes = nodes.slice(0, 10); 61 | return ( 62 | 63 | 64 | 65 |
66 | {header} 67 |
68 |

69 | {paragraph} 70 |

71 |
72 |
73 |

74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 92 | 93 | 94 |
95 | ); 96 | } 97 | } 98 | } 99 | 100 | export default BC; 101 | -------------------------------------------------------------------------------- /frontend/src/components/AutoSearch.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import React, { useState } from "react"; 3 | import { Search, Grid, Label, Button } from "semantic-ui-react"; 4 | 5 | const initialState = { 6 | loading: false, 7 | results: [], 8 | value: "", 9 | }; 10 | 11 | function exampleReducer(state, action) { 12 | switch (action.type) { 13 | case "CLEAN_QUERY": 14 | return initialState; 15 | case "START_SEARCH": 16 | return { ...state, loading: true, value: action.query }; 17 | case "FINISH_SEARCH": 18 | return { ...state, loading: false, results: action.results }; 19 | case "UPDATE_SELECTION": 20 | return { ...state, value: action.selection }; 21 | 22 | default: 23 | throw new Error(); 24 | } 25 | } 26 | const resultRenderer = ({ title }) =>