Browse Source

Merge pull request #17 from jrtechs/rest-api

Rest API
pull/19/head
Jeffery Russell 4 years ago
committed by GitHub
parent
commit
d0cb202d0d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 55574 additions and 123 deletions
  1. +14
    -0
      .gitignore
  2. +2
    -2
      README.md
  3. +1
    -1
      conf/graph.properties
  4. +16
    -7
      pom.xml
  5. +2
    -1
      src/main/java/net/jrtechs/www/App.java
  6. +3
    -6
      src/main/java/net/jrtechs/www/SteamAPI/APIConnection.java
  7. +123
    -98
      src/main/java/net/jrtechs/www/graphDB/SteamGraph.java
  8. +1
    -2
      src/main/java/net/jrtechs/www/model/Game.java
  9. +1
    -1
      src/main/java/net/jrtechs/www/model/Player.java
  10. +58
    -0
      src/main/java/net/jrtechs/www/server/WebServer.java
  11. +2
    -1
      src/main/java/net/jrtechs/www/server/old/Client.java
  12. +1
    -1
      src/main/java/net/jrtechs/www/server/old/Server.java
  13. +1
    -1
      src/main/java/net/jrtechs/www/webCrawler/FileIO.java
  14. +1
    -1
      src/main/java/net/jrtechs/www/webCrawler/SteamWebCrawler.java
  15. +100
    -0
      src/main/resources/website/js/profileGen.js
  16. +107
    -0
      website/friendsGraph.html
  17. +1
    -1
      website/graph.html
  18. +28
    -0
      website/js/createOrgInfo.js
  19. +65
    -0
      website/js/createOrgRepoGraph.js
  20. +79
    -0
      website/js/createOrgTable.js
  21. +307
    -0
      website/js/friendsGraph.js
  22. +19
    -0
      website/js/profileGen.js
  23. +110
    -0
      website/js/profileTimeLine.js
  24. +0
    -0
      website/js/repoGen.js
  25. +74
    -0
      website/js/steamAPI.js
  26. +11
    -0
      website/js/utilities.js
  27. BIN
      website/js/vis/img/network/acceptDeleteIcon.png
  28. BIN
      website/js/vis/img/network/addNodeIcon.png
  29. BIN
      website/js/vis/img/network/backIcon.png
  30. BIN
      website/js/vis/img/network/connectIcon.png
  31. BIN
      website/js/vis/img/network/cross.png
  32. BIN
      website/js/vis/img/network/cross2.png
  33. BIN
      website/js/vis/img/network/deleteIcon.png
  34. BIN
      website/js/vis/img/network/downArrow.png
  35. BIN
      website/js/vis/img/network/editIcon.png
  36. BIN
      website/js/vis/img/network/leftArrow.png
  37. BIN
      website/js/vis/img/network/minus.png
  38. BIN
      website/js/vis/img/network/plus.png
  39. BIN
      website/js/vis/img/network/rightArrow.png
  40. BIN
      website/js/vis/img/network/upArrow.png
  41. BIN
      website/js/vis/img/network/zoomExtends.png
  42. +34
    -0
      website/js/vis/vis-graph3d.min.js
  43. +1
    -0
      website/js/vis/vis-network.min.css
  44. +42
    -0
      website/js/vis/vis-network.min.js
  45. +1
    -0
      website/js/vis/vis-timeline-graph2d.min.css
  46. +40
    -0
      website/js/vis/vis-timeline-graph2d.min.js
  47. +1448
    -0
      website/js/vis/vis.css
  48. +52833
    -0
      website/js/vis/vis.js
  49. +0
    -0
      website/js/vis/vis.js.map
  50. +1
    -0
      website/js/vis/vis.map
  51. +1
    -0
      website/js/vis/vis.min.css
  52. +46
    -0
      website/js/vis/vis.min.js

+ 14
- 0
.gitignore View File

@ -0,0 +1,14 @@
# Build/Maven #
###############
**/target/
# Eclipse #
###########
.project
.classpath
.settings
**/bin/
.metadata/
# IntelliJ #
############
.idea/
*.iml

+ 2
- 2
README.md View File

@ -5,7 +5,7 @@ gremlin graph database. Currently this project scrapes the steam API for friends
and their friends which can be used to generate a graph. This information is stored and their friends which can be used to generate a graph. This information is stored
locally in a gremlin server and is then sent to the client via a web socket. locally in a gremlin server and is then sent to the client via a web socket.
![Diagram](website/Diagram.svg)
![Diagram](src/main/resources/website/Diagram.svg)
[Video Of Friends of Friends Graph](https://www.youtube.com/watch?v=DoDaHmyIPvQ) [Video Of Friends of Friends Graph](https://www.youtube.com/watch?v=DoDaHmyIPvQ)
@ -16,7 +16,7 @@ If you are lucky, you will find it live at [http://steam.jrtechs.net](http://ste
It is still being actively developed and does not have permanent hosting so there is a %60 It is still being actively developed and does not have permanent hosting so there is a %60
chance at any time that you will be able to access it. chance at any time that you will be able to access it.
![Graph](website/img/jrtechs2.png)
![Graph](src/main/resources/website/img/jrtechs2.png)
## Running this Project ## Running this Project

+ 1
- 1
conf/graph.properties View File

@ -2,5 +2,5 @@ gremlin.graph=org.janusgraph.core.JanusGraphFactory
storage.backend=berkeleyje storage.backend=berkeleyje
storage.directory=./steam-graph/db
storage.directory=./steam-graph/dbv4
query.smart-limit=false query.smart-limit=false

+ 16
- 7
pom.xml View File

@ -17,7 +17,7 @@
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<spark.version>2.7.2</spark.version>
<gson.version>2.8.5</gson.version> <gson.version>2.8.5</gson.version>
</properties> </properties>
@ -35,6 +35,21 @@
<version>0.3.1</version> <version>0.3.1</version>
</dependency> </dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>${spark.version}</version>
</dependency>
<!-- JSON utils -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json --> <!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency> <dependency>
<groupId>org.json</groupId> <groupId>org.json</groupId>
@ -49,12 +64,6 @@
<version>1.5.0</version> <version>1.5.0</version>
</dependency> </dependency>
<!-- JSON utils -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.janusgraph/janusgraph-core --> <!-- https://mvnrepository.com/artifact/org.janusgraph/janusgraph-core -->
<!--<dependency>--> <!--<dependency>-->

+ 2
- 1
src/main/java/net/jrtechs/www/App.java View File

@ -1,12 +1,13 @@
package net.jrtechs.www; package net.jrtechs.www;
import net.jrtechs.www.server.Server;
import net.jrtechs.www.server.old.Server;
/** /**
* Launcher for the server * Launcher for the server
* *
* @author Jeffery Russell 6-9-18 * @author Jeffery Russell 6-9-18
*/ */
@Deprecated
public class App public class App
{ {
public static void main( String[] args ) public static void main( String[] args )

+ 3
- 6
src/main/java/net/jrtechs/www/SteamAPI/APIConnection.java View File

@ -1,7 +1,7 @@
package net.jrtechs.www.SteamAPI; package net.jrtechs.www.SteamAPI;
import net.jrtechs.www.server.Game;
import net.jrtechs.www.server.Player;
import net.jrtechs.www.model.Game;
import net.jrtechs.www.model.Player;
import net.jrtechs.www.utils.ConfigLoader; import net.jrtechs.www.utils.ConfigLoader;
import net.jrtechs.www.utils.WebScraper; import net.jrtechs.www.utils.WebScraper;
@ -9,12 +9,9 @@ import net.jrtechs.www.webCrawler.APIThrottler;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import javax.json.JsonArray;
import javax.json.JsonObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.IntStream; import java.util.stream.IntStream;
/** /**
@ -133,7 +130,7 @@ public class APIConnection
JSONObject object = new JSONObject(apiData); JSONObject object = new JSONObject(apiData);
System.out.println(object); System.out.println(object);
if(object.has("response"))
if(object.has("response") && object.getJSONObject("response").has("games"))
{ {
JSONArray gamesJ = object.getJSONObject("response").getJSONArray("games"); JSONArray gamesJ = object.getJSONObject("response").getJSONArray("games");
IntStream.range(0, gamesJ.length()).forEach(i -> IntStream.range(0, gamesJ.length()).forEach(i ->

+ 123
- 98
src/main/java/net/jrtechs/www/graphDB/SteamGraph.java View File

@ -1,17 +1,19 @@
package net.jrtechs.www.graphDB; package net.jrtechs.www.graphDB;
import com.google.gson.Gson;
import net.jrtechs.www.SteamAPI.SteamConnectionException; import net.jrtechs.www.SteamAPI.SteamConnectionException;
import net.jrtechs.www.server.Game;
import net.jrtechs.www.server.Player;
import net.jrtechs.www.model.Game;
import net.jrtechs.www.model.Player;
import net.jrtechs.www.SteamAPI.APIConnection; import net.jrtechs.www.SteamAPI.APIConnection;
import net.jrtechs.www.utils.WrappedFileWriter;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* Does graph based operations with {@link Player} * Does graph based operations with {@link Player}
* and
* and {@link Game}
* *
* @author Jeffery Russell 5-26-17 * @author Jeffery Russell 5-26-17
*/ */
@ -62,28 +64,21 @@ public class SteamGraph
* *
* @param * @param
*/ */
private void insertPlayerIntoGraph(Player p, boolean check)
private void insertPlayerIntoGraph(Player p)
{ {
try
{
if(!check || !this.alreadyInGraph(p.getId()))
{
System.out.println("inserting " + p.getName() + " into graph");
this.con.getTraversal()
.addV(SteamGraph.KEY_PLAYER)
.property(Player.KEY_USERNAME, p.getName())
.property(SteamGraph.KEY_CRAWLED_STATUS, 0)
.property(SteamGraph.KEY_CRAWLED_GAME_STATUS, 0)
.property(Player.KEY_STEAM_ID, p.getId())
.property(Player.KEY_AVATAR, p.getAvatar())
.property(Player.KEY_REAL_NAME, p.getRealName())
.property(Player.KEY_TIME_CREATED, p.getTimeCreated())
.id().next();
}
}
catch (Exception e)
if(!this.alreadyInGraph(p.getId()))
{ {
e.printStackTrace();
System.out.println("inserting " + p.getName() + " into graph");
this.con.getTraversal()
.addV(SteamGraph.KEY_PLAYER)
.property(Player.KEY_USERNAME, p.getName())
.property(SteamGraph.KEY_CRAWLED_STATUS, 0)
.property(SteamGraph.KEY_CRAWLED_GAME_STATUS, 0)
.property(Player.KEY_STEAM_ID, p.getId())
.property(Player.KEY_AVATAR, p.getAvatar())
.property(Player.KEY_REAL_NAME, p.getRealName())
.property(Player.KEY_TIME_CREATED, p.getTimeCreated())
.id().next();
} }
} }
@ -136,20 +131,12 @@ public class SteamGraph
*/ */
private boolean edgeAlreadyInGraph(String p1, String p2) private boolean edgeAlreadyInGraph(String p1, String p2)
{ {
try
{
return !this.con.getTraversal()
.V().hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, p1)
.both()
.has(Player.KEY_STEAM_ID, p2)
.toList().isEmpty();
}
catch(Exception e)
{
return false;
}
return !this.con.getTraversal()
.V().hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, p1)
.both()
.has(Player.KEY_STEAM_ID, p2)
.toList().isEmpty();
} }
@ -159,30 +146,22 @@ public class SteamGraph
* @param p1 * @param p1
* @param p2 * @param p2
*/ */
private void insertEdgeIntoGraph(String p1, String p2)
private void insertFriendshipIntoGraph(String p1, String p2)
{ {
try
if(!this.edgeAlreadyInGraph(p1, p2))
{ {
if(!this.edgeAlreadyInGraph(p1, p2))
{
System.out.println("Inserting edge: " + p1 + ":" + p2);
this.con.getTraversal()
.V()
.hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, p1)
.as("p1")
.V().hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, p2)
.as("p2")
.addE(Player.KEY_FRIENDS)
.from("p1").to("p2").id().next();
}
}
catch (Exception e)
{
e.printStackTrace();
System.out.println("Inserting edge: " + p1 + ":" + p2);
this.con.getTraversal()
.V()
.hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, p1)
.as("p1")
.V().hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, p2)
.as("p2")
.addE(Player.KEY_FRIENDS)
.from("p1").to("p2").id().next();
} }
} }
@ -211,19 +190,11 @@ public class SteamGraph
*/ */
private boolean playerPropertyIndexed(String id, String key) private boolean playerPropertyIndexed(String id, String key)
{ {
try
{
return this.con.getTraversal()
.V().hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, id)
.has(key, 0)
.toList().isEmpty();
}
catch(Exception e)
{
e.printStackTrace();
}
return true;
return this.con.getTraversal()
.V().hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, id)
.has(key, 0)
.toList().isEmpty();
} }
@ -280,9 +251,7 @@ public class SteamGraph
private List<Player> getFriendsFromGraph(String id) private List<Player> getFriendsFromGraph(String id)
{ {
System.out.println("fetching friends from graph"); System.out.println("fetching friends from graph");
return new ArrayList<Player>()
{{
con.getTraversal().V()
return con.getTraversal().V()
.hasLabel(SteamGraph.KEY_PLAYER) .hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, id) .has(Player.KEY_STEAM_ID, id)
.outE() .outE()
@ -290,26 +259,23 @@ public class SteamGraph
.hasLabel(SteamGraph.KEY_PLAYER) .hasLabel(SteamGraph.KEY_PLAYER)
.valueMap() .valueMap()
.toStream() .toStream()
.forEach(r ->
add(new Player(r)));
}};
.map(Player::new)
.collect(Collectors.toList());
} }
private List<Game> getPlayerGamesFromGraph(String id) private List<Game> getPlayerGamesFromGraph(String id)
{ {
System.out.println("fetching games from graph"); System.out.println("fetching games from graph");
return new ArrayList<Game>()
{{
con.getTraversal().V()
.hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, id)
.outE()
.inV()
.hasLabel(Game.KEY_DB)
.valueMap()
.toStream().forEach(r ->
add(new Game(r)));
}};
return con.getTraversal().V()
.hasLabel(SteamGraph.KEY_PLAYER)
.has(Player.KEY_STEAM_ID, id)
.outE()
.inV()
.hasLabel(Game.KEY_DB)
.valueMap()
.toStream()
.map(Game::new)
.collect(Collectors.toList());
} }
@ -332,10 +298,10 @@ public class SteamGraph
this.api.getPlayers(notInDatabase) this.api.getPlayers(notInDatabase)
.forEach(p -> .forEach(p ->
insertPlayerIntoGraph(p, false));
insertPlayerIntoGraph(p));
friendsIds.forEach(s-> friendsIds.forEach(s->
this.insertEdgeIntoGraph(id, s));
this.insertFriendshipIntoGraph(id, s));
this.updateCrawledStatusFriends(id); this.updateCrawledStatusFriends(id);
this.con.commit(); this.con.commit();
@ -351,7 +317,7 @@ public class SteamGraph
return games; return games;
} }
public List<Game> getGameList(String id)
public synchronized List<Game> getGameList(String id)
{ {
return this.playerGamesAlreadyIndexed(id) ? return this.playerGamesAlreadyIndexed(id) ?
this.getPlayerGamesFromGraph(id) : this.getPlayerGamesFromGraph(id) :
@ -365,7 +331,7 @@ public class SteamGraph
* @param id * @param id
* @return * @return
*/ */
public Player getPlayer(String id)
public synchronized Player getPlayer(String id)
{ {
Player p; Player p;
if(this.alreadyInGraph(id)) // yay if(this.alreadyInGraph(id)) // yay
@ -384,7 +350,7 @@ public class SteamGraph
try try
{ {
p = this.api.getSingle(id); p = this.api.getSingle(id);
this.insertPlayerIntoGraph(p, false);
this.insertPlayerIntoGraph(p);
this.indexPersonFriends(id); this.indexPersonFriends(id);
p.setFriends(this.getFriendsFromGraph(id)); p.setFriends(this.getFriendsFromGraph(id));
} }
@ -417,9 +383,68 @@ public class SteamGraph
public static void main(String[] args) public static void main(String[] args)
{ {
SteamGraph graph = new SteamGraph(); SteamGraph graph = new SteamGraph();
graph.getPlayer("76561198188400721");
graph.getGameList("76561198188400721");
System.out.println(
graph.con.getTraversal().E().hasLabel(Game.KEY_RELATIONSHIP).count().next()
);
//
Object o =
graph.con.getTraversal()
.V()
.hasLabel(Game.KEY_DB)
.match(
__.as("c").values(Game.KEY_STEAM_GAME_ID).as("gameID"),
__.as("c").values(Game.KEY_GAME_NAME).as("gameName"),
__.as("c").inE(Game.KEY_RELATIONSHIP).values(Game.KEY_PLAY_TIME).as("time")
).select("gameID", "time", "gameName").toList();
// System.out.println(new Gson().toJson(o));
WrappedFileWriter.writeToFile(new Gson().toJson(o).toLowerCase(), "games.json");
// System.out.println(
// graph.con.getTraversal()
// .V()
// .hasLabel(Game.KEY_DB)
// .match(
// __.as("c").values(Game.KEY_STEAM_GAME_ID).as("gameID"),
// __.as("c").values(Game.KEY_GAME_NAME).as("gameName"),
// __.as("c").inE(Game.KEY_RELATIONSHIP).values(Game.KEY_PLAY_TIME).as("averageGameTime")
// ).select("gameID", "time", "averageGameTime").toList()
// );
// System.out.println(
// graph.con.getTraversal().V()
// .hasLabel(Game.KEY_DB)
// .as("g").inE()
// .hasLabel(Game.KEY_RELATIONSHIP)
// .as("p")
// .project(Game.KEY_PLAY_TIME, Game.KEY_STEAM_GAME_ID).value().toList()
// );
System.out.println(
graph.con.getTraversal().E()
.hasLabel(Game.KEY_RELATIONSHIP).values(Game.KEY_PLAY_TIME).max().toList()
);
//
// graph.getPlayer("76561198188400721")
// .getFriends().forEach(f->
// {
// graph.getGameList(f.getId());
// graph.getPlayer(f.getId()).getFriends().forEach(f2->
// {
// graph.getGameList(f2.getId());
// graph.getPlayer(f2.getId());
// });
// });
System.out.println(
graph.con.getTraversal().V().hasLabel(SteamGraph.KEY_PLAYER)
.has(SteamGraph.KEY_CRAWLED_GAME_STATUS, 1)
.count().next()
);
// graph.getPlayer("76561198062300654");
//graph.getGameList("76561198188400721");
// //graph.getPlayer("76561198068098265").getFriends().stream().forEach(System.out::println); // //graph.getPlayer("76561198068098265").getFriends().stream().forEach(System.out::println);
//// graph.indexPersonFriends("76561198188400721"); //// graph.indexPersonFriends("76561198188400721");
// graph.indexPersonsGames("76561198188400721"); // graph.indexPersonsGames("76561198188400721");

src/main/java/net/jrtechs/www/server/Game.java → src/main/java/net/jrtechs/www/model/Game.java View File

@ -1,9 +1,8 @@
package net.jrtechs.www.server;
package net.jrtechs.www.model;
import org.json.JSONObject; import org.json.JSONObject;
import javax.json.JsonObject;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;

src/main/java/net/jrtechs/www/server/Player.java → src/main/java/net/jrtechs/www/model/Player.java View File

@ -1,4 +1,4 @@
package net.jrtechs.www.server;
package net.jrtechs.www.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

+ 58
- 0
src/main/java/net/jrtechs/www/server/WebServer.java View File

@ -0,0 +1,58 @@
package net.jrtechs.www.server;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import net.jrtechs.www.graphDB.SteamGraph;
import net.jrtechs.www.model.Game;
import net.jrtechs.www.model.Player;
import java.lang.reflect.Type;
import java.util.List;
import static spark.Spark.*;
/**
* Quick and dirty web server to serve as an API backend to
* the graph interface.
*
* @author Jeffery Russell 7-12-20
*/
public class WebServer
{
private SteamGraph graph;
private Gson gson;
public static String GET_PLAYER = "/player";
public static String GET_GAMES = "/games";
public WebServer()
{
this.graph = new SteamGraph();
this.gson = new Gson();
int maxThreads = 8;
int minThreads = 2;
int timeOutMillis = 3000000;
threadPool(maxThreads, minThreads, timeOutMillis);
Type typePlayer = new TypeToken<Player>(){}.getType();
Type typeGames = new TypeToken<List<Game>>(){}.getType();
staticFiles.externalLocation("./website");
get("/player/:id", (req, res) ->
gson.toJson(
graph.getPlayer(req.params(":id")), typePlayer));
get("/games/:id", (req, res) ->
gson.toJson(
graph.getGameList(req.params(":id")), typeGames));
System.out.println("Finished starting web server");
}
public static void main(String[] arguments)
{
new WebServer();
}
}

src/main/java/net/jrtechs/www/server/Client.java → src/main/java/net/jrtechs/www/server/old/Client.java View File

@ -1,6 +1,7 @@
package net.jrtechs.www.server;
package net.jrtechs.www.server.old;
import net.jrtechs.www.graphDB.SteamGraph; import net.jrtechs.www.graphDB.SteamGraph;
import net.jrtechs.www.model.Player;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
import org.json.JSONObject; import org.json.JSONObject;

src/main/java/net/jrtechs/www/server/Server.java → src/main/java/net/jrtechs/www/server/old/Server.java View File

@ -1,4 +1,4 @@
package net.jrtechs.www.server;
package net.jrtechs.www.server.old;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshake;

+ 1
- 1
src/main/java/net/jrtechs/www/webCrawler/FileIO.java View File

@ -1,6 +1,6 @@
package net.jrtechs.www.webCrawler; package net.jrtechs.www.webCrawler;
import net.jrtechs.www.server.Player;
import net.jrtechs.www.model.Player;
import net.jrtechs.www.utils.FileReader; import net.jrtechs.www.utils.FileReader;
import net.jrtechs.www.utils.WrappedFileWriter; import net.jrtechs.www.utils.WrappedFileWriter;
import org.json.JSONArray; import org.json.JSONArray;

+ 1
- 1
src/main/java/net/jrtechs/www/webCrawler/SteamWebCrawler.java View File

@ -1,7 +1,7 @@
package net.jrtechs.www.webCrawler; package net.jrtechs.www.webCrawler;
import net.jrtechs.www.SteamAPI.APIConnection; import net.jrtechs.www.SteamAPI.APIConnection;
import net.jrtechs.www.server.Player;
import net.jrtechs.www.model.Player;
import java.util.*; import java.util.*;

+ 100
- 0
src/main/resources/website/js/profileGen.js View File

@ -0,0 +1,100 @@
function profileGen(username, container)
{
queryAPIByUser("", username, (user) =>
{
if(!user.hasOwnProperty("id"))
{
alert("User Does Not Exist");
window.location.href = "./GraphGenerator.html";
}
parseOrgs(user.login).then( (orgsReturn) => {
if (user.blog) {
const rx = new RegExp("^(http|https)://", "i");
const match = rx.test(user.blog);
user.blog = match ? user.blog : `http://${user.blog}`;
}
let html = `
<div class="card shadow-sm" style="font-size: 16px;">
<div class="card-img-top" style="position: relative;">
<img src="${user.avatar_url}" alt="${user.avatar_url}" class="img-fluid"/>
<div class="actions">
<a href="${graphUrl(user.login)}" class="btns" title="View Friends Graph">
<svg viewBox="0 0 24 24">
<path d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
</svg>
</a>
<a href="${timelineUrl(user.login)}" class="btns" title="View Timeline Graph">
<svg viewBox="0 0 24 24">
<path d="M2,2H4V20H22V22H2V2M7,10H17V13H7V10M11,15H21V18H11V15M6,4H22V8H20V6H8V8H6V4Z" />
</svg>
</a>
</div>
</div>
<div class="card-body">
${user.name ? `<h5 class="card-title mb-1">${user.name}</h5>` : ""}
<a href="${user.html_url}" class="card-subtitle text-muted">${user.login}</a>
${user.bio != null ? `<p class="my-2">${user.bio}</p>` : ""}
<ul class="list-unstyled">
${user.blog != null ? `<a href="${user.blog}"><li>${user.blog}</li></a>` : ""}
${user.email != null ? `<li>Email: ${user.email}</li>` : ""}
${user.location != null ? `<li>Location: ${user.location}</li>` : ""}
${user.company != null ? `<li>Company: ${user.company}</li>` : ""}
</ul>
<hr />
<ul class="list-unstyled">
<li>Followers: ${user.followers}</li>
<li>Following: ${user.following}</li>
<li>Repositories: ${user.public_repos}</li>
</ul>
${orgsReturn != [] ? `<hr /> <p>Organizations</p> ${orgsReturn}` : ""}
</div>
</div>
`;
$("#"+container).html(html);
})
}, () =>
{
alert("User Does Not Exist");
window.location.href = "./GraphGenerator.html";
});
}
function parseOrgs(name) {
return new Promise( (resolve, reject) => {
let urlpath = `api/users/${name}/orgs`;
let orgs_final = [];
queryUrl(urlpath, (orgs) => {
var prom= [];
for(var i = 0;i < orgs.length; i++) {
prom.push( new Promise( (res, rej) => {
url = orgs[i].url;
queryUrl(url, (orgData) => {
console.log(orgData);
orgs_final.push("<a href=\"OrgRepoGraph.html?name="+orgData.login+"\"><img src=\""+orgData.avatar_url+"\" class=\"img-fluid\" style=\"max-width:35px\"></img></a>");
res();
}, (error) => {
console.log(error);
rej(error);
console.error("error getting org info");
});
})
)
}
Promise.all(prom).then(function() {
resolve(orgs_final.join(" "));
})
}, (error) => {
resolve([]);
});
})
}
function graphUrl(user) {
return "/FriendsGraph.html?name="+user;
}
function timelineUrl(user) {
return "/TimeLineGraph.html?name="+user;
}

+ 107
- 0
website/friendsGraph.html View File

@ -0,0 +1,107 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Friends - Steam Graphs</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css" />
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous">
</script>
<script src="js/steamAPI.js"></script>
<script src="js/friendsGraph.js"></script>
<script src="js/profileGen.js"></script>
<script src="js/utilities.js"></script>
<script type="text/javascript" src="js/vis/vis.js"></script>
<link href="js/vis/vis-network.min.css" rel="stylesheet" type="text/css" />
</head>
<body class="friends-graph-page">
<nav class="navbar navbar-dark bg-dark navbar-expand-md">
<a class="navbar-brand" href="/"><img src="img/githubgraph-logo.svg" alt=""></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#main-menu" aria-controls="main-menu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="main-menu" class="collapse navbar-collapse">
<ul class="navbar-nav flex-fill justify-content-end">
<li class="nav-item">
<a class="nav-link" href="./GraphGenerator.html">Generate graphs</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/jrtechs/github-graphs/">View on GitHub</a>
</li>
<li class="nav-item">
<a class="nav-link" href="./about.html">About</a>
</li>
</ul>
</div>
</nav>
<div class="main">
<div class="container-fluid container-xl">
<div class="row pt-5">
<div class="col-lg-3 col-md-4 col-12"></div>
<div class="col-lg-9 col-md-8 col-12">
<div class="d-flex align-items-baseline justify-content-between my-4">
<div class="d-flex align-items-center">
<svg class="mr-3" width="36" height="36" viewBox="0 0 36 36" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M18 0C8.064 0 0 8.064 0 18C0 27.936 8.064 36 18 36C27.936 36 36 27.936 36 18C36 8.064 27.936 0 18 0ZM18 5.4C20.988 5.4 23.4 7.812 23.4 10.8C23.4 13.788 20.988 16.2 18 16.2C15.012 16.2 12.6 13.788 12.6 10.8C12.6 7.812 15.012 5.4 18 5.4ZM18 30.96C15.8613 30.96 13.7559 30.4308 11.8715 29.4194C9.98707 28.4081 8.3822 26.9462 7.2 25.164C7.254 21.582 14.4 19.62 18 19.62C21.582 19.62 28.746 21.582 28.8 25.164C27.6178 26.9462 26.0129 28.4081 24.1285 29.4194C22.2441 30.4308 20.1387 30.96 18 30.96Z"
fill="white" />
</svg>
<h1 class="text-white font-weight-bold">Interactive friend chart</h1>
</div>
<div>
<a id="TimelineLink" class="text-light" href="#">View repo timeline</a>
</div>
</div>
</div>
</div>
<div class="row pb-4">
<div class="col-lg-3 col-md-4 col-12">
<div id="profileGen"></div>
</div>
<div class="col-lg-9 col-md-8 col-12">
<div class="card shadow text-white bg-dark border-white">
<h2 id="graphLoading"></h2>
<div id="myGraph" class="w-100"></div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
function createGraphs(id)
{
options.width = $("#myGraph").width() + "px";
options.height = "700px";
createFriendsGraph(id, "myGraph", "graphLoading");
profileGen(id, "profileGen");
}
if(findGetParameter("id") !== null)
{
createGraphs(findGetParameter("id"))
}
$('#TimelineLink').attr("href", "TimeLineGraph.html?name=" + findGetParameter("name"));
</script>
</html>

+ 1
- 1
website/graph.html View File

@ -194,7 +194,7 @@
/** Web socket to communicate with the steam friend java server */ /** Web socket to communicate with the steam friend java server */
connection = new WebSocket('ws://steam.student.rit.edu:4444');
connection = new WebSocket('ws://localhost:4444');
setTimeout(function() setTimeout(function()

+ 28
- 0
website/js/createOrgInfo.js View File

@ -0,0 +1,28 @@
function createOrgInfo(orgName, container) {
queryAPIByOrg("", orgName, (orgData) => {
var html = `
<div class="card shadow py-4 px-3">
<div class="row align-items-center">
<div class="col-md-2 text-center">
<img src="${orgData.avatar_url}" alt="${orgData.name}" class="img-fluid" style="max-width: 100px"/>
</div>
<div class="col-md-10">
<div class="media-body">
<h1 class="h4">${orgData.name}</h1>
<p class="text-muted">${orgData.description}</p>
<ul class="d-flex list-unstyled mb-0">
<li>${orgData.location}</li>
</ul>
</div>
</div>
</div>
</div>
`;
$("#" + container).html(html);
}, function(error) {
alert("Organization Does Not Exist");
window.location.href = "./GraphGenerator.html";
});
}

+ 65
- 0
website/js/createOrgRepoGraph.js View File

@ -0,0 +1,65 @@
/**
* Add's all the members of the organization into the graphs
* node objects
*
* @param orgname
* @param page
* @returns {Promise<any>}
*/
function addOrgUsers(orgname)
{
return new Promise(function(resolve, reject)
{
getOrganizationMembers(orgname, (data)=>
{
for(var i = 0;i < data.length; i++)
{
addPersonToGraph(data[i]);
}
total = data.length;
resolve();
}, (error)=>
{
console.log(error);
resolve();
})
})
}
/**
* Creates a graph
* @param username
* @param containerName
* @param graphsTitle
*/
function createOrgRepoGraph(orgname, containerName, graphsTitle)
{
progressID = graphsTitle;
nodes = [];
edges = [];
addOrgUsers(orgname).then(function()
{
createConnections().then( () => {
var container = document.getElementById(containerName);
var data = {
nodes: nodes,
edges: edges
};
var network = new vis.Network(container, data, options);
network.on("click", function (params) {
params.event = "[original event]";
if(this.getNodeAt(params.pointer.DOM) !== NaN)
{
bringUpProfileView(this.getNodeAt(params.pointer.DOM));
}
});
$("#graphLoading").html("");
});
}).catch(function(error) {
alert("Invalid Organization");
});
}

+ 79
- 0
website/js/createOrgTable.js View File

@ -0,0 +1,79 @@
function generateHtmlRow(repoData) {
var html = `
<tr>
<td>
${repoData.language === 'null'
? '<div class="bg-light d-inline-block" style="height: 14px; width: 14px; border-radius: 7px"></div>'
: `<i class="devicon-${repoData.language}-plain colored"></i>`}
<a class="text-reset ml-1" href="${repoData.html_url}" target="_blank">${repoData.name}</a>
</td>
<td class="text-right">${repoData.forks}</td>
</tr>
`;
return html;
}
var repos = [];
function fetchAllRepositories(orgName)
{
console.log("Going for it");
return new Promise((resolve, reject)=>
{
getOrganizationRepositories(orgName,
(data)=>
{
console.log("Dam did got it");
repos.push(...data);
resolve();
},
(error)=>
{
console.log("Unable to load table data");
reject("Error fetching repositories");
});
});
}
function createOrgTable(orgName, tableContainer)
{
var html = "";
fetchAllRepositories(orgName).then(function() {
for (var i=0; i < repos.length; i++) {
let icon = repos[i].language;
icon === null
? icon = 'null'
: icon = icon.toLowerCase();
icon === 'c++'
? icon = 'cplusplus'
: null;
icon === 'c#'
? icon = 'csharp'
: null;
repos[i].language = icon;
html += generateHtmlRow(repos[i]);
}
$("#" + tableContainer).html(html);
setTimeout(function() {
$('#dataTable').DataTable({
pageLength: 15,
pagingType: 'simple',
bLengthChange : false,
"bFilter" : false
});
}, 1500);
}).catch(function(error)
{
console.log("Unable to create table");
});
}

+ 307
- 0
website/js/friendsGraph.js View File

@ -0,0 +1,307 @@
/** Nodes in the vis js graph */
var nodes;
/** Edges used to make the Vis JS graph*/
var edges;
/** Used for the loading bar */
var total = 1;
var indexed = 0;
var progressID;
/** Github id of the user being indexed */
var baseID;
/**
* Vis js graph options
*/
var options = {
nodes: {
borderWidth:4,
size:30,
color: {
border: '#222222',
background: '#666666'
},
font:{color:'#eeeeee'}
},
edges: {
color: 'lightgray'
}
};
/**
* Checks if a user is a node in the graph
*
* @param userID
* @returns {boolean}
*/
function alreadyInGraph(username)
{
for(var i = 0; i < nodes.length; i++)
{
if(nodes[i].id === username)
{
return true;
}
}
return false;
}
/**
* adds a person to the nodes list
*
* @param profileData
*/
function addPersonToGraph(profileData)
{
addManualToGraph(profileData.id, profileData.avatar);
for(var i = 0; i < profileData.friends.length; i++)
{
addManualToGraph(profileData.friends[i].id,
profileData.friends[i].avatar);
}
}
function addManualToGraph(id,avatar)
{
nodes.push(
{
id:id,
shape: 'circularImage',
image:avatar
});
}
/**
* Adds the followers/following of a person
* to the graph
*
* @param username
* @param apiPath
* @returns {Promise<any>}
*/
function addFriends(username)
{
updateProgress();
return new Promise((resolve, reject)=>
{
getPersonAPI(username, (data)=>
{
for(var i = 0; i < data.length; i++)
{
if(!alreadyInGraph(data[i].login))
{
addPersonToGraph(data[i]);
}
}
resolve();
},
(error)=>
{
reject(error);
})
});
}
/**
* Greedy function which checks to see if a edge is in the graphs
*
* @param id1
* @param id2
* @returns {boolean}
*/
function edgeInGraph(id1, id2)
{
for(var i = 0;i < edges.length; i++)
{
if((edges[i].to === id1 && edges[i].from === id2) ||
(edges[i].from === id1 && edges[i].to === id2))
{
return true;
}
}
return false;
}
/**
* Adds a connection to the graph
*
* @param person1
* @param person2
*/
function addConnection(id1, id2)
{
if(id1 !== id2)
{
if(alreadyInGraph(id2) && !edgeInGraph(id1, id2))
{
network.body.data.edges.add([{
from: id1,
to: id2
}]);
}
}
}
/**
* Processes all the connections of a user and adds them to the graph
*
* @param user has .id and .name
* @returns {Promise<any>}
*/
function processUserConnections(node)
{
getPersonAPI(node.id,
(data)=>
{
updateProgress();
for(var i = 0; i < data.friends.length; i++)
{
addConnection(node.id, data.friends[i].id)
}
}, (error)=>
{
console.log(error);
});
}
/**
* Creates connections between all the nodes in
* the graph.
*
* @returns {Promise<any>}
*/
function createConnections()
{
var prom = [];
for(var i = 0; i < nodes.length; i++)
{
processUserConnections(nodes[i]);
}
}
/**
* Updates progress bar for loading the JS graph
*/
function updateProgress()
{
indexed++;
if(indexed >= total)
{
$("#" + progressID).html("");
}
else
{
const percent = parseInt((indexed/total)*100);
$("#" + progressID).html("<div class=\"progress\">\n" +
" <div class=\"progress-bar progress-bar-striped progress-bar-animated\" role=\"progressbar\" style=\"width: " + percent + "%\" aria-valuenow=\"" + percent + "\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n" +
"</div>");
}
}
var selfData;
/**
* Adds the base person to the graph.
*
* @param username
* @returns {Promise<any>}
*/
function addSelfToGraph(id)
{
return new Promise((resolve, reject)=>
{
getPersonAPI(id, (data)=>
{
selfData = data;
baseID = data.id;
total = data.friends.length;
addPersonToGraph(data);
resolve();
},
(error)=>
{
reject(error);
});
});
}
/**
* Used for the on graph click event
*
* @param github id
*/
function bringUpProfileView(uname)
{
console.log(uname);
if(uname === selfData.id)
{
profileGen(selfData, "profileGen");
}
else
{
for(var i = 0; i < selfData.friends.length; i++)
{
if(selfData.friends[i].id === uname)
{
profileGen(selfData.friends[i], "profileGen");
}
}
}
}
var network;
/**
* Creates a graph
* @param username
* @param containerName
* @param progressBarID
*/
function createFriendsGraph(username, containerName, progressBarID)
{
progressID = progressBarID;
nodes = [];
edges = [];
addSelfToGraph(username).then(()=>
{
$("#" + progressID).html("");
var container = document.getElementById(containerName);
var data =
{
nodes: nodes,
edges: edges
};
network = new vis.Network(container, data, options);
bringUpProfileView(selfData.id);
network.on("click", function (params)
{
if(Number(this.getNodeAt(params.pointer.DOM)) !== NaN)
{
bringUpProfileView(this.getNodeAt(params.pointer.DOM));
}
});
createConnections().then(()=>
{
// $("#" + progressID).html("");
console.log("Finished");
})
}).catch((error)=>
{
//$("#" + graphsTitle).html("Error Fetching Data From API");
alert("Invalid User");
});
}

+ 19
- 0
website/js/profileGen.js View File

@ -0,0 +1,19 @@
function profileGen(data, container)
{
let html = `
<div class="card shadow-sm" style="font-size: 16px;">
<div class="card-img-top" style="position: relative;">
<img src="${data.avatar}" alt="${data.avatar}" width="100%" class="img-fluid"/>
</div>
<div class="card-body">
${data.name ? `<h5 class="card-title mb-1">${data.name}</h5>` : ""}
<a href="${data.avatar}" class="card-subtitle text-muted">${data.realName}</a>
<ul class="list-unstyled">
<li><a href="https://steamcommunity.com/profiles/${data.id}">Steam profile</a></li>
<li><a href="/friendsGraph.html?id=${data.id}">Friends Graph</a></li>
</ul>
</div>
</div>
`;
$("#"+container).html(html);
}

+ 110
- 0
website/js/profileTimeLine.js View File

@ -0,0 +1,110 @@
var events = [];
var repositoryData;
function addEvent(group, date, content)
{
var dateFormat = new Date(date);
var dd = new Date(dateFormat.getFullYear(), dateFormat.getMonth(), dateFormat.getDay());
events.push({id: events.length, group: group, start: dd, content: content});
}
// {id: 0, group: 0, start: new Date(2013,7,1), end: new Date(2017,5,15), content: 'High School'},
function addRepositories(userName, groupID)
{
return new Promise(function(resolve, reject)
{
getUserRepositories(userName,
function(data)
{
repositoryData = data;
for(var i = 0; i < data.length; i++)
{
data[i].id = events.length;
addEvent(groupID, data[i]['created_at'], data[i]['name'])
}
resolve();
},
function(error)
{
console.log(error);
reject(error);
})
})
}
function timeLineClickEvent(properties)
{
if(properties.item !== null && typeof repositoryData[properties.item].name !== 'undefined')
{
var item = repositoryData[properties.item];
if (item.license === null) {
item.license = new Object();
item.license.name = 'None';
}
var html = `
<div class="card shadow" style="font-size: 16px">
<div class="card-body">
<h5 class="card-title">${item.name}</h5>
<p class="card-subtitle text-muted">${item.description ? item.description : 'No description'}</p>
<div class="row py-3">
<div class="col-12 col-md-8">
${item.homepage ? `<p class="mb-0">Homepage: <a href="${item.license.name}">${item.license.name}</a></p>` : ''}
<p class="mb-0">Repository URL: <a href="${item.html_url}">${item.html_url}</a></p>
<p class="mb-0">Languages: ${item.language ? item.language : 'Not specified'}</p>
<p class="mb-0">License: ${item.license.name}</p>
</div>
<div class="col-12 col-md-4">
<p class="mb-0">Fork Count: ${item.forks}</p>
<p class="mb-0">Open Issues: ${item.open_issues_count}</p>
<p class="mb-0">Watchers: ${item.watchers}</p>
</div>
</div>
</div>
</div>
`;
$("#repositoryInformation").html(html);
}
}
function createProfileTimeLine(username, containerName)
{
var container = document.getElementById(containerName);
var prom = [addRepositories(username, 1)];
var groups = new vis.DataSet([
{id: 1, content: 'Repositories', value: 2}
]);
Promise.all(prom).then(function()
{
// note that months are zero-based in the JavaScript Date object
var items = new vis.DataSet(events);
var options = {
// option groupOrder can be a property name or a sort function
// the sort function must compare two groups and return a value
// > 0 when a > b
// < 0 when a < b
// 0 when a == b
groupOrder: function (a, b) {
return a.value - b.value;
},
margin: {
item: 20,
axis: 40
}
};
var timeline = new vis.Timeline(container);
timeline.setOptions(options);
timeline.setGroups(groups);
timeline.setItems(items);
timeline.on('click', timeLineClickEvent);
});
}

+ 0
- 0
website/js/repoGen.js View File


+ 74
- 0
website/js/steamAPI.js View File

@ -0,0 +1,74 @@
/**
* Simple file which uses jQuery's ajax
* calls to make it easier to get data
* from the steam api.
*
* @author Jeffery Russell 2-16-19, 7-12-20
*/
const APIROOT = "";
const API_USER_PATH = "/player/";
const API_GAMES_PATH = "/games/"
/**
* Fetches a list of fiends for a user.
*
* @param {*} userName
* @param {*} suc
* @param {*} err
*/
function getPersonAPI(userID, suc, err)
{
// api/friends/jrtechs
const urlpath = APIROOT + API_USER_PATH + userID;
runAjax(urlpath, suc, err);
}
function getUserGames(userID, suc, err)
{
//ex: http://localhost:7000/api/repositories/jwflory
const urlpath = APIROOT + "/repositories/" + userID;
runAjax(urlpath, suc, err);
}
/**
* Queries github API end points with the backend
* proxy server for github graphs.
*
* @param {*} url
* @param {*} successCallBack
* @param {*} errorCallBack
*/
function queryUrl(url, successCallBack, errorCallBack)
{
url = url.split("https://api.github.com/").join("api/");
runAjax(url, successCallBack, errorCallBack);
}
/**
* Wrapper for AJAX calls so we can unify
* all of our settings.
*
* @param {*} url -- url to query
* @param {*} successCallBack -- callback with data retrieved
* @param {*} errorCallBack -- callback with error message
*/
function runAjax(url, successCallBack, errorCallBack)
{
console.log(url);
$.ajax({
type:'GET',
url: url,
crossDomain: true,
dataType: "json",
success: successCallBack,
error:errorCallBack,
timeout: 300000
});
}

+ 11
- 0
website/js/utilities.js View File

@ -0,0 +1,11 @@
function findGetParameter(parameterName)
{
var result = null,
tmp = [];
var items = location.search.substr(1).split("&");
for (var index = 0; index < items.length; index++) {
tmp = items[index].split("=");
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
}
return result;
}

BIN
website/js/vis/img/network/acceptDeleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
website/js/vis/img/network/addNodeIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
website/js/vis/img/network/backIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
website/js/vis/img/network/connectIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
website/js/vis/img/network/cross.png View File

Before After
Width: 7  |  Height: 7  |  Size: 18 KiB

BIN
website/js/vis/img/network/cross2.png View File

Before After
Width: 5  |  Height: 5  |  Size: 17 KiB

BIN
website/js/vis/img/network/deleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
website/js/vis/img/network/downArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
website/js/vis/img/network/editIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
website/js/vis/img/network/leftArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
website/js/vis/img/network/minus.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.0 KiB

BIN
website/js/vis/img/network/plus.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.2 KiB

BIN
website/js/vis/img/network/rightArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
website/js/vis/img/network/upArrow.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

BIN
website/js/vis/img/network/zoomExtends.png View File

Before After
Width: 30  |  Height: 30  |  Size: 4.4 KiB

+ 34
- 0
website/js/vis/vis-graph3d.min.js
File diff suppressed because it is too large
View File


+ 1
- 0
website/js/vis/vis-network.min.css
File diff suppressed because it is too large
View File


+ 42
- 0
website/js/vis/vis-network.min.js
File diff suppressed because it is too large
View File


+ 1
- 0
website/js/vis/vis-timeline-graph2d.min.css
File diff suppressed because it is too large
View File


+ 40
- 0
website/js/vis/vis-timeline-graph2d.min.js
File diff suppressed because it is too large
View File


+ 1448
- 0
website/js/vis/vis.css
File diff suppressed because it is too large
View File


+ 52833
- 0
website/js/vis/vis.js
File diff suppressed because it is too large
View File


+ 0
- 0
website/js/vis/vis.js.map View File


+ 1
- 0
website/js/vis/vis.map
File diff suppressed because it is too large
View File


+ 1
- 0
website/js/vis/vis.min.css
File diff suppressed because it is too large
View File


+ 46
- 0
website/js/vis/vis.min.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save