From 7bf02ea60c6fd05f0662b27b75135a6ff2d99863 Mon Sep 17 00:00:00 2001 From: Tao Bojlen Date: Mon, 3 Sep 2018 20:15:28 +0200 Subject: [PATCH] various UI improvements --- apiv1/serializers.py | 15 +++-- frontend/src/components/Graph.jsx | 6 +- frontend/src/components/Sidebar.tsx | 61 ++++++++++++++++++- frontend/src/redux/types.ts | 2 + .../space/fediverse/graph/GraphBuilder.java | 2 +- scraper/management/commands/scrape.py | 2 +- 6 files changed, 77 insertions(+), 11 deletions(-) diff --git a/apiv1/serializers.py b/apiv1/serializers.py index e508f3d..2efcd1d 100644 --- a/apiv1/serializers.py +++ b/apiv1/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers +import math from collections import OrderedDict -from scraper.models import Instance, PeerRelationship +from scraper.models import Instance, Edge class InstanceListSerializer(serializers.ModelSerializer): @@ -39,19 +40,23 @@ class InstanceDetailSerializer(serializers.ModelSerializer): class Meta: model = Instance - fields = ('name', 'description', 'version', 'userCount', 'statusCount', 'domainCount', 'peers', 'lastUpdated') + fields = ('name', 'description', 'version', 'userCount', 'statusCount', 'domainCount', 'peers', 'lastUpdated', 'status') class EdgeSerializer(serializers.ModelSerializer): id = serializers.SerializerMethodField('get_pk') + size = serializers.SerializerMethodField('get_weight') class Meta: - model = PeerRelationship - fields = ('source', 'target', 'id') + model = Edge + fields = ('source', 'target', 'id', 'size') def get_pk(self, obj): return obj.pk + def get_weight(self, obj): + return obj.weight + class NodeSerializer(serializers.ModelSerializer): id = serializers.SerializerMethodField('get_name') @@ -68,7 +73,7 @@ class NodeSerializer(serializers.ModelSerializer): return obj.name def get_size(self, obj): - return obj.user_count or 1 + return math.log(obj.user_count) if obj.user_count else 1 def get_x(self, obj): return obj.x_coord diff --git a/frontend/src/components/Graph.jsx b/frontend/src/components/Graph.jsx index e2b0e3d..6bce589 100644 --- a/frontend/src/components/Graph.jsx +++ b/frontend/src/components/Graph.jsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { connect } from 'react-redux'; -import { RandomizeNodePositions, RelativeSize, Sigma, SigmaEnableWebGL, Filter } from 'react-sigma'; +import { Sigma, SigmaEnableWebGL, Filter, ForceAtlas2 } from 'react-sigma'; import { selectAndLoadInstance } from '../redux/actions'; @@ -19,6 +19,9 @@ const SETTINGS = { drawLabels: true, edgeColor: "default", labelColor: "default", + labelThreshold: 10, + maxEdgeSize: 1, + minEdgeSize: 0.3, } class GraphImpl extends React.Component { @@ -37,6 +40,7 @@ class GraphImpl extends React.Component { onClickStage={(e) => this.props.selectAndLoadInstance(null)} > + ) } diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index f58f42e..2b906a5 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -4,7 +4,9 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import * as sanitize from 'sanitize-html'; -import { Card, Classes, Divider, Elevation, HTMLTable, NonIdealState } from '@blueprintjs/core'; +import { + AnchorButton, Card, Classes, Divider, Elevation, HTMLTable, NonIdealState, Position, Tooltip +} from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { selectAndLoadInstance } from '../redux/actions'; @@ -30,11 +32,14 @@ class SidebarImpl extends React.Component { return this.renderLoadingState(); } else if (!this.props.instanceDetails) { return this.renderEmptyState(); + } else if (this.props.instanceDetails.status.toLowerCase().indexOf('personalinstance') > -1) { + return this.renderPersonalInstanceErrorState(); + } else if (this.props.instanceDetails.status !== 'success') { + return this.renderMissingDataState(); } return (
-

{this.props.instanceName || "No instance selected"}

- + {this.renderHeading()} {this.renderDescription()} {this.renderVersion()} {this.renderCounts()} @@ -43,6 +48,32 @@ class SidebarImpl extends React.Component { ); } + private renderHeading = () => { + let content: JSX.Element; + if (!this.props.instanceName) { + content = {"No instance selected"}; + } else { + content = ( + + {this.props.instanceName + ' '} + + + + + ); + } + return ( +
+

{content}

+ +
+ ); + } + private renderDescription = () => { const description = this.props.instanceDetails!.description; if (!description) { @@ -166,6 +197,30 @@ class SidebarImpl extends React.Component { ); } + private renderPersonalInstanceErrorState = () => { + return ( + + ) + } + + private renderMissingDataState = () => { + return ( + + ) + } + + private openInstanceLink = () => { + window.open("https://" + this.props.instanceName, "_blank"); + } + private selectInstance = (e: any)=> { this.props.selectAndLoadInstance(e.target.innerText); } diff --git a/frontend/src/redux/types.ts b/frontend/src/redux/types.ts index 03151f4..058eac0 100644 --- a/frontend/src/redux/types.ts +++ b/frontend/src/redux/types.ts @@ -27,6 +27,7 @@ export interface IInstanceDetails { userCount?: number; version?: string; lastUpdated?: string; + status: string; } interface IGraphNode { @@ -42,6 +43,7 @@ interface IGraphEdge { source: string; target: string; id?: string; + size?: number; } export interface IGraph { diff --git a/gephi/src/main/java/space/fediverse/graph/GraphBuilder.java b/gephi/src/main/java/space/fediverse/graph/GraphBuilder.java index 13d298f..79550e8 100644 --- a/gephi/src/main/java/space/fediverse/graph/GraphBuilder.java +++ b/gephi/src/main/java/space/fediverse/graph/GraphBuilder.java @@ -83,7 +83,7 @@ public class GraphBuilder { importController.process(container, new DefaultProcessor(), workspace); // Layout - AutoLayout autoLayout = new AutoLayout(1, TimeUnit.MINUTES); + AutoLayout autoLayout = new AutoLayout(2, TimeUnit.MINUTES); autoLayout.setGraphModel(graphModel); // YifanHuLayout firstLayout = new YifanHuLayout(null, new StepDisplacement(1f)); ForceAtlas2 forceAtlas2Layout = new ForceAtlas2(null); diff --git a/scraper/management/commands/scrape.py b/scraper/management/commands/scrape.py index 70c06fa..4f7b58a 100644 --- a/scraper/management/commands/scrape.py +++ b/scraper/management/commands/scrape.py @@ -31,7 +31,7 @@ from scraper.management.commands._util import require_lock, InvalidResponseExcep SEED = 'mastodon.social' TIMEOUT = 20 # seconds -NUM_THREADS = 64 +NUM_THREADS = 64 # roughly 40MB each PERSONAL_INSTANCE_THRESHOLD = 5 # instances with <= this many users won't be scraped STATUS_SCRAPE_LIMIT = 5000