From fa7d3fdb365e2f3dd39e2b4ee2cb02b8d262b267 Mon Sep 17 00:00:00 2001 From: iglocska Date: Sun, 8 Oct 2017 19:50:28 +0200 Subject: [PATCH] new: First round of updates to the correlation engine ready - node deletion temporarily disabled until a bug is resolved --- app/Controller/EventsController.php | 13 +- app/Controller/GalaxiesController.php | 14 + app/Controller/GalaxyClustersController.php | 2 + app/Controller/TagsController.php | 16 + app/Lib/Tools/CorrelationGraphTool.php | 46 +- app/Model/Galaxy.php | 2 - app/Model/GalaxyCluster.php | 2 +- app/View/Elements/side_menu.ctp | 38 +- app/View/Events/view_graph.ctp | 576 +------------------- app/View/Galaxies/view.ctp | 5 +- app/View/GalaxyClusters/ajax/index.ctp | 1 + app/View/Tags/index.ctp | 1 + app/View/Taxonomies/view.ctp | 43 +- app/webroot/css/correlation-graph.css | 80 +++ app/webroot/css/main.css | 4 + app/webroot/js/correlation-graph.js | 507 +++++++++++++++++ 16 files changed, 752 insertions(+), 598 deletions(-) create mode 100644 app/webroot/css/correlation-graph.css create mode 100644 app/webroot/js/correlation-graph.js diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 1c718205d..551cbbb16 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -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'); diff --git a/app/Controller/GalaxiesController.php b/app/Controller/GalaxiesController.php index 3626a539c..e3e871647 100644 --- a/app/Controller/GalaxiesController.php +++ b/app/Controller/GalaxiesController.php @@ -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'); + } } diff --git a/app/Controller/GalaxyClustersController.php b/app/Controller/GalaxyClustersController.php index 97c850f5d..b2101d9b8 100644 --- a/app/Controller/GalaxyClustersController.php +++ b/app/Controller/GalaxyClustersController.php @@ -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); } diff --git a/app/Controller/TagsController.php b/app/Controller/TagsController.php index 03108b827..df38b8f2b 100644 --- a/app/Controller/TagsController.php +++ b/app/Controller/TagsController.php @@ -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'); + } } diff --git a/app/Lib/Tools/CorrelationGraphTool.php b/app/Lib/Tools/CorrelationGraphTool.php index aefe60013..71f88bf2f 100644 --- a/app/Lib/Tools/CorrelationGraphTool.php +++ b/app/Lib/Tools/CorrelationGraphTool.php @@ -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']) { diff --git a/app/Model/Galaxy.php b/app/Model/Galaxy.php index 7e6f19830..7c049c3d2 100644 --- a/app/Model/Galaxy.php +++ b/app/Model/Galaxy.php @@ -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'))); } diff --git a/app/Model/GalaxyCluster.php b/app/Model/GalaxyCluster.php index 1f6f58580..9df5f7aa6 100644 --- a/app/Model/GalaxyCluster.php +++ b/app/Model/GalaxyCluster.php @@ -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); } diff --git a/app/View/Elements/side_menu.ctp b/app/View/Elements/side_menu.ctp index ea0cca33a..63c1e8ea3 100644 --- a/app/View/Elements/side_menu.ctp +++ b/app/View/Elements/side_menu.ctp @@ -29,7 +29,7 @@
  • View Event
  • -
  • View Correlation Graph
  • +
  • View Correlation Graph
  • View Event History
  • @@ -273,14 +273,26 @@ case 'tags': ?>
  • Html->link('List Favourite Tags', array('action' => 'index', true));?>
  • Html->link('List Tags', array('action' => 'index'));?>
  • - -
  • Html->link('Add Tag', array('action' => 'add'));?>
  • - +
  • Html->link('Add Tag', array('action' => 'add'));?>
  • + -
  • Html->link('Edit Tag', array('action' => 'edit'));?>
  • - +
  • Html->link('Edit Tag', array('action' => 'edit'));?>
  • + +
  • View Taxonomy
  • + +
  • View Correlation Graph
  • + Form->postLink('Update Galaxies', array('controller' => 'galaxies', 'action' => 'update'), null, __('Are you sure you want to reimport all galaxies from the submodule?')); ?> +
  • View Galaxy
  • +
  • View Cluster
  • +
  • View Correlation Graph
  • +
  • View Galaxy
  • - -
  • View Cluster
  • Html->css('font-awesome'); -echo $this->Html->script('d3'); ?> - + echo $this->Html->css('font-awesome'); + echo $this->Html->css('correlation-graph'); + echo $this->Html->script('d3'); + echo $this->Html->script('correlation-graph'); +?>
    + 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); ?> - diff --git a/app/View/Galaxies/view.ctp b/app/View/Galaxies/view.ctp index 581a4dee4..431831518 100644 --- a/app/View/Galaxies/view.ctp +++ b/app/View/Galaxies/view.ctp @@ -4,7 +4,10 @@
    -

    galaxy

    +

    +   + galaxy +

    Galaxy ID
    diff --git a/app/View/GalaxyClusters/ajax/index.ctp b/app/View/GalaxyClusters/ajax/index.ctp index f60504694..c72fe4fd5 100644 --- a/app/View/GalaxyClusters/ajax/index.ctp +++ b/app/View/GalaxyClusters/ajax/index.ctp @@ -50,6 +50,7 @@   + Html->link('', array('controller' => 'galaxies', 'action' => 'viewGraph', $item['GalaxyCluster']['id']), array('class' => 'fa fa-line-chart', 'title' => 'View graph'));?> Html->link('', array('action' => 'view', $item['GalaxyCluster']['id']), array('class' => 'icon-list-alt', 'title' => 'View'));?> diff --git a/app/View/Tags/index.ctp b/app/View/Tags/index.ctp index 7a2e773d1..1a620429e 100644 --- a/app/View/Tags/index.ctp +++ b/app/View/Tags/index.ctp @@ -104,6 +104,7 @@ foreach ($list as $k => $item): ?> + Html->link('', array('controller' => 'tags', 'action' => 'viewGraph', $item['Tag']['id']), array('class' => 'fa fa-line-chart', 'title' => 'View graph'));?> Html->link('', array('action' => 'edit', $item['Tag']['id']), array('class' => 'icon-edit', 'title' => 'Edit'));?> 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']));?> diff --git a/app/View/Taxonomies/view.ctp b/app/View/Taxonomies/view.ctp index e0d26140b..5d768fdb3 100644 --- a/app/View/Taxonomies/view.ctp +++ b/app/View/Taxonomies/view.ctp @@ -122,35 +122,36 @@ Html->link('', array('controller' => 'tags', 'action' => 'viewGraph', $item['existing_tag']['Tag']['id']), array('class' => 'fa fa-line-chart black', 'title' => 'View graph')); endif; ?> - 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;')); + 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(); + ?> + + + + + Form->end(); + } else { + echo 'N/A'; + } ?> - - - - - Form->end(); - } else { - echo 'N/A'; - } - ?> diff --git a/app/webroot/css/correlation-graph.css b/app/webroot/css/correlation-graph.css new file mode 100644 index 000000000..1c258bb39 --- /dev/null +++ b/app/webroot/css/correlation-graph.css @@ -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; +} diff --git a/app/webroot/css/main.css b/app/webroot/css/main.css index 5523060a9..291441cb9 100644 --- a/app/webroot/css/main.css +++ b/app/webroot/css/main.css @@ -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);} diff --git a/app/webroot/js/correlation-graph.js b/app/webroot/js/correlation-graph.js new file mode 100644 index 000000000..268f61884 --- /dev/null +++ b/app/webroot/js/correlation-graph.js @@ -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 ''; + }); + + 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('
  • ' + expandName + '
  • '); + 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('
  • ' + deleteName + '
  • '); + 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 = $('').text(title + ': '); + var span2 = $('').text(d[e]); + var li = $('
  • '); + li.append(span1); + li.append(span2); + if (i == 0) li.addClass('graphMenuTitle'); + i++; + $("#" + type + "-menu").append(li); + }); + $("#" + type + "-menu").append('
  • Actions
  • '); + if ($.inArray("navigate", data["actions"]) !== -1) { + $("#" + type + "-menu").append('
  • Go to ' + d.type + '
  • '); + } + 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); + } + } + } + }); +});