Skip to content
Snippets Groups Projects
Commit 2f00565f authored by Eike Cochu's avatar Eike Cochu
Browse files

improved network graph

now adding links between already loaded nodes and new nodes
fixed bug where loading would create double links
improved style
added legend
parent 502a478e
Branches
No related tags found
No related merge requests found
<div class="graph" vis-graph="graph" vis-data="data" vis-options="options" vis-select="select" vis-dblclick="open"></div> <div class="graph"
\ No newline at end of file vis-graph
vis-type="article"
ng-model="article">
<div class="graph-legend">
<span style="color:#BBC9D2">Articles</span><br>
<span style="color:#DBB234">Topics</span><br>
</div>
</div>
\ No newline at end of file
...@@ -88,123 +88,6 @@ ...@@ -88,123 +88,6 @@
ArticleFactory.get({id: $stateParams.id}, function(response) { ArticleFactory.get({id: $stateParams.id}, function(response) {
$scope.article = response.data; $scope.article = response.data;
$scope.nodes = new vis.DataSet();
$scope.edges = new vis.DataSet();
$scope.data = {
nodes: $scope.nodes,
edges: $scope.edges
};
$scope.options = {
nodes: {
font: {
size: 11
},
scaling: {
label: true
},
shadow: true
},
layout: {
randomSeed: 1
},
physics: {
barnesHut: {
centralGravity: 0.1,
springLength: 200,
springConstant: 0.01,
avoidOverlap: 0.1
}
}
};
// initial network
var id = 0,
ids = {},
nodes = [],
edges = [],
topics = $scope.article.topics,
articleColor = '#BBC9D2',
topicColor = '#DBB234';
// root node
nodes.push({id: ++id, title: $scope.article.title, label: $scope.article.title.ellipsize(20), type: 'article', article: $scope.article.id, loaded: true, color: {background: articleColor}});
ids[$scope.article.id] = id;
// child nodes
for(var i = 0; i < topics.length; i++) {
var topic = topics[i].topic;
nodes.push({id: ++id, label: topic.name, type: 'topic', topic: topic.id, color: {background: topicColor}});
edges.push({from: 1, to: id});
ids[topic.id] = id;
}
$scope.nodes.add(nodes);
$scope.edges.add(edges);
// on node select
$scope.select = function(props) {
var node = $scope.nodes.get(props.nodes[0]);
if(node && !node.loaded) {
if(node.type === 'topic') {
// node is topic, load topic to get articles
TopicFactory.get({id:node.topic}, function(res) {
if(res.data && res.data.articles) {
var articles = res.data.articles;
var newNodes = [];
var newEdges = [];
for(var i = 0; i < articles.length; i++) {
var article = articles[i];
if(!ids.hasOwnProperty(article.id)) {
newNodes.push({id: ++id, title: article.title, label: article.title.ellipsize(20), type: 'article', article: article.id, color: {background: articleColor}});
newEdges.push({from:node.id, to:id});
ids[article.id] = id;
}
}
if(newNodes.length)
$scope.nodes.add(newNodes);
if(newEdges.length)
$scope.edges.add(newEdges);
}
});
} else if(node.type === 'article') {
// node is article, load article to get topics
ArticleFactory.get({id:node.article}, function(res) {
if(res.data && res.data.topics) {
var topics = res.data.topics;
var newNodes = [];
var newEdges = [];
for(var i = 0; i < topics.length; i++) {
var topic = topics[i].topic;
if(!ids.hasOwnProperty(topic.id)) {
newNodes.push({id: ++id, title: topic.name, label: topic.name.ellipsize(20), type: 'topic', topic: topic.id, color: {background: topicColor}});
newEdges.push({from:node.id, to:id});
ids[topic.id] = id;
}
}
if(newNodes.length)
$scope.nodes.add(newNodes);
if(newEdges.length)
$scope.edges.add(newEdges);
}
});
}
node.loaded = true;
$scope.nodes.update(node);
}
};
// on node open
$scope.open = function(props) {
var node = $scope.nodes.get(props.nodes[0]);
if(node) {
if(node.type === 'article') {
$state.transitionTo('articles.detail.show', {id:node.article});
} else if(node.type === 'topic') {
$state.transitionTo('topics.show', {id:node.topic});
}
}
};
}); });
}]); }]);
......
...@@ -56,23 +56,164 @@ ...@@ -56,23 +56,164 @@
}; };
}); });
app.directive('visGraph', function() { app.directive('visGraph', ['$state', 'ArticleFactory', 'TopicFactory',
function($state, ArticleFactory, TopicFactory) {
return { return {
scope: { scope: {
visGraph: '=', ngModel: '=',
visType: '@',
visData: '=', visData: '=',
visOptions: '=', visOptions: '='
visSelect: '&',
visDblclick: '&'
}, },
transclude: true,
template: '<ng-transclude/><div class="graph" id="visgraph"></div>',
link: function($scope, $element) { link: function($scope, $element) {
$scope.$watchGroup(['visData', 'visOptions'], function() { var id = 0,
$scope.visGraph = new vis.Network($element[0], $scope.visData, $scope.visOptions); ids = {},
$scope.visGraph.on('selectNode', $scope.visSelect() || function() {}); nodes = [],
$scope.visGraph.on('doubleClick', $scope.visDblclick() || function() {}); edges = [],
articleColor = '#BBC9D2',
topicColor = '#DBB234',
container = $element.find("#visgraph")[0];
$scope.articleColor = articleColor;
$scope.topicColor = topicColor;
$scope.nodes = new vis.DataSet();
$scope.edges = new vis.DataSet();
$scope.data = {
nodes: $scope.nodes,
edges: $scope.edges
};
$scope.options = {
nodes: {
font: { size: 11 },
shape: 'dot',
borderWidth: 0
},
layout: { randomSeed: 1 },
physics: {
barnesHut: {
springConstant: 0.005,
gravitationalConstant: -5000
}
}
};
var topicNode = function(topic) {
return {
id: ++id,
title: topic.name,
label: topic.name.ellipsize(20),
type: 'topic',
topic: topic.id,
color: {
background: topicColor,
highlight: { background: topicColor }
}
};
};
var articleNode = function(article) {
return {
id: ++id,
title: article.title,
label: article.title.ellipsize(20),
type: 'article',
article: article.id,
color: {
background: articleColor,
highlight: { background: articleColor }
}
};
};
// on node select
$scope.select = function(props) {
var node = $scope.nodes.get(props.nodes[0]);
if(node && !node.loaded) {
if(node.type === 'article') {
// node is article, load article to get topics
ArticleFactory.get({id:node.article}, function(res) {
if(res.data && res.data.topics) {
var topics = res.data.topics,
newNodes = [],
newEdges = [];
for(var i = 0; i < topics.length; i++) {
var topic = topics[i].topic;
if(ids.hasOwnProperty(topic.id)) {
if(!$scope.nodes.get(ids[topic.id]).loaded)
newEdges.push({from:node.id, to:ids[topic.id]});
} else {
newNodes.push(topicNode(topic));
newEdges.push({from:node.id, to:id});
ids[topic.id] = id;
}
}
if(newNodes.length) $scope.nodes.add(newNodes);
if(newEdges.length) $scope.edges.add(newEdges);
}
});
} else {
// node is topic, load topic to get articles
TopicFactory.get({id:node.topic}, function(res) {
if(res.data && res.data.articles) {
var articles = res.data.articles,
newNodes = [],
newEdges = [];
for(var i = 0; i < articles.length; i++) {
var article = articles[i];
if(ids.hasOwnProperty(article.id)) {
if(!$scope.nodes.get(ids[article.id]).loaded)
newEdges.push({from:ids[article.id], to:node.id});
} else {
newNodes.push(articleNode(article));
newEdges.push({from:id, to:node.id});
ids[article.id] = id;
}
}
if(newNodes.length) $scope.nodes.add(newNodes);
if(newEdges.length) $scope.edges.add(newEdges);
}
});
}
node.loaded = true;
$scope.nodes.update(node);
}
};
// on node open
$scope.open = function(props) {
var node = $scope.nodes.get(props.nodes[0]);
if(node.type === 'article')
$state.transitionTo('articles.detail.show', {id:node.article});
else
$state.transitionTo('topics.show', {id:node.topic});
};
// watch for changes to model
$scope.$watch('ngModel', function(newVal, oldVal) {
if(!newVal) return;
// root node
if($scope.visType === 'article')
nodes.push(articleNode($scope.ngModel));
else
nodes.push(topicNode($scope.ngModel));
ids[$scope.ngModel.id] = id;
// add nodes and edges
$scope.nodes.add(nodes);
$scope.edges.add(edges);
// create graph
$scope.visGraph = new vis.Network(container, $scope.data, $scope.options);
$scope.visGraph.on('selectNode', $scope.select);
$scope.visGraph.on('doubleClick', $scope.open);
}); });
} }
}; };
}); }]);
})(); })();
\ No newline at end of file
...@@ -82,7 +82,7 @@ body { ...@@ -82,7 +82,7 @@ body {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: rgba(0,0,0,0.2); background: rgba(0,0,0,0.1);
content: " "; content: " ";
z-index: 9999; z-index: 9999;
} }
...@@ -101,12 +101,22 @@ body { ...@@ -101,12 +101,22 @@ body {
.graph { .graph {
position: absolute; position: absolute;
top: 50px; top: 10px;
left: 0; left: 0;
right: 0; right: 0;
bottom: 50px; bottom: 50px;
} }
.graph-legend {
position: absolute;
top: 60px;
left: 10px;
font-weight: bold;
padding: 10px;
border: 1px solid #aaa;
border-radius: 3px;
}
.noselect { .noselect {
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-user-select: none; -webkit-user-select: none;
......
...@@ -50,13 +50,13 @@ public class Constants { ...@@ -50,13 +50,13 @@ public class Constants {
* Number of topics to discover with topic modeling, if the selected topic * Number of topics to discover with topic modeling, if the selected topic
* modeling library supports this parameter. * modeling library supports this parameter.
*/ */
public static final int K_TOPICS = 20; public static final int K_TOPICS = 50;
/** /**
* Number of words in a discovered topic, if the selected topic modeling * Number of words in a discovered topic, if the selected topic modeling
* library supports this parameter. * library supports this parameter.
*/ */
public static final int K_TOPIC_WORDS = 50; public static final int K_TOPIC_WORDS = 80;
/** /**
* Precision of likeliness numbers. Likeliness is calculated for words to * Precision of likeliness numbers. Likeliness is calculated for words to
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment