don't display quiet nodes on graph
This commit is contained in:
parent
8d00ba9372
commit
e9b8901aa5
|
@ -32,8 +32,6 @@ class NodeView(viewsets.ReadOnlyModelViewSet):
|
|||
"""
|
||||
Endpoint to get a list of the graph's nodes in a SigmaJS-friendly format.
|
||||
"""
|
||||
queryset = Instance.objects.filter(status='success')\
|
||||
.filter(x_coord__isnull=False)\
|
||||
.filter(y_coord__isnull=False)\
|
||||
.filter(user_count__isnull=False)
|
||||
queryset = Instance.objects.filter(status='success', x_coord__isnull=False, y_coord__isnull=False, user_count__isnull=False)\
|
||||
.exclude(sources__isnull=True, targets__isnull=True)
|
||||
serializer_class = NodeSerializer
|
||||
|
|
24
backend/scraper/migrations/0002_auto_20190419_1346.py
Normal file
24
backend/scraper/migrations/0002_auto_20190419_1346.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.1.7 on 2019-04-19 13:46
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('scraper', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='edge',
|
||||
name='source',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='targets', to='scraper.Instance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='edge',
|
||||
name='target',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sources', to='scraper.Instance'),
|
||||
),
|
||||
]
|
|
@ -51,8 +51,8 @@ class Edge(models.Model):
|
|||
It aggregates stats from the asymmetrical PeerRelationship to a symmetrical one that's suitable for serving
|
||||
to the front-end.
|
||||
"""
|
||||
source = models.ForeignKey(Instance, related_name='+', on_delete=models.CASCADE)
|
||||
target = models.ForeignKey(Instance, related_name='+', on_delete=models.CASCADE)
|
||||
source = models.ForeignKey(Instance, related_name='targets', on_delete=models.CASCADE)
|
||||
target = models.ForeignKey(Instance, related_name='sources', on_delete=models.CASCADE)
|
||||
weight = models.FloatField(blank=True, null=True)
|
||||
|
||||
# Metadata
|
||||
|
|
|
@ -1,102 +1,107 @@
|
|||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Sigma, Filter, ForceAtlas2 } from 'react-sigma';
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Sigma, Filter, ForceAtlas2 } from "react-sigma";
|
||||
|
||||
import { selectAndLoadInstance } from '../redux/actions';
|
||||
import { ErrorState } from './ErrorState';
|
||||
import { selectAndLoadInstance } from "../redux/actions";
|
||||
import { ErrorState } from "./ErrorState";
|
||||
|
||||
const STYLE = {
|
||||
bottom: "0",
|
||||
left: "0",
|
||||
position: "absolute",
|
||||
right: "0",
|
||||
top: "50px",
|
||||
}
|
||||
bottom: "0",
|
||||
left: "0",
|
||||
position: "absolute",
|
||||
right: "0",
|
||||
top: "50px"
|
||||
};
|
||||
const DEFAULT_NODE_COLOR = "#CED9E0";
|
||||
const SELECTED_NODE_COLOR = "#48AFF0";
|
||||
const SETTINGS = {
|
||||
defaultEdgeColor: "#5C7080",
|
||||
defaultLabelColor: "#F5F8FA",
|
||||
defaultNodeColor: DEFAULT_NODE_COLOR,
|
||||
drawEdges: true,
|
||||
drawLabels: true,
|
||||
edgeColor: "default",
|
||||
labelColor: "default",
|
||||
labelThreshold: 10,
|
||||
maxEdgeSize: 1,
|
||||
minEdgeSize: 0.3,
|
||||
}
|
||||
defaultEdgeColor: "#5C7080",
|
||||
defaultLabelColor: "#F5F8FA",
|
||||
defaultNodeColor: DEFAULT_NODE_COLOR,
|
||||
drawEdges: true,
|
||||
drawLabels: true,
|
||||
edgeColor: "default",
|
||||
labelColor: "default",
|
||||
labelThreshold: 10,
|
||||
maxEdgeSize: 1,
|
||||
minEdgeSize: 0.3
|
||||
};
|
||||
|
||||
class GraphImpl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.sigmaComponent = React.createRef();
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.sigmaComponent = React.createRef();
|
||||
render() {
|
||||
let graph = this.props.graph;
|
||||
if (!graph) {
|
||||
return <ErrorState />;
|
||||
}
|
||||
// Check that all nodes have size & coordinates; otherwise the graph will look messed up
|
||||
const lengthBeforeFilter = graph.nodes.length;
|
||||
graph = { ...graph, nodes: graph.nodes.filter(n => n.size && n.x && n.y) };
|
||||
if (graph.nodes.length !== lengthBeforeFilter) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(
|
||||
"Some nodes were missing details: " +
|
||||
this.props.graph.nodes.filter(n => !n.size || !n.x || !n.y).map(n => n.label)
|
||||
);
|
||||
return <ErrorState />;
|
||||
}
|
||||
return (
|
||||
<Sigma
|
||||
graph={graph}
|
||||
renderer="webgl"
|
||||
settings={SETTINGS}
|
||||
style={STYLE}
|
||||
onClickNode={this.onClickNode}
|
||||
onClickStage={this.onClickStage}
|
||||
ref={this.sigmaComponent}
|
||||
>
|
||||
<Filter neighborsOf={this.props.currentInstanceName} />
|
||||
<ForceAtlas2 iterationsPerRender={1} timeout={10000} />
|
||||
</Sigma>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let graph = this.props.graph;
|
||||
if (!graph) {
|
||||
return <ErrorState />;
|
||||
}
|
||||
// Check that all nodes have size & coordinates; otherwise the graph will look messed up
|
||||
const lengthBeforeFilter = graph.nodes.length;
|
||||
graph = {...graph, nodes: graph.nodes.filter(n => n.size && n.x && n.y)};
|
||||
if (graph.nodes.length !== lengthBeforeFilter) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error("Some nodes were missing details: " + this.props.graph.nodes.filter(n => !n.size || !n.x || !n.y).map(n => n.label));
|
||||
return <ErrorState />;
|
||||
}
|
||||
return (
|
||||
<Sigma
|
||||
graph={graph}
|
||||
renderer="webgl"
|
||||
settings={SETTINGS}
|
||||
style={STYLE}
|
||||
onClickNode={this.onClickNode}
|
||||
onClickStage={this.onClickStage}
|
||||
ref={this.sigmaComponent}
|
||||
>
|
||||
<Filter neighborsOf={this.props.currentInstanceName} />
|
||||
<ForceAtlas2 iterationsPerRender={1} timeout={10000}/>
|
||||
</Sigma>
|
||||
)
|
||||
componentDidUpdate() {
|
||||
const sigma = this.sigmaComponent && this.sigmaComponent.current.sigma;
|
||||
// Check if sigma exists s.t. nothing breaks if the graph didn't load (for whatever reason)
|
||||
if (sigma) {
|
||||
sigma.graph.nodes().map(this.colorNodes);
|
||||
sigma.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const sigma = this.sigmaComponent && this.sigmaComponent.current.sigma;
|
||||
// Check if sigma exists s.t. nothing breaks if the graph didn't load (for whatever reason)
|
||||
if (sigma) {
|
||||
sigma.graph.nodes().map(this.colorNodes);
|
||||
sigma.refresh();
|
||||
}
|
||||
}
|
||||
onClickNode = e => {
|
||||
this.props.selectAndLoadInstance(e.data.node.label);
|
||||
};
|
||||
|
||||
onClickNode = (e) => {
|
||||
this.props.selectAndLoadInstance(e.data.node.label);
|
||||
onClickStage = e => {
|
||||
// Deselect the instance (unless this was a drag event)
|
||||
if (!e.data.captor.isDragging) {
|
||||
this.props.selectAndLoadInstance(null);
|
||||
}
|
||||
};
|
||||
|
||||
onClickStage = (e) => {
|
||||
// Deselect the instance (unless this was a drag event)
|
||||
if (!e.data.captor.isDragging) {
|
||||
this.props.selectAndLoadInstance(null);
|
||||
}
|
||||
}
|
||||
|
||||
colorNodes = (n) => {
|
||||
if (this.props.currentInstanceName && n.id === this.props.currentInstanceName) {
|
||||
n.color = SELECTED_NODE_COLOR;
|
||||
} else {
|
||||
n.color = DEFAULT_NODE_COLOR;
|
||||
}
|
||||
colorNodes = n => {
|
||||
if (this.props.currentInstanceName && n.id === this.props.currentInstanceName) {
|
||||
n.color = SELECTED_NODE_COLOR;
|
||||
} else {
|
||||
n.color = DEFAULT_NODE_COLOR;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
currentInstanceName: state.currentInstance.currentInstanceName,
|
||||
graph: state.data.graph,
|
||||
})
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
selectAndLoadInstance: (instanceName) => dispatch(selectAndLoadInstance(instanceName)),
|
||||
})
|
||||
export const Graph = connect(mapStateToProps, mapDispatchToProps)(GraphImpl)
|
||||
const mapStateToProps = state => ({
|
||||
currentInstanceName: state.currentInstance.currentInstanceName,
|
||||
graph: state.data.graph
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
selectAndLoadInstance: instanceName => dispatch(selectAndLoadInstance(instanceName))
|
||||
});
|
||||
export const Graph = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(GraphImpl);
|
||||
|
|
|
@ -80,6 +80,12 @@ class SidebarImpl extends React.Component<ISidebarProps, ISidebarState> {
|
|||
return this.renderMissingDataState();
|
||||
} else if (this.props.instanceLoadError) {
|
||||
return <ErrorState />;
|
||||
} else if (
|
||||
this.props.graph &&
|
||||
this.props.instanceName &&
|
||||
this.props.graph.nodes.map(n => n.id).indexOf(this.props.instanceName) < 0
|
||||
) {
|
||||
return this.renderQuietInstanceState();
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
|
@ -299,6 +305,16 @@ class SidebarImpl extends React.Component<ISidebarProps, ISidebarState> {
|
|||
);
|
||||
};
|
||||
|
||||
private renderQuietInstanceState = () => {
|
||||
return (
|
||||
<NonIdealState
|
||||
icon={IconNames.CLEAN}
|
||||
title="No interactions"
|
||||
description="Users on this instance have not publicly interacted with any other instances recently. "
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
private openInstanceLink = () => {
|
||||
window.open("https://" + this.props.instanceName, "_blank");
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue