mirror of https://github.com/MISP/MISP
new: First round of updates to the correlation engine ready
- node deletion temporarily disabled until a bug is resolvedpull/2547/head
parent
b5b4652c61
commit
fa7d3fdb36
|
@ -3955,12 +3955,23 @@ class EventsController extends AppController {
|
|||
$event = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id));
|
||||
if (empty($event)) throw new MethodNotAllowedException('Invalid Event.');
|
||||
$this->set('event', $event[0]);
|
||||
$this->set('scope', 'event');
|
||||
$this->set('id', $id);
|
||||
}
|
||||
|
||||
/*
|
||||
public function deleteNode($id) {
|
||||
if (!$this->request->is('post')) throw new MethodNotAllowedException('Only POST requests are allowed.');
|
||||
App::uses('CorrelationGraphTool', 'Tools');
|
||||
$grapher = new CorrelationGraphTool();
|
||||
$grapher->construct($this->Event, $this->Taxonomy, $this->GalaxyCluster, $this->Auth->user(), $this->request->data);
|
||||
$json = $grapher->deleteNode($id);
|
||||
}
|
||||
*/
|
||||
|
||||
public function updateGraph($id, $type = 'event') {
|
||||
$validTools = array('event', 'galaxy', 'tag');
|
||||
if (!in_array($type, $validTools)) throw new NotAllowedException('Invalid type.');
|
||||
if (!in_array($type, $validTools)) throw new MethodNotAllowedException('Invalid type.');
|
||||
$this->loadModel('Taxonomy');
|
||||
$this->loadModel('GalaxyCluster');
|
||||
App::uses('CorrelationGraphTool', 'Tools');
|
||||
|
|
|
@ -140,4 +140,18 @@ class GalaxiesController extends AppController {
|
|||
$this->redirect($this->referer());
|
||||
}
|
||||
}
|
||||
|
||||
public function viewGraph($id) {
|
||||
$cluster = $this->Galaxy->GalaxyCluster->find('first', array(
|
||||
'conditions' => array('GalaxyCluster.id' => $id),
|
||||
'contain' => array('Galaxy'),
|
||||
'recursive' => -1
|
||||
));
|
||||
if (empty($cluster)) throw new MethodNotAllowedException('Invalid Galaxy.');
|
||||
$this->set('cluster', $cluster);
|
||||
$this->set('scope', 'galaxy');
|
||||
$this->set('id', $id);
|
||||
$this->set('galaxy_id' , $cluster['Galaxy']['id']);
|
||||
$this->render('/Events/view_graph');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,6 +113,8 @@ class GalaxyClustersController extends AppController {
|
|||
$cluster['GalaxyCluster']['tag_id'] = $tag['Tag']['id'];
|
||||
}
|
||||
}
|
||||
$this->set('id', $id);
|
||||
$this->set('galaxy_id' , $cluster['Galaxy']['id']);
|
||||
$this->set('cluster', $cluster);
|
||||
}
|
||||
|
||||
|
|
|
@ -749,4 +749,20 @@ class TagsController extends AppController {
|
|||
return $this->RestResponse->saveFailResponse('Tags', 'removeTagFromObject', false, 'Failed to remove tag from object.', $this->response->type());
|
||||
}
|
||||
}
|
||||
|
||||
public function viewGraph($id) {
|
||||
$tag = $this->Tag->find('first', array(
|
||||
'conditions' => array('Tag.id' => $id),
|
||||
'recursive' => -1
|
||||
));
|
||||
if (empty($tag)) throw new MethodNotAllowedException('Invalid Tag.');
|
||||
$this->loadModel('Taxonomy');
|
||||
$taxonomy = $this->Taxonomy->getTaxonomyForTag($tag['Tag']['name']);
|
||||
if (!empty($taxonomy)) {
|
||||
$this->set('taxonomy', $taxonomy);
|
||||
}
|
||||
$this->set('scope', 'tag');
|
||||
$this->set('id', $id);
|
||||
$this->render('/Events/view_graph');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@
|
|||
|
||||
public function buildGraphJson($id, $type = 'event', $action = 'create') {
|
||||
if ($action == 'delete') {
|
||||
|
||||
return $this->__json;
|
||||
}
|
||||
switch ($type) {
|
||||
|
@ -76,10 +75,8 @@
|
|||
}
|
||||
|
||||
private function __deleteObject($id) {
|
||||
unset($this->__json['nodes'][$id]);
|
||||
foreach ($this->__json['links'] as $k => $link) {
|
||||
debug($link);
|
||||
}
|
||||
$this->cleanLinks();
|
||||
return $this->__json;
|
||||
}
|
||||
|
||||
private function __handleObjects($objects, $anchor_id, $full = false) {
|
||||
|
@ -121,6 +118,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
private function __addTag($id) {
|
||||
$tag = $this->__eventModel->EventTag->Tag->find('first', array(
|
||||
'conditions' => array('Tag.id' => $id),
|
||||
'recursive' => -1
|
||||
));
|
||||
return $this->__createNode('tag', $tag['Tag']);
|
||||
}
|
||||
|
||||
private function __handleTags($tags, $anchor_id) {
|
||||
foreach ($tags as $tag) {
|
||||
if (strpos($tag['name'], 'misp-galaxy:') === 0) {
|
||||
|
@ -143,7 +148,9 @@
|
|||
|
||||
private function __expandTag($id) {
|
||||
$current_tag_id = $this->graphJsonContains('tag', array('id' => $id));
|
||||
if (empty($current_tag_id)) return false;
|
||||
if (empty($current_tag_id)) {
|
||||
$current_tag_id = $this->__addTag($id);
|
||||
}
|
||||
$this->cleanLinks();
|
||||
$events = $this->__eventModel->EventTag->Tag->fetchSimpleEventsForTag($id, $this->__user);
|
||||
foreach ($events as $event) {
|
||||
|
@ -161,13 +168,17 @@
|
|||
}
|
||||
|
||||
private function __expandGalaxy($id) {
|
||||
foreach ($this->__json['nodes'] as $k => $node) {
|
||||
if ($node['type'] == 'galaxy' && $node['id'] == $id) {
|
||||
$current_galaxy_id = $k;
|
||||
$tag_name = $node['tag_name'];
|
||||
if (!empty($this->__json['nodes'])) {
|
||||
foreach ($this->__json['nodes'] as $k => $node) {
|
||||
if ($node['type'] == 'galaxy' && $node['id'] == $id) {
|
||||
$current_galaxy_id = $k;
|
||||
$tag_name = $node['tag_name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($current_galaxy_id)) return false;
|
||||
if (empty($current_galaxy_id)) {
|
||||
$current_galaxy_id = $this->__addGalaxy($id);
|
||||
}
|
||||
$this->cleanLinks();
|
||||
$events = $this->__eventModel->EventTag->Tag->fetchSimpleEventsForTag($this->__json['nodes'][$current_galaxy_id]['tag_name'], $this->__user, true);
|
||||
foreach ($events as $event) {
|
||||
|
@ -177,6 +188,15 @@
|
|||
$this->_json['nodes'][$current_galaxy_id]['expanded'] = 1;
|
||||
}
|
||||
|
||||
private function __addGalaxy($id) {
|
||||
$temp = $this->__galaxyClusterModel->getCluster($id);
|
||||
// move stuff around to resemble the galaxies attached to events
|
||||
$galaxy = $temp['GalaxyCluster']['Galaxy'];
|
||||
unset($temp['GalaxyCluster']['Galaxy']);
|
||||
$galaxy['GalaxyCluster'][0] = $temp['GalaxyCluster'];
|
||||
return $this->__createNode('galaxy', $galaxy);
|
||||
}
|
||||
|
||||
private function __addLink($from_id, $to_id, $linkDistance = 150) {
|
||||
$link = $this->graphJsonContainsLink($from_id, $to_id);
|
||||
if ($link === false) $this->__json['links'][] = array('source' => $from_id, 'target' => $to_id, 'linkDistance' => $linkDistance);
|
||||
|
@ -304,8 +324,8 @@
|
|||
foreach ($this->__json['nodes'] as $k => $node) {
|
||||
if ($link['source'] == $node) $temp['source'] = $k;
|
||||
if ($link['target'] == $node) $temp['target'] = $k;
|
||||
$temp['linkDistance'] = $link['linkDistance'];
|
||||
}
|
||||
$temp['linkDistance'] = $link['linkDistance'];
|
||||
$links[] = $temp;
|
||||
}
|
||||
$this->__json['links'] = $links;
|
||||
|
@ -333,7 +353,7 @@
|
|||
if ($type == 'event' && $node['type'] == 'event' && $node['id'] == $element['id']) {
|
||||
return $k;
|
||||
}
|
||||
if ($type == 'attribute' && $node['type'] == 'attribute' && $node['id'] == $element['id']) {
|
||||
if ($type == 'attribute' && $node['type'] == 'attribute' && $node['name'] == $element['value']) {
|
||||
return $k;
|
||||
}
|
||||
if ($type == 'tag' && $node['type'] == 'tag' && $node['id'] == $element['id']) {
|
||||
|
|
|
@ -49,7 +49,6 @@ class Galaxy extends AppModel{
|
|||
}
|
||||
foreach ($galaxies as $k => $galaxy) {
|
||||
if (isset($existingGalaxies[$galaxy['uuid']])) {
|
||||
//debug($galaxy);
|
||||
if (
|
||||
$existingGalaxies[$galaxy['uuid']]['version'] < $galaxy['version'] ||
|
||||
(!empty($galaxy['icon']) && ($existingGalaxies[$galaxy['uuid']]['icon'] != $galaxy['icon']))
|
||||
|
@ -62,7 +61,6 @@ class Galaxy extends AppModel{
|
|||
$this->save($galaxy);
|
||||
}
|
||||
}
|
||||
//throw new Exception();
|
||||
return $this->find('list', array('recursive' => -1, 'fields' => array('type', 'id')));
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ class GalaxyCluster extends AppModel{
|
|||
* - maybe in the future remove the galaxy itself once we have logos with each galaxy
|
||||
*/
|
||||
public function getCluster($name) {
|
||||
$conditions = array('GalaxyCluster.tag_name' => $name);
|
||||
$conditions = array('GalaxyCluster.tag_name ' => $name);
|
||||
if (is_numeric($name)) {
|
||||
$conditions = array('GalaxyCluster.id' => $name);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<li class="divider"></li>
|
||||
<?php endif;?>
|
||||
<li id='liviewEvent'><a href="<?php echo $baseurl;?>/events/view/<?php echo h($event['Event']['id']);?>">View Event</a></li>
|
||||
<li id='liviewEventGraph'><a href="<?php echo $baseurl;?>/events/viewGraph/<?php echo h($event['Event']['id']);?>">View Correlation Graph</a></li>
|
||||
<li id='liviewGraph'><a href="<?php echo $baseurl;?>/events/viewGraph/<?php echo h($event['Event']['id']);?>">View Correlation Graph</a></li>
|
||||
<li id='lieventLog'><a href="<?php echo $baseurl;?>/logs/event_index/<?php echo h($event['Event']['id']);?>">View Event History</a></li>
|
||||
<li class="divider"></li>
|
||||
<?php if ($isSiteAdmin || (isset($mayModify) && $mayModify)): ?>
|
||||
|
@ -273,14 +273,26 @@
|
|||
case 'tags': ?>
|
||||
<li id='liindexfav'><?php echo $this->Html->link('List Favourite Tags', array('action' => 'index', true));?></li>
|
||||
<li id='liindex'><?php echo $this->Html->link('List Tags', array('action' => 'index'));?></li>
|
||||
<?php if ($isAclTagEditor): ?>
|
||||
<li id='liadd'><?php echo $this->Html->link('Add Tag', array('action' => 'add'));?></li>
|
||||
<?php
|
||||
<?php
|
||||
if ($isAclTagEditor):
|
||||
?>
|
||||
<li id='liadd'><?php echo $this->Html->link('Add Tag', array('action' => 'add'));?></li>
|
||||
<?php
|
||||
endif;
|
||||
if ($menuItem === 'edit'):
|
||||
?>
|
||||
<li class="active"><?php echo $this->Html->link('Edit Tag', array('action' => 'edit'));?></li>
|
||||
<?php
|
||||
?>
|
||||
<li class="active"><?php echo $this->Html->link('Edit Tag', array('action' => 'edit'));?></li>
|
||||
<?php
|
||||
endif;
|
||||
if ($menuItem === 'viewGraph'):
|
||||
if (!empty($taxonomy)):
|
||||
?>
|
||||
<li><a href="<?php echo $baseurl; ?>/taxonomies/view/<?php echo h($taxonomy['Taxonomy']['id']); ?>">View Taxonomy</a></li>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
<li id='liviewGraph'><a href="<?php echo $baseurl;?>/tags/viewGraph/<?php echo h($id); ?>">View Correlation Graph</a></li>
|
||||
<?php
|
||||
endif;
|
||||
break;
|
||||
|
||||
|
@ -350,14 +362,16 @@
|
|||
<li><?php echo $this->Form->postLink('Update Galaxies', array('controller' => 'galaxies', 'action' => 'update'), null, __('Are you sure you want to reimport all galaxies from the submodule?')); ?></li>
|
||||
<?php
|
||||
endif;
|
||||
if ($menuItem === 'viewGraph' || $menuItem === 'view_cluster'): ?>
|
||||
<li><a href="<?php echo $baseurl;?>/galaxies/view/<?php echo h($galaxy_id); ?>">View Galaxy</a></li>
|
||||
<li id='liview_cluster'><a href="<?php echo $baseurl;?>/galaxy_clusters/view/<?php echo h($id); ?>">View Cluster</a></li>
|
||||
<li id='liviewGraph'><a href="<?php echo $baseurl;?>/galaxies/viewGraph/<?php echo h($id); ?>">View Correlation Graph</a></li>
|
||||
<?php
|
||||
endif;
|
||||
|
||||
if ($menuItem === 'view'):
|
||||
?>
|
||||
<li class="active"><a href="#">View Galaxy</a></li>
|
||||
<?php
|
||||
endif;
|
||||
if ($menuItem === 'view_cluster'):
|
||||
?>
|
||||
<li class="active"><a href="#">View Cluster</a></li>
|
||||
<?php
|
||||
endif;
|
||||
break;
|
||||
|
|
|
@ -1,91 +1,13 @@
|
|||
<?php
|
||||
$mayModify = (($isAclModify && $event['Event']['user_id'] == $me['id'] && $event['Orgc']['id'] == $me['org_id']) || ($isAclModifyOrg && $event['Orgc']['id'] == $me['org_id']));
|
||||
$mayPublish = ($isAclPublish && $event['Orgc']['id'] == $me['org_id']);
|
||||
echo $this->Html->css('font-awesome');
|
||||
echo $this->Html->script('d3'); ?>
|
||||
<style>
|
||||
|
||||
.node circle {
|
||||
cursor: pointer;
|
||||
stroke: #3182bd;
|
||||
stroke-width: 1.5px;
|
||||
if ($scope == 'event') {
|
||||
$mayModify = (($isAclModify && $event['Event']['user_id'] == $me['id'] && $event['Orgc']['id'] == $me['org_id']) || ($isAclModifyOrg && $event['Orgc']['id'] == $me['org_id']));
|
||||
$mayPublish = ($isAclPublish && $event['Orgc']['id'] == $me['org_id']);
|
||||
}
|
||||
.node text {
|
||||
font: 10px sans-serif;
|
||||
pointer-events: none;
|
||||
text-anchor: middle;
|
||||
}
|
||||
line.link {
|
||||
fill: none;
|
||||
stroke: #9ecae1;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
#main {
|
||||
display: inline-block;
|
||||
background-color: grey;
|
||||
position: relative;
|
||||
}
|
||||
.menu {
|
||||
border: 1px solid black;
|
||||
background-color: grey;
|
||||
display: none;
|
||||
position: relative;
|
||||
}
|
||||
.menu,
|
||||
.menu li {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
width: 250px;
|
||||
}
|
||||
.menu li {
|
||||
color: white;
|
||||
}
|
||||
.menu > li:hover {
|
||||
background-color: lightblue;
|
||||
color: black;
|
||||
}
|
||||
.menu li {
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.menu li:hover ul {
|
||||
display:inline-block;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
.menu > li > span > a {
|
||||
color:white;
|
||||
font-weight:bold;
|
||||
}
|
||||
.menu > li > span:first-child {
|
||||
font-weight:bold;
|
||||
}
|
||||
.graphMenuTitle {
|
||||
background-color:#0088cc;
|
||||
font-weight:bold;
|
||||
color:white;
|
||||
}
|
||||
.graphMenuActions {
|
||||
background-color:#0088cc;
|
||||
color:white;
|
||||
}
|
||||
.graphMenuAction {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
position:absolute;
|
||||
width:300px;
|
||||
}
|
||||
|
||||
.font-white {
|
||||
color:white;
|
||||
}
|
||||
.corrected-icon {
|
||||
top:-5px;
|
||||
margin-left:100px;
|
||||
}
|
||||
</style>
|
||||
echo $this->Html->css('font-awesome');
|
||||
echo $this->Html->css('correlation-graph');
|
||||
echo $this->Html->script('d3');
|
||||
echo $this->Html->script('correlation-graph');
|
||||
?>
|
||||
<div class="view">
|
||||
<div id="chart" style="width:100%;height:100%"></div>
|
||||
<div id="hover-menu-container" class="menu-container">
|
||||
|
@ -103,466 +25,26 @@ echo $this->Html->script('d3'); ?>
|
|||
<li id="context-delete">Delete</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="graph_init" class="hidden" data-id="<?php echo h($id);?>" data-scope="<?php echo h($scope);?>">
|
||||
</div>
|
||||
<?php
|
||||
echo $this->element('side_menu', array('menuList' => 'event', 'menuItem' => 'viewEventGraph', 'mayModify' => $mayModify, 'mayPublish' => $mayPublish));
|
||||
$scope_list = array(
|
||||
'event' => 'event',
|
||||
'galaxy' => 'galaxies',
|
||||
'tag' => 'tags'
|
||||
);
|
||||
$params = array(
|
||||
'menuList' => $scope_list[$scope],
|
||||
'menuItem' => 'viewGraph'
|
||||
);
|
||||
if ($scope == 'event') {
|
||||
$params['mayModify'] = $mayModify;
|
||||
$params['mayPublish'] = $mayPublish;
|
||||
}
|
||||
if ($scope == 'tag') {
|
||||
if (!empty($taxoomy)) {
|
||||
$params['taxonomy'] = $taxonomy['Taxonomy']['id'];
|
||||
}
|
||||
}
|
||||
echo $this->element('side_menu', $params);
|
||||
?>
|
||||
<script>
|
||||
var currentMousePos = { x: -1, y: -1 };
|
||||
$(document).mousemove(function(event) {
|
||||
currentMousePos.x = event.pageX;
|
||||
currentMousePos.y = event.pageY;
|
||||
});
|
||||
|
||||
var margin = {top: -5, right: -5, bottom: -5, left: -5},
|
||||
width = $(window).width() - margin.left - margin.right,
|
||||
height = $(window).height() - 160 - margin.top - margin.bottom;
|
||||
var menu_x_buffer_ = width - 150;
|
||||
var menu_y_buffer = height - 100;
|
||||
$('.menu-container').css('left', '200px');
|
||||
$('#hover-menu-container').css('top', '50px');
|
||||
$('#hover-menu-container').css('z-index', 1);
|
||||
$('#selected-menu-container').css('top', '400px');
|
||||
$('#selected-menu-container').css('z-index', 2);
|
||||
|
||||
var root;
|
||||
|
||||
var highlighted;
|
||||
var hovered;
|
||||
|
||||
var icon_sizes = {
|
||||
"event": 24,
|
||||
"object": 12,
|
||||
"attribute": 12,
|
||||
"galaxy": 32,
|
||||
"tag": 24
|
||||
}
|
||||
|
||||
var selection_radius_sizes = {
|
||||
"event": 18,
|
||||
"object": 12,
|
||||
"attribute": 12,
|
||||
"galaxy": 18,
|
||||
"tag": 18
|
||||
}
|
||||
|
||||
var force = d3.layout.force()
|
||||
.linkDistance(function (d) {
|
||||
return d.linkDistance;
|
||||
})
|
||||
.linkStrength(0.9)
|
||||
.friction(0.5)
|
||||
.theta(0.9)
|
||||
.charge(-500)
|
||||
.gravity(0.21)
|
||||
.size([width, height])
|
||||
.on("tick", tick);
|
||||
|
||||
var vis = d3.select("#chart");
|
||||
|
||||
var svg = vis.append("svg:svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("pointer-events", "all");
|
||||
|
||||
var rect = svg.append("svg:rect")
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('fill', 'white')
|
||||
.call(d3.behavior.zoom().on("zoom", zoomhandler));
|
||||
|
||||
var plotting_area = svg.append("g")
|
||||
.attr("class", "plotting-area");
|
||||
|
||||
var drag1 = d3.behavior.drag()
|
||||
.on("dragstart", dragstart)
|
||||
.on("drag", dragmove)
|
||||
.on("dragend", dragend);
|
||||
|
||||
var link = plotting_area.selectAll(".link");
|
||||
var node = plotting_area.selectAll(".node");
|
||||
|
||||
d3.json("/events/updateGraph/<?php echo $id; ?>.json", function(error, json) {
|
||||
root = json;
|
||||
update();
|
||||
});
|
||||
|
||||
var graphElementScale = 1;
|
||||
var graphElementTranslate = [0, 0];
|
||||
|
||||
function zoomhandler() {
|
||||
plotting_area.attr("transform",
|
||||
"translate(" + d3.event.translate + ")"
|
||||
+ " scale(" + d3.event.scale + ")");
|
||||
graphElementScale = d3.event.scale;
|
||||
graphElementTranslate = d3.event.translate;
|
||||
}
|
||||
|
||||
function update() {
|
||||
var nodes = root['nodes'], links = root['links'];
|
||||
|
||||
|
||||
// Restart the force layout.
|
||||
force.nodes(nodes).links(links).start();
|
||||
|
||||
// Update links.
|
||||
link = link.data(links);
|
||||
link.exit().remove();
|
||||
link.enter().insert("line", ".node").attr("class", "link");
|
||||
// Update nodes.
|
||||
node = node.data(nodes);
|
||||
node.exit().remove();
|
||||
|
||||
var nodeEnter = node.enter().append("g").attr("class", "node").call(drag1);
|
||||
|
||||
nodeEnter.attr('id', function(d) { return 'id-' + d.unique_id; })
|
||||
|
||||
nodeEnter.insert("circle", ".circle")
|
||||
.classed("highlighted_circle", true)
|
||||
.attr("cx", function(d) { return d.x_axis; })
|
||||
.attr("cy", function(d) { return d.y_axis; })
|
||||
.attr("r", function(d) { return selection_radius_sizes[d.type] })
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-opacity", "0")
|
||||
.attr("fill-opacity", "0")
|
||||
.attr("fill", "red");
|
||||
|
||||
nodeEnter.filter(function(d) {return d.image !== undefined})
|
||||
.append("svg:image")
|
||||
.attr("class", "circle")
|
||||
.attr("xlink:href", function(d) {
|
||||
return d.image
|
||||
})
|
||||
.attr("x", function(d) {
|
||||
return (0 - (icon_sizes[d.type]/2)) + "px";
|
||||
})
|
||||
.attr("y", function(d) {
|
||||
return (0 - (icon_sizes[d.type]/2)) + "px";
|
||||
})
|
||||
.attr("width", function(d) {
|
||||
return ((icon_sizes[d.type])) + "px";
|
||||
})
|
||||
.attr("height", function(d) {
|
||||
return ((icon_sizes[d.type])) + "px";
|
||||
});
|
||||
|
||||
nodeEnter.filter(function(d) {return d.imgClass !== undefined})
|
||||
.append("g")
|
||||
.append('svg:foreignObject')
|
||||
.attr("width", 12)
|
||||
.attr("height", 12)
|
||||
.attr("x", function(d) {
|
||||
if (d.type == 'galaxy' || d.type == 'tag') {
|
||||
return '-10px';
|
||||
} else {
|
||||
return '-6px';
|
||||
}
|
||||
}
|
||||
)
|
||||
.attr("y", function(d) {
|
||||
if (d.type == 'galaxy' || d.type == 'tag') {
|
||||
return '-12px';
|
||||
} else {
|
||||
return '-8px';
|
||||
}
|
||||
}
|
||||
)
|
||||
.append("xhtml:body")
|
||||
.html(function (d) {
|
||||
var result = 'fa-' + d.imgClass;
|
||||
if (d.type == 'galaxy' || d.type == 'tag') result = 'fa-2x ' + result;
|
||||
return '<i class="fa ' + result + '"></i>';
|
||||
});
|
||||
|
||||
nodeEnter.append("text")
|
||||
.attr("dy", function(d) {
|
||||
if (d.type == "event" || d.type == "galaxy") {
|
||||
return "10px";
|
||||
} else {
|
||||
return "0px";
|
||||
}
|
||||
})
|
||||
.attr("fill", function(d) {
|
||||
if (d.type == "event") {
|
||||
if (d.expanded == 1) {
|
||||
return "#0000ff";
|
||||
} else {
|
||||
return "#ff0000";
|
||||
}
|
||||
}
|
||||
})
|
||||
.text(function(d) {
|
||||
return d.type + ': ' + d.name;
|
||||
});
|
||||
|
||||
node.selectAll("text")
|
||||
.attr("y", 20);
|
||||
|
||||
node.on('mouseover', function(d) {
|
||||
link.style('stroke', function(l) {
|
||||
if (d === l.source || d === l.target)
|
||||
return "#ff0000";
|
||||
else
|
||||
return "#9ecae1";
|
||||
});
|
||||
link.style('stroke-width', function(l) {
|
||||
if (d === l.source || d === l.target)
|
||||
return 2;
|
||||
else
|
||||
return 1;
|
||||
});
|
||||
showPane(d, 'hover');
|
||||
});
|
||||
|
||||
node.on('mouseout', function() {
|
||||
link.style('stroke-width', 1);
|
||||
link.style('stroke', "#9ecae1");
|
||||
});
|
||||
|
||||
node.on("click", function(d) {
|
||||
showPane(d, 'selected');
|
||||
});
|
||||
}
|
||||
|
||||
function highlightNode(d) {
|
||||
d3.selectAll('.highlighted_circle')
|
||||
.style("stroke-opacity", 0);
|
||||
d3.select('#id-' + d.unique_id)
|
||||
.select('.highlighted_circle')
|
||||
.style("stroke", "red")
|
||||
.style("stroke-opacity", 0.5);
|
||||
}
|
||||
|
||||
function contextMenu(d, newContext) {
|
||||
d3.event.preventDefault();
|
||||
if (d.type == 'event') showPane(d, 'context');
|
||||
}
|
||||
|
||||
function bindExpand(d, type) {
|
||||
if (!d.expanded) {
|
||||
var expandName = 'Expand (ctrl+x)';
|
||||
if (type == 'selected') {
|
||||
expandName = 'Expand (x)';
|
||||
}
|
||||
$("#" + type + "-menu").append('<li id="expand_' + type + '_' + d.id +'" class="graphMenuAction"><span>' + expandName + '</span></li>');
|
||||
d3.select('#expand_' + type + '_' + d.id)
|
||||
.on('click', function() {
|
||||
expand(d);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function bindDelete(d, type) {
|
||||
var deleteName = 'Delete (ctrl+d)';
|
||||
if (type == 'selected') {
|
||||
deleteName = 'Delete (d)';
|
||||
}
|
||||
$("#" + type + "-menu").append('<li id="remove_' + type + '_' + d.id +'" class="graphMenuAction"><span>' + deleteName + '</span></li>');
|
||||
d3.select('#remove_' + type + '_' + d.id)
|
||||
.on('click', function() {
|
||||
remove(d);
|
||||
});
|
||||
}
|
||||
|
||||
function createInfoPane(d, data, type) {
|
||||
var i = 0;
|
||||
var view_urls = {
|
||||
'event': '/events/view/' + parseInt(d.id),
|
||||
'tag': '/tags/view/' + parseInt(d.id),
|
||||
'galaxy': '/galaxy_clusters/view/' + parseInt(d.id)
|
||||
};
|
||||
data["fields"].forEach(function(e) {
|
||||
var title = e;
|
||||
if (i == 0) title = d.type;
|
||||
title = title.split("_").join(" ");
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
var span1 = $('<span />').text(title + ': ');
|
||||
var span2 = $('<span />').text(d[e]);
|
||||
var li = $('<li />');
|
||||
li.append(span1);
|
||||
li.append(span2);
|
||||
if (i == 0) li.addClass('graphMenuTitle');
|
||||
i++;
|
||||
$("#" + type + "-menu").append(li);
|
||||
});
|
||||
$("#" + type + "-menu").append('<li class="graphMenuActions">Actions</li>');
|
||||
if ($.inArray("navigate", data["actions"]) !== -1) {
|
||||
console.log($.inArray("navigate", data["actions"]));
|
||||
$("#" + type + "-menu").append('<li><span><a href="' + view_urls[d.type] + '">Go to ' + d.type + '</a></span></li>');
|
||||
}
|
||||
if ($.inArray("expand", data["actions"]) !== -1) {
|
||||
bindExpand(d, type);
|
||||
}
|
||||
if ($.inArray("delete", data["actions"]) !== -1) {
|
||||
bindDelete(d, type);
|
||||
}
|
||||
}
|
||||
|
||||
function showPane(d, type) {
|
||||
if (type == 'hover') {
|
||||
hovered = d;
|
||||
} else {
|
||||
highlighted = d;
|
||||
highlightNode(d);
|
||||
}
|
||||
$('#' + type + '-header').show();
|
||||
d3.select("#" + type + "-menu").style('display', 'inline-block');
|
||||
$("#" + type + "-menu").empty();
|
||||
if (d.type== 'attribute') {
|
||||
var data = {
|
||||
"fields": ["id", "name", "category", "type", "comment"],
|
||||
"actions": ["delete"]
|
||||
}
|
||||
}
|
||||
if (d.type== 'event') {
|
||||
var tempid = parseInt(d.id);
|
||||
var data = {
|
||||
"fields": ["id", "info", "date", "analysis", "org"],
|
||||
"actions": ["expand", "delete", "navigate"]
|
||||
}
|
||||
}
|
||||
if (d.type == 'tag') {
|
||||
var data = {
|
||||
"fields": ["id", "name"],
|
||||
"actions": ["expand", "delete"]
|
||||
}
|
||||
if (d.taxonomy !== undefined) {
|
||||
data["fields"].push("taxonomy");
|
||||
data["fields"].push("taxonomy_description");
|
||||
if (d.description !== "") {
|
||||
data["fields"].push("Description");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d.type == 'galaxy') {
|
||||
var data = {
|
||||
"fields": ["id", "name", "galaxy", "synonyms", "authors", "description", "source"],
|
||||
"actions": ["expand", "delete", "navigate"]
|
||||
}
|
||||
}
|
||||
if (d.type == 'object') {
|
||||
var data = {
|
||||
"fields": ["id", "name", "metacategory", "description", "comment"],
|
||||
"actions": ["delete"]
|
||||
}
|
||||
}
|
||||
createInfoPane(d, data, type);
|
||||
}
|
||||
|
||||
function expand(d) {
|
||||
if (d.type == 'event' || d.type == 'galaxy' || d.type == 'tag') {
|
||||
d3.xhr("/events/updateGraph/" + d.id + "/" + d.type + ".json")
|
||||
.header("Content-Type", "application/json")
|
||||
.post(
|
||||
JSON.stringify(root),
|
||||
function(err, rawData){
|
||||
root = JSON.parse(rawData.response);
|
||||
update();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function tick() {
|
||||
link.attr("x1", function(d) { return d.source.x; })
|
||||
.attr("y1", function(d) { return d.source.y; })
|
||||
.attr("x2", function(d) { return d.target.x; })
|
||||
.attr("y2", function(d) { return d.target.y; });
|
||||
|
||||
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
||||
}
|
||||
|
||||
// Returns a list of all nodes under the root.
|
||||
function flatten(root) {
|
||||
var nodes = [], i = 0;
|
||||
|
||||
function recurse(node) {
|
||||
if (node.children) node.children.forEach(recurse);
|
||||
if (!node.id) node.id = ++i;
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
recurse(root);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
function dragstart(d, i) {
|
||||
force.stop();
|
||||
}
|
||||
|
||||
function dragmove(d, i) {
|
||||
d.px += d3.event.dx;
|
||||
d.py += d3.event.dy;
|
||||
d.x += d3.event.dx;
|
||||
d.y += d3.event.dy;
|
||||
tick();
|
||||
}
|
||||
|
||||
function dragend(d, i) {
|
||||
d.fixed = true;
|
||||
tick();
|
||||
force.resume();
|
||||
}
|
||||
|
||||
function searchArray(arr, val) {
|
||||
for (var i=0; i < arr.length; i++)
|
||||
if (arr[i] === val)
|
||||
return i;
|
||||
return false;
|
||||
}
|
||||
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.which == 69) {
|
||||
if (highlighted == undefined) {
|
||||
showPane(root['nodes'][0], 'selected');
|
||||
} else {
|
||||
var current = searchArray(root['nodes'], highlighted);
|
||||
if (current == root['nodes'].length-1) {
|
||||
showPane(root['nodes'][0], 'selected');
|
||||
} else {
|
||||
showPane(root['nodes'][current+1], 'selected');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.which == 81) {
|
||||
if (highlighted == undefined) {
|
||||
showPane(root['nodes'][root['nodes'].length-1], 'selected');
|
||||
} else {
|
||||
var current = searchArray(root['nodes'], highlighted);
|
||||
if (current == 0) {
|
||||
showPane(root['nodes'][root['nodes'].length-1], 'selected');
|
||||
} else {
|
||||
showPane(root['nodes'][current-1], 'selected');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.which == 68) {
|
||||
e.preventDefault();
|
||||
if (e.ctrlKey) {
|
||||
if (hovered != undefined) {
|
||||
remove(hovered);
|
||||
}
|
||||
} else {
|
||||
if (highlighted != undefined) {
|
||||
remove(highlighted);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.which == 88) {
|
||||
e.preventDefault();
|
||||
if (e.ctrlKey) {
|
||||
if (hovered != undefined) {
|
||||
expand(hovered);
|
||||
}
|
||||
} else {
|
||||
if (highlighted != undefined) {
|
||||
expand(highlighted);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
<div class="galaxy view">
|
||||
<div class="row-fluid">
|
||||
<div class="span8">
|
||||
<h2><?php echo h($galaxy['Galaxy']['name']); ?> galaxy</h2>
|
||||
<h2>
|
||||
<span class="fa fa-<?php echo h($galaxy['Galaxy']['icon']); ?>"></span>
|
||||
<?php echo h($galaxy['Galaxy']['name']); ?> galaxy
|
||||
</h2>
|
||||
<dl>
|
||||
<dt>Galaxy ID</dt>
|
||||
<dd><?php echo h($galaxy['Galaxy']['id']); ?></dd>
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
</td>
|
||||
<td><?php echo h($item['GalaxyCluster']['description']); ?> </td>
|
||||
<td class="short action-links">
|
||||
<?php echo $this->Html->link('', array('controller' => 'galaxies', 'action' => 'viewGraph', $item['GalaxyCluster']['id']), array('class' => 'fa fa-line-chart', 'title' => 'View graph'));?>
|
||||
<?php echo $this->Html->link('', array('action' => 'view', $item['GalaxyCluster']['id']), array('class' => 'icon-list-alt', 'title' => 'View'));?>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -104,6 +104,7 @@ foreach ($list as $k => $item): ?>
|
|||
</td>
|
||||
<?php if ($isSiteAdmin): ?>
|
||||
<td class="short action-links">
|
||||
<?php echo $this->Html->link('', array('controller' => 'tags', 'action' => 'viewGraph', $item['Tag']['id']), array('class' => 'fa fa-line-chart', 'title' => 'View graph'));?>
|
||||
<?php echo $this->Html->link('', array('action' => 'edit', $item['Tag']['id']), array('class' => 'icon-edit', 'title' => 'Edit'));?>
|
||||
<?php echo $this->Form->postLink('', array('action' => 'delete', $item['Tag']['id']), array('class' => 'icon-trash', 'title' => 'Delete'), __('Are you sure you want to delete "%s"?', $item['Tag']['name']));?>
|
||||
</td>
|
||||
|
|
|
@ -122,35 +122,36 @@
|
|||
<a href="<?php echo $url;?>" class="<?php echo $isAclTagger ? 'tagFirstHalf' : 'tag' ?>" style="background-color:<?php echo h($item['existing_tag']['Tag']['colour']);?>;color:<?php echo $this->TextColour->getTextColour($item['existing_tag']['Tag']['colour']);?>"><?php echo h($item['existing_tag']['Tag']['name']); ?></a>
|
||||
<?php
|
||||
endif;
|
||||
echo ' ' . $this->Html->link('', array('controller' => 'tags', 'action' => 'viewGraph', $item['existing_tag']['Tag']['id']), array('class' => 'fa fa-line-chart black', 'title' => 'View graph'));
|
||||
endif;
|
||||
?>
|
||||
</td>
|
||||
<td class="action">
|
||||
<?php
|
||||
if ($isAclTagger && $taxonomy['enabled']) {
|
||||
echo $this->Form->create('Tag', array('id' => 'quick_' . h($k), 'url' => '/taxonomies/addTag/', 'style' => 'margin:0px;'));
|
||||
echo $this->Form->input('name', array('type' => 'hidden', 'value' => $item['tag']));
|
||||
echo $this->Form->input('taxonomy_id', array('type' => 'hidden', 'value' => $taxonomy['id']));
|
||||
echo $this->Form->end();
|
||||
if ($item['existing_tag'] && !$item['existing_tag']['Tag']['hide_tag']):
|
||||
echo $this->Form->create('Tag', array('id' => 'quick_disable_' . h($k), 'url' => '/taxonomies/disableTag/', 'style' => 'margin:0px;'));
|
||||
<?php
|
||||
if ($isAclTagger && $taxonomy['enabled']) {
|
||||
echo $this->Form->create('Tag', array('id' => 'quick_' . h($k), 'url' => '/taxonomies/addTag/', 'style' => 'margin:0px;'));
|
||||
echo $this->Form->input('name', array('type' => 'hidden', 'value' => $item['tag']));
|
||||
echo $this->Form->input('taxonomy_id', array('type' => 'hidden', 'value' => $taxonomy['id']));
|
||||
echo $this->Form->end();
|
||||
if ($item['existing_tag'] && !$item['existing_tag']['Tag']['hide_tag']):
|
||||
echo $this->Form->create('Tag', array('id' => 'quick_disable_' . h($k), 'url' => '/taxonomies/disableTag/', 'style' => 'margin:0px;'));
|
||||
echo $this->Form->input('name', array('type' => 'hidden', 'value' => $item['tag']));
|
||||
echo $this->Form->input('taxonomy_id', array('type' => 'hidden', 'value' => $taxonomy['id']));
|
||||
echo $this->Form->end();
|
||||
?>
|
||||
<span class="icon-refresh useCursorPointer" title="Refresh" role="button" tabindex="0" aria-label="Refresh" onClick="submitQuickTag('<?php echo 'quick_' . h($k); ?>');"></span>
|
||||
<span class="icon-minus useCursorPointer" title="Disable" role="button" tabindex="0" aria-label="Disable" onClick="submitQuickTag('<?php echo 'quick_disable_' . h($k); ?>');"></span>
|
||||
<?php
|
||||
else:
|
||||
?>
|
||||
<span class="icon-plus useCursorPointer" title="Enable" role="button" tabindex="0" aria-label="Refresh or enable" onClick="submitQuickTag('<?php echo 'quick_' . h($k); ?>');"></span>
|
||||
<?php
|
||||
endif;
|
||||
echo $this->Form->end();
|
||||
} else {
|
||||
echo 'N/A';
|
||||
}
|
||||
?>
|
||||
<span class="icon-refresh useCursorPointer" title="Refresh" role="button" tabindex="0" aria-label="Refresh" onClick="submitQuickTag('<?php echo 'quick_' . h($k); ?>');"></span>
|
||||
<span class="icon-minus useCursorPointer" title="Disable" role="button" tabindex="0" aria-label="Disable" onClick="submitQuickTag('<?php echo 'quick_disable_' . h($k); ?>');"></span>
|
||||
<?php
|
||||
else:
|
||||
?>
|
||||
<span class="icon-plus useCursorPointer" title="Enable" role="button" tabindex="0" aria-label="Refresh or enable" onClick="submitQuickTag('<?php echo 'quick_' . h($k); ?>');"></span>
|
||||
<?php
|
||||
endif;
|
||||
echo $this->Form->end();
|
||||
} else {
|
||||
echo 'N/A';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
.node circle {
|
||||
cursor: pointer;
|
||||
stroke: #3182bd;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
.node text {
|
||||
font: 10px sans-serif;
|
||||
pointer-events: none;
|
||||
text-anchor: middle;
|
||||
}
|
||||
line.link {
|
||||
fill: none;
|
||||
stroke: #9ecae1;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
#main {
|
||||
display: inline-block;
|
||||
background-color: grey;
|
||||
position: relative;
|
||||
}
|
||||
.menu {
|
||||
border: 1px solid black;
|
||||
background-color: grey;
|
||||
display: none;
|
||||
position: relative;
|
||||
}
|
||||
.menu,
|
||||
.menu li {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
width: 250px;
|
||||
}
|
||||
.menu li {
|
||||
color: white;
|
||||
}
|
||||
.menu > li:hover {
|
||||
background-color: lightblue;
|
||||
color: black;
|
||||
}
|
||||
.menu li {
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.menu li:hover ul {
|
||||
display:inline-block;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
.menu > li > span > a {
|
||||
color:white;
|
||||
font-weight:bold;
|
||||
}
|
||||
.menu > li > span:first-child {
|
||||
font-weight:bold;
|
||||
}
|
||||
.graphMenuTitle {
|
||||
background-color:#0088cc;
|
||||
font-weight:bold;
|
||||
color:white;
|
||||
}
|
||||
.graphMenuActions {
|
||||
background-color:#0088cc;
|
||||
color:white;
|
||||
}
|
||||
.graphMenuAction {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
position:absolute;
|
||||
width:300px;
|
||||
}
|
||||
|
||||
.font-white {
|
||||
color:white;
|
||||
}
|
||||
.corrected-icon {
|
||||
top:-5px;
|
||||
margin-left:100px;
|
||||
}
|
|
@ -1912,6 +1912,10 @@ table tr:hover .down-expand-button {
|
|||
border:50px solid black;
|
||||
}
|
||||
|
||||
.action-links > .fa {
|
||||
color:black;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from {-webkit-transform: rotate(0deg);}
|
||||
to {-webkit-transform: rotate(359deg);}
|
||||
|
|
|
@ -0,0 +1,507 @@
|
|||
$(document).ready( function() {
|
||||
|
||||
var currentMousePos = { x: -1, y: -1 };
|
||||
$(document).mousemove(function(event) {
|
||||
currentMousePos.x = event.pageX;
|
||||
currentMousePos.y = event.pageY;
|
||||
});
|
||||
|
||||
var margin = {top: -5, right: -5, bottom: -5, left: -5},
|
||||
width = $(window).width() - margin.left - margin.right,
|
||||
height = $(window).height() - 160 - margin.top - margin.bottom;
|
||||
var menu_x_buffer_ = width - 150;
|
||||
var menu_y_buffer = height - 100;
|
||||
$('.menu-container').css('left', '200px');
|
||||
$('#hover-menu-container').css('top', '50px');
|
||||
$('#hover-menu-container').css('z-index', 1);
|
||||
$('#selected-menu-container').css('top', '400px');
|
||||
$('#selected-menu-container').css('z-index', 2);
|
||||
|
||||
var root;
|
||||
|
||||
var highlighted;
|
||||
var hovered;
|
||||
|
||||
var icon_sizes = {
|
||||
"event": 24,
|
||||
"object": 12,
|
||||
"attribute": 12,
|
||||
"galaxy": 32,
|
||||
"tag": 24
|
||||
}
|
||||
|
||||
var selection_radius_sizes = {
|
||||
"event": 18,
|
||||
"object": 12,
|
||||
"attribute": 12,
|
||||
"galaxy": 18,
|
||||
"tag": 18
|
||||
}
|
||||
|
||||
var force = d3.layout.force()
|
||||
.linkDistance(function (d) {
|
||||
return d.linkDistance;
|
||||
})
|
||||
.linkStrength(0.9)
|
||||
.friction(0.5)
|
||||
.theta(0.9)
|
||||
.charge(-500)
|
||||
.gravity(0.21)
|
||||
.size([width, height])
|
||||
.on("tick", tick);
|
||||
|
||||
var vis = d3.select("#chart");
|
||||
|
||||
var svg = vis.append("svg:svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("pointer-events", "all");
|
||||
|
||||
var rect = svg.append("svg:rect")
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('fill', 'white')
|
||||
.call(d3.behavior.zoom().on("zoom", zoomhandler));
|
||||
|
||||
var plotting_area = svg.append("g")
|
||||
.attr("class", "plotting-area");
|
||||
|
||||
var drag1 = d3.behavior.drag()
|
||||
.on("dragstart", dragstart)
|
||||
.on("drag", dragmove)
|
||||
.on("dragend", dragend);
|
||||
|
||||
var link = plotting_area.selectAll(".link");
|
||||
var node = plotting_area.selectAll(".node");
|
||||
|
||||
var scope_id = $('#graph_init').data('id');
|
||||
var scope = $('#graph_init').data('scope');
|
||||
|
||||
d3.json("/events/updateGraph/" + scope_id + "/" + scope + ".json", function(error, json) {
|
||||
root = json;
|
||||
update();
|
||||
});
|
||||
|
||||
var graphElementScale = 1;
|
||||
var graphElementTranslate = [0, 0];
|
||||
|
||||
function zoomhandler() {
|
||||
plotting_area.attr("transform",
|
||||
"translate(" + d3.event.translate + ")"
|
||||
+ " scale(" + d3.event.scale + ")");
|
||||
graphElementScale = d3.event.scale;
|
||||
graphElementTranslate = d3.event.translate;
|
||||
}
|
||||
|
||||
function update() {
|
||||
var nodes = root['nodes'], links = root['links'];
|
||||
|
||||
|
||||
// Restart the force layout.
|
||||
force.nodes(nodes).links(links).start();
|
||||
|
||||
// Update links.
|
||||
link = link.data(links);
|
||||
link.exit().remove();
|
||||
link.enter().insert("line", ".node").attr("class", "link");
|
||||
// Update nodes.
|
||||
node = node.data(nodes);
|
||||
node.exit().remove();
|
||||
|
||||
var nodeEnter = node.enter().append("g").attr("class", "node").call(drag1);
|
||||
|
||||
nodeEnter.attr('id', function(d) { return 'id-' + d.unique_id; })
|
||||
|
||||
nodeEnter.insert("circle", ".circle")
|
||||
.classed("highlighted_circle", true)
|
||||
.attr("cx", function(d) { return d.x_axis; })
|
||||
.attr("cy", function(d) { return d.y_axis; })
|
||||
.attr("r", function(d) { return selection_radius_sizes[d.type] })
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-opacity", "0")
|
||||
.attr("fill-opacity", "0")
|
||||
.attr("fill", "red");
|
||||
|
||||
nodeEnter.filter(function(d) {return d.image !== undefined})
|
||||
.append("svg:image")
|
||||
.attr("class", "circle")
|
||||
.attr("xlink:href", function(d) {
|
||||
return d.image
|
||||
})
|
||||
.attr("x", function(d) {
|
||||
return (0 - (icon_sizes[d.type]/2)) + "px";
|
||||
})
|
||||
.attr("y", function(d) {
|
||||
return (0 - (icon_sizes[d.type]/2)) + "px";
|
||||
})
|
||||
.attr("width", function(d) {
|
||||
return ((icon_sizes[d.type])) + "px";
|
||||
})
|
||||
.attr("height", function(d) {
|
||||
return ((icon_sizes[d.type])) + "px";
|
||||
});
|
||||
|
||||
nodeEnter.filter(function(d) {return d.imgClass !== undefined})
|
||||
.append("g")
|
||||
.append('svg:foreignObject')
|
||||
.attr("width", 12)
|
||||
.attr("height", 12)
|
||||
.attr("x", function(d) {
|
||||
if (d.type == 'galaxy' || d.type == 'tag') {
|
||||
return '-10px';
|
||||
} else {
|
||||
return '-6px';
|
||||
}
|
||||
}
|
||||
)
|
||||
.attr("y", function(d) {
|
||||
if (d.type == 'galaxy' || d.type == 'tag') {
|
||||
return '-12px';
|
||||
} else {
|
||||
return '-8px';
|
||||
}
|
||||
}
|
||||
)
|
||||
.append("xhtml:body")
|
||||
.html(function (d) {
|
||||
var result = 'fa-' + d.imgClass;
|
||||
if (d.type == 'galaxy' || d.type == 'tag') result = 'fa-2x ' + result;
|
||||
return '<i class="fa ' + result + '"></i>';
|
||||
});
|
||||
|
||||
nodeEnter.append("text")
|
||||
.attr("dy", function(d) {
|
||||
if (d.type == "event" || d.type == "galaxy") {
|
||||
return "10px";
|
||||
} else {
|
||||
return "0px";
|
||||
}
|
||||
})
|
||||
.attr("fill", function(d) {
|
||||
if (d.type == "event") {
|
||||
if (d.expanded == 1) {
|
||||
return "#0000ff";
|
||||
} else {
|
||||
return "#ff0000";
|
||||
}
|
||||
}
|
||||
})
|
||||
.text(function(d) {
|
||||
return d.type + ': ' + d.name;
|
||||
});
|
||||
|
||||
node.selectAll("text")
|
||||
.attr("y", 20);
|
||||
|
||||
node.on('mouseover', function(d) {
|
||||
link.style('stroke', function(l) {
|
||||
if (d === l.source || d === l.target)
|
||||
return "#ff0000";
|
||||
else
|
||||
return "#9ecae1";
|
||||
});
|
||||
link.style('stroke-width', function(l) {
|
||||
if (d === l.source || d === l.target)
|
||||
return 2;
|
||||
else
|
||||
return 1;
|
||||
});
|
||||
showPane(d, 'hover');
|
||||
});
|
||||
|
||||
node.on('mouseout', function() {
|
||||
link.style('stroke-width', 1);
|
||||
link.style('stroke', "#9ecae1");
|
||||
});
|
||||
|
||||
node.on("click", function(d) {
|
||||
showPane(d, 'selected');
|
||||
});
|
||||
}
|
||||
|
||||
function highlightNode(d) {
|
||||
d3.selectAll('.highlighted_circle')
|
||||
.style("stroke-opacity", 0);
|
||||
d3.select('#id-' + d.unique_id)
|
||||
.select('.highlighted_circle')
|
||||
.style("stroke", "red")
|
||||
.style("stroke-opacity", 0.5);
|
||||
}
|
||||
|
||||
function contextMenu(d, newContext) {
|
||||
d3.event.preventDefault();
|
||||
if (d.type == 'event') showPane(d, 'context');
|
||||
}
|
||||
|
||||
function bindExpand(d, type) {
|
||||
if (!d.expanded) {
|
||||
var expandName = 'Expand (ctrl+x)';
|
||||
if (type == 'selected') {
|
||||
expandName = 'Expand (x)';
|
||||
}
|
||||
$("#" + type + "-menu").append('<li id="expand_' + type + '_' + d.id +'" class="graphMenuAction"><span>' + expandName + '</span></li>');
|
||||
d3.select('#expand_' + type + '_' + d.id)
|
||||
.on('click', function() {
|
||||
expand(d);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function remove_node(d) {
|
||||
var index = root.nodes.indexOf(d);
|
||||
if (index > -1) {
|
||||
root.nodes.splice(index, 1);
|
||||
}
|
||||
var temp = [];
|
||||
root['links'].forEach(function(n) {
|
||||
if (n.source != d && n.target != d) {
|
||||
temp.push(n);
|
||||
}
|
||||
});
|
||||
root['links'] = temp;
|
||||
|
||||
var i = 0;
|
||||
var links = [];
|
||||
root.links.forEach(function(l) {
|
||||
var j = 0;
|
||||
var temp = {};
|
||||
root.nodes.forEach(function(n) {
|
||||
if (l.source == n) {
|
||||
temp.source = j;
|
||||
}
|
||||
if (l.target == n) {
|
||||
temp.target = j;
|
||||
}
|
||||
var j = j+1;
|
||||
});
|
||||
temp.linkDistance = l.linkDistance;
|
||||
links.push(temp);
|
||||
var i = i+1;
|
||||
});
|
||||
root = {
|
||||
'nodes': root['nodes'],
|
||||
'links': root['links']
|
||||
};
|
||||
update();
|
||||
}
|
||||
|
||||
/*
|
||||
function bindDelete(d, type) {
|
||||
var deleteName = 'Delete (ctrl+d)';
|
||||
if (type == 'selected') {
|
||||
deleteName = 'Delete (d)';
|
||||
}
|
||||
$("#" + type + "-menu").append('<li id="remove_' + type + '_' + d.id +'" class="graphMenuAction"><span>' + deleteName + '</span></li>');
|
||||
d3.select('#remove_' + type + '_' + d.id)
|
||||
.on('click', function() {
|
||||
remove_node(d);
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
function createInfoPane(d, data, type) {
|
||||
var i = 0;
|
||||
var view_urls = {
|
||||
'event': '/events/view/' + parseInt(d.id),
|
||||
'tag': '/tags/view/' + parseInt(d.id),
|
||||
'galaxy': '/galaxy_clusters/view/' + parseInt(d.id)
|
||||
};
|
||||
data["fields"].forEach(function(e) {
|
||||
var title = e;
|
||||
if (i == 0) title = d.type;
|
||||
title = title.split("_").join(" ");
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
var span1 = $('<span />').text(title + ': ');
|
||||
var span2 = $('<span />').text(d[e]);
|
||||
var li = $('<li />');
|
||||
li.append(span1);
|
||||
li.append(span2);
|
||||
if (i == 0) li.addClass('graphMenuTitle');
|
||||
i++;
|
||||
$("#" + type + "-menu").append(li);
|
||||
});
|
||||
$("#" + type + "-menu").append('<li class="graphMenuActions">Actions</li>');
|
||||
if ($.inArray("navigate", data["actions"]) !== -1) {
|
||||
$("#" + type + "-menu").append('<li><span><a href="' + view_urls[d.type] + '">Go to ' + d.type + '</a></span></li>');
|
||||
}
|
||||
if ($.inArray("expand", data["actions"]) !== -1) {
|
||||
bindExpand(d, type);
|
||||
}
|
||||
if ($.inArray("delete", data["actions"]) !== -1) {
|
||||
bindDelete(d, type);
|
||||
}
|
||||
}
|
||||
|
||||
function showPane(d, type) {
|
||||
if (type == 'hover') {
|
||||
hovered = d;
|
||||
} else {
|
||||
highlighted = d;
|
||||
highlightNode(d);
|
||||
}
|
||||
$('#' + type + '-header').show();
|
||||
d3.select("#" + type + "-menu").style('display', 'inline-block');
|
||||
$("#" + type + "-menu").empty();
|
||||
if (d.type== 'attribute') {
|
||||
var data = {
|
||||
"fields": ["id", "name", "category", "type", "comment"],
|
||||
"actions": ["delete"]
|
||||
}
|
||||
}
|
||||
if (d.type== 'event') {
|
||||
var tempid = parseInt(d.id);
|
||||
var data = {
|
||||
"fields": ["id", "info", "date", "analysis", "org"],
|
||||
"actions": ["expand", "delete", "navigate"]
|
||||
}
|
||||
}
|
||||
if (d.type == 'tag') {
|
||||
var data = {
|
||||
"fields": ["id", "name"],
|
||||
"actions": ["expand", "delete"]
|
||||
}
|
||||
if (d.taxonomy !== undefined) {
|
||||
data["fields"].push("taxonomy");
|
||||
data["fields"].push("taxonomy_description");
|
||||
if (d.description !== "") {
|
||||
data["fields"].push("Description");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d.type == 'galaxy') {
|
||||
var data = {
|
||||
"fields": ["id", "name", "galaxy", "synonyms", "authors", "description", "source"],
|
||||
"actions": ["expand", "delete", "navigate"]
|
||||
}
|
||||
}
|
||||
if (d.type == 'object') {
|
||||
var data = {
|
||||
"fields": ["id", "name", "metacategory", "description", "comment"],
|
||||
"actions": ["delete"]
|
||||
}
|
||||
}
|
||||
createInfoPane(d, data, type);
|
||||
}
|
||||
|
||||
function expand(d) {
|
||||
if (d.type == 'event' || d.type == 'galaxy' || d.type == 'tag') {
|
||||
d3.xhr("/events/updateGraph/" + d.id + "/" + d.type + ".json")
|
||||
.header("Content-Type", "application/json")
|
||||
.post(
|
||||
JSON.stringify(root),
|
||||
function(err, rawData){
|
||||
root = JSON.parse(rawData.response);
|
||||
update();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function tick() {
|
||||
link.attr("x1", function(d) { return d.source.x; })
|
||||
.attr("y1", function(d) { return d.source.y; })
|
||||
.attr("x2", function(d) { return d.target.x; })
|
||||
.attr("y2", function(d) { return d.target.y; });
|
||||
|
||||
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
||||
}
|
||||
|
||||
// Returns a list of all nodes under the root.
|
||||
function flatten(root) {
|
||||
var nodes = [], i = 0;
|
||||
|
||||
function recurse(node) {
|
||||
if (node.children) node.children.forEach(recurse);
|
||||
if (!node.id) node.id = ++i;
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
recurse(root);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
function dragstart(d, i) {
|
||||
force.stop();
|
||||
}
|
||||
|
||||
function dragmove(d, i) {
|
||||
d.px += d3.event.dx;
|
||||
d.py += d3.event.dy;
|
||||
d.x += d3.event.dx;
|
||||
d.y += d3.event.dy;
|
||||
tick();
|
||||
}
|
||||
|
||||
function dragend(d, i) {
|
||||
d.fixed = true;
|
||||
tick();
|
||||
force.resume();
|
||||
}
|
||||
|
||||
function searchArray(arr, val) {
|
||||
for (var i=0; i < arr.length; i++)
|
||||
if (arr[i] === val)
|
||||
return i;
|
||||
return false;
|
||||
}
|
||||
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.which == 69) {
|
||||
if (highlighted == undefined) {
|
||||
showPane(root['nodes'][0], 'selected');
|
||||
} else {
|
||||
var current = searchArray(root['nodes'], highlighted);
|
||||
if (current == root['nodes'].length-1) {
|
||||
showPane(root['nodes'][0], 'selected');
|
||||
} else {
|
||||
showPane(root['nodes'][current+1], 'selected');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.which == 81) {
|
||||
if (highlighted == undefined) {
|
||||
showPane(root['nodes'][root['nodes'].length-1], 'selected');
|
||||
} else {
|
||||
var current = searchArray(root['nodes'], highlighted);
|
||||
if (current == 0) {
|
||||
showPane(root['nodes'][root['nodes'].length-1], 'selected');
|
||||
} else {
|
||||
showPane(root['nodes'][current-1], 'selected');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.which == 68) {
|
||||
e.preventDefault();
|
||||
if (e.ctrlKey) {
|
||||
if (hovered != undefined) {
|
||||
remove_node(hovered);
|
||||
}
|
||||
} else {
|
||||
if (highlighted != undefined) {
|
||||
remove_node(highlighted);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.which == 88) {
|
||||
e.preventDefault();
|
||||
if (e.ctrlKey) {
|
||||
if (hovered != undefined) {
|
||||
expand(hovered);
|
||||
}
|
||||
} else {
|
||||
if (highlighted != undefined) {
|
||||
expand(highlighted);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue