├── .dockerignore ├── .gitignore ├── Botometer-cache.pdf ├── DEPLOYMENT_README.md ├── LICENSE.txt ├── README.md ├── deploy_website.sh ├── docker-compose.yml ├── documentation ├── graph.js.md ├── timeline.md ├── twitter_search_timeline.md └── vue-app.md ├── frontend ├── android-chrome-192x192.png ├── android-chrome-256x256.png ├── apple-touch-icon.png ├── browserconfig.xml ├── config.example.js ├── config.min.js ├── faq.php ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── includes │ ├── footer.html │ ├── header.html │ └── includes.html ├── index.php ├── index_commented.php ├── mstile-150x150.png ├── news_sources │ └── README.md ├── safari-pinned-tab.svg ├── site.webmanifest ├── static │ ├── 5bots.png │ ├── 5nots.png │ ├── assets │ │ ├── Twitter_logo_blue_32-cropped.png │ │ ├── default.png │ │ ├── favicon.ico │ │ └── forkme.png │ ├── css │ │ ├── external.css │ │ ├── external.min.css │ │ ├── hoaxy_styles.css │ │ ├── hoaxy_styles.min.css │ │ ├── nv.d3.css │ │ ├── nv.d3.min.css │ │ ├── widget.css │ │ └── widget.min.css │ ├── js │ │ ├── axios.min.js │ │ ├── d3.v3.min.js │ │ ├── es6-promise.auto.min.js │ │ ├── es6-promise.min.js │ │ ├── getparas.js │ │ ├── graph.js │ │ ├── graph.min.js │ │ ├── index.js │ │ ├── index.min.js │ │ ├── jquery.min.js │ │ ├── moment.min.js │ │ ├── moment@2.18.1.js │ │ ├── nv.d3.min.js │ │ ├── oauth.min.js │ │ ├── papaparse.min.js │ │ ├── prybar.all.min.js │ │ ├── timeline.js │ │ ├── timeline.min.js │ │ ├── twitter.js │ │ ├── twitter.min.js │ │ ├── twitter_search_timeline.js │ │ ├── twitter_search_timeline.min.js │ │ ├── vue-app.js │ │ ├── vue-app.min.js │ │ ├── vue@2.4.0.js │ │ └── vue@2.4.0.min.js │ ├── sigmaplugins │ │ ├── sigma.layout.forceAtlas2.min.js │ │ ├── sigma.renderers.parallelEdges.min.js │ │ ├── sigma.renderers.snapshot.js │ │ └── sigma.renderers.snapshot.min.js │ ├── tutorial_slides │ │ ├── Slide1.PNG │ │ └── Slide2.PNG │ └── widget_images │ │ └── HoaxyLogo.png └── stats.php ├── hoaxy_botometer_flowchart.png ├── nginx.conf └── php.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | hoaxy_botometer_flowchart.png 2 | *.pdf 3 | *.doc 4 | *.txt 5 | .git 6 | .git/ 7 | .git/* 8 | .git/** 9 | docker-compose.* 10 | config.example.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__/ 3 | *.pyc 4 | 5 | .vagrant 6 | vagrantfile 7 | frontend/node_modules 8 | frontend/gulpfile.js 9 | frontend/package.json 10 | frontend/config.js 11 | frontend/config.min.js 12 | frontend/.htaccess 13 | frontend/sampledata 14 | frontend/package-lock.json 15 | frontend/news_sources/top-news-usa.json 16 | frontend/index_commented.php -------------------------------------------------------------------------------- /Botometer-cache.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/Botometer-cache.pdf -------------------------------------------------------------------------------- /DEPLOYMENT_README.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | ## Summary 4 | 5 | If changes to the Hoaxy interface are made, the updates need to be deployed before they will appear on the live site. 6 | 7 | General changes to the functionality of the interface should be made to the `master` git branch and then merged into this `deployment` branch. 8 | 9 | Changes specific to the live Branded Hoaxy Website at can and should be made directly to this `deployment` branch. This includes updates to the FAQ or changes to the Hoaxy or IU branding not found in the generic version. 10 | 11 | ## Instructions 12 | 13 | ### Method 1 - Updating directly on `lisa` 14 | 15 | 1) Log into `lisa` using the truthy user. 16 | 2) cd to the repository: `cd /home/data/apps/hoaxy/hoaxy-frontend` 17 | 3) Confirm that you are on the development branch: `git branch` should display a list of branches and `deployment` should have a `*` next to it as follows: 18 | 19 | truthy@lisa:~/home/data/apps/hoaxy/hoaxy-frontend$ git branch 20 | * deployment 21 | master 22 | truthy@lisa:~/home/data/apps/hoaxy/hoaxy-frontend$ 23 | 4) If not on `deployment` branch, switch branch to deployment using `git checkout deployment` 24 | 5) Make changes using your favorite editor (e.g. `nano frontend/faq.html`). Save your work. 25 | 6) Commit changes (e.g. `git commit -a -m 'Updates FAQ'`) 26 | 7) Push the changes to github: `git push origin deployment` 27 | 8) Run the deployment script: `./deploy_website.sh` 28 | 9) Confirm the website was successfully updated. 29 | 10) If successful, celebrate. 30 | 11) If not successful, **Don't panic** and contact a developer. 31 | 32 | ### Method 2 - Updating on github.com 33 | 34 | 1) Log into github.com 35 | 2) Go to the deployment branch of the frontend repository: 36 | 3) Open a file that you want to edit. 37 | 4) Using the pencil icon at the top of the code listing to "Edit this file" 38 | 5) Make changes to the file. 39 | 6) Add a commit message and make sure to select the radio button: "Commit directly to the `deployment` branch." 40 | 7) Click "Commit changes" to commit the changes. 41 | 8) Log into `lisa` as the truthy user using your favorite SSH terminal 42 | 9) cd to the repository: `cd /home/data/apps/hoaxy/hoaxy-frontend` 43 | 10) Confirm that you are on the development branch: `git branch` should display a list of branches and `development` should have a `*` next to it as follows: 44 | 45 | truthy@lisa:~/home/data/apps/hoaxy/hoaxy-frontend$ git branch 46 | * deployment 47 | master 48 | truthy@lisa:~/home/data/apps/hoaxy/hoaxy-frontend$ 49 | 11) If not on `deployment` branch, switch branch to deployment using `git checkout deployment` 50 | 12) Pull changes from the github repo: `git pull origin deployment` 51 | 13) Run the deployment script: `./deploy_website.sh` 52 | 14) Confirm the website was successfully updated. 53 | 15) If successful, celebrate. 54 | 16) If not successful, **Don't panic** and contact a developer. 55 | 56 | ### Method 3 - Updating using a local repo 57 | 58 | **This method assumes you already have a local development environment configured and the repo cloned to your local environment.** 59 | 60 | 1) Confirm that you are on the `deployment` branch 61 | 2) If not on `deployment` branch, switch branch to deployment (e.g. `git checkout deployment`) 62 | 3) Pull any remote changes to sync your repo with the latest updates in github: `git pull origin deployment` 63 | 3) Make your updates, save your files, commit your changes. 64 | 4) Push your changes to the github repo: `git push origin deployment` 65 | 5) Log into `lisa` as the truthy user using your favorite SSH terminal 66 | 6) cd to the repository: `cd /home/data/apps/hoaxy/hoaxy-frontend` 67 | 7) Confirm that you are on the development branch: `git branch` should display a list of branches and `development` should have a `*` next to it as follows: 68 | 69 | truthy@lisa:~/home/data/apps/hoaxy/hoaxy-frontend$ git branch 70 | * deployment 71 | master 72 | truthy@lisa:~/home/data/apps/hoaxy/hoaxy-frontend$ 73 | 8) If not on `deployment` branch, switch branch to deployment using `git checkout deployment` 74 | 9) Pull changes from the github repo: `git pull origin deployment` 75 | 10) Run the deployment script: `./deploy_website.sh` 76 | 11) Confirm the website was successfully updated. 77 | 12) If successful, celebrate. 78 | 13) If not successful, **Don't panic** and contact a developer. 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disclaimer 2 | 3 | The name Hoaxy is a trademark of Indiana University. Neither the name "Hoaxy" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 4 | 5 | # Deployment 6 | 7 | There are two primary branches in this repository. The `master` branch is the generic, unbranded version of the frontend. `deployment` features branding, styles, and customizations featured only on the official website. `master` should be merged into `deployment` when new features have been added. `deployment` should **never** be merged into `master`, and as such, should only include additions specific to the live site. 8 | 9 | More details regarding the deployment of the Hoaxy frontend to the official site at http://hoaxy.osome.iu.edu can be found in the [DEPLOYMENT_README.md](https://github.com/IUNetSci/hoaxy-frontend/blob/deployment/DEPLOYMENT_README.md) file in the `deployment` branch of this repository. 10 | 11 | # Hoaxy frontend 12 | 13 | This is the frontend of [Hoaxy](http://hoaxy.iuni.iu.edu), and it is intended to be used in conjunction with an implementation of the Hoaxy backend platform, which is available at: http://github.com/iunetsci/hoaxy-backend 14 | 15 | We strongly recommend that you implement the Hoaxy backend before this frontend. For more information, including a description of the application and information on how to collaborate with the Hoaxy team, please visit [the official Hoaxy website](http://hoaxy.osome.iu.edu). 16 | 17 | ## Requirements 18 | 19 | ### Templating and Server 20 | 21 | Header, Footer, and Global Includes are located under `/includes/`. 22 | 23 | This site uses PHP to handle templating, which is available by default on many apache servers. 24 | 25 | ### Dependencies 26 | 27 | The JavaScript dependencies are listed in `includes/includes.html`. Many libraries are included via CDNs. 28 | 29 | 30 | * [Vue.js](https://vuejs.org/) 31 | * [Bootstrap 4](https://getbootstrap.com/docs/4.0/getting-started/introduction/) 32 | * [ES6-Promise](https://github.com/stefanpenner/es6-promise) 33 | * [axios](https://github.com/mzabriskie/axios) 34 | * [Moment.js](http://momentjs.com/) 35 | * [Font Awesome 4](https://fontawesome.com/v4.7.0/) 36 | * [sigma.js](http://sigmajs.org/) 37 | * [D3](https://d3js.org/) 38 | * [NV.D3](http://nvd3.org/) 39 | 40 | The graph visualization uses Sigma.js, and it was tested with v1.2.0. The timeline chart is implemented using NV.D3, a plugin for D3.js, and it was tested with NV.D3 v1.8.1 and D3 v3.5.17. Feel free to use more recent versions at your own risk. 41 | 42 | ### Configuration 43 | 44 | API endpoints must be defined in a `frontend/config.js`. An example config file - `config.example.js` - has been provided to show you the correct format. It features endpoints from the [public Hoaxy Rapid-API](https://rapidapi.com/truthy/api/hoaxy). These endpoints should be modified to point to your own implementation of the [Hoaxy Backend](https://github.com/IUNetSci/hoaxy-backend). 45 | 46 | If the back-end is configured to use a local API endpoint in place of RapidAPI, the front-end config needs to use the corresponding local URL. RapidAPI HTTP headers will be ignored. 47 | 48 | ## Build hoaxy frontend with Docker 49 | 50 | Make sure you have installed docker-compose. 51 | * Update the config in `frontend/config.js` to reflect rapidapi endpoints. 52 | * Run `docker-compose up` 53 | * Go to your browser and run http://localhost:8080/ 54 | -------------------------------------------------------------------------------- /deploy_website.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | chgrp -R truthy frontend && 3 | chmod -R a+r,g+rw frontend && 4 | rsync -r frontend/ /home/data/www/DocumentRoot/hoaxy.osome.iu.edu && 5 | echo "Site is updated." && 6 | echo "Don't forget to commit and push any changes made on the server." 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3.6' 3 | services: 4 | 5 | php: 6 | image: php:7.2-fpm-alpine3.10 7 | volumes: 8 | - ./frontend:/frontend 9 | 10 | web: 11 | image: nginx:1-alpine 12 | ports: 13 | - "8080:80" 14 | links: 15 | - php 16 | volumes: 17 | - ./frontend:/frontend 18 | - ./nginx.conf:/etc/nginx/conf.d/default.conf -------------------------------------------------------------------------------- /documentation/graph.js.md: -------------------------------------------------------------------------------- 1 | ## `graph.js` Functions 2 | 3 |
4 |
UpdateEdges(newEdges)Array.<Object>
5 |

Update edges on the graph

6 |
7 |
setBotScore(importedID, importedBotscore)
8 |

Handles imported bot scores from CSV/JSON

9 |
10 |
getBotCacheScores()
11 |

Get bot scores from the cache url specified in config.js

12 |
13 |
getNewScores()
14 |

Get fresh bot scores, 20 per batch

15 |
16 |
getBotScoreTimer(index)Boolean | function
17 |

Gets a new botscore every second to avoid quickly hitting the rate limit

18 |
19 |
updateUserBotScore(user)Promise
20 |

Update an individual, specific user's botscore

21 |
22 |
twitterResponseFail(error)
23 |

If the twitter promise in getNewBotometerScore fails, 24 | warn in the console with a detailed error.

25 |
26 |
getNewBotometerScore(user_id)Promise
27 |

Get individual scores from Botometer based on user_id

28 |
29 |
getBotScore(user_object, potentially_old_sn)Number
30 |

Get the botscore (and maybe fresh screenname) of the user

31 |
32 |
refreshGraph()
33 |

Refresh the graph no more than 30 times per second

34 |
35 |
updateNodeColor(node_id, score)
36 |

Update an individual node's color

37 |
38 |
getBaseColor(score)Object
39 |

Set up base colors to allow for calling r, g, b of color
Helper function for getNodeColor() and getBorderColor()

40 |
41 |
getRenderer()Object
42 |

Used to take a snapshot of the graph

43 |
44 |
getNodeColor(score)Object
45 |

Get the node's color based on botscore

46 |
47 |
getBorderColor(score)Object
48 |

Get the node's border's color

49 |
50 |
KillGraph()
51 |

Removes all information from the graph to start over

52 |
53 |
UpdateGraph(start_time, end_time)
54 |

Create the graph

55 |
56 |
GenerateUserModal(e)
57 |

Generates the modal that pops up when clicking on a node

58 |
59 |
drawGraph()
60 |

Draws the Sigma graph

61 |
62 |
zoomIn()
63 |

Zoom in on the graph

64 |
65 |
zoomOut()
66 |

Zoom out on the graph

67 |
68 |
redraw()
69 |

Draw the graph

70 |
71 |
FilterEdges(filterTimestamp)
72 |

Filter edges shown based on timestamp window chosen in timeline

73 |
74 |
AnimateFilter(timestamp)
75 |

Animates graph from beginning to end (or paused location to end)

76 |
77 |
StartAnimation()
78 |

Start the graph animation (show tweets as they happened)

79 |
80 |
StopAnimation()
81 |

Stop the graph animation and show all nodes and edges again

82 |
83 |
PauseAnimation()
84 |

Pause the graph animation

85 |
86 |
UnpauseAnimation()
87 |

Resume the graph animation from a paused state

88 |
89 |
filterNodesByScore(max, min)
90 |

Filter nodes by botscore (e.g. between 3.0 and 4.0)

91 |
92 |
93 | 94 | 95 | 96 | ## UpdateEdges(newEdges) ⇒ Array.<Object> 97 | Update edges on the graph 98 | 99 | **Returns**: Array.<Object> - The edges that are on the graph at the end of the function 100 | 101 | | Param | Type | Description | 102 | | --- | --- | --- | 103 | | newEdges | Array.<Object> | The edges to be placed on the graph | 104 | 105 | 106 | 107 | ## setBotScore(importedID, importedBotscore) 108 | Handles imported bot scores from CSV/JSON 109 | 110 | 111 | | Param | Type | Description | 112 | | --- | --- | --- | 113 | | importedID | String | The imported ID of the user | 114 | | importedBotscore | Number | The imported botscore of the user | 115 | 116 | 117 | 118 | ## getBotCacheScores() 119 | Get bot scores from the cache url specified in `config.js` 120 | 121 | 122 | 123 | ## getNewScores() 124 | Get fresh bot scores, 20 per batch 125 | 126 | 127 | 128 | ## getBotScoreTimer(index) ⇒ Boolean \| function 129 | Gets a new botscore every second to avoid quickly hitting the rate limit 130 | 131 | **Returns**: Boolean \| function - Returns setTimeout(), or false if no scores left 132 | 133 | | Param | Type | Description | 134 | | --- | --- | --- | 135 | | index | Number | The index for the botscores; avoids repeat score queries | 136 | 137 | 138 | 139 | ## updateUserBotScore(user) ⇒ Promise 140 | Update an individual, specific user's botscore 141 | 142 | **Returns**: Promise - Successful botscore update (or not) 143 | 144 | | Param | Type | Description | 145 | | --- | --- | --- | 146 | | user | Object | The user to update the botscore for | 147 | 148 | 149 | 150 | ## twitterResponseFail(error) 151 | If the twitter promise in getNewBotometerScore fails, 152 | warn in the console with a detailed error. 153 | 154 | 155 | | Param | Type | Description | 156 | | --- | --- | --- | 157 | | error | Object | The twitter error | 158 | 159 | 160 | 161 | ## getNewBotometerScore(user_id) ⇒ Promise 162 | Get individual scores from Botometer based on user_id 163 | 164 | **Returns**: Promise - The botscore of the user 165 | 166 | | Param | Type | Description | 167 | | --- | --- | --- | 168 | | user_id | Number | The twitter user's user ID | 169 | 170 | 171 | 172 | ## getBotScore(user_object, potentially_old_sn) ⇒ Number 173 | Get the botscore (and maybe fresh screenname) of the user 174 | 175 | **Returns**: Number - The botscore of the user 176 | 177 | | Param | Type | Description | 178 | | --- | --- | --- | 179 | | user_object | Object | The user object (containing ID, screenname, etc.) | 180 | | potentially_old_sn | String | The user's screenname | 181 | 182 | 183 | 184 | ## refreshGraph() 185 | Refresh the graph no more than 30 times per second 186 | 187 | 188 | 189 | ## updateNodeColor(node_id, score) 190 | Update an individual node's color 191 | 192 | 193 | | Param | Type | Description | 194 | | --- | --- | --- | 195 | | node_id | Number | The id of the node | 196 | | score | Number | The botscore of the account for that node | 197 | 198 | 199 | 200 | ## getBaseColor(score) ⇒ Object 201 | Set up base colors to allow for calling r, g, b of color \ 202 | Helper function for getNodeColor() and getBorderColor() 203 | 204 | **Returns**: Object - The color of the node with r, g, b properties 205 | 206 | | Param | Type | Description | 207 | | --- | --- | --- | 208 | | score | Number | The botscore used to get the correct color | 209 | 210 | 211 | 212 | ## getRenderer() ⇒ Object 213 | Used to take a snapshot of the graph 214 | 215 | **Returns**: Object - The render of the graph 216 | 217 | 218 | ## getNodeColor(score) ⇒ Object 219 | Get the node's color based on botscore 220 | 221 | **Returns**: Object - The color of the node 222 | 223 | | Param | Type | Description | 224 | | --- | --- | --- | 225 | | score | Number | The botscore | 226 | 227 | 228 | 229 | ## getBorderColor(score) ⇒ Object 230 | Get the node's border's color 231 | 232 | **Returns**: Object - The color of the node's border 233 | 234 | | Param | Type | Description | 235 | | --- | --- | --- | 236 | | score | Number | The botscore | 237 | 238 | 239 | 240 | ## KillGraph() 241 | Removes all information from the graph to start over 242 | 243 | 244 | 245 | ## UpdateGraph(start_time, end_time) 246 | Create the graph 247 | 248 | 249 | | Param | Type | Description | 250 | | --- | --- | --- | 251 | | start_time | Date | The earliest tweet, leftmost date on graph | 252 | | end_time | Date | The latest tweet, rightmost date on graph | 253 | 254 | 255 | 256 | ## GenerateUserModal(e) 257 | Generates the modal that pops up when clicking on a node 258 | 259 | 260 | | Param | Type | Description | 261 | | --- | --- | --- | 262 | | e | Object | `clickNode` type containing data pertinent to the Twitter account | 263 | 264 | 265 | 266 | ## drawGraph() 267 | Draws the Sigma graph 268 | 269 | 270 | 271 | ## zoomIn() 272 | Zoom in on the graph 273 | 274 | 275 | 276 | ## zoomOut() 277 | Zoom out on the graph 278 | 279 | 280 | 281 | ## redraw() 282 | Draw the graph 283 | 284 | 285 | 286 | ## FilterEdges(filterTimestamp) 287 | Filter edges shown based on timestamp window chosen in timeline 288 | 289 | 290 | | Param | Type | Description | 291 | | --- | --- | --- | 292 | | filterTimestamp | Number | The timestamp that counts as \ `timespan.end_time` during timelapse animation | 293 | 294 | 295 | 296 | ## AnimateFilter(timestamp) 297 | Animates graph from beginning to end (or paused location to end) 298 | 299 | 300 | | Param | Type | Description | 301 | | --- | --- | --- | 302 | | timestamp | Number | The current timestamp of the animation | 303 | 304 | 305 | 306 | ## StartAnimation() 307 | Start the graph animation (show tweets as they happened) 308 | 309 | 310 | 311 | ## StopAnimation() 312 | Stop the graph animation and show all nodes and edges again 313 | 314 | 315 | 316 | ## PauseAnimation() 317 | Pause the graph animation 318 | 319 | 320 | 321 | ## UnpauseAnimation() 322 | Resume the graph animation from a paused state 323 | 324 | 325 | 326 | ## filterNodesByScore(max, min) 327 | Filter nodes by botscore (e.g. between 3.0 and 4.0) 328 | 329 | 330 | | Param | Type | Description | 331 | | --- | --- | --- | 332 | | max | Number | The high end botscore to filter nodes by | 333 | | min | Number | The low end botscore to filter nodes by | 334 | 335 | -------------------------------------------------------------------------------- /documentation/timeline.md: -------------------------------------------------------------------------------- 1 | ## Functions 2 | 3 |
4 |
redraw()
5 |

Redraw the timeline

6 |
7 |
removeUpdateDateRangeCallback()
8 |

Initialize the timeline

9 |
10 |
dateFormatter(d)String
11 |

Formats the date using d3.time

12 |
13 |
calculateTweetRates(chartData)
14 |

Shows how many new tweets of a particular type occurred at a point in time

15 |
16 |
_updateDateRange(extent)
17 |

Updates the date range if the user selected a different one

18 |
19 |
Update(data)Boolean
20 |

Updates the timeline with new data

21 |
22 |
UpdateTimestamp()
23 |

Update timestamp based on the graph's timestamp

24 |
25 |
26 | 27 | 28 | 29 | ## redraw() 30 | Redraw the timeline 31 | 32 | 33 | 34 | ## removeUpdateDateRangeCallback() 35 | Initialize the timeline 36 | 37 | 38 | 39 | ## dateFormatter(d) ⇒ String 40 | Formats the date using d3.time 41 | 42 | **Returns**: String - The formatted time 43 | 44 | | Param | Type | Description | 45 | | --- | --- | --- | 46 | | d | String | The date to be formatted | 47 | 48 | 49 | 50 | ## calculateTweetRates(chartData) 51 | Shows how many new tweets of a particular type occurred at a point in time 52 | 53 | | Param | Type | Description | 54 | | --- | --- | --- | 55 | | chartData | Object | The data that the timeline was drawn with | 56 | 57 | 58 | 59 | ## \_updateDateRange(extent) 60 | Updates the date range if the user selected a different one 61 | 62 | | Param | Type | Description | 63 | | --- | --- | --- | 64 | | extent | Object | The timeframe selection by the user | 65 | 66 | 67 | 68 | ## Update(data) ⇒ Boolean 69 | Updates the timeline with new data 70 | 71 | **Returns**: Boolean - Will only return false if there's no data 72 | 73 | | Param | Type | Description | 74 | | --- | --- | --- | 75 | | data | Object | The data to update the timeline with | 76 | 77 | 78 | 79 | ## UpdateTimestamp() 80 | Update timestamp based on the graph's timestamp 81 | -------------------------------------------------------------------------------- /documentation/twitter_search_timeline.md: -------------------------------------------------------------------------------- 1 | ## Functions 2 | 3 |
4 |
redraw()
5 |

Redraw the timeline

6 |
7 |
removeUpdateDateRangeCallback()
8 |

Initialize the timeline

9 |
10 |
dateFormatter(d)String
11 |

Formats the date using d3.time

12 |
13 |
calculateTweetRates(chartData)
14 |

Shows how many new tweets of a particular type occurred at a point in time

15 |
16 |
_updateDateRange(extent)
17 |

Updates the date range if the user selected a different one

18 |
19 |
triggerUpdateRange()
20 |

Updates the range of dates on graph
Seen outside twitter_search_timeline as updateDateRange()
(May be deprecated)

21 |
22 |
UpdateTimestamp()
23 |

Update timestamp based on the graph's timestamp

24 |
25 |
26 | 27 | 28 | 29 | ## redraw() 30 | Redraw the timeline 31 | 32 | 33 | 34 | ## removeUpdateDateRangeCallback() 35 | Initialize the timeline 36 | 37 | 38 | 39 | ## dateFormatter(d) ⇒ String 40 | Formats the date using d3.time 41 | 42 | **Returns**: String - The formatted time 43 | 44 | | Param | Type | Description | 45 | | --- | --- | --- | 46 | | d | String | The date to be formatted | 47 | 48 | 49 | 50 | ## calculateTweetRates(chartData) 51 | Shows how many new tweets of a particular type occurred at a point in time 52 | 53 | | Param | Type | Description | 54 | | --- | --- | --- | 55 | | chartData | Object | The data that the timeline was drawn with | 56 | 57 | 58 | 59 | ## \_updateDateRange(extent) 60 | Updates the date range if the user selected a different one 61 | 62 | | Param | Type | Description | 63 | | --- | --- | --- | 64 | | extent | Object | The timeframe selection by the user | 65 | 66 | 67 | 68 | ## triggerUpdateRange() 69 | Updates the range of dates on graph \ 70 | Seen outside twitter_search_timeline as updateDateRange() \ 71 | (May be deprecated) 72 | 73 | 74 | 75 | ## UpdateTimestamp() 76 | Update timestamp based on the graph's timestamp 77 | -------------------------------------------------------------------------------- /frontend/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/android-chrome-256x256.png -------------------------------------------------------------------------------- /frontend/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2b5797 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/config.example.js: -------------------------------------------------------------------------------- 1 | configuration = function(){ 2 | var obj = {} 3 | 4 | var rapidapi_key = "YOURKEY"; 5 | 6 | obj.articles_url = "https://api-hoaxy.p.rapidapi.com/articles"; 7 | obj.articles_headers = { 8 | "X-RapidAPI-Key": rapidapi_key, 9 | "Accept": "application/json" 10 | }; 11 | 12 | obj.timeline_url = "https://api-hoaxy.p.rapidapi.com/timeline"; 13 | obj.timeline_headers = { 14 | "X-RapidAPI-Key": rapidapi_key, 15 | "Accept": "application/json" 16 | }; 17 | 18 | obj.network_url = "https://api-hoaxy.p.rapidapi.com/network"; 19 | obj.network_headers = { 20 | "X-RapidAPI-Key": rapidapi_key, 21 | "Accept": "application/json" 22 | }; 23 | 24 | obj.top_articles_url = 'https://api-hoaxy.p.rapidapi.com/top-articles'; 25 | obj.top_articles_headers = { 'X-RapidAPI-Key': rapidapi_key}; 26 | 27 | obj.top_users_url = 'https://api-hoaxy.p.rapidapi.com/top-users'; 28 | obj.top_users_headers = { 'X-RapidAPI-Key': rapidapi_key}; 29 | 30 | obj.botometer_url = "https://example.com"; 31 | obj.botometer_headers = { 32 | "X-RapidAPI-Key": "--KEY--", 33 | "Accept": "application/json" 34 | }; 35 | 36 | obj.botcache_url = "https://example.com/api/scores"; 37 | obj.feedback_url = "https://example.com/api/feedback"; 38 | 39 | obj.twitter_key = ""; 40 | 41 | obj.botcache_chunk_sizes = [ 42 | 2000 43 | ]; 44 | 45 | 46 | return obj; 47 | }(); 48 | -------------------------------------------------------------------------------- /frontend/config.min.js: -------------------------------------------------------------------------------- 1 | configuration = function(){ 2 | var obj = {} 3 | 4 | var rapidapi_key = "vF2x0KQ4n1msh62hluPiRaRcediWp1sAdQ2jsnfXCZCj3exoD6"; 5 | 6 | obj.articles_url = "https://api-hoaxy.p.rapidapi.com/articles"; 7 | obj.articles_headers = { 8 | "X-RapidAPI-Key": rapidapi_key, 9 | "Accept": "application/json" 10 | }; 11 | 12 | obj.timeline_url = "https://api-hoaxy.p.rapidapi.com/timeline"; 13 | obj.timeline_headers = { 14 | "X-RapidAPI-Key": rapidapi_key, 15 | "Accept": "application/json" 16 | }; 17 | 18 | obj.network_url = "https://api-hoaxy.p.rapidapi.com/network"; 19 | obj.network_headers = { 20 | "X-RapidAPI-Key": rapidapi_key, 21 | "Accept": "application/json" 22 | }; 23 | 24 | obj.top_articles_url = 'https://api-hoaxy.p.rapidapi.com/top-articles'; 25 | obj.top_articles_headers = { 'X-RapidAPI-Key': rapidapi_key}; 26 | 27 | obj.top_users_url = 'https://api-hoaxy.p.rapidapi.com/top-users'; 28 | obj.top_users_headers = { 'X-RapidAPI-Key': rapidapi_key}; 29 | 30 | obj.botometer_url = "https://osome-botometer.p.rapidapi.com/2/check_account"; 31 | obj.botometer_headers = { 32 | "X-RapidAPI-Key": "jP9TlfCBbRmsht7hzCNHKoH6Ug7ap1nl2cVjsnEVnybLYaP9T8", 33 | "Accept": "application/json" 34 | }; 35 | 36 | obj.botcache_url = "https://hoaxy.iuni.iu.edu/hoaxy-botometer/api/scores"; 37 | obj.feedback_url = "https://hoaxy.iuni.iu.edu/hoaxy-botometer/api/feedback"; 38 | 39 | obj.twitter_key = "sVd-jEeeo2eGMii4JzWTwq4JMVw"; 40 | 41 | 42 | 43 | obj.botcache_chunk_sizes = [ 44 | // 50, 45 | // 200, 46 | 2000 47 | ]; 48 | 49 | return obj; 50 | }(); 51 | -------------------------------------------------------------------------------- /frontend/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/favicon.ico -------------------------------------------------------------------------------- /frontend/includes/footer.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 |

12 | IUNI | CNetS | SICE 14 |

15 |

16 | Hoaxy® is Copyright © the Trustees of Indiana University 2016 - 17 | 18 | 25 |

26 |
27 |
28 | -------------------------------------------------------------------------------- /frontend/includes/header.html: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 | 54 |
55 |
56 |
57 |

HOAXYbeta

58 |

59 | Visualize the spread of information on Twitter 60 |

61 |

62 |
63 |
64 | 69 |
70 | -------------------------------------------------------------------------------- /frontend/includes/includes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /frontend/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/news_sources/README.md: -------------------------------------------------------------------------------- 1 | # What is this? 2 | 3 | This folder location is used for the Hoaxy Botometer dashboard. Essentially, the `vue-app.js` code file reads the `/news_sources/top-news-usa.json` location in order to render all of the pre-populated news sources that come from the CRON job that runs intermittently, populating the `.json` file with news sources. The news are rendered to the Hoaxy-Botomter landing screen dashboard. 4 | 5 | # What should I do? 6 | 7 | Place a file called `top-news-usa.json` in this location populated with json data regarding news that has the following schema: 8 | 9 | ```json 10 | {"id": 11 | {"headline":"", 12 | "source":"", 13 | "url":"" 14 | }, 15 | "id2": 16 | {... 17 | }, 18 | "id3": 19 | {... 20 | }, 21 | ... 22 | } 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /frontend/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 21 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/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-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/static/5bots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/static/5bots.png -------------------------------------------------------------------------------- /frontend/static/5nots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/static/5nots.png -------------------------------------------------------------------------------- /frontend/static/assets/Twitter_logo_blue_32-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/static/assets/Twitter_logo_blue_32-cropped.png -------------------------------------------------------------------------------- /frontend/static/assets/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/static/assets/default.png -------------------------------------------------------------------------------- /frontend/static/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/static/assets/favicon.ico -------------------------------------------------------------------------------- /frontend/static/assets/forkme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/frontend/static/assets/forkme.png -------------------------------------------------------------------------------- /frontend/static/css/external.css: -------------------------------------------------------------------------------- 1 | .btn:not(#searchByTwitter):not(#searchByHoaxy):not(#searchByTwitter2):not(#searchByHoaxy2) 2 | { 3 | -webkit-box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 4 | -moz-box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 5 | box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 6 | } 7 | .btn-primary 8 | { 9 | background-color: #DFDFDF; 10 | border-color: #8A8A8A; 11 | color: black; 12 | } 13 | .btn-primary.disabled, .btn-primary:disabled 14 | { 15 | background-color: #FAFAFA; 16 | border-color: #ADADAD; 17 | color: #797979; 18 | } 19 | .btn-primary:hover 20 | { 21 | background-color: #ADADAD; 22 | border-color: #797979; 23 | background-color: #0062ff; 24 | border-color: #005cbf; 25 | } 26 | .search-btn { 27 | width: 150px; 28 | text-align: left; 29 | position: relative; 30 | } 31 | .search-btn span { 32 | margin-left: 6px; 33 | } 34 | #searchByTwitter2, #searchByHoaxy2 { 35 | width: 150px; 36 | } 37 | .selected_node_filter 38 | { 39 | border: solid black 2px; 40 | } 41 | 42 | .botimages 43 | { 44 | width: 300px; 45 | height: 2em; 46 | } 47 | .botimages div 48 | { 49 | background-size: 300px auto; 50 | background-position: left center; 51 | background-repeat: no-repeat; 52 | height: 100%; 53 | width: 100%; 54 | } 55 | .images-nots 56 | { 57 | background-image: url(../5nots.png); 58 | } 59 | .images-bots 60 | { 61 | background-image: url(../5bots.png); 62 | } 63 | 64 | #faq-q10:target + dd, 65 | #faq-q30:target + dd { 66 | background: rgb(204, 212, 226); 67 | } 68 | 69 | 70 | #tutorial 71 | { 72 | position: fixed; 73 | left: 0; 74 | right: 0; 75 | top: 0; 76 | bottom: 0; 77 | z-index: 25000; 78 | background: rgba( 0,0,0,.7); 79 | } 80 | #tutorial-carousel 81 | { 82 | width: 100%; 83 | } 84 | #tutorial-content 85 | { 86 | height: 70%; 87 | width: 100%; 88 | background: rgba( 0,0,0,.5); 89 | /* background: gray; */ 90 | } 91 | #tutorial-content .carousel-item img 92 | { 93 | max-width: 90%; 94 | margin: 0 auto; 95 | max-height: 100%; 96 | } 97 | .carousel-inner 98 | { 99 | position: absolute; 100 | /* padding-top: 4em; 101 | padding-bottom: 4em; */ 102 | top: 4em; 103 | left: 0; 104 | right: 0; 105 | bottom: 4em; 106 | } 107 | .carousel-item > div 108 | { 109 | height: 100%; 110 | } 111 | #tutorial-content .carousel-item 112 | { 113 | text-align: center; 114 | height: 100%; 115 | position: relative; 116 | } 117 | #tutorial-content .carousel-item 118 | { 119 | display: none; 120 | } 121 | #tutorial-content .carousel-item.active 122 | { 123 | display: block; 124 | } 125 | .carousel-control-prev, 126 | .carousel-control-next 127 | { 128 | width: 5%; 129 | } 130 | .first-slide img, .last-slide img 131 | { 132 | visibility: hidden; 133 | } 134 | .first-slide-content, .last-slide-content 135 | { 136 | position: relative; 137 | } 138 | .first-slide-content > div, .last-slide-content > div 139 | { 140 | position: absolute; 141 | top: 0; 142 | bottom: 0; 143 | left: 0; right: 0; 144 | margin: 0 auto; 145 | width: 90%; 146 | background: white; 147 | /* background: green; */ 148 | } 149 | .close-tutorial-button 150 | { 151 | margin-top: -3rem; 152 | margin-bottom: 0rem; 153 | } 154 | 155 | .fade-enter-active, .fade-leave-active { 156 | transition: opacity .5s, transform .5s; 157 | } 158 | .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { 159 | opacity: 0; 160 | } 161 | 162 | 163 | /*article list transition*/ 164 | .slide_in-enter-active, .slide_in-leave-active { 165 | transition: transform .5s; 166 | transition: max-height .5s, transform .5s; 167 | max-height: 200rem; 168 | } 169 | .slide_in-enter, .slide_in-leave-to /* .fade-leave-active below version 2.1.8 */ { 170 | /* opacity: 0; */ 171 | transform: scaleY(0); 172 | max-height: 0; 173 | } 174 | 175 | 176 | #article_list_container 177 | { 178 | overflow: hidden; 179 | transform-origin: center top; 180 | } 181 | 182 | 183 | 184 | 185 | /*Header*/ 186 | header 187 | { 188 | margin-top: 2em; 189 | margin-bottom: 3rem; 190 | } 191 | footer 192 | { 193 | margin-top: 3rem; 194 | } 195 | 196 | section 197 | { 198 | margin-bottom: 3rem; 199 | } 200 | section#secondary_form 201 | { 202 | margin-bottom: 1rem; 203 | } 204 | 205 | /* Overriding bootstrap styling for Hoaxy/Twitter landing page search */ 206 | /* #searchByHoaxy { 207 | border-top-left-radius: 0px; 208 | border-bottom-left-radius: 0px; 209 | border-top-right-radius: 0px; 210 | border-bottom-right-radius: 0px; 211 | } 212 | 213 | #searchByTwitter { 214 | border-top-right-radius: 0px; 215 | border-bottom-right-radius: 0px; 216 | } */ 217 | 218 | /*#searchByLabel { 219 | border-color: gray; 220 | border-style: solid; 221 | border-width: thin; 222 | }*/ 223 | 224 | /* #query { 225 | border-top-left-radius: 0px; 226 | border-bottom-left-radius: 0px; 227 | border-left-color: white; 228 | } */ 229 | 230 | .loader { 231 | border: 16px solid #f3f3f3; 232 | border-radius: 50%; 233 | border-top: 16px solid #3498db; 234 | width: 120px; 235 | height: 120px; 236 | -webkit-animation: spin 2s linear infinite; 237 | animation: spin 2s linear infinite; 238 | } 239 | 240 | @-webkit-keyframes spin { 241 | 0% { 242 | -webkit-transform: rotate(0deg); 243 | } 244 | 100% { 245 | -webkit-transform: rotate(360deg); 246 | } 247 | } 248 | 249 | @keyframes spin { 250 | 0% { 251 | transform: rotate(0deg); 252 | } 253 | 100% { 254 | transform: rotate(360deg); 255 | } 256 | } 257 | 258 | 259 | 260 | 261 | 262 | li.claim, li.claim a, .claim_label 263 | { 264 | color: black; 265 | background-color: darkgray; 266 | } 267 | li.fact_checking, li.fact_checking a, .fact_checking_label 268 | { 269 | color: black; 270 | background-color: rgb(238,210,2); 271 | } 272 | li label 273 | { 274 | /*padding: 1rem; 275 | display: inline-block; 276 | width: 100%;*/ 277 | } 278 | li 279 | { 280 | /* margin-bottom: 1rem; */ 281 | } 282 | 283 | .claim_label, .fact_checking_label 284 | { 285 | 286 | height: .25rem; 287 | display: inline-block; 288 | vertical-align: middle; 289 | } 290 | .line 291 | { 292 | width: 3.5rem; 293 | } 294 | .tab-content .claim_label, .tab-content .fact_checking_label 295 | { 296 | width: auto; 297 | height: auto; 298 | } 299 | 300 | 301 | 302 | #visualize_top 303 | { 304 | /* margin-top: .75rem; */ 305 | 306 | } 307 | #articles_controls 308 | { 309 | /* position: sticky; */ 310 | } 311 | 312 | #spinner 313 | { 314 | position: fixed; 315 | top: 0; 316 | left: 0; 317 | bottom: 0; 318 | right: 0; 319 | background: rgba(200, 200, 255, 1); 320 | /*background: green;*/ 321 | z-index: 20000; 322 | } 323 | #spinner.transparent, .modal 324 | { 325 | background: rgba(200,200,255,.7); 326 | } 327 | #spinner i 328 | { 329 | 330 | font-size: 149px; 331 | line-height: 149px; 332 | } 333 | #spinner span, #spinner > div 334 | { 335 | position: absolute; 336 | /* top: 2rem; 337 | right: 2rem; */ 338 | position: absolute; 339 | display: block; 340 | top: 50%; 341 | left: 50%; 342 | height: 150px; 343 | width: 150px; 344 | margin-left: -75px; 345 | margin-top: -75px; 346 | /*vertical-align: middle;*/ 347 | text-align: center; 348 | 349 | /*font-size: 6rem;*/ 350 | /*height: 6rem; 351 | width: 6rem;*/ 352 | /*display: inline-block; 353 | vertical-align: middle; 354 | text-align: center;*/ 355 | /*line-height: 10rem; 356 | margin-top: -3rem; 357 | margin-left: -3rem;*/ 358 | } 359 | .outline 360 | { 361 | text-shadow: 362 | 1px 1px 1px rgba(255, 255, 255, 1), 363 | 1px -1px 1px rgba(255, 255, 255, 1), 364 | -1px -1px 1px rgba(255, 255, 255, 1), 365 | -1px 1px 1px rgba(255, 255, 255, 1), 366 | 0px 1px 1px rgba(255, 255, 255, 1), 367 | 0px -1px 1px rgba(255, 255, 255, 1), 368 | -1px 0px 1px rgba(255, 255, 255, 1), 369 | -1px 0px 1px rgba(255, 255, 255, 1) 370 | ; 371 | 372 | } 373 | #spinner > div 374 | { 375 | margin-top: 100px; 376 | display: none; 377 | } 378 | 379 | #sigmagraph 380 | { 381 | position: relative; 382 | } 383 | #graph_help_text 384 | { 385 | position: absolute; 386 | bottom: 0em; 387 | right: 2em; 388 | } 389 | 390 | 391 | 392 | #zoom_buttons 393 | { 394 | position: absolute; 395 | top: 1.5rem; 396 | right: .5rem; 397 | /*left: 0;*/ 398 | } 399 | #zoom_buttons > div 400 | { 401 | white-space: nowrap; 402 | line-height: 1; 403 | } 404 | .graph_legend 405 | { 406 | position: relative; 407 | /*z-index: 11;*/ 408 | } 409 | .graph_legend > div 410 | { 411 | position: relative; 412 | } 413 | .bg-light.bg-semi-transparent 414 | { 415 | position: absolute; 416 | top: 0; 417 | left: 0; 418 | right: 0; 419 | bottom: 0; 420 | 421 | opacity: .8; 422 | /*z-index: 10;*/ 423 | } 424 | 425 | #bot_legend_gradient 426 | { 427 | width: 4rem; 428 | position: absolute; 429 | right: 0; 430 | } 431 | 432 | .bot_legend_section 433 | { 434 | height: 4rem; 435 | max-height: 9vh; 436 | vertical-align: center; 437 | } 438 | .bot_legend_section span 439 | { 440 | color: black; 441 | /* font-weight: bold; */ 442 | } 443 | 444 | #focus_label 445 | { 446 | /* margin-top: -80px; */ 447 | position: absolute; 448 | bottom: 5.5em; 449 | right: 0; 450 | left: 0; 451 | padding: 0 .25em; 452 | } 453 | 454 | /* Widget Dialog Box should be a little wider than the conventional dialog box */ 455 | #modalDialogWidget { 456 | max-width: 850px; 457 | } 458 | 459 | .nowrap 460 | { 461 | white-space: nowrap; 462 | } 463 | 464 | .modal-show 465 | { 466 | display: block; 467 | z-index: 15000; 468 | } 469 | .modal{ 470 | opacity: 0; 471 | transition: opacity .1s linear; 472 | } 473 | 474 | .modal-dialog 475 | { 476 | position: fixed; 477 | top: 0; 478 | bottom: 0; 479 | left: 0; 480 | right: 0; 481 | margin: auto; 482 | } 483 | .modal-dialog > .alert 484 | { 485 | pointer-events: auto; 486 | } 487 | /*.modal-dialog.modal-dialog-fade 488 | { 489 | opacity: .5; 490 | /*}*/ 491 | .modal-body 492 | { 493 | overflow: auto; 494 | } 495 | .modal-content 496 | { 497 | font-size: .875rem; 498 | position: absolute; 499 | top: 3rem; 500 | bottom: 3rem; 501 | /* 502 | left: 3rem; 503 | right: 3rem; 504 | */ 505 | overflow: auto; 506 | } 507 | .modal-content h2 508 | { 509 | font-size: 1.25rem; 510 | } 511 | .modal-content h3 512 | { 513 | font-size: 1rem; 514 | margin-left: 1rem; 515 | margin-top: .75rem; 516 | } 517 | 518 | .modal-content .article_headline 519 | { 520 | margin-left: 2rem; 521 | } 522 | .modal-content .modal_links 523 | { 524 | margin-left: 3rem; 525 | } 526 | 527 | 528 | #popular_articles_claim tr td:first-child, 529 | #popular_articles_fact_checking tr td:first-child 530 | { 531 | white-space: nowrap; 532 | } 533 | 534 | #articles.hidden, 535 | #graphs.hidden 536 | { 537 | display: none; 538 | } 539 | 540 | #article_list a 541 | { 542 | text-decoration: underline; 543 | } 544 | 545 | #article_list input[type="checkbox"] 546 | { 547 | display: none; 548 | } 549 | #article_list .article_title 550 | { 551 | display: block; 552 | } 553 | .check_icons{ 554 | } 555 | input + .check_icons > .fa-check-square-o, 556 | input + .check_icons > .fa-square-o 557 | { 558 | display: none; 559 | font-size: 2rem; 560 | vertical-align: middle; 561 | } 562 | 563 | input:checked + .check_icons > .fa-check-square-o, 564 | input:not(:checked) + .check_icons > .fa-square-o 565 | { 566 | display: inline-block; 567 | } 568 | 569 | 570 | 571 | #graph_error 572 | { 573 | position: absolute; 574 | top: 0; 575 | bottom: 0; 576 | left: 0; 577 | right: 0; 578 | } 579 | 580 | 581 | #graphs 582 | { 583 | position: relative; 584 | } 585 | #timeline 586 | { 587 | /*position: absolute; 588 | height: 50%; 589 | 590 | bottom: 2rem; 591 | left: 2rem; 592 | display: none; 593 | 594 | background: lightgray; 595 | border: solid gray 1px;*/ 596 | } 597 | #shrink_right_button, #shrink_left_button 598 | { 599 | position: absolute; 600 | top: 50%; 601 | } 602 | #shrink_left_button 603 | { 604 | right: 0; 605 | } 606 | #shrink_right_button 607 | { 608 | left: 0; 609 | } 610 | #chart 611 | { 612 | height: 100%; 613 | width: 100%; 614 | /*padding: 1rem;*/ 615 | } 616 | 617 | #errorModalBody i 618 | { 619 | vertical-align: middle; 620 | } 621 | 622 | 623 | /*#toggle_timeline_button 624 | { 625 | position: absolute; 626 | bottom: 2rem; 627 | left: 2rem; 628 | } 629 | #timeline .toggle_button 630 | { 631 | position: absolute; 632 | top: 1rem; 633 | right: 1rem; 634 | }*/ 635 | #legend label 636 | { 637 | /*display: block;*/ 638 | margin: 0 auto; 639 | } 640 | 641 | .animation-control, .layout-button 642 | { 643 | vertical-align: middle; 644 | font-size: .75rem; 645 | display: block; 646 | 647 | } 648 | .layout-button 649 | { 650 | margin-top: .5rem; 651 | /*line-height: 2rem;*/ 652 | /* margin: 1rem auto; */ 653 | 654 | } 655 | .layout-button span i 656 | { 657 | vertical-align: middle; 658 | /*margin: 4px;*/ 659 | } 660 | .layout-button > span 661 | { 662 | display: inline-block; 663 | height: 2em; 664 | width: 1em; 665 | /*line-height: 2rem;*/ 666 | vertical-align: middle; 667 | padding: 2px; 668 | font-size: 1em; 669 | overflow: hidden; 670 | color: #222222; 671 | background: white; 672 | border-radius: 2px; 673 | border: solid gray 1px; 674 | /*border: solid darkgray 1px;*/ 675 | } 676 | .layout-button-timeline > span 677 | { 678 | /*border: solid white 1px;*/ 679 | /*background: white;*/ 680 | width: 3em; 681 | } 682 | 683 | .layout-button-split > span:first-child 684 | { 685 | /*border: solid white 1px;*/ 686 | width: 1.1em; 687 | /*background: white;*/ 688 | border-right: solid white 1px; 689 | } 690 | .layout-button-split > span:last-child 691 | { 692 | /*border: solid black 1px;*/ 693 | width: 1.9em; 694 | /*background: black;*/ 695 | } 696 | .layout-button-graph > span 697 | { 698 | /*border: solid black 1px;*/ 699 | width: 3em; 700 | /*background: black;*/ 701 | } 702 | .layout-button-graph i 703 | { 704 | position: absolute; 705 | display: block; 706 | margin: auto; 707 | width: 2em; 708 | height: 2em; 709 | margin: 0 5px; 710 | 711 | } 712 | 713 | .graph_legend div.row .info-button 714 | { 715 | color: lightgray; 716 | } 717 | .graph_legend div.row:hover .info-button 718 | { 719 | color: black; 720 | } 721 | 722 | .nvtooltip table tr:nth-child(3) 723 | { 724 | display: none; 725 | } 726 | 727 | .twitter_tooltip table tr:nth-child(2) 728 | { 729 | display: none; 730 | } 731 | 732 | /* LANDING PAGE DASHBOARD STYLING BELOW*/ 733 | .table-borderless td, 734 | .table-borderless th { 735 | border: 0; 736 | } 737 | 738 | .tight-span { 739 | position: relative; 740 | top: -4px; 741 | } 742 | 743 | .modal-informational 744 | { 745 | background-color: lightgray; 746 | } 747 | -------------------------------------------------------------------------------- /frontend/static/css/external.min.css: -------------------------------------------------------------------------------- 1 | .selected_node_filter{border:solid #000 2px}.botimages{width:300px;height:2em}.botimages div{background-size:300px auto;background-position:left center;background-repeat:no-repeat;height:100%;width:100%}.images-nots{background-image:url(../5nots.png)}.images-bots{background-image:url(../5bots.png)}#tutorial{position:fixed;left:0;right:0;top:0;bottom:0;z-index:25000;background:rgba(0,0,0,.7)}#tutorial-carousel{width:100%}#tutorial-content{height:70%;width:100%;background:rgba(0,0,0,.5)}#tutorial-content .carousel-item img{max-width:90%;margin:0 auto;max-height:100%}.carousel-inner{position:absolute;top:4em;left:0;right:0;bottom:4em}.carousel-item>div{height:100%}#tutorial-content .carousel-item{text-align:center;height:100%;position:relative}#tutorial-content .carousel-item{display:none}#tutorial-content .carousel-item.active{display:block}.carousel-control-next,.carousel-control-prev{width:5%}.first-slide img,.last-slide img{visibility:hidden}.first-slide-content,.last-slide-content{position:relative}.first-slide-content>div,.last-slide-content>div{position:absolute;top:0;bottom:0;left:0;right:0;margin:0 auto;width:90%;background:#fff}.close-tutorial-button{margin-top:-3rem;margin-bottom:0}.fade-enter-active,.fade-leave-active{transition:opacity .5s,transform .5s}.fade-enter,.fade-leave-to{opacity:0}.slide_in-enter-active,.slide_in-leave-active{transition:transform .5s;transition:max-height .5s,transform .5s;max-height:200rem}.slide_in-enter,.slide_in-leave-to{transform:scaleY(0);max-height:0}#article_list_container{overflow:hidden;transform-origin:center top}header{margin-top:2em;margin-bottom:3rem}footer{margin-top:3rem}section{margin-bottom:3rem}section#secondary_form{margin-bottom:1rem}.loader{border:16px solid #f3f3f3;border-radius:50%;border-top:16px solid #3498db;width:120px;height:120px;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.claim_label,li.claim,li.claim a{color:#000;background-color:#a9a9a9}.fact_checking_label,li.fact_checking,li.fact_checking a{color:#000;background-color:#eed202}li{margin-bottom:1rem}.claim_label,.fact_checking_label{height:.25rem;display:inline-block;vertical-align:middle}.line{width:3.5rem}.tab-content .claim_label,.tab-content .fact_checking_label{width:auto;height:auto}#spinner{position:fixed;top:0;left:0;bottom:0;right:0;background:rgba(200,200,255,1);z-index:20000}#spinner.transparent,.modal{background:rgba(200,200,255,.7)}#spinner i{font-size:149px;line-height:149px}#spinner span,#spinner>div{position:absolute;position:absolute;display:block;top:50%;left:50%;height:150px;width:150px;margin-left:-75px;margin-top:-75px;text-align:center}.outline{text-shadow:1px 1px 1px rgba(255,255,255,1),1px -1px 1px rgba(255,255,255,1),-1px -1px 1px rgba(255,255,255,1),-1px 1px 1px rgba(255,255,255,1),0 1px 1px rgba(255,255,255,1),0 -1px 1px rgba(255,255,255,1),-1px 0 1px rgba(255,255,255,1),-1px 0 1px rgba(255,255,255,1)}#spinner>div{margin-top:100px;display:none}#sigmagraph{position:relative}#graph_help_text{position:absolute;bottom:0;right:2em}#zoom_buttons{position:absolute;top:1.5rem;right:.5rem}#zoom_buttons>div{white-space:nowrap;line-height:1}.graph_legend{position:relative}.graph_legend>div{position:relative}.bg-light.bg-semi-transparent{position:absolute;top:0;left:0;right:0;bottom:0;opacity:.8}#bot_legend_gradient{width:4rem;position:absolute;right:0}.bot_legend_section{height:4rem;max-height:9vh;vertical-align:center}.bot_legend_section span{color:#000}#focus_label{position:absolute;bottom:5.5em;right:0;left:0;padding:0 .25em}#modalDialogWidget{max-width:850px}.nowrap{white-space:nowrap}.modal-show{display:block;z-index:15000}.modal{opacity:0;transition:opacity .1s linear}.modal-dialog{position:fixed;top:0;bottom:0;left:0;right:0;margin:auto}.modal-body{overflow:auto}.modal-content{font-size:.875rem;position:absolute;top:3rem;bottom:3rem;overflow:auto}.modal-content h2{font-size:1.25rem}.modal-content h3{font-size:1rem;margin-left:1rem;margin-top:.75rem}.modal-content .article_headline{margin-left:2rem}.modal-content .modal_links{margin-left:3rem}#popular_articles_claim tr td:first-child,#popular_articles_fact_checking tr td:first-child{white-space:nowrap}#articles.hidden,#graphs.hidden{display:none}#article_list a{text-decoration:underline}#article_list input[type=checkbox]{display:none}#article_list .article_title{display:block}input+.check_icons>.fa-check-square-o,input+.check_icons>.fa-square-o{display:none;font-size:2rem;vertical-align:middle}input:checked+.check_icons>.fa-check-square-o,input:not(:checked)+.check_icons>.fa-square-o{display:inline-block}#graph_error{position:absolute;top:0;bottom:0;left:0;right:0}#graphs{position:relative}#shrink_left_button,#shrink_right_button{position:absolute;top:50%}#shrink_left_button{right:0}#shrink_right_button{left:0}#chart{height:100%;width:100%}#errorModalBody i{vertical-align:middle}#legend label{margin:0 auto}.animation-control,.layout-button{vertical-align:middle;font-size:.75rem;display:block}.layout-button{margin-top:.5rem}.layout-button span i{vertical-align:middle}.layout-button>span{display:inline-block;height:2em;width:1em;vertical-align:middle;padding:2px;font-size:1em;overflow:hidden;color:#222;background:#fff;border-radius:2px;border:solid gray 1px}.layout-button-timeline>span{width:3em}.layout-button-split>span:first-child{width:1.1em;border-right:solid #fff 1px}.layout-button-split>span:last-child{width:1.9em}.layout-button-graph>span{width:3em}.layout-button-graph i{position:absolute;display:block;margin:auto;width:2em;height:2em;margin:0 5px}.graph_legend div.row .info-button{color:#d3d3d3}.graph_legend div.row:hover .info-button{color:#000}.nvtooltip table tr:nth-child(3){display:none}.twitter_tooltip table tr:nth-child(2){display:none}.table-borderless td,.table-borderless th{border:0}.tight-span{position:relative;top:-4px}.modal-informational{background-color:#d3d3d3} -------------------------------------------------------------------------------- /frontend/static/css/hoaxy_styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | 7 | body { 8 | /*padding-top: 50px;*/ 9 | background: #144073 10 | 11 | } 12 | 13 | 14 | .reg:after 15 | { 16 | content: "\ae"; 17 | vertical-align: sub; 18 | font-size: .4em; 19 | } 20 | nav.navbar a, nav.navbar small 21 | { 22 | color: white; 23 | } 24 | 25 | .navbar 26 | { 27 | border-radius: 0; 28 | } 29 | 30 | .hoaxy-logo a { 31 | font-family: 'Montserrat', sans-serif; 32 | } 33 | 34 | .hoaxy-logo a:hover{ 35 | text-decoration: none; 36 | } 37 | 38 | /*Header*/ 39 | 40 | .top-section { 41 | padding-bottom: 80px; 42 | box-shadow: 0 4px 10px #CFCFCF; 43 | } 44 | 45 | 46 | #q_str { 47 | width:70%; 48 | margin-top:50px; 49 | height: 40px; 50 | } 51 | 52 | /*input[placeholder] { 53 | border: 1px solid #969696; 54 | color: #C8C8C8; 55 | font-family: 'Lato', sans-serif; 56 | font-size: 14px; 57 | font-weight: 300; 58 | padding-left: 20px; 59 | 60 | 61 | }*/ 62 | input#query 63 | { 64 | display: inline; 65 | width: 100%; 66 | } 67 | 68 | #underline-hoaxy { 69 | display: block; 70 | border-color: #C8C8C8; 71 | margin-top: 1.5em; 72 | margin-bottom: 1.5em; 73 | border-width: 1px; 74 | width: 50px; 75 | } 76 | 77 | 78 | /* 79 | * Global add-ons 80 | */ 81 | 82 | .sub-header { 83 | padding-bottom: 10px; 84 | border-bottom: 1px solid #eee; 85 | } 86 | 87 | 88 | /* 89 | * Top navigation 90 | * Hide default border to remove 1px line. 91 | */ 92 | 93 | .navbar-fixed-top { 94 | border: 0; 95 | 96 | } 97 | 98 | 99 | /* 100 | * Sidebar 101 | */ 102 | 103 | 104 | /* Hide for mobile, show later */ 105 | 106 | .sidebar { 107 | display: none; 108 | } 109 | 110 | @media (min-width: 768px) { 111 | .sidebar { 112 | position: fixed; 113 | top: 51px; 114 | bottom: 0; 115 | left: 0; 116 | z-index: 1000; 117 | display: block; 118 | padding: 20px; 119 | overflow-x: hidden; 120 | overflow-y: auto; 121 | /* Scrollable contents if viewport is shorter than content. */ 122 | background-color: #f5f5f5; 123 | border-right: 1px solid #eee; 124 | } 125 | } 126 | 127 | 128 | /* Sidebar navigation */ 129 | 130 | .nav-sidebar { 131 | margin-right: -21px; 132 | /* 20px padding + 1px border */ 133 | margin-bottom: 20px; 134 | margin-left: -20px; 135 | } 136 | 137 | .nav-sidebar > li > a { 138 | padding-right: 20px; 139 | padding-left: 20px; 140 | } 141 | 142 | .nav-sidebar > .active > a, 143 | .nav-sidebar > .active > a:hover, 144 | .nav-sidebar > .active > a:focus { 145 | color: #fff; 146 | background-color: #428bca; 147 | } 148 | 149 | 150 | /* 151 | * Main content 152 | */ 153 | 154 | .main { 155 | padding: 20px; 156 | } 157 | 158 | @media (min-width: 768px) { 159 | .main { 160 | padding-right: 40px; 161 | padding-left: 40px; 162 | } 163 | } 164 | 165 | .main .page-header { 166 | margin-top: 0; 167 | } 168 | 169 | 170 | /* 171 | * Placeholder dashboard ideas 172 | */ 173 | 174 | .placeholders { 175 | margin-bottom: 30px; 176 | text-align: center; 177 | } 178 | 179 | .placeholders h4 { 180 | margin-bottom: 0; 181 | } 182 | 183 | .placeholder { 184 | margin-bottom: 20px; 185 | } 186 | 187 | .placeholder img { 188 | display: inline-block; 189 | border-radius: 50%; 190 | } 191 | 192 | div#fixedheader { 193 | position: fixed; 194 | top: 0px; 195 | left: 0px; 196 | width: 100%; 197 | color: #CCC; 198 | background: #333; 199 | padding: 20px; 200 | } 201 | 202 | div#fixedfooter { 203 | position: fixed; 204 | bottom: 0px; 205 | left: 0px; 206 | width: 100%; 207 | color: #CCC; 208 | background: #333; 209 | height: 6vh; 210 | /*padding:8px;*/ 211 | } 212 | 213 | .block { 214 | margin-top: 15px; 215 | margin-bottom: 20px; 216 | } 217 | 218 | .footer { 219 | margin-top: 50px; 220 | } 221 | 222 | .footer a, 223 | p { 224 | font-size: 12px; 225 | } 226 | 227 | .loader { 228 | border: 16px solid #f3f3f3; 229 | border-radius: 50%; 230 | border-top: 16px solid #3498db; 231 | width: 120px; 232 | height: 120px; 233 | -webkit-animation: spin 2s linear infinite; 234 | animation: spin 2s linear infinite; 235 | } 236 | 237 | @-webkit-keyframes spin { 238 | 0% { 239 | -webkit-transform: rotate(0deg); 240 | } 241 | 100% { 242 | -webkit-transform: rotate(360deg); 243 | } 244 | } 245 | 246 | @keyframes spin { 247 | 0% { 248 | transform: rotate(0deg); 249 | } 250 | 100% { 251 | transform: rotate(360deg); 252 | } 253 | } 254 | 255 | 256 | 257 | /*#article_list li, */ 258 | #select_all_section 259 | { 260 | display: block; 261 | } 262 | /*#article_list label, */ 263 | #select_all_section label 264 | { 265 | display: block; 266 | position: relative; 267 | } 268 | 269 | /*#article_list input,*/ 270 | #select_all_section input 271 | { 272 | float: left; 273 | /*margin: .5rem;*/ 274 | /*margin-top: 50%;*/ 275 | top: 0; 276 | height: 100%; 277 | position: absolute; 278 | top: 0; 279 | left: 1.5rem; 280 | bottom: 0; 281 | transform: scale(2); 282 | margin: 1px; 283 | display: none; 284 | 285 | } 286 | 287 | #article_list .fa 288 | { 289 | 290 | font-size: 3rem; 291 | margin-left: .5rem; 292 | } 293 | 294 | #select_all_section .fa 295 | { 296 | position: absolute; 297 | height: 100%; 298 | top: 0; 299 | left: 1.5rem; 300 | display: block; 301 | font-size: 3rem; 302 | cursor: pointer; 303 | 304 | position: absolute; 305 | top: 50%; 306 | margin-top: -1.5rem; 307 | } 308 | /*#article_list .fa-check-square-o,*/ 309 | #select_all_section .fa-check-square-o 310 | { 311 | display: none; 312 | } 313 | /*#article_list input:checked + .fa-square-o,*/ 314 | #select_all_section input:checked + .fa-square-o 315 | { 316 | display: none; 317 | } 318 | /*#article_list input:checked + .fa-square-o + .fa-check-square-o,*/ 319 | #select_all_section input:checked + .fa-square-o + .fa-check-square-o 320 | { 321 | display: inline-block; 322 | } 323 | /*#article_list label > div, #select_all_section label > div,*/ 324 | #select_all_section label > div, #select_all_section label > div 325 | { 326 | display: block; 327 | padding: .5rem; 328 | padding-left: 5rem; 329 | padding-right: 1rem; 330 | /*border-radius: .25rem;*/ 331 | /*border: solid lightgray 1px;*/ 332 | } 333 | 334 | #article_list .article_title 335 | { 336 | display: block; 337 | font-weight: bold; 338 | } 339 | #article_list .article_domain, #article_list .article_date 340 | { 341 | 342 | } 343 | #article_list span.icon 344 | { 345 | float: right; 346 | font-size: 2em; 347 | display: inline-block; 348 | margin-right: .5rem; 349 | } 350 | #article_list li 351 | { 352 | margin-bottom: auto; 353 | } 354 | 355 | #articles .fact_checking_label, #articles .claim_label 356 | { 357 | padding: 0 .5rem; 358 | /*border-radius: .25rem;*/ 359 | width: auto; 360 | height: auto; 361 | display: inline; 362 | vertical-align: top; 363 | } 364 | 365 | /*li.claim, li.claim a, .claim_label 366 | { 367 | color: white; 368 | background-color: #4B3F72; 369 | } 370 | li.fact_checking, li.fact_checking a, .fact_checking_label 371 | { 372 | color: white; 373 | background-color: #F46036; 374 | } 375 | li.claim .article_domain, li.claim .article_domain a, li.claim .article_date, #article_list li.claim div.article_stats 376 | { 377 | color: rgba(255, 255, 255, .7); 378 | } 379 | li.claim .article_domain a:hover 380 | { 381 | color: rgba(255, 255, 255, 1); 382 | } 383 | li.fact_checking .article_domain, li.fact_checking .article_domain a, li.fact_checking .article_date, #article_list li.fact_checking .article_stats 384 | { 385 | /*color: rgba(0, 0, 0, .5);*/ 386 | /*color: rgba(255, 255, 255, .8); 387 | } 388 | li.fact_checking .article_domain a:hover 389 | {*/ 390 | /*color: rgba(0, 0, 0, 1);*/ 391 | /* 392 | color: rgba(255, 255, 255, 1); 393 | }*/ 394 | 395 | .article_stats span 396 | { 397 | padding-left: 1rem; 398 | } 399 | 400 | 401 | #visualize_top 402 | { 403 | margin-top: .75rem; 404 | } 405 | /* #sharing_buttons 406 | { 407 | padding-top: 1.25rem; 408 | } */ 409 | #select_all_section > label > div 410 | { 411 | /*background: lightgray;*/ 412 | height: 4rem; 413 | line-height: 4rem; 414 | vertical-align: middle; 415 | /*position: relative;*/ 416 | } 417 | #select_all_section span 418 | { 419 | display: inline-block; 420 | line-height: 1; 421 | position: absolute; 422 | top: 50%; 423 | margin-top: -.5rem; 424 | } 425 | #select_all_section input:checked + .fa + .fa + div 426 | { 427 | /*background: darkgray;*/ 428 | } 429 | 430 | #graphs>div { 431 | /*background: darkgray;*/ 432 | } 433 | section, nav.tab_container 434 | { 435 | position: relative; 436 | background: #FAFAFA; 437 | padding-top: 2rem; 438 | margin-bottom: 0; 439 | 440 | } 441 | section#form, section#graphs, section#articles 442 | { 443 | padding-top: 5rem; 444 | /*margin-top: 3rem;*/ 445 | padding-bottom: 2rem; 446 | } 447 | section#dashboard 448 | { 449 | padding-bottom: 2rem; 450 | } 451 | section#secondary_form 452 | { 453 | margin-bottom: 0; 454 | } 455 | .btn:not(#searchByTwitter):not(#searchByHoaxy):not(#searchByTwitter2):not(#searchByHoaxy2), section#dashboard, .btn-group-toggle 456 | { 457 | -webkit-box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 458 | -moz-box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 459 | box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 460 | } 461 | 462 | /* .btn-group-toggle .btn 463 | { 464 | -webkit-box-shadow: 0px; 465 | -moz-box-shadow: 0px; 466 | box-shadow: 0px; 467 | } */ 468 | 469 | .form-control, .btn 470 | { 471 | /*border-radius: 0;*/ 472 | } 473 | h1, h2 474 | { 475 | color: #4A90E2; 476 | font-size: 1.5rem; 477 | } 478 | h2 479 | { 480 | font-size: 1.25rem; 481 | } 482 | .btn-blue 483 | { 484 | background: #4A90E2; 485 | } 486 | 487 | hr 488 | { 489 | background: #4A90E2; 490 | color: #4A90E2; 491 | } 492 | header.container-fluid 493 | { 494 | position: relative; 495 | padding-top: 3rem; 496 | background: #FAFAFA; 497 | padding-bottom: 1rem; 498 | /*-webkit-box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 499 | -moz-box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2); 500 | box-shadow: 0px 2px 2px 2px rgba(50, 50, 50, 0.2);*/ 501 | } 502 | .radio-container 503 | { 504 | padding-right: 1rem; 505 | } 506 | footer 507 | { 508 | background: #144073; 509 | color: white; 510 | padding: 1rem 0; 511 | margin-top: 0; 512 | } 513 | footer a 514 | { 515 | color: white; 516 | text-decoration: underline; 517 | } 518 | header 519 | { 520 | z-index: 10; 521 | padding-bottom: 0; 522 | margin-bottom: 0; 523 | } 524 | #form 525 | { 526 | z-index: 9; 527 | padding-top: 0; 528 | } 529 | #articles 530 | { 531 | z-index: 8; 532 | } 533 | #graphs 534 | { 535 | z-index: 7; 536 | } 537 | footer 538 | { 539 | z-index: 6; 540 | } 541 | #suggestions a 542 | { 543 | padding-right: 1rem; 544 | font-size: .75rem; 545 | } 546 | 547 | .form-group 548 | { 549 | margin-bottom: 1rem; 550 | } 551 | 552 | #header_links a 553 | { 554 | display: inline-block; 555 | padding: 0 1rem; 556 | } 557 | #spinner 558 | { 559 | position: fixed; 560 | top: 0; 561 | left: 0; 562 | bottom: 0; 563 | right: 0; 564 | background: rgba(255, 255, 255, .7); 565 | /*background: green;*/ 566 | z-index: 20000; 567 | } 568 | 569 | 570 | #sigmagraph 571 | { 572 | position: relative; 573 | } 574 | #graph_help_text 575 | { 576 | position: absolute; 577 | bottom: 0em; 578 | right: 2em; 579 | } 580 | #zoom_buttons 581 | { 582 | position: absolute; 583 | top: 2em; 584 | right: 2em; 585 | } 586 | 587 | .modal-border-bottom 588 | { 589 | border-bottom: 1px solid #ccc; 590 | } 591 | 592 | .modal-border-top 593 | { 594 | border-top: 1px solid #ccc; 595 | } 596 | 597 | .modal-content 598 | { 599 | font-size: .875rem; 600 | } 601 | .modal-content h2 602 | { 603 | font-size: 1.25rem; 604 | } 605 | .modal-content h3 606 | { 607 | font-size: 1rem; 608 | margin-left: 1rem; 609 | margin-top: .75rem; 610 | } 611 | 612 | .modal-content .article_headline 613 | { 614 | margin-left: 2rem; 615 | } 616 | .modal-content .modal_links 617 | { 618 | margin-left: 3rem; 619 | } 620 | 621 | .modal-content .tweet_link, .modal-content .article_link 622 | { 623 | margin-left: 3rem; 624 | } 625 | 626 | /* #sharing_buttons iframe 627 | { 628 | display: inline-block; 629 | vertical-align: bottom; 630 | } */ 631 | 632 | 633 | header h1 634 | { 635 | font-size: 5rem; 636 | margin-top: 2rem; 637 | } 638 | 639 | header p 640 | { 641 | font-size: 1.25rem; 642 | } 643 | 644 | .fixed-top 645 | { 646 | position: fixed; 647 | top: 0; 648 | left: 0; 649 | right: 0; 650 | z-index: 200; 651 | background: #144073; 652 | } 653 | 654 | 655 | .number 656 | { 657 | position: absolute; 658 | font-weight: bold; 659 | color: #144073; 660 | font-size: 1.5rem; 661 | border: solid #144073 3px; 662 | border-radius: 50%; 663 | line-height: 2rem; 664 | height: 2.5rem; 665 | width: 2.5rem; 666 | vertical-align: middle; 667 | text-align: center; 668 | margin-left: -3rem; 669 | bottom: 0; 670 | } 671 | 672 | #popular_articles_claim tr td:first-child, 673 | #popular_articles_fact_checking tr td:first-child 674 | { 675 | white-space: nowrap; 676 | } 677 | 678 | 679 | .visualize_sticky 680 | { 681 | z-index: 5000000; 682 | position: fixed; 683 | } 684 | 685 | 686 | /*@keyframes pulse_animation { 687 | } 688 | 689 | @-webkit-keyframes pulse { 690 | 0% { -webkit-transform: scale(1); } 691 | 30% { -webkit-transform: scale(1); } 692 | 40% { -webkit-transform: scale(1.08); } 693 | 50% { -webkit-transform: scale(1); } 694 | 60% { -webkit-transform: scale(1); } 695 | 70% { -webkit-transform: scale(1.05); } 696 | 80% { -webkit-transform: scale(1); } 697 | 100% { -webkit-transform: scale(1); } 698 | 699 | }*/ 700 | 701 | @keyframes pulse { 702 | 0% { transform: none; } 703 | 10% { transform: scale(1.2); } 704 | 20% { transform: none; } 705 | 100% { transform: none; } 706 | 707 | } 708 | 709 | 710 | .pulse { 711 | animation-name: pulse; 712 | animation-duration: 4000ms; 713 | /*transform-origin:70% 70%;*/ 714 | animation-iteration-count: infinite; 715 | animation-timing-function: linear; 716 | } 717 | 718 | 719 | 720 | 721 | .navbar div { 722 | background: inherit; 723 | } 724 | 725 | #source_dropdown, #help_dropdown { 726 | position: relative; 727 | background: inherit; 728 | } 729 | 730 | #source_dropdown ul, #help_dropdown ul { 731 | z-index: 20; 732 | display: none; 733 | position: absolute; 734 | right: 0; 735 | top: 0; 736 | margin-top: 2rem; 737 | padding: 1rem; 738 | padding-top: .5rem; 739 | background: inherit; 740 | -webkit-box-shadow: 0px 2px 1px 0px rgba(0, 0, 0, 0.4); 741 | -moz-box-shadow: 0px 2px 1px 0px rgba(0, 0, 0, 0.4); 742 | box-shadow: 0px 2px 1px 0px rgba(0, 0, 0, 0.4); 743 | white-space: nowrap; 744 | } 745 | 746 | #source_dropdown:hover ul, #help_dropdown:hover ul { 747 | display: block; 748 | } 749 | 750 | #tutorial_link 751 | { 752 | font-weight: bold; 753 | } 754 | #tutorial_link i 755 | { 756 | font-size: 1.5em; 757 | vertical-align: top; 758 | } 759 | 760 | #tutorialModal .modal-dialog 761 | { 762 | max-width: 800px; 763 | } 764 | #tutorialModal iframe 765 | { 766 | margin: 0 auto; 767 | } 768 | 769 | .btn-secondary 770 | { 771 | background-color: #DFDFDF; 772 | border-color: #8A8A8A; 773 | color: black; 774 | } 775 | .btn-primary 776 | { 777 | background-color: #DFDFDF; 778 | border-color: #8A8A8A; 779 | color: black; 780 | } 781 | .btn-primary.disabled, .btn-primary:disabled 782 | { 783 | background-color: #FAFAFA; 784 | border-color: #ADADAD; 785 | color: #797979; 786 | } 787 | .btn-primary:hover 788 | { 789 | background-color: #ADADAD; 790 | border-color: #797979; 791 | background-color: #0062ff; 792 | border-color: #005cbf; 793 | } 794 | 795 | /* FAQ Styling */ 796 | dt::before { 797 | display: block; 798 | content: " "; 799 | margin-top: -100px; 800 | height: 100px; 801 | visibility: hidden; 802 | pointer-events: none; 803 | } 804 | -------------------------------------------------------------------------------- /frontend/static/css/hoaxy_styles.min.css: -------------------------------------------------------------------------------- 1 | body{background:#144073}.reg:after{content:"\ae";vertical-align:sub;font-size:.4em}nav.navbar a,nav.navbar small{color:#fff}.navbar{border-radius:0}.hoaxy-logo a{font-family:Montserrat,sans-serif}.hoaxy-logo a:hover{text-decoration:none}.top-section{padding-bottom:80px;box-shadow:0 4px 10px #cfcfcf}#q_str{width:70%;margin-top:50px;height:40px}input#query{display:inline;width:100%}#underline-hoaxy{display:block;border-color:#c8c8c8;margin-top:1.5em;margin-bottom:1.5em;border-width:1px;width:50px}.sub-header{padding-bottom:10px;border-bottom:1px solid #eee}.navbar-fixed-top{border:0}.sidebar{display:none}@media (min-width:768px){.sidebar{position:fixed;top:51px;bottom:0;left:0;z-index:1000;display:block;padding:20px;overflow-x:hidden;overflow-y:auto;background-color:#f5f5f5;border-right:1px solid #eee}}.nav-sidebar{margin-right:-21px;margin-bottom:20px;margin-left:-20px}.nav-sidebar>li>a{padding-right:20px;padding-left:20px}.nav-sidebar>.active>a,.nav-sidebar>.active>a:focus,.nav-sidebar>.active>a:hover{color:#fff;background-color:#428bca}.main{padding:20px}@media (min-width:768px){.main{padding-right:40px;padding-left:40px}}.main .page-header{margin-top:0}.placeholders{margin-bottom:30px;text-align:center}.placeholders h4{margin-bottom:0}.placeholder{margin-bottom:20px}.placeholder img{display:inline-block;border-radius:50%}div#fixedheader{position:fixed;top:0;left:0;width:100%;color:#ccc;background:#333;padding:20px}div#fixedfooter{position:fixed;bottom:0;left:0;width:100%;color:#ccc;background:#333;height:6vh}.block{margin-top:15px;margin-bottom:20px}.footer{margin-top:50px}.footer a,p{font-size:12px}.loader{border:16px solid #f3f3f3;border-radius:50%;border-top:16px solid #3498db;width:120px;height:120px;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}#select_all_section{display:block}#select_all_section label{display:block;position:relative}#select_all_section input{float:left;top:0;height:100%;position:absolute;top:0;left:1.5rem;bottom:0;transform:scale(2);margin:1px;display:none}#article_list .fa{font-size:3rem;margin-left:.5rem}#select_all_section .fa{position:absolute;height:100%;top:0;left:1.5rem;display:block;font-size:3rem;cursor:pointer;position:absolute;top:50%;margin-top:-1.5rem}#select_all_section .fa-check-square-o{display:none}#select_all_section input:checked+.fa-square-o{display:none}#select_all_section input:checked+.fa-square-o+.fa-check-square-o{display:inline-block}#select_all_section label>div{display:block;padding:.5rem;padding-left:5rem;padding-right:1rem}#article_list .article_title{display:block;font-weight:700}#article_list span.icon{float:right;font-size:2em;display:inline-block;margin-right:.5rem}#article_list li{margin-bottom:auto}#articles .claim_label,#articles .fact_checking_label{padding:0 .5rem;width:auto;height:auto;display:inline;vertical-align:top}.article_stats span{padding-left:1rem}#visualize_top{margin-top:.75rem}#select_all_section>label>div{height:4rem;line-height:4rem;vertical-align:middle}#select_all_section span{display:inline-block;line-height:1;position:absolute;top:50%;margin-top:-.5rem}nav.tab_container,section{position:relative;background:#fafafa;padding-top:2rem;margin-bottom:0}section#articles,section#form,section#graphs{padding-top:5rem;padding-bottom:2rem}section#dashboard{padding-bottom:2rem}section#secondary_form{margin-bottom:0}.btn-group-toggle,.btn:not(#searchByTwitter):not(#searchByHoaxy):not(#searchByTwitter2):not(#searchByHoaxy2),section#dashboard{-webkit-box-shadow:0 2px 2px 2px rgba(50,50,50,.2);-moz-box-shadow:0 2px 2px 2px rgba(50,50,50,.2);box-shadow:0 2px 2px 2px rgba(50,50,50,.2)}h1,h2{color:#4a90e2;font-size:1.5rem}h2{font-size:1.25rem}.btn-blue{background:#4a90e2}hr{background:#4a90e2;color:#4a90e2}header.container-fluid{position:relative;padding-top:3rem;background:#fafafa;padding-bottom:1rem}.radio-container{padding-right:1rem}footer{background:#144073;color:#fff;padding:1rem 0;margin-top:0}footer a{color:#fff;text-decoration:underline}header{z-index:10;padding-bottom:0;margin-bottom:0}#form{z-index:9;padding-top:0}#articles{z-index:8}#graphs{z-index:7}footer{z-index:6}#suggestions a{padding-right:1rem;font-size:.75rem}.form-group{margin-bottom:1rem}#header_links a{display:inline-block;padding:0 1rem}#spinner{position:fixed;top:0;left:0;bottom:0;right:0;background:rgba(255,255,255,.7);z-index:20000}#sigmagraph{position:relative}#graph_help_text{position:absolute;bottom:0;right:2em}#zoom_buttons{position:absolute;top:2em;right:2em}.modal-border-bottom{border-bottom:1px solid #ccc}.modal-border-top{border-top:1px solid #ccc}.modal-content{font-size:.875rem}.modal-content h2{font-size:1.25rem}.modal-content h3{font-size:1rem;margin-left:1rem;margin-top:.75rem}.modal-content .article_headline{margin-left:2rem}.modal-content .modal_links{margin-left:3rem}.modal-content .article_link,.modal-content .tweet_link{margin-left:3rem}header h1{font-size:5rem;margin-top:2rem}header p{font-size:1.25rem}.fixed-top{position:fixed;top:0;left:0;right:0;z-index:200;background:#144073}.number{position:absolute;font-weight:700;color:#144073;font-size:1.5rem;border:solid #144073 3px;border-radius:50%;line-height:2rem;height:2.5rem;width:2.5rem;vertical-align:middle;text-align:center;margin-left:-3rem;bottom:0}#popular_articles_claim tr td:first-child,#popular_articles_fact_checking tr td:first-child{white-space:nowrap}.visualize_sticky{z-index:5000000;position:fixed}@keyframes pulse{0%{transform:none}10%{transform:scale(1.2)}20%{transform:none}100%{transform:none}}.pulse{animation-name:pulse;animation-duration:4s;animation-iteration-count:infinite;animation-timing-function:linear}.navbar div{background:inherit}#help_dropdown,#source_dropdown{position:relative;background:inherit}#help_dropdown ul,#source_dropdown ul{z-index:20;display:none;position:absolute;right:0;top:0;margin-top:2rem;padding:1rem;padding-top:.5rem;background:inherit;-webkit-box-shadow:0 2px 1px 0 rgba(0,0,0,.4);-moz-box-shadow:0 2px 1px 0 rgba(0,0,0,.4);box-shadow:0 2px 1px 0 rgba(0,0,0,.4);white-space:nowrap}#help_dropdown:hover ul,#source_dropdown:hover ul{display:block}#tutorial_link{font-weight:700}#tutorial_link i{font-size:1.5em;vertical-align:top}#tutorialModal .modal-dialog{max-width:800px}#tutorialModal iframe{margin:0 auto}.btn-secondary{background-color:#dfdfdf;border-color:#8a8a8a;color:#000}.btn-primary{background-color:#dfdfdf;border-color:#8a8a8a;color:#000}.btn-primary.disabled,.btn-primary:disabled{background-color:#fafafa;border-color:#adadad;color:#797979}.btn-primary:hover{background-color:#adadad;border-color:#797979;background-color:#0062ff;border-color:#005cbf}dt::before{display:block;content:" ";margin-top:-100px;height:100px;visibility:hidden;pointer-events:none} -------------------------------------------------------------------------------- /frontend/static/css/nv.d3.css: -------------------------------------------------------------------------------- 1 | /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */ 2 | .nvd3 .nv-axis { 3 | pointer-events:none; 4 | opacity: 1; 5 | } 6 | 7 | .nvd3 .nv-axis path { 8 | fill: none; 9 | stroke: #000; 10 | stroke-opacity: .75; 11 | shape-rendering: crispEdges; 12 | } 13 | 14 | .nvd3 .nv-axis path.domain { 15 | stroke-opacity: .75; 16 | } 17 | 18 | .nvd3 .nv-axis.nv-x path.domain { 19 | stroke-opacity: 0; 20 | } 21 | 22 | .nvd3 .nv-axis line { 23 | fill: none; 24 | stroke: #e5e5e5; 25 | shape-rendering: crispEdges; 26 | } 27 | 28 | .nvd3 .nv-axis .zero line, 29 | /*this selector may not be necessary*/ .nvd3 .nv-axis line.zero { 30 | stroke-opacity: .75; 31 | } 32 | 33 | .nvd3 .nv-axis .nv-axisMaxMin text { 34 | font-weight: bold; 35 | } 36 | 37 | .nvd3 .x .nv-axis .nv-axisMaxMin text, 38 | .nvd3 .x2 .nv-axis .nv-axisMaxMin text, 39 | .nvd3 .x3 .nv-axis .nv-axisMaxMin text { 40 | text-anchor: middle 41 | } 42 | 43 | .nvd3 .nv-axis.nv-disabled { 44 | opacity: 0; 45 | } 46 | 47 | .nvd3 .nv-bars rect { 48 | fill-opacity: .75; 49 | 50 | transition: fill-opacity 250ms linear; 51 | -moz-transition: fill-opacity 250ms linear; 52 | -webkit-transition: fill-opacity 250ms linear; 53 | } 54 | 55 | .nvd3 .nv-bars rect.hover { 56 | fill-opacity: 1; 57 | } 58 | 59 | .nvd3 .nv-bars .hover rect { 60 | fill: lightblue; 61 | } 62 | 63 | .nvd3 .nv-bars text { 64 | fill: rgba(0,0,0,0); 65 | } 66 | 67 | .nvd3 .nv-bars .hover text { 68 | fill: rgba(0,0,0,1); 69 | } 70 | 71 | .nvd3 .nv-multibar .nv-groups rect, 72 | .nvd3 .nv-multibarHorizontal .nv-groups rect, 73 | .nvd3 .nv-discretebar .nv-groups rect { 74 | stroke-opacity: 0; 75 | 76 | transition: fill-opacity 250ms linear; 77 | -moz-transition: fill-opacity 250ms linear; 78 | -webkit-transition: fill-opacity 250ms linear; 79 | } 80 | 81 | .nvd3 .nv-multibar .nv-groups rect:hover, 82 | .nvd3 .nv-multibarHorizontal .nv-groups rect:hover, 83 | .nvd3 .nv-candlestickBar .nv-ticks rect:hover, 84 | .nvd3 .nv-discretebar .nv-groups rect:hover { 85 | fill-opacity: 1; 86 | } 87 | 88 | .nvd3 .nv-discretebar .nv-groups text, 89 | .nvd3 .nv-multibarHorizontal .nv-groups text { 90 | font-weight: bold; 91 | fill: rgba(0,0,0,1); 92 | stroke: rgba(0,0,0,0); 93 | } 94 | 95 | /* boxplot CSS */ 96 | .nvd3 .nv-boxplot circle { 97 | fill-opacity: 0.5; 98 | } 99 | 100 | .nvd3 .nv-boxplot circle:hover { 101 | fill-opacity: 1; 102 | } 103 | 104 | .nvd3 .nv-boxplot rect:hover { 105 | fill-opacity: 1; 106 | } 107 | 108 | .nvd3 line.nv-boxplot-median { 109 | stroke: black; 110 | } 111 | 112 | .nv-boxplot-tick:hover { 113 | stroke-width: 2.5px; 114 | } 115 | /* bullet */ 116 | .nvd3.nv-bullet { font: 10px sans-serif; } 117 | .nvd3.nv-bullet .nv-measure { fill-opacity: .8; } 118 | .nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; } 119 | .nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; } 120 | .nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; } 121 | .nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; } 122 | .nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; } 123 | .nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; } 124 | .nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; } 125 | .nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; } 126 | .nvd3.nv-bullet .nv-subtitle { fill: #999; } 127 | 128 | 129 | .nvd3.nv-bullet .nv-range { 130 | fill: #bababa; 131 | fill-opacity: .4; 132 | } 133 | .nvd3.nv-bullet .nv-range:hover { 134 | fill-opacity: .7; 135 | } 136 | 137 | .nvd3.nv-candlestickBar .nv-ticks .nv-tick { 138 | stroke-width: 1px; 139 | } 140 | 141 | .nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover { 142 | stroke-width: 2px; 143 | } 144 | 145 | .nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect { 146 | stroke: #2ca02c; 147 | fill: #2ca02c; 148 | } 149 | 150 | .nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect { 151 | stroke: #d62728; 152 | fill: #d62728; 153 | } 154 | 155 | .with-transitions .nv-candlestickBar .nv-ticks .nv-tick { 156 | transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 157 | -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 158 | -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 159 | 160 | } 161 | 162 | .nvd3.nv-candlestickBar .nv-ticks line { 163 | stroke: #333; 164 | } 165 | 166 | 167 | .nvd3 .nv-legend .nv-disabled rect { 168 | /*fill-opacity: 0;*/ 169 | } 170 | 171 | .nvd3 .nv-check-box .nv-box { 172 | fill-opacity:0; 173 | stroke-width:2; 174 | } 175 | 176 | .nvd3 .nv-check-box .nv-check { 177 | fill-opacity:0; 178 | stroke-width:4; 179 | } 180 | 181 | .nvd3 .nv-series.nv-disabled .nv-check-box .nv-check { 182 | fill-opacity:0; 183 | stroke-opacity:0; 184 | } 185 | 186 | .nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check { 187 | opacity: 0; 188 | } 189 | 190 | /* line plus bar */ 191 | .nvd3.nv-linePlusBar .nv-bar rect { 192 | fill-opacity: .75; 193 | } 194 | 195 | .nvd3.nv-linePlusBar .nv-bar rect:hover { 196 | fill-opacity: 1; 197 | } 198 | .nvd3 .nv-groups path.nv-line { 199 | fill: none; 200 | } 201 | 202 | .nvd3 .nv-groups path.nv-area { 203 | stroke: none; 204 | } 205 | 206 | .nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { 207 | fill-opacity: 0; 208 | stroke-opacity: 0; 209 | } 210 | 211 | .nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { 212 | fill-opacity: .5 !important; 213 | stroke-opacity: .5 !important; 214 | } 215 | 216 | 217 | .with-transitions .nvd3 .nv-groups .nv-point { 218 | transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 219 | -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 220 | -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 221 | 222 | } 223 | 224 | .nvd3.nv-scatter .nv-groups .nv-point.hover, 225 | .nvd3 .nv-groups .nv-point.hover { 226 | stroke-width: 7px; 227 | fill-opacity: .95 !important; 228 | stroke-opacity: .95 !important; 229 | } 230 | 231 | 232 | .nvd3 .nv-point-paths path { 233 | stroke: #aaa; 234 | stroke-opacity: 0; 235 | fill: #eee; 236 | fill-opacity: 0; 237 | } 238 | 239 | 240 | 241 | .nvd3 .nv-indexLine { 242 | cursor: ew-resize; 243 | } 244 | 245 | /******************** 246 | * SVG CSS 247 | */ 248 | 249 | /******************** 250 | Default CSS for an svg element nvd3 used 251 | */ 252 | svg.nvd3-svg { 253 | -webkit-touch-callout: none; 254 | -webkit-user-select: none; 255 | -khtml-user-select: none; 256 | -ms-user-select: none; 257 | -moz-user-select: none; 258 | user-select: none; 259 | display: block; 260 | width:100%; 261 | height:100%; 262 | } 263 | 264 | /******************** 265 | Box shadow and border radius styling 266 | */ 267 | .nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip { 268 | -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); 269 | -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); 270 | box-shadow: 0 5px 10px rgba(0,0,0,.2); 271 | 272 | -webkit-border-radius: 5px; 273 | -moz-border-radius: 5px; 274 | border-radius: 5px; 275 | } 276 | 277 | 278 | .nvd3 text { 279 | font: normal 12px Arial; 280 | } 281 | 282 | .nvd3 .title { 283 | font: bold 14px Arial; 284 | } 285 | 286 | .nvd3 .nv-background { 287 | fill: white; 288 | fill-opacity: 0; 289 | } 290 | 291 | .nvd3.nv-noData { 292 | font-size: 18px; 293 | font-weight: bold; 294 | } 295 | 296 | 297 | /********** 298 | * Brush 299 | */ 300 | 301 | .nv-brush .extent { 302 | fill-opacity: .125; 303 | shape-rendering: crispEdges; 304 | } 305 | 306 | .nv-brush .resize path { 307 | fill: #eee; 308 | stroke: #666; 309 | } 310 | 311 | 312 | /********** 313 | * Legend 314 | */ 315 | 316 | .nvd3 .nv-legend .nv-series { 317 | cursor: pointer; 318 | } 319 | 320 | .nvd3 .nv-legend .nv-disabled circle { 321 | fill-opacity: 0; 322 | } 323 | 324 | /* focus */ 325 | .nvd3 .nv-brush .extent { 326 | fill-opacity: 0 !important; 327 | } 328 | 329 | .nvd3 .nv-brushBackground rect { 330 | stroke: #000; 331 | stroke-width: .4; 332 | fill: #fff; 333 | fill-opacity: .7; 334 | } 335 | 336 | 337 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick { 338 | stroke-width: 1px; 339 | } 340 | 341 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover { 342 | stroke-width: 2px; 343 | } 344 | 345 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive { 346 | stroke: #2ca02c; 347 | } 348 | 349 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative { 350 | stroke: #d62728; 351 | } 352 | 353 | 354 | .nvd3 .background path { 355 | fill: none; 356 | stroke: #EEE; 357 | stroke-opacity: .4; 358 | shape-rendering: crispEdges; 359 | } 360 | 361 | .nvd3 .foreground path { 362 | fill: none; 363 | stroke-opacity: .7; 364 | } 365 | 366 | .nvd3 .nv-parallelCoordinates-brush .extent 367 | { 368 | fill: #fff; 369 | fill-opacity: .6; 370 | stroke: gray; 371 | shape-rendering: crispEdges; 372 | } 373 | 374 | .nvd3 .nv-parallelCoordinates .hover { 375 | fill-opacity: 1; 376 | stroke-width: 3px; 377 | } 378 | 379 | 380 | .nvd3 .missingValuesline line { 381 | fill: none; 382 | stroke: black; 383 | stroke-width: 1; 384 | stroke-opacity: 1; 385 | stroke-dasharray: 5, 5; 386 | } 387 | .nvd3.nv-pie path { 388 | stroke-opacity: 0; 389 | transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 390 | -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 391 | -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 392 | 393 | } 394 | 395 | .nvd3.nv-pie .nv-pie-title { 396 | font-size: 24px; 397 | fill: rgba(19, 196, 249, 0.59); 398 | } 399 | 400 | .nvd3.nv-pie .nv-slice text { 401 | stroke: #000; 402 | stroke-width: 0; 403 | } 404 | 405 | .nvd3.nv-pie path { 406 | stroke: #fff; 407 | stroke-width: 1px; 408 | stroke-opacity: 1; 409 | } 410 | 411 | .nvd3.nv-pie .hover path { 412 | fill-opacity: .7; 413 | } 414 | .nvd3.nv-pie .nv-label { 415 | pointer-events: none; 416 | } 417 | .nvd3.nv-pie .nv-label rect { 418 | fill-opacity: 0; 419 | stroke-opacity: 0; 420 | } 421 | 422 | /* scatter */ 423 | .nvd3 .nv-groups .nv-point.hover { 424 | stroke-width: 20px; 425 | stroke-opacity: .5; 426 | } 427 | 428 | .nvd3 .nv-scatter .nv-point.hover { 429 | fill-opacity: 1; 430 | } 431 | .nv-noninteractive { 432 | pointer-events: none; 433 | } 434 | 435 | .nv-distx, .nv-disty { 436 | pointer-events: none; 437 | } 438 | 439 | /* sparkline */ 440 | .nvd3.nv-sparkline path { 441 | fill: none; 442 | } 443 | 444 | .nvd3.nv-sparklineplus g.nv-hoverValue { 445 | pointer-events: none; 446 | } 447 | 448 | .nvd3.nv-sparklineplus .nv-hoverValue line { 449 | stroke: #333; 450 | stroke-width: 1.5px; 451 | } 452 | 453 | .nvd3.nv-sparklineplus, 454 | .nvd3.nv-sparklineplus g { 455 | pointer-events: all; 456 | } 457 | 458 | .nvd3 .nv-hoverArea { 459 | fill-opacity: 0; 460 | stroke-opacity: 0; 461 | } 462 | 463 | .nvd3.nv-sparklineplus .nv-xValue, 464 | .nvd3.nv-sparklineplus .nv-yValue { 465 | stroke-width: 0; 466 | font-size: .9em; 467 | font-weight: normal; 468 | } 469 | 470 | .nvd3.nv-sparklineplus .nv-yValue { 471 | stroke: #f66; 472 | } 473 | 474 | .nvd3.nv-sparklineplus .nv-maxValue { 475 | stroke: #2ca02c; 476 | fill: #2ca02c; 477 | } 478 | 479 | .nvd3.nv-sparklineplus .nv-minValue { 480 | stroke: #d62728; 481 | fill: #d62728; 482 | } 483 | 484 | .nvd3.nv-sparklineplus .nv-currentValue { 485 | font-weight: bold; 486 | font-size: 1.1em; 487 | } 488 | /* stacked area */ 489 | .nvd3.nv-stackedarea path.nv-area { 490 | fill-opacity: .7; 491 | stroke-opacity: 0; 492 | transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 493 | -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 494 | -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 495 | } 496 | 497 | .nvd3.nv-stackedarea path.nv-area.hover { 498 | fill-opacity: .9; 499 | } 500 | 501 | 502 | .nvd3.nv-stackedarea .nv-groups .nv-point { 503 | stroke-opacity: 0; 504 | fill-opacity: 0; 505 | } 506 | 507 | 508 | .nvtooltip { 509 | position: absolute; 510 | background-color: rgba(255,255,255,1.0); 511 | color: rgba(0,0,0,1.0); 512 | padding: 1px; 513 | border: 1px solid rgba(0,0,0,.2); 514 | z-index: 10000; 515 | display: block; 516 | 517 | font-family: Arial; 518 | font-size: 13px; 519 | text-align: left; 520 | pointer-events: none; 521 | 522 | white-space: nowrap; 523 | 524 | -webkit-touch-callout: none; 525 | -webkit-user-select: none; 526 | -khtml-user-select: none; 527 | -moz-user-select: none; 528 | -ms-user-select: none; 529 | user-select: none; 530 | } 531 | 532 | .nvtooltip { 533 | background: rgba(255,255,255, 0.8); 534 | border: 1px solid rgba(0,0,0,0.5); 535 | border-radius: 4px; 536 | } 537 | 538 | /*Give tooltips that old fade in transition by 539 | putting a "with-transitions" class on the container div. 540 | */ 541 | .nvtooltip.with-transitions, .with-transitions .nvtooltip { 542 | transition: opacity 50ms linear; 543 | -moz-transition: opacity 50ms linear; 544 | -webkit-transition: opacity 50ms linear; 545 | 546 | transition-delay: 200ms; 547 | -moz-transition-delay: 200ms; 548 | -webkit-transition-delay: 200ms; 549 | } 550 | 551 | .nvtooltip.x-nvtooltip, 552 | .nvtooltip.y-nvtooltip { 553 | padding: 8px; 554 | } 555 | 556 | .nvtooltip h3 { 557 | margin: 0; 558 | padding: 4px 14px; 559 | line-height: 18px; 560 | font-weight: normal; 561 | background-color: rgba(247,247,247,0.75); 562 | color: rgba(0,0,0,1.0); 563 | text-align: center; 564 | 565 | border-bottom: 1px solid #ebebeb; 566 | 567 | -webkit-border-radius: 5px 5px 0 0; 568 | -moz-border-radius: 5px 5px 0 0; 569 | border-radius: 5px 5px 0 0; 570 | } 571 | 572 | .nvtooltip p { 573 | margin: 0; 574 | padding: 5px 14px; 575 | text-align: center; 576 | } 577 | 578 | .nvtooltip span { 579 | display: inline-block; 580 | margin: 2px 0; 581 | } 582 | 583 | .nvtooltip table { 584 | margin: 6px; 585 | border-spacing:0; 586 | } 587 | 588 | 589 | .nvtooltip table td { 590 | padding: 2px 9px 2px 0; 591 | vertical-align: middle; 592 | } 593 | 594 | .nvtooltip table td.key { 595 | font-weight:normal; 596 | } 597 | .nvtooltip table td.value { 598 | text-align: right; 599 | font-weight: bold; 600 | } 601 | 602 | .nvtooltip table tr.highlight td { 603 | padding: 1px 9px 1px 0; 604 | border-bottom-style: solid; 605 | border-bottom-width: 1px; 606 | border-top-style: solid; 607 | border-top-width: 1px; 608 | } 609 | 610 | .nvtooltip table td.legend-color-guide div { 611 | width: 8px; 612 | height: 8px; 613 | vertical-align: middle; 614 | } 615 | 616 | .nvtooltip table td.legend-color-guide div { 617 | width: 12px; 618 | height: 12px; 619 | border: 1px solid #999; 620 | } 621 | 622 | .nvtooltip .footer { 623 | padding: 3px; 624 | text-align: center; 625 | } 626 | 627 | .nvtooltip-pending-removal { 628 | pointer-events: none; 629 | display: none; 630 | } 631 | 632 | 633 | /**** 634 | Interactive Layer 635 | */ 636 | .nvd3 .nv-interactiveGuideLine { 637 | pointer-events:none; 638 | } 639 | .nvd3 line.nv-guideline { 640 | stroke: #ccc; 641 | } 642 | -------------------------------------------------------------------------------- /frontend/static/css/nv.d3.min.css: -------------------------------------------------------------------------------- 1 | .nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:transparent}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-discretebar .nv-groups rect,.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover,.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:transparent}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3 .nv-groups .nv-point.hover,.nvd3.nv-scatter .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:normal 12px Arial}.nvd3 .title{font:bold 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#eee;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:.2s;-moz-transition-delay:.2s;-webkit-transition-delay:.2s}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} -------------------------------------------------------------------------------- /frontend/static/css/widget.css: -------------------------------------------------------------------------------- 1 | .hoaxy-widget 2 | { 3 | box-sizing:border-box !important; 4 | color:black !important; 5 | background-color:white !important; 6 | border:solid !important; 7 | border-width:thin !important; 8 | border-color:gray !important; 9 | border-radius:10px !important; 10 | width:625px !important; 11 | height:250px !important; 12 | 13 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif !important; 14 | color: #212529 !important; 15 | line-height: 1.5 !important; 16 | background:#FAFAFA !important; 17 | } 18 | 19 | .hoaxy-widget-leftdiv 20 | { 21 | float:left !important; 22 | width:250px !important; 23 | height:250px !important; 24 | display:inline-block !important; 25 | margin-right:0px !important; 26 | 27 | font-family:inherit !important; 28 | color:inherit !important; 29 | } 30 | 31 | .hoaxy-widget-leftdiv-toplogo 32 | { 33 | box-sizing:border-box !important; 34 | text-align:center !important; 35 | vertical-align:middle !important; 36 | color:blue !important; 37 | font-size:35px !important; 38 | font-weight:bold !important; 39 | height:90px !important; 40 | padding-top:20px !important; 41 | padding-bottom:20px !important; 42 | padding-left:5px !important; 43 | padding-right:5px !important; 44 | 45 | font-family:inherit !important; 46 | color:inherit !important; 47 | } 48 | 49 | .hoaxy-widget-leftdiv-toplogo img 50 | { 51 | height: 60px !important; 52 | width: 200px !important; 53 | } 54 | 55 | .hoaxy-widget-leftdiv-bottominfo 56 | { 57 | overflow:hidden !important; 58 | word-break:break-word !important; 59 | box-sizing:border-box !important; 60 | padding:5px !important; 61 | font-size:16px !important; 62 | height: 150px !important; 63 | 64 | font-family:inherit !important; 65 | color:inherit !important; 66 | } 67 | 68 | .hoaxy-widget-leftdiv-bottominfo a 69 | { 70 | color: #337ab7 !important; 71 | text-decoration: none !important; 72 | } 73 | 74 | .hoaxy-widget-leftdiv-bottominfo a:hover 75 | { 76 | color: #23527c !important; 77 | text-decoration: underline !important; 78 | } 79 | 80 | .hoaxy-widget-rightdiv 81 | { 82 | margin-left:0px !important; 83 | float:left !important; 84 | width:370px !important; 85 | height:250px !important; 86 | display:inline-block !important; 87 | border-left:1px solid !important; 88 | border-color:#D3D3D3 !important; 89 | 90 | font-family:inherit !important; 91 | color:inherit !important; 92 | } 93 | 94 | .hoaxy-widget-rightdiv-imgvis 95 | { 96 | box-sizing:border-box !important; 97 | width:370px !important; 98 | height:240px !important; 99 | padding-top:5px !important; 100 | padding-right:5px !important; 101 | 102 | font-family:inherit !important; 103 | color:inherit !important; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /frontend/static/css/widget.min.css: -------------------------------------------------------------------------------- 1 | .hoaxy-widget{box-sizing:border-box!important;color:#000!important;background-color:#fff!important;border:solid!important;border-width:thin!important;border-color:gray!important;border-radius:10px!important;width:625px!important;height:250px!important;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif!important;color:#212529!important;line-height:1.5!important;background:#fafafa!important}.hoaxy-widget-leftdiv{float:left!important;width:250px!important;height:250px!important;display:inline-block!important;margin-right:0!important;font-family:inherit!important;color:inherit!important}.hoaxy-widget-leftdiv-toplogo{box-sizing:border-box!important;text-align:center!important;vertical-align:middle!important;color:#00f!important;font-size:35px!important;font-weight:700!important;height:90px!important;padding-top:20px!important;padding-bottom:20px!important;padding-left:5px!important;padding-right:5px!important;font-family:inherit!important;color:inherit!important}.hoaxy-widget-leftdiv-toplogo img{height:60px!important;width:200px!important}.hoaxy-widget-leftdiv-bottominfo{overflow:hidden!important;word-break:break-word!important;box-sizing:border-box!important;padding:5px!important;font-size:16px!important;height:150px!important;font-family:inherit!important;color:inherit!important}.hoaxy-widget-leftdiv-bottominfo a{color:#337ab7!important;text-decoration:none!important}.hoaxy-widget-leftdiv-bottominfo a:hover{color:#23527c!important;text-decoration:underline!important}.hoaxy-widget-rightdiv{margin-left:0!important;float:left!important;width:370px!important;height:250px!important;display:inline-block!important;border-left:1px solid!important;border-color:#d3d3d3!important;font-family:inherit!important;color:inherit!important}.hoaxy-widget-rightdiv-imgvis{box-sizing:border-box!important;width:370px!important;height:240px!important;padding-top:5px!important;padding-right:5px!important;font-family:inherit!important;color:inherit!important} -------------------------------------------------------------------------------- /frontend/static/js/axios.min.js: -------------------------------------------------------------------------------- 1 | /* axios v0.21.1 | (c) 2020 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new i(e),n=s(i.prototype.request,t);return o.extend(n,i.prototype,t),o.extend(n,t),n}var o=n(2),s=n(3),i=n(4),a=n(22),u=n(10),c=r(u);c.Axios=i,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),c.isAxiosError=n(26),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function s(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function i(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},s.forEach(["delete","get","head"],function(e){u.headers[e]={}}),s.forEach(["post","put","patch"],function(e){u.headers[e]=s.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),s=n(16),i=n(5),a=n(17),u=n(20),c=n(21),f=n(14);e.exports=function(e){return new Promise(function(t,n){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";d.Authorization="Basic "+btoa(h+":"+m)}var y=a(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),i(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in l?u(l.getAllResponseHeaders()):null,s=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:s,status:l.status,statusText:l.statusText,headers:r,config:e,request:l};o(t,n,i),l=null}},l.onabort=function(){l&&(n(f("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){n(f("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=(e.withCredentials||c(y))&&e.xsrfCookieName?s.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),n(e),l=null)}),p||(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,s){var i=new Error(e);return r(i,t,n,o,s)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,s,i){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(s)&&a.push("domain="+s),i===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(18),o=n(19);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,s,i={};return e?(r.forEach(e.split("\n"),function(e){if(s=e.indexOf(":"),t=r.trim(e.substr(0,s)).toLowerCase(),n=r.trim(e.substr(s+1)),t){if(i[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?i[t]=(i[t]?i[t]:[]).concat([n]):i[t]=i[t]?i[t]+", "+n:n}}),i):i}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){function n(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function o(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(s[o]=n(void 0,e[o])):s[o]=n(e[o],t[o])}t=t||{};var s={},i=["url","method","data"],a=["headers","auth","proxy","params"],u=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],c=["validateStatus"];r.forEach(i,function(e){r.isUndefined(t[e])||(s[e]=n(void 0,t[e]))}),r.forEach(a,o),r.forEach(u,function(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(s[o]=n(void 0,e[o])):s[o]=n(void 0,t[o])}),r.forEach(c,function(r){r in t?s[r]=n(e[r],t[r]):r in e&&(s[r]=n(void 0,e[r]))});var f=i.concat(a).concat(u).concat(c),p=Object.keys(e).concat(Object.keys(t)).filter(function(e){return f.indexOf(e)===-1});return r.forEach(p,o),s}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},function(e,t){"use strict";e.exports=function(e){return"object"==typeof e&&e.isAxiosError===!0}}])}); 3 | //# sourceMappingURL=axios.min.map -------------------------------------------------------------------------------- /frontend/static/js/es6-promise.auto.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.ES6Promise=e()}(this,function(){"use strict";function t(t){var e=typeof t;return null!==t&&("object"===e||"function"===e)}function e(t){return"function"==typeof t}function n(t){I=t}function r(t){J=t}function o(){return function(){return process.nextTick(a)}}function i(){return"undefined"!=typeof H?function(){H(a)}:c()}function s(){var t=0,e=new V(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){return t.port2.postMessage(0)}}function c(){var t=setTimeout;return function(){return t(a,1)}}function a(){for(var t=0;t 9 ? ""+num : "0"+num; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/static/js/index.min.js: -------------------------------------------------------------------------------- 1 | function getFilename(n){return["Timeline",$.map(chartData,function(n){return n.key}).join("&")].join("_")+"."+n}function set2Digit(n){return n>9?""+n:"0"+n} -------------------------------------------------------------------------------- /frontend/static/js/timeline.js: -------------------------------------------------------------------------------- 1 | function HoaxyTimeline(settings){ 2 | var updateDateRangeCallback = settings.updateDateRangeCallback; 3 | var graphAnimation = settings.graphAnimation; 4 | 5 | var returnObj = {}; 6 | var chart = null; 7 | var lastData = null; 8 | var chartData = []; 9 | var chartDataWithTweetRates = {}; 10 | 11 | chart = nv.models.lineWithFocusChart() 12 | .showLegend(false) 13 | .useInteractiveGuideline(true); 14 | 15 | // Including interactive tooltips that contain the time period date 16 | // Along with the new claims and fact checks for that period 17 | chart.interactiveLayer.tooltip.contentGenerator(function(chartData) { 18 | var currentTimeStepIndex; 19 | // Date comes from Hoaxy as MM/DD/YYYY in this timeline as local time zone 20 | // Now the date comes in as MM/DD/YYYY HH:MM:SS, so we need to remove the HH:MM:SS part 21 | // for the tooltip to work 22 | var chartDataValueArr = chartData.value.split(' ') 23 | var chartLocalValue = chartDataValueArr[0] 24 | var currDateLocal = new Date(chartLocalValue); 25 | 26 | // var currDateLocal = new Date(chartData.value); 27 | var currentTimeStepDate = currDateLocal.getTime(); 28 | 29 | // Finding the date match from the chartDataWithTweetRates object 30 | // We tried to directly use indexes before but indexes get changed 31 | // Due to the way D3js handles tooltips/charts 32 | for (dateRateIx in chartDataWithTweetRates[0].values) { 33 | var dateRateMatch = 34 | new Date(chartDataWithTweetRates[0].values[dateRateIx].x).getTime(); 35 | if (currentTimeStepDate === dateRateMatch) { 36 | currentTimeStepIndex = dateRateIx; 37 | } 38 | } 39 | 40 | // Returning formatted and styled tooltip 41 | return "
" + String(chartLocalValue) 42 | + "
" 43 | + "
\ 46 |
\ 53 |
\ 54 |
New Claims: " 55 | + String(chartDataWithTweetRates[0].values[currentTimeStepIndex].y) 56 | + "
\ 57 |
" 58 | + "
\ 61 |
\ 68 |
\ 69 |
New Fact-Checks: " 70 | + String(chartDataWithTweetRates[1].values[currentTimeStepIndex].y) 71 | + "
\ 72 |
"; 73 | }); 74 | 75 | chart.margin({right: 50, bottom: 75}); 76 | 77 | chart.xAxis 78 | .tickFormat(dateFormatter); 79 | chart.x2Axis 80 | .tickFormat(dateFormatter); 81 | chart.forceY([0]) 82 | chart.yAxis.axisLabel("Cumulative Tweets"); 83 | 84 | chart.color([colors.edge_colors.claim, colors.edge_colors.fact_checking, "#00ff00"]); //color match with those of nodes 85 | 86 | 87 | // var insertLinebreaks = function (d) { 88 | // var el = d3.select(this); 89 | // var words = d.split(' '); 90 | // el.text(''); 91 | 92 | // for (var i = 0; i < words.length; i++) { 93 | // var tspan = el.append('tspan').text(words[i]); 94 | // if (i > 0) 95 | // tspan.attr('x', 0).attr('dy', '15'); 96 | // } 97 | // }; 98 | 99 | // svg.selectAll('g.x.axis g text').each(insertLinebreaks); 100 | 101 | /** 102 | * Redraw the timeline 103 | */ 104 | function redraw(){ 105 | if(chart) 106 | { 107 | chart.dispatch.on("brush", null); 108 | d3.select('#chart svg') 109 | .call(chart); 110 | chart.dispatch.on("brush", updateDateRange); 111 | 112 | d3.select('#chart svg .nvd3 > g') 113 | .attr("transform", "translate(0, -10)"); 114 | } 115 | } 116 | /** 117 | * Initialize the timeline 118 | */ 119 | function removeUpdateDateRangeCallback(){ 120 | chart.dispatch.on("brush", null); 121 | } 122 | 123 | /** 124 | * Formats the date using d3.time 125 | * @param {String} d The date to be formatted 126 | * @return {String} The formatted time 127 | */ 128 | function dateFormatter(d) { 129 | var date = new Date(d); 130 | var formattedDate = d3.time.format('%x %H:%M')(date); 131 | return formattedDate; 132 | } 133 | 134 | /** 135 | * Shows how many new tweets of a particular type occurred at a point in time 136 | * @param {Object} chartData The data that the timeline was drawn with 137 | */ 138 | function calculateTweetRates(chartData) { 139 | // Deep copy of the chart data as any shallow copy will mess up 140 | // The timeline itself, we only use this new copy for the 141 | // Computed tooltips 142 | chartDataWithTweetRates = JSON.parse(JSON.stringify(chartData)); 143 | 144 | var previousTimestepClaims = 0; 145 | var previousTimestepFactChecks = 0; 146 | var numTimeIncrements = chartDataWithTweetRates[0].values.length; 147 | 148 | // Calculating rates 149 | for (var i = 0; i < numTimeIncrements; i++) { 150 | // Converting the chartDataWithTweetRates from UTC to local time zone 151 | // And truncating the hours/days/minutes as in chartData 152 | var oldDate = new Date(chartDataWithTweetRates[0].values[i].x); 153 | var year = oldDate.getFullYear(); 154 | var month = oldDate.getMonth(); 155 | var day = oldDate.getDate(); 156 | var newDate = new Date(year, month, day); 157 | chartDataWithTweetRates[0].values[i].x = newDate; 158 | chartDataWithTweetRates[1].values[i].x = newDate; 159 | 160 | // Calculating New Claims for time stsep 161 | var currentClaims = chartDataWithTweetRates[0].values[i].y; 162 | chartDataWithTweetRates[0].values[i].y = 163 | currentClaims - previousTimestepClaims; 164 | previousTimestepClaims = currentClaims; 165 | 166 | // Calculating New Fact Checks for time step 167 | var currentFactChecks = chartDataWithTweetRates[1].values[i].y; 168 | chartDataWithTweetRates[1].values[i].y = 169 | currentFactChecks - previousTimestepFactChecks; 170 | previousTimestepFactChecks = currentFactChecks; 171 | } 172 | } 173 | 174 | var debounce_timer = 0; 175 | var updateDateRange = function(extent){ 176 | clearTimeout(debounce_timer); 177 | debounce_timer = setTimeout(function(){ 178 | _updateDateRange(extent); 179 | }, 200); 180 | }; 181 | 182 | /** 183 | * Updates the date range if the user selected a different one 184 | * @param {Object} extent The timeframe selection by the user 185 | */ 186 | function _updateDateRange(extent){ 187 | if(document.getElementById("extent-0")) 188 | document.getElementById("extent-0").innerHTML = extent.extent[0]; 189 | if(document.getElementById("extent-1")) 190 | document.getElementById("extent-1").innerHTML = extent.extent[1]; 191 | 192 | if(document.getElementById("extent-00")) 193 | document.getElementById("extent-00").innerHTML = new Date(extent.extent[0]).toISOString(); 194 | if(document.getElementById("extent-11")) 195 | document.getElementById("extent-11").innerHTML = new Date(extent.extent[1]).toISOString(); 196 | 197 | var starting_time = extent.extent[0], 198 | ending_time = extent.extent[1]; 199 | 200 | try 201 | { 202 | updateDateRangeCallback(starting_time, ending_time); 203 | } 204 | catch(e) 205 | { 206 | if(e === "Tried to make graph, but there is no data.") 207 | { 208 | console.info(e, "This is not an error."); 209 | } 210 | else 211 | { 212 | console.warn(e); 213 | } 214 | 215 | setTimeout(function(){ 216 | updateDateRange(extent); 217 | }, 500); 218 | } 219 | } 220 | 221 | var max = 0; 222 | 223 | /** 224 | * Updates the timeline with new data 225 | * @param {Object} data The data to update the timeline with 226 | * @return {Boolean} Will only return false if there's no data 227 | */ 228 | var Update = function(data){ 229 | max = 0; 230 | lastData = data; 231 | var max_time = 0; 232 | if(!data) 233 | { 234 | return {"time": null, "factChecking_values": [], "fake_values": []}; 235 | } 236 | time = data.claim.timestamp, 237 | volume_factchecking = data.fact_checking.volume, volume_fake = data.claim.volume, 238 | factChecking_values = [], fake_values = []; 239 | 240 | for (var i in volume_fake) 241 | { 242 | var ts = new Date(time[i]).getTime(); 243 | if(volume_factchecking[i] > max) 244 | { 245 | max = volume_factchecking[i]; 246 | } 247 | if(volume_fake[i] > max) 248 | { 249 | max = volume_fake[i]; 250 | } 251 | if(ts > max_time) 252 | { 253 | max_time = ts; 254 | } 255 | 256 | factChecking_values.push({x: new Date(time[i]), y: volume_factchecking[i]}); 257 | fake_values.push({x: new Date(time[i]), y: volume_fake[i]}); 258 | } 259 | 260 | chartData.length = 0; 261 | if(!!chart.update){ 262 | chart.update(); 263 | } 264 | 265 | var factChecking_series = { 266 | key: 'Fact-checks', 267 | values: factChecking_values, 268 | c: colors.edge_colors.fact_checking 269 | }; 270 | var fake_series = { 271 | key: 'Claims', 272 | values: fake_values, 273 | c:colors.edge_colors.claim 274 | }; 275 | 276 | chartData.push(fake_series); 277 | chartData.push(factChecking_series); 278 | calculateTweetRates(chartData); 279 | 280 | // This adds an event handler to the focus chart 281 | try { 282 | d3.select('#chart svg') 283 | .datum(chartData) 284 | .call(chart); 285 | } 286 | catch(e){ 287 | console.debug(e); 288 | } 289 | } 290 | 291 | /** 292 | * Update timestamp based on the graph's timestamp 293 | */ 294 | function UpdateTimestamp(){ 295 | if(graphAnimation.current_timestamp) 296 | { 297 | chartData[2] = { 298 | key: 'Time', 299 | values: [ 300 | { x: new Date(graphAnimation.current_timestamp), y: 0}, 301 | { x: new Date(graphAnimation.current_timestamp), y: max} 302 | ], 303 | disableTooltip: true 304 | }; 305 | } 306 | else 307 | { 308 | delete chartData[2]; 309 | } 310 | 311 | chart.dispatch.on("brush", null); 312 | d3.select('#chart svg') 313 | .datum(chartData) 314 | .call(chart); 315 | chart.dispatch.on("brush", updateDateRange); 316 | } 317 | 318 | returnObj.removeUpdateDateRangeCallback = removeUpdateDateRangeCallback; 319 | returnObj.update = Update; 320 | returnObj.chart = chart; 321 | returnObj.redraw = redraw; 322 | returnObj.updateTimestamp = UpdateTimestamp; 323 | return returnObj; 324 | } 325 | -------------------------------------------------------------------------------- /frontend/static/js/timeline.min.js: -------------------------------------------------------------------------------- 1 | function HoaxyTimeline(e){var t=e.updateDateRangeCallback,a=e.graphAnimation,n={},i=null,c=[],l={};function o(e){return d3.time.format("%x")(new Date(e))}(i=nv.models.lineWithFocusChart().showLegend(!1).useInteractiveGuideline(!0)).interactiveLayer.tooltip.contentGenerator(function(e){var t,a=new Date(e.value).getTime();for(dateRateIx in l[0].values){a===new Date(l[0].values[dateRateIx].x).getTime()&&(t=dateRateIx)}return"
"+String(e.value)+"
New Claims: "+String(l[0].values[t].y)+"
New Fact-Checks: "+String(l[1].values[t].y)+"
"}),i.margin({right:50,bottom:75}),i.xAxis.tickFormat(o),i.x2Axis.tickFormat(o),i.forceY([0]),i.yAxis.axisLabel("Cumulative Tweets"),i.color([colors.edge_colors.claim,colors.edge_colors.fact_checking,"#00ff00"]);var r=0,s=function(e){clearTimeout(r),r=setTimeout(function(){!function(e){document.getElementById("extent-0")&&(document.getElementById("extent-0").innerHTML=e.extent[0]);document.getElementById("extent-1")&&(document.getElementById("extent-1").innerHTML=e.extent[1]);document.getElementById("extent-00")&&(document.getElementById("extent-00").innerHTML=new Date(e.extent[0]).toISOString());document.getElementById("extent-11")&&(document.getElementById("extent-11").innerHTML=new Date(e.extent[1]).toISOString());var a=e.extent[0],n=e.extent[1];try{t(a,n)}catch(t){"Tried to make graph, but there is no data."===t?console.info(t,"This is not an error."):console.warn(t),setTimeout(function(){s(e)},500)}}(e)},200)};var u=0;return n.removeUpdateDateRangeCallback=function(){i.dispatch.on("brush",null)},n.update=function(e){u=0,e;var t=0;if(!e)return{time:null,factChecking_values:[],fake_values:[]};for(var a in time=e.claim.timestamp,volume_factchecking=e.fact_checking.volume,volume_fake=e.claim.volume,factChecking_values=[],fake_values=[],volume_fake){var n=new Date(time[a]).getTime();volume_factchecking[a]>u&&(u=volume_factchecking[a]),volume_fake[a]>u&&(u=volume_fake[a]),n>t&&(t=n),factChecking_values.push({x:new Date(time[a]),y:volume_factchecking[a]}),fake_values.push({x:new Date(time[a]),y:volume_fake[a]})}c.length=0,i.update&&i.update();var o={key:"Fact-checks",values:factChecking_values,c:colors.edge_colors.fact_checking},r={key:"Claims",values:fake_values,c:colors.edge_colors.claim};c.push(r),c.push(o),function(e){for(var t=0,a=0,n=(l=JSON.parse(JSON.stringify(e)))[0].values.length,i=0;i g").attr("transform","translate(0, -10)"))},n.updateDateRange=function(){try{d3.select("#chart svg").datum(c).call(i)}catch(e){console.debug("Error in triggerUpdataRange.",e)}},n.updateTimestamp=function(){a.current_timestamp?c[2]={key:"Time",values:[{x:new Date(a.current_timestamp),y:0},{x:new Date(a.current_timestamp),y:u}],disableTooltip:!0}:delete c[2],i.dispatch.on("brush",null),d3.select("#chart svg").datum(c).call(i),i.dispatch.on("brush",s)},n} -------------------------------------------------------------------------------- /frontend/static/js/twitter.js: -------------------------------------------------------------------------------- 1 | var Twitter = function(initialize_key){ 2 | var obj = {}; 3 | 4 | /* OAUTH API shouldn't have a trailing slash */ 5 | var OAUTH_URL = 'https://oauth.truthy.indiana.edu', 6 | OAUTH_API = OAUTH_URL + '/api'; 7 | 8 | var tokenCacheKey = 'oauth_twitter_token', 9 | providerCacheKey = 'oauth_twitter_provider', 10 | _token, _me; 11 | 12 | OAuth.initialize(initialize_key); 13 | OAuth.setOAuthdURL(OAUTH_URL); 14 | 15 | /** 16 | * @todo 17 | */ 18 | function getCachedToken(){ 19 | if(!_token){ 20 | var keys = JSON.parse(localStorage.getItem(tokenCacheKey)), 21 | provider = JSON.parse(localStorage.getItem(providerCacheKey)); 22 | if(keys && provider){ 23 | // console.log('Creating token from localStorage:', keys); 24 | // console.log('OAuth provider from localStorage:', provider); 25 | /* Should probably test that the key is good first, e.g. with me() */ 26 | try { 27 | _token = OAuth.create('twitter', keys, provider); 28 | } catch (e) { 29 | console.warn(e); 30 | localStorage.removeItem(tokenCacheKey, providerCacheKey); 31 | } 32 | } 33 | } 34 | return _token 35 | } 36 | 37 | /** 38 | * @todo 39 | */ 40 | function cacheToken(token){ 41 | // console.debug(token); 42 | _token = token; 43 | axios.get(OAUTH_API + '/providers/twitter?extend=true') 44 | .then(function(provider){ 45 | // console.debug(provider, token); 46 | localStorage.setItem(tokenCacheKey, JSON.stringify(token)); 47 | localStorage.setItem(providerCacheKey, JSON.stringify(provider)); 48 | }, function(error){ 49 | console.debug(error); 50 | }); 51 | } 52 | 53 | /** 54 | * @todo 55 | */ 56 | function verifyMe(token){ 57 | if(!_me){ 58 | // var dfd = $q.defer(); 59 | // _me = dfd.promise; 60 | 61 | _me = new Promise(function(resolve, reject){ 62 | 63 | //console.log('Verifying user credentials with token:', token) 64 | return token.get('/1.1/account/verify_credentials.json', {data: { 65 | 'include_email': false, 66 | 'include_entities': false, 67 | 'skip_status': true 68 | }}).then( 69 | // function(myUserData){ dfd.resolve(myUserData) }, 70 | function(myUserData){ resolve(myUserData); }, 71 | // function(error){ dfd.reject(error) } 72 | function(error){ reject(error) } 73 | ); 74 | }); 75 | } 76 | return _me 77 | } 78 | 79 | /** 80 | * @todo 81 | */ 82 | function getToken(force_refresh){ 83 | // var dfd = $q.defer(), 84 | var dfd = new Promise(function(resolve, reject){ 85 | token = !force_refresh && getCachedToken(); 86 | 87 | 88 | if(token){ 89 | verifyMe(token).then(function(myUserData){ 90 | // console.log('Verified credentials:', myUserData); 91 | resolve(token); 92 | }, function(){ 93 | return getToken(true); 94 | }); 95 | } else { 96 | OAuth.popup('twitter').done(function(result){ 97 | // console.log('OAuth result:', result); 98 | cacheToken(result); 99 | resolve(result); 100 | verifyMe(result); 101 | }).fail(function(error){ 102 | // console.warn("OAuth ERROR:", error); 103 | reject(error); 104 | }); 105 | 106 | } 107 | }); 108 | 109 | // return dfd.promise 110 | return dfd; 111 | } 112 | 113 | /** 114 | * @todo 115 | */ 116 | function apiCall(method, url, params, errMsg){ 117 | // var dfd = $q.defer(); 118 | var dfd = new Promise(function(resolve, reject){ 119 | method = method.toLowerCase(); 120 | 121 | getToken().then(function(token){ 122 | // console.log('GET ', url, params); 123 | token[method](url, {data: params}).done(function(response){ 124 | resolve(response) 125 | }).fail(function(error){ 126 | /* 127 | * var errMsg = params.errMsg || "We were unable to complete your" + 128 | * "request. Try again later."; 129 | */ 130 | reject({error: error, message: errMsg}); 131 | }) 132 | }, function(error){ 133 | reject({error: error}) 134 | }) 135 | }); 136 | return dfd; 137 | } 138 | 139 | var followerCursor = {}, friendCursor = {}; 140 | /** 141 | * @todo 142 | */ 143 | function getNeighbors(relationship, screenName, count){ 144 | var dfd = new Promise(function(resolve, reject){ 145 | var cursorObj = relationship.startsWith('follower') ? followerCursor : friendCursor, 146 | cursor = cursorObj[screenName], 147 | params = { 148 | screen_name: screenName, 149 | count: count, 150 | cursor: cursor, 151 | skip_status: true 152 | }; 153 | 154 | var endpoint; 155 | if (relationship.startsWith('follower')){ 156 | endpoint = '/1.1/followers/list.json'; 157 | } else if (relationship.startsWith('friend') || 158 | relationship.startsWith('following')){ 159 | endpoint = '/1.1/friends/list.json'; 160 | } else { 161 | throw 'relationship type must be "followers" or "friends"' 162 | } 163 | 164 | apiCall('GET', endpoint, params, 165 | "Twitter was unable to retrieve followers of this user. Is there a typo?") 166 | .then(function(response){ 167 | followerCursor[screenName] = response.next_cursor; 168 | resolve(response.users) 169 | }); 170 | }); 171 | return dfd; 172 | } 173 | 174 | /** 175 | * @todo 176 | */ 177 | obj.getUserMentions = function(screenName){ 178 | return apiCall('GET', '/1.1/search/tweets.json', {q: '@'+screenName, count:100}, 179 | "Twitter was unable to retrieve mentions of this user, is there a typo?") 180 | } 181 | 182 | /** 183 | * @todo 184 | */ 185 | obj.getUserDataById = function(user_id){ 186 | return apiCall('GET', '/1.1/users/show.json', {user_id: user_id}, 187 | "Twitter was unable to retrieve information for this user, is there a typo?") 188 | } 189 | 190 | /** 191 | * @todo 192 | */ 193 | obj.getUserTimelineById = function(user_id){ 194 | return apiCall('GET', '/1.1/statuses/user_timeline.json', {user_id: user_id, count:200}, 195 | "Twitter was unable to retrieve a timeline for this user, is there a typo?") 196 | } 197 | 198 | /** 199 | * @todo 200 | */ 201 | obj.getFollowers = function(screenName, count){ 202 | return getNeighbors('followers', screenName, count); 203 | } 204 | 205 | /** 206 | * @todo 207 | */ 208 | obj.getFollowing = function(screenName, count){ 209 | return getNeighbors('friends', screenName, count); 210 | } 211 | 212 | /** 213 | * @todo 214 | */ 215 | obj.getUserLanguage = function(lang){ 216 | return apiCall('GET', '/1.1/search/tweets.json', lang) 217 | } 218 | 219 | /** 220 | * @todo 221 | */ 222 | obj.getTweets = function(query, lang, max_id, result_type){ 223 | if (!lang || lang == '') 224 | { 225 | return apiCall('GET', '/1.1/search/tweets.json', {q: query, lang: '', max_id: max_id, result_type: result_type, count: 100, include_entities: 1}, 226 | "Twitter was unable to retrieve mentions of this user, is there a typo?"); 227 | } 228 | 229 | return apiCall('GET', '/1.1/search/tweets.json', {q: query, lang: lang, max_id: max_id, result_type: result_type, count: 100, include_entities: 1}, 230 | "Twitter was unable to retrieve mentions of this user, is there a typo?"); 231 | } 232 | 233 | /** 234 | * @todo 235 | */ 236 | obj.blockUser = function(userId){ 237 | return apiCall('POST', '/1.1/blocks/create.json', { 238 | user_id: userId 239 | , include_entities: false 240 | , skip_status: true 241 | }) 242 | } 243 | 244 | /** 245 | * @todo 246 | */ 247 | obj.unblockUser = function(userId){ 248 | return apiCall('POST', '/1.1/blocks/destroy.json', { 249 | user_id: userId 250 | , include_entities: false 251 | , skip_status: true 252 | }) 253 | } 254 | 255 | /** 256 | * @todo 257 | */ 258 | obj.followUser = function(userId){ 259 | return apiCall('POST', '/1.1/friendships/create.json', {user_id:userId}) 260 | } 261 | 262 | /** 263 | * @todo 264 | */ 265 | obj.unfollowUser = function(userId){ 266 | return apiCall('POST', '/1.1/friendships/destroy.json', {user_id:userId}) 267 | } 268 | 269 | /** 270 | * @todo 271 | */ 272 | obj.me = function(){return _me}; 273 | 274 | /** 275 | * @todo 276 | */ 277 | obj.verifyMe = function(){ 278 | var dfd = new Promise(function(resolve, reject){ 279 | getToken().then(function(token){ 280 | verifyMe(token).then(resolve, reject); 281 | }, reject); 282 | }); 283 | return dfd 284 | }; 285 | 286 | /** 287 | * @todo 288 | */ 289 | obj.logOut = function(){ 290 | _token = undefined; 291 | _me = undefined; 292 | localStorage.removeItem(tokenCacheKey); 293 | } 294 | 295 | 296 | return obj; 297 | } 298 | -------------------------------------------------------------------------------- /frontend/static/js/twitter.min.js: -------------------------------------------------------------------------------- 1 | var Twitter=function(t){var e,n,r={},o="https://oauth.truthy.indiana.edu",i=o+"/api",s="oauth_twitter_token",u="oauth_twitter_provider";function a(t){return n||(n=new Promise(function(e,n){return t.get("/1.1/account/verify_credentials.json",{data:{include_email:!1,include_entities:!1,skip_status:!0}}).then(function(t){e(t)},function(t){n(t)})})),n}function c(t){return new Promise(function(n,r){token=!t&&function(){if(!e){var t=JSON.parse(localStorage.getItem(s)),n=JSON.parse(localStorage.getItem(u));if(t&&n)try{e=OAuth.create("twitter",t,n)}catch(t){console.warn(t),localStorage.removeItem(s,u)}}return e}(),token?a(token).then(function(t){n(token)},function(){return c(!0)}):OAuth.popup("twitter").done(function(t){var r;e=r=t,axios.get(i+"/providers/twitter?extend=true").then(function(t){localStorage.setItem(s,JSON.stringify(r)),localStorage.setItem(u,JSON.stringify(t))},function(t){console.debug(t)}),n(t),a(t)}).fail(function(t){r(t)})})}function f(t,e,n,r){return new Promise(function(o,i){t=t.toLowerCase(),c().then(function(s){s[t](e,{data:n}).done(function(t){o(t)}).fail(function(t){i({error:t,message:r})})},function(t){i({error:t})})})}OAuth.initialize(t),OAuth.setOAuthdURL(o);var l={},h={};function w(t,e,n){return new Promise(function(r,o){var i,s=(t.startsWith("follower")?l:h)[e],u={screen_name:e,count:n,cursor:s,skip_status:!0};if(t.startsWith("follower"))i="/1.1/followers/list.json";else{if(!t.startsWith("friend")&&!t.startsWith("following"))throw'relationship type must be "followers" or "friends"';i="/1.1/friends/list.json"}f("GET",i,u,"Twitter was unable to retrieve followers of this user. Is there a typo?").then(function(t){l[e]=t.next_cursor,r(t.users)})})}return r.getUserMentions=function(t){return f("GET","/1.1/search/tweets.json",{q:"@"+t,count:100},"Twitter was unable to retrieve mentions of this user, is there a typo?")},r.getUserDataById=function(t){return f("GET","/1.1/users/show.json",{user_id:t},"Twitter was unable to retrieve information for this user, is there a typo?")},r.getUserTimelineById=function(t){return f("GET","/1.1/statuses/user_timeline.json",{user_id:t,count:200},"Twitter was unable to retrieve a timeline for this user, is there a typo?")},r.getFollowers=function(t,e){return w("followers",t,e)},r.getFollowing=function(t,e){return w("friends",t,e)},r.getUserLanguage=function(t){return f("GET","/1.1/search/tweets.json",t)},r.getTweets=function(t,e,n,r){return f("GET","/1.1/search/tweets.json",{q:t,lang:e,max_id:n,result_type:r,count:100,include_entities:1},"Twitter was unable to retrieve mentions of this user, is there a typo?")},r.blockUser=function(t){return f("POST","/1.1/blocks/create.json",{user_id:t,include_entities:!1,skip_status:!0})},r.unblockUser=function(t){return f("POST","/1.1/blocks/destroy.json",{user_id:t,include_entities:!1,skip_status:!0})},r.followUser=function(t){return f("POST","/1.1/friendships/create.json",{user_id:t})},r.unfollowUser=function(t){return f("POST","/1.1/friendships/destroy.json",{user_id:t})},r.me=function(){return n},r.verifyMe=function(){return new Promise(function(t,e){c().then(function(n){a(n).then(t,e)},e)})},r.logOut=function(){e=void 0,n=void 0,localStorage.removeItem(s)},r}; -------------------------------------------------------------------------------- /frontend/static/js/twitter_search_timeline.js: -------------------------------------------------------------------------------- 1 | function TwitterSearchTimeline(settings){ 2 | var updateDateRangeCallback = settings.updateDateRangeCallback; 3 | var graphAnimation = settings.graphAnimation; 4 | 5 | var returnObj = {}; 6 | var chart = null; 7 | var lastData = null; 8 | var chartData = []; 9 | var chartDataWithTweetRates = {}; 10 | 11 | chart = nv.models.lineWithFocusChart() 12 | .showLegend(false) 13 | .useInteractiveGuideline(true); 14 | 15 | // Including interactive tooltips that contain the time period date 16 | // Along with the new tweets on that time period 17 | chart.interactiveLayer.tooltip.contentGenerator(function(chartData) { 18 | var currentTimeStepIndex; 19 | // In Twitter case, we convert data from: MM/DD/YYYY HH:MM (AM/PM) 20 | // and extract all components to create a date 21 | var fullRawDate = chartData.value; 22 | var dateSplits = fullRawDate.split(' '); 23 | var monthDayYear = dateSplits[0].split('/'); 24 | var month = monthDayYear[0]; 25 | var day = monthDayYear[1]; 26 | var year = monthDayYear[2]; 27 | var hoursMinutesSeconds = dateSplits[1].split(':'); 28 | var hours = hoursMinutesSeconds[0]; 29 | var minutes = hoursMinutesSeconds[1]; 30 | 31 | // Twitter format is now only hours and minutes: 32 | 33 | //var seconds = hoursMinutesSeconds[2]; 34 | 35 | // We subtract 1 from month because Date takes 0 indexed months 36 | var currentTimeStepDate = new Date(parseInt(year), parseInt(month)-1, 37 | parseInt(day), parseInt(hours), parseInt(minutes)).getTime(); 38 | // Finding the date match from the chartDataWithTweetRates object 39 | // We tried to directly use indexes before but indexes get changed 40 | // Due to the way D3js handles tooltips/charts 41 | for (dateRateIx in chartDataWithTweetRates[0].values) { 42 | var dateRateMatchWithSeconds = 43 | new Date(chartDataWithTweetRates[0].values[dateRateIx].x); 44 | 45 | // setting seconds to 0 to match Twitter format that now only includes HH:MM 46 | var dateRateMatch = dateRateMatchWithSeconds.setSeconds(0) 47 | 48 | if (currentTimeStepDate === dateRateMatch) { 49 | currentTimeStepIndex = dateRateIx; 50 | } 51 | } 52 | // Returning formatted and styled tooltip 53 | return "
" + String(chartData.value) 54 | + "
" 55 | + "
\ 58 |
\ 65 |
\ 66 |
New Tweets: " 67 | + String(chartDataWithTweetRates[0].values[currentTimeStepIndex].y) 68 | + "
\ 69 |
"; 70 | }); 71 | 72 | chart.margin({right: 70, bottom: 80}) 73 | 74 | chart.xAxis 75 | .tickFormat(dateFormatter) 76 | .ticks(5) 77 | .staggerLabels(true); 78 | chart.x2Axis 79 | .tickFormat(dateFormatter) 80 | .staggerLabels(true) 81 | .ticks(5); 82 | 83 | // chart.focus.margin({bottom: 50}); 84 | // chart.xAxis 85 | // .tickValues(function(x){ 86 | // console.debug(x); 87 | // }); 88 | // chart.xAxis.rotateLabels(-45); 89 | 90 | chart.forceY([0]) 91 | chart.yAxis.axisLabel("Cumulative Tweets"); 92 | 93 | chart.color([colors.edge_colors.claim, "#00ff00"]); //color match with those of nodes 94 | 95 | /** 96 | * Redraw the timeline 97 | */ 98 | function redraw(){ 99 | if(chart) 100 | { 101 | chart.dispatch.on("brush", null); 102 | // chart.x2Axis.margin({bottom: 100}); 103 | d3.select('#chart svg') 104 | .call(chart); 105 | chart.dispatch.on("brush", updateDateRange); 106 | 107 | d3.selectAll('#chart svg .nv-axisMax-x text') 108 | .attr("transform", "translate(0, 12)"); 109 | d3.select('#chart svg .nvd3 > g') 110 | .attr("transform", "translate(0, -10)"); 111 | } 112 | } 113 | /** 114 | * Initialize the timeline 115 | */ 116 | function removeUpdateDateRangeCallback(){ 117 | chart.dispatch.on("brush", null); 118 | } 119 | 120 | /** 121 | * Formats the date using d3.time 122 | * @param {String} d The date to be formatted 123 | * @return {String} The formatted time 124 | */ 125 | function dateFormatter(d) { 126 | // return d3.time.format('%m/%d/%Y %H:%M:%S %p')(new Date(d)) 127 | var date = new Date(d); 128 | var formattedDate = d3.time.format('%x %H:%M')(date); 129 | return formattedDate; 130 | } 131 | 132 | /** 133 | * Shows how many new tweets of a particular type occurred at a point in time 134 | * @param {Object} chartData The data that the timeline was drawn with 135 | */ 136 | function calculateTweetRates(chartData) { 137 | // Deep copy of the chart data as any shallow copy will mess up 138 | // The timeline itself, we only use this new copy for the 139 | // Computed tooltips 140 | chartDataWithTweetRates = JSON.parse(JSON.stringify(chartData)); 141 | 142 | var previousTimestepTweets = 0; 143 | var numTimeIncrements = chartDataWithTweetRates[0].values.length; 144 | 145 | for (var i = 0; i < numTimeIncrements; i++) { 146 | var currentTweets = chartDataWithTweetRates[0].values[i].y; 147 | chartDataWithTweetRates[0].values[i].y = currentTweets - previousTimestepTweets; 148 | previousTimestepTweets = currentTweets; 149 | } 150 | } 151 | 152 | var debounce_timer = 0; 153 | var updateDateRange = function(extent){ 154 | clearTimeout(debounce_timer); 155 | debounce_timer = setTimeout(function(){ 156 | _updateDateRange(extent); 157 | }, 200); 158 | }; 159 | 160 | /** 161 | * Updates the date range if the user selected a different one 162 | * @param {Object} extent The timeframe selection by the user 163 | */ 164 | function _updateDateRange(extent){ 165 | if(document.getElementById("extent-0")) 166 | document.getElementById("extent-0").innerHTML = extent.extent[0]; 167 | if(document.getElementById("extent-1")) 168 | document.getElementById("extent-1").innerHTML = extent.extent[1]; 169 | 170 | if(document.getElementById("extent-00")) 171 | document.getElementById("extent-00").innerHTML = new Date(extent.extent[0]).toISOString(); 172 | if(document.getElementById("extent-11")) 173 | document.getElementById("extent-11").innerHTML = new Date(extent.extent[1]).toISOString(); 174 | 175 | var starting_time = extent.extent[0], 176 | ending_time = extent.extent[1]; 177 | 178 | //only proceed when s.graph is ready 179 | // if (edges) 180 | try 181 | { 182 | updateDateRangeCallback(starting_time, ending_time); 183 | } 184 | catch(e) 185 | { 186 | if(e === "Tried to make graph, but there is no data.") 187 | { 188 | console.info(e, "This is not an error."); 189 | } 190 | else 191 | { 192 | console.warn(e); 193 | } 194 | 195 | setTimeout(function(){ 196 | updateDateRange(extent); 197 | }, 500); 198 | } 199 | 200 | } 201 | 202 | var max = 0; 203 | var Update = function(data){ 204 | 205 | max = 0; 206 | lastData = data; 207 | var max_time = 0; 208 | if(!data) 209 | { 210 | return {"time": null, "tweet_values": []}; 211 | } 212 | time = data.claim.timestamp, 213 | volume_tweets = data.claim.volume, 214 | tweet_values = []; 215 | 216 | for (var i in volume_tweets) 217 | { 218 | var ts = new Date(time[i]).getTime(); 219 | 220 | if(volume_tweets[i] > max) 221 | { 222 | max = volume_tweets[i]; 223 | } 224 | 225 | if(ts > max_time) 226 | { 227 | max_time = ts; 228 | } 229 | 230 | tweet_values.push({x: new Date(time[i]), y: volume_tweets[i]}); 231 | } 232 | 233 | chartData.length = 0; 234 | if(!!chart.update){ 235 | chart.update(); 236 | } 237 | 238 | var tweet_series = { 239 | key: 'Tweets', 240 | values: tweet_values, 241 | c:colors.edge_colors.claim 242 | }; 243 | 244 | chartData.push(tweet_series); 245 | calculateTweetRates(chartData); 246 | 247 | // This adds an event handler to the focus chart 248 | try { 249 | d3.select('#chart svg') 250 | .datum(chartData) 251 | .call(chart); 252 | 253 | 254 | d3.selectAll('#chart svg .nv-axisMax-x text') 255 | .attr("transform", "translate(0, 12)"); 256 | d3.select('#chart svg .nvd3 > g') 257 | .attr("transform", "translate(0, -10)"); 258 | } 259 | catch(e){ 260 | console.debug(e); 261 | } 262 | } 263 | 264 | /** 265 | * Updates the range of dates on graph \ 266 | * Seen outside twitter_search_timeline as updateDateRange() \ 267 | * (May be deprecated) 268 | */ 269 | function triggerUpdateRange(){ 270 | try{ 271 | d3.select('#chart svg') 272 | .datum(chartData) 273 | .call(chart); 274 | 275 | 276 | d3.selectAll('#chart svg .nv-axisMax-x text') 277 | .attr("transform", "translate(0, 12)"); 278 | d3.select('#chart svg .nvd3 > g') 279 | .attr("transform", "translate(0, -10)"); 280 | } 281 | catch(e){ 282 | console.debug("Error in triggerUpdataRange.", e); 283 | } 284 | } 285 | 286 | /** 287 | * Update timestamp based on the graph's timestamp 288 | */ 289 | function UpdateTimestamp(){ 290 | if(graphAnimation.current_timestamp) 291 | { 292 | chartData[1] = { 293 | key: 'Time', 294 | values: [ 295 | { x: new Date(graphAnimation.current_timestamp), y: 0}, 296 | { x: new Date(graphAnimation.current_timestamp), y: max} 297 | ], 298 | disableTooltip: true 299 | }; 300 | 301 | } 302 | else 303 | { 304 | delete chartData[1]; 305 | } 306 | 307 | chart.dispatch.on("brush", null); 308 | d3.select('#chart svg') 309 | .datum(chartData) 310 | .call(chart); 311 | 312 | d3.selectAll('#chart svg .nv-axisMax-x text') 313 | .attr("transform", "translate(0, 12)"); 314 | d3.select('#chart svg .nvd3 > g') 315 | .attr("transform", "translate(0, -10)"); 316 | chart.dispatch.on("brush", updateDateRange); 317 | } 318 | 319 | returnObj.removeUpdateDateRangeCallback = removeUpdateDateRangeCallback; 320 | returnObj.update = Update; 321 | returnObj.chart = chart; 322 | returnObj.redraw = redraw; 323 | returnObj.updateDateRange = triggerUpdateRange; 324 | returnObj.updateTimestamp = UpdateTimestamp; 325 | return returnObj; 326 | } 327 | -------------------------------------------------------------------------------- /frontend/static/js/twitter_search_timeline.min.js: -------------------------------------------------------------------------------- 1 | function TwitterSearchTimeline(t){var e=t.updateDateRangeCallback,a=t.graphAnimation,n={},r=null,s=[],l={};function i(t){return d3.time.format("%m/%d/%Y %H:%M:%S %p")(new Date(t))}(r=nv.models.lineWithFocusChart().showLegend(!1).useInteractiveGuideline(!0)).interactiveLayer.tooltip.contentGenerator(function(t){var e,a=t.value.split(" "),n=a[0].split("/"),r=n[0],s=n[1],i=n[2],c=a[1].split(":"),o=c[0],u=c[1],d=c[2],m=new Date(parseInt(i),parseInt(r)-1,parseInt(s),parseInt(o),parseInt(u),parseInt(d)).getTime();for(dateRateIx in l[0].values){m===new Date(l[0].values[dateRateIx].x).getTime()&&(e=dateRateIx)}return"
"+String(t.value)+"
New Tweets: "+String(l[0].values[e].y)+"
"}),r.margin({right:70,bottom:80}),r.xAxis.tickFormat(i).ticks(5).staggerLabels(!0),r.x2Axis.tickFormat(i).staggerLabels(!0).ticks(5),r.forceY([0]),r.yAxis.axisLabel("Cumulative Tweets"),r.color([colors.edge_colors.claim,"#00ff00"]);var c=0,o=function(t){clearTimeout(c),c=setTimeout(function(){!function(t){document.getElementById("extent-0")&&(document.getElementById("extent-0").innerHTML=t.extent[0]);document.getElementById("extent-1")&&(document.getElementById("extent-1").innerHTML=t.extent[1]);document.getElementById("extent-00")&&(document.getElementById("extent-00").innerHTML=new Date(t.extent[0]).toISOString());document.getElementById("extent-11")&&(document.getElementById("extent-11").innerHTML=new Date(t.extent[1]).toISOString());var a=t.extent[0],n=t.extent[1];try{e(a,n)}catch(e){"Tried to make graph, but there is no data."===e?console.info(e,"This is not an error."):console.warn(e),setTimeout(function(){o(t)},500)}}(t)},200)};var u=0;return n.removeUpdateDateRangeCallback=function(){r.dispatch.on("brush",null)},n.update=function(t){u=0,t;var e=0;if(!t)return{time:null,tweet_values:[]};for(var a in time=t.claim.timestamp,volume_tweets=t.claim.volume,tweet_values=[],volume_tweets){var n=new Date(time[a]).getTime();volume_tweets[a]>u&&(u=volume_tweets[a]),n>e&&(e=n),tweet_values.push({x:new Date(time[a]),y:volume_tweets[a]})}s.length=0,r.update&&r.update();var i={key:"Tweets",values:tweet_values,c:colors.edge_colors.claim};s.push(i),function(t){for(var e=0,a=(l=JSON.parse(JSON.stringify(t)))[0].values.length,n=0;n g").attr("transform","translate(0, -10)")}catch(t){console.debug(t)}},n.chart=r,n.redraw=function(){r&&(r.dispatch.on("brush",null),d3.select("#chart svg").call(r),r.dispatch.on("brush",o),d3.selectAll("#chart svg .nv-axisMax-x text").attr("transform","translate(0, 12)"),d3.select("#chart svg .nvd3 > g").attr("transform","translate(0, -10)"))},n.updateDateRange=function(){try{d3.select("#chart svg").datum(s).call(r),d3.selectAll("#chart svg .nv-axisMax-x text").attr("transform","translate(0, 12)"),d3.select("#chart svg .nvd3 > g").attr("transform","translate(0, -10)")}catch(t){console.debug("Error in triggerUpdataRange.",t)}},n.updateTimestamp=function(){a.current_timestamp?s[1]={key:"Time",values:[{x:new Date(a.current_timestamp),y:0},{x:new Date(a.current_timestamp),y:u}],disableTooltip:!0}:delete s[1],r.dispatch.on("brush",null),d3.select("#chart svg").datum(s).call(r),d3.selectAll("#chart svg .nv-axisMax-x text").attr("transform","translate(0, 12)"),d3.select("#chart svg .nvd3 > g").attr("transform","translate(0, -10)"),r.dispatch.on("brush",o)},n} -------------------------------------------------------------------------------- /frontend/static/sigmaplugins/sigma.layout.forceAtlas2.min.js: -------------------------------------------------------------------------------- 1 | (function(undefined){"use strict";function Supervisor(sigInst,options){var _this=this,workerFn=sigInst.getForceAtlas2Worker&&sigInst.getForceAtlas2Worker();if(options=options||{},_root.URL=_root.URL||_root.webkitURL,this.sigInst=sigInst,this.graph=this.sigInst.graph,this.ppn=10,this.ppe=3,this.config={},this.shouldUseWorker=options.worker!==!1&&webWorkers,this.workerUrl=options.workerUrl,this.started=!1,this.running=!1,this.shouldUseWorker){if(this.workerUrl)this.worker=new Worker(this.workerUrl);else{var blob=this.makeBlob(workerFn);this.worker=new Worker(URL.createObjectURL(blob))}this.worker.postMessage=this.worker.webkitPostMessage||this.worker.postMessage}else eval(workerFn);this.msgName=this.worker?"message":"newCoords",this.listener=function(t){_this.nodesByteArray=new Float32Array(t.data.nodes),_this.running&&(_this.applyLayoutChanges(),_this.sendByteArrayToWorker(),_this.sigInst.refresh())},(this.worker||document).addEventListener(this.msgName,this.listener),this.graphToByteArrays(),sigInst.bind("kill",function(){sigInst.killForceAtlas2()})}if("undefined"==typeof sigma)throw"sigma is not declared";var _root=this,webWorkers="Worker"in _root;Supervisor.prototype.makeBlob=function(t){var e;try{e=new Blob([t],{type:"application/javascript"})}catch(s){_root.BlobBuilder=_root.BlobBuilder||_root.WebKitBlobBuilder||_root.MozBlobBuilder,e=new BlobBuilder,e.append(t),e=e.getBlob()}return e},Supervisor.prototype.graphToByteArrays=function(){var t,e,s,r=this.graph.nodes(),i=this.graph.edges(),o=r.length*this.ppn,n=i.length*this.ppe,a={};for(this.nodesByteArray=new Float32Array(o),this.edgesByteArray=new Float32Array(n),t=e=0,s=r.length;t=0;t--)for(e in arguments[t])s[e]=arguments[t][e];return s}function s(t){var e;for(e in t)"hasOwnProperty"in t&&!t.hasOwnProperty(e)||delete t[e];return t}function r(t,e,s){s=s||{};a=t,h=e,u.nodesLength=a.length,u.edgesLength=h.length,i(s)}function i(t){u.settings=e(t,u.settings)}function o(){var t,e,s,r,i,o,n,g,d,l,c,f,y,w,v;for(s=0;s=0)k=a[s]=0){if(w=Math.sqrt(Math.pow(a[s]-p[e+7],2)+Math.pow(a[s+1]-p[e+8],2)),2*p[e+3]/w0?(v=l*a[s+6]*p[e+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v):w<0&&(v=-l*a[s+6]*p[e+6]/w,a[s+2]+=c*v,a[s+3]+=f*v):w>0&&(v=l*a[s+6]*p[e+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v),p[e+4]<0)break;e=p[e+4];continue}e=p[e+5]}else{if(p[e]>=0&&p[e]!==s&&(c=a[s]-a[p[e]],f=a[s+1]-a[p[e]+1],w=Math.sqrt(c*c+f*f),u.settings.adjustSizes?w>0?(v=l*a[s+6]*a[p[e]+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v):w<0&&(v=-l*a[s+6]*a[p[e]+6]/w,a[s+2]+=c*v,a[s+3]+=f*v):w>0&&(v=l*a[s+6]*a[p[e]+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v)),p[e+4]<0)break;e=p[e+4]}else for(l=u.settings.scalingRatio,r=0;r0?(v=l*a[r+6]*a[i+6]/w/w,a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]+=c*v,a[i+3]+=f*v):w<0&&(v=100*l*a[r+6]*a[i+6],a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]-=c*v,a[i+3]-=f*v)):(w=Math.sqrt(c*c+f*f),w>0&&(v=l*a[r+6]*a[i+6]/w/w,a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]-=c*v,a[i+3]-=f*v));for(g=u.settings.gravity/u.settings.scalingRatio,l=u.settings.scalingRatio,s=0;s0&&(v=l*a[s+6]*g):w>0&&(v=l*a[s+6]*g/w),a[s+2]-=c*v,a[s+3]-=f*v;for(l=1*(u.settings.outboundAttractionDistribution?d:1),o=0;o0&&(v=-l*y*Math.log(1+w)/w/a[r+6]):w>0&&(v=-l*y*Math.log(1+w)/w):u.settings.outboundAttractionDistribution?w>0&&(v=-l*y/a[r+6]):w>0&&(v=-l*y)):(w=Math.sqrt(Math.pow(c,2)+Math.pow(f,2)),u.settings.linLogMode?u.settings.outboundAttractionDistribution?w>0&&(v=-l*y*Math.log(1+w)/w/a[r+6]):w>0&&(v=-l*y*Math.log(1+w)/w):u.settings.outboundAttractionDistribution?(w=1,v=-l*y/a[r+6]):(w=1,v=-l*y)),w>0&&(a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]-=c*v,a[i+3]-=f*v);var W,L,F,_;if(u.settings.adjustSizes)for(s=0;su.maxForce&&(a[s+2]=a[s+2]*u.maxForce/W,a[s+3]=a[s+3]*u.maxForce/W),L=a[s+6]*Math.sqrt((a[s+4]-a[s+2])*(a[s+4]-a[s+2])+(a[s+5]-a[s+3])*(a[s+5]-a[s+3])),F=Math.sqrt((a[s+4]+a[s+2])*(a[s+4]+a[s+2])+(a[s+5]+a[s+3])*(a[s+5]+a[s+3]))/2,_=.1*Math.log(1+F)/(1+Math.sqrt(L)),a[s]=a[s]+a[s+2]*(_/u.settings.slowDown),a[s+1]=a[s+1]+a[s+3]*(_/u.settings.slowDown));else for(s=0;s 2 | 3 | 4 | Hoaxy® : Dashboard 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 |
14 | 15 | 46 | 47 |
48 |
49 |
50 |
51 |

Most popular {{formatArticleType(type)}} in the last month

52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
Capture dateTitleTweets
{{article.capture_date}}{{article.title}}{{article.number_of_tweets}}
Could not find any articles
68 |
69 |
70 |
71 |
72 |
73 | 74 |
75 |
76 |
77 |

Most influential accounts sharing {{formatArticleType(type)}} in the last month

78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
Screen NameNumber of TweetsBotometer Score
{{spreader.user_screen_name}}{{spreader.number_of_tweets}}{{spreader.bot_percent}}
Could not find any accounts
94 |
95 |
96 |
97 |
98 | 99 |
100 |
101 |
102 |

Most active accounts sharing {{formatArticleType(type)}} in the last month

103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
Screen NameNumber of TweetsBotometer Score
{{spreader.user_screen_name}}{{spreader.number_of_tweets}}{{spreader.bot_percent}}
Could not find any accounts
119 |
120 |
121 |
122 |
123 |
124 | 125 | 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /hoaxy_botometer_flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osome-iu/hoaxy-frontend/17bc7ba6356c1202b1888b6b3ae297ee8a1ef5a4/hoaxy_botometer_flowchart.png -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | server_name _; 4 | index index.php index.html; 5 | root /frontend/; 6 | 7 | error_log /var/log/nginx/error.log; 8 | access_log /var/log/nginx/access.log; 9 | 10 | # Hide Nginx Server Version 11 | server_tokens off; 12 | 13 | # Size Limits & Buffer Overflows 14 | client_body_buffer_size 100K; 15 | client_header_buffer_size 1k; 16 | client_max_body_size 100k; 17 | large_client_header_buffers 2 1k; 18 | 19 | # Allow a larger response buffer 20 | subrequest_output_buffer_size 100k; 21 | 22 | # X-Frame-Options is to prevent from clickJacking attack 23 | add_header X-Frame-Options "SAMEORIGIN"; 24 | 25 | # Disable content-type sniffing on some browsers. 26 | add_header X-Content-Type-Options "nosniff"; 27 | 28 | # This header enables the Cross-site scripting (XSS) filter 29 | add_header X-XSS-Protection "1; mode=block"; 30 | 31 | # This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack 32 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;"; 33 | 34 | # Limit HTTP methods 35 | if ($request_method !~ ^(GET|HEAD|POST)$ ) { 36 | return 405; 37 | } 38 | 39 | # Deny access to (dot) hidden files 40 | location ~ /\. { 41 | access_log off; 42 | log_not_found off; 43 | deny all; 44 | } 45 | 46 | # Serve static assets 47 | location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ { 48 | access_log off; 49 | log_not_found off; 50 | expires 7d; 51 | } 52 | 53 | # Handle PHP reverse proxy 54 | location ~ \.php$ { 55 | try_files $uri =404; 56 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 57 | fastcgi_pass php:9000; 58 | fastcgi_index index.php; 59 | include fastcgi_params; 60 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 61 | fastcgi_param PATH_INFO $fastcgi_path_info; 62 | } 63 | } -------------------------------------------------------------------------------- /php.ini: -------------------------------------------------------------------------------- 1 | # TODO: Add hardened php configs 2 | # https://www.owasp.org/index.php/PHP_Configuration_Cheat_Sheet 3 | # https://gist.github.com/yohang88/bab3c43eb2f4414eafd0cb145548651d 4 | # https://www.if-not-true-then-false.com/2011/nginx-and-php-fpm-configuration-and-optimizing-tips-and-tricks/ --------------------------------------------------------------------------------