chg: [clusterRelations] Improved UI of relation_graph and

relation_viewer
pull/6120/head
mokaddem 2020-05-06 11:44:53 +02:00
parent 1af22c1258
commit 2a6c6f439d
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
8 changed files with 346 additions and 42 deletions

View File

@ -831,9 +831,24 @@ class GalaxyClustersController extends AppController
throw new NotFoundException('Invalid galaxy cluster');
}
$cluster = $cluster[0];
$relations = $this->GalaxyCluster->GalaxyClusterRelation->getExistingRelationships();
$relations = Hash::extract($relations, '{n}.ObjectRelationship.name');
$existingRelations = $this->GalaxyCluster->GalaxyClusterRelation->getExistingRelationships();
$cluster = $this->GalaxyCluster->attachClusterToRelations($this->Auth->user(), $cluster);
$tree = array(array(
'GalaxyCluster' => $cluster['GalaxyCluster'],
'children' => array()
));
// add relation info between the two clusters
foreach($cluster['GalaxyClusterRelation'] as $relation) {
$tmp = array(
'Relation' => array_diff_key($relation, array_flip(array('GalaxyCluster'))),
'children' => array(
array('GalaxyCluster' => $relation['GalaxyCluster']),
)
);
$tree[0]['children'][] = $tmp;
}
$this->set('existingRelations', $existingRelations);
$this->set('cluster', $cluster);
$this->set('relations', $relations);
$this->set('tree', $tree);
}
}

View File

@ -613,7 +613,9 @@ class Galaxy extends AppModel
// $cluster['GalaxyCluster']['name'] = $cluster['GalaxyCluster']['uuid'];
// $referencedCluster['GalaxyCluster']['name'] = $referencedCluster['GalaxyCluster']['uuid'];
$nodes[$referencedClusterId] = $referencedCluster['GalaxyCluster'];
$nodes[$referencedClusterId]['group'] = $referencedCluster['GalaxyCluster']['type'];
$nodes[$relation['galaxy_cluster_id']] = $cluster['GalaxyCluster'];
$nodes[$relation['galaxy_cluster_id']]['group'] = $cluster['GalaxyCluster']['type'];
if (true) {
$links[] = array(
'source' => $relation['galaxy_cluster_id'],

View File

@ -448,12 +448,12 @@ class GalaxyCluster extends AppModel
if ($cluster['GalaxyCluster']['distribution'] != 4) {
unset($clusters[$i]['SharingGroup']);
}
if ($cluster['GalaxyCluster']['org_id'] == 0) {
unset($clusters[$i]['Org']);
}
if ($cluster['GalaxyCluster']['orgc_id'] == 0) {
unset($clusters[$i]['Orgc']);
}
// if ($cluster['GalaxyCluster']['org_id'] == 0) {
// unset($clusters[$i]['Org']);
// }
// if ($cluster['GalaxyCluster']['orgc_id'] == 0) {
// unset($clusters[$i]['Orgc']);
// }
$clusters[$i] = $this->GalaxyClusterRelation->massageRelationTag($clusters[$i]);
}
return $clusters;
@ -576,4 +576,18 @@ class GalaxyCluster extends AppModel
}
return array_values($clusterTags);
}
public function attachClusterToRelations($user, $cluster)
{
if (isset($cluster['GalaxyClusterRelation'])) {
foreach ($cluster['GalaxyClusterRelation'] as $k => $relation) {
$conditions = array('conditions' => array('GalaxyCluster.id' => $relation['referenced_galaxy_cluster_id']));
$relatedCluster = $this->fetchGalaxyClusters($user, $conditions, false);
if (!empty($relatedCluster)) {
$cluster['GalaxyClusterRelation'][$k]['GalaxyCluster'] = $relatedCluster[0]['GalaxyCluster'];
}
}
}
return $cluster;
}
}

View File

@ -33,9 +33,10 @@ class GalaxyClusterRelation extends AppModel
public function getExistingRelationships()
{
$existingRelationships = $this->find('all', array(
$existingRelationships = $this->find('list', array(
'recursive' => -1,
'fields' => array('referenced_galaxy_cluster_type')
'fields' => array('referenced_galaxy_cluster_type'),
'group' => array('referenced_galaxy_cluster_type')
), false, false);
return $existingRelationships;
}

View File

@ -1,32 +1,47 @@
<?php
echo $this->element('genericElements/assetLoader', array(
'js' => array('d3')
));
?>
<div style="display: flex; min-height: 600px;">
<div style="flex: 1; padding: 5px;">
<div>
<div style="padding: 5px; background-color: #f6f6f6; border-bottom: 1px solid #ccc; ">
<div id="relationsQuickAddForm">
<label for="RelationshipSource"><?= __('Source UUID') ?></label>
<input id="RelationshipSource" type="text" value="<?= h($cluster['GalaxyCluster']['uuid']) ?>" disabled></input>
<label for="RelationshipType"><?= __('Relationship type') ?></label>
<select id="RelationshipType">
<?php foreach ($relations as $relation): ?>
<option value="<?= h($relation) ?>"><?= h($relation) ?></option>
<?php endforeach; ?>
</select>
<label for="RelationshipTarget"><?= __('Target UUID') ?></label>
<select></select>
<input id="RelationshipTarget" type="text"></input>
<button id="buttonAddRelationship" type="button" class="btn btn-block btn-primary">
<div class="input">
<label for="RelationshipSource"><?= __('Source UUID') ?></label>
<input id="RelationshipSource" type="text" value="<?= h($cluster['GalaxyCluster']['uuid']) ?>" disabled></input>
</div>
<div class="input">
<label for="RelationshipType"><?= __('Relationship type') ?></label>
<select id="RelationshipType">
<?php foreach ($existingRelations as $relation): ?>
<option value="<?= h($relation) ?>"><?= h($relation) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="input">
<label for="RelationshipTarget"><?= __('Target UUID') ?></label>
<select></select>
<input id="RelationshipTarget" type="text"></input>
</div>
<button id="buttonAddRelationship" type="button" class="btn btn-primary" style="margin-top: 20px">
<i class="fas fa-plus"></i>
Add relationship
</button>
<div class="clear"></div>
</div>
</div>
<div style="flex: 4; padding: 5px; background-color: steelblue;">
<?php debug($relations); ?>
</div>
</div>
<div style="padding: 5px; min-height: 600px;">
<svg id="treeSVG" style="width: 100%; height: 100%; min-height: 500px;"></svg>
</div>
<script>
var treeData = <?= json_encode($tree) ?>;
var margin = {top: 10, right: 10, bottom: 10, left: 20};
var treeWidth, treeHeight;
var colors = d3.scale.category10();
$(document).ready(function() {
// $('#relationsQuickAddForm select').chosen();
})
@ -60,4 +75,186 @@
function toggleLoadingButton(loading) {
}
function buildTree() {
var $tree = $('#treeSVG');
treeWidth = $tree.width() - margin.right - margin.left;
treeHeight = $tree.height() - margin.top - margin.bottom;
var tree = d3.layout.tree(treeData)
.size([treeHeight, treeWidth]);
var diagonal = function link(d) {
return "M" + d.source.y + "," + d.source.x
+ "C" + (d.source.y + d.target.y) / 2 + "," + d.source.x
+ " " + (d.source.y + d.target.y) / 2 + "," + d.target.x
+ " " + d.target.y + "," + d.target.x;
};
var svg = d3.select("#treeSVG")
.attr("width", treeWidth + margin.right + margin.left)
.attr("height", treeHeight + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var root = treeData[0];
root.isRoot = true;
root.x0 = treeHeight / 2;
root.y0 = 0;
var nodes = tree.nodes(root).reverse();
var links = tree.links(nodes);
var maxDepth = 0;
var leftMaxTextLength = 0;
nodes.forEach(function(d) {
maxDepth = maxDepth > d.depth ? maxDepth : d.depth;
if (d.GalaxyCluster !== undefined) {
var clusterLength = d.GalaxyCluster.type.length > d.GalaxyCluster.value.length ? d.GalaxyCluster.type.length : d.GalaxyCluster.value.length;
leftMaxTextLength = leftMaxTextLength > clusterLength ? leftMaxTextLength : clusterLength;
} else if (d.Relation !== undefined) {
var tagLength = 0;
if (d.Relation.Tag !== undefined) {
tagLength = d.Relation.Tag.name / 2;
}
var relationLength = tagLength > d.Relation.referenced_galaxy_cluster_type.length ? tagLength : d.Relation.referenced_galaxy_cluster_type.length;
leftMaxTextLength = leftMaxTextLength > relationLength ? leftMaxTextLength : relationLength;
}
})
var offsetLeafLength = leftMaxTextLength * 6.7; // font-size of body is 12px
var ratioFactor = (treeWidth - offsetLeafLength) / maxDepth;
nodes.forEach(function(d) { d.y = d.depth * ratioFactor; });
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return getId(d, true) });
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
.on("mouseover", nodeHover)
.on("dblclick", nodeDbclick);
var gEnter = nodeEnter.append('g');
drawEntities(gEnter);
var link = svg.selectAll("path.link")
.data(links, function(d) { return getId(d.target); });
link.enter().insert("path", "g")
.attr("class", "link")
.style("fill", "none")
.style("stroke", "#ccc")
.style("stroke-width", "2px")
.attr("d", function(d) {
return diagonal(d);
});
}
function drawEntities(gEnter) {
gEnter.filter(function(d) { return d.GalaxyCluster !== undefined }).call(drawCluster);
gEnter.filter(function(d) { return d.Relation !== undefined }).call(drawRelation);
}
function drawCluster(gEnter) {
gEnter.append("circle")
.attr("r", 5)
.style("fill", function(d) { return colors(d.GalaxyCluster.type); })
.style("stroke", "#000")
.style("stroke-width", "2px");
drawLabel(gEnter, {
text: [function(d) { return d.GalaxyCluster.value }, function(d) { return d.GalaxyCluster.type }],
x: function(d) { return d.children ? "0em" : "1.5em"; },
y: function(d) { return d.children ? "2em" : ""; },
textAnchor: 'start',
fontWeight: 'bold'
});
}
function drawRelation(gEnter) {
var paddingX = 9;
gEnter.append("foreignObject")
.attr("height", 50)
.attr("y", -24)
.attr("width", function(d) { return getTextWidth(d.Relation.referenced_galaxy_cluster_type) + 2*paddingX + 'px'; })
.append("xhtml:div")
.append("div")
.attr("class", "well well-small")
// .attr("title", function(d) { return d.children ? "Version" : "<?= __('Latest version of the parent cluster') ?>" })
.html(function(d) { return d.Relation.referenced_galaxy_cluster_type; })
paddingX = 6;
gEnter.append("foreignObject")
.attr("height", 18)
.attr("y", 20)
.attr("x", function(d) { return -(d.Relation.Tag !== undefined ? getTextWidth(d.Relation.Tag.name, {'white-space': 'nowrap', 'font-weight': 'bold'}) - 2*paddingX : 0)/2 + 'px'; })
.attr("width", function(d) { return (d.Relation.Tag !== undefined ? getTextWidth(d.Relation.Tag.name, {'white-space': 'nowrap', 'font-weight': 'bold'}) + 2*paddingX : 0) + 'px'; })
.append("xhtml:div")
.append("span")
.attr("class", "tag")
.style('white-space', 'nowrap')
.style('background-color', function(d) {return d.Relation.Tag !== undefined ? d.Relation.Tag.colour : '';})
.style('color', function(d) {return d.Relation.Tag !== undefined ? getTextColour(d.Relation.Tag.colour) : 'white';})
// .attr("title", function(d) { return d.children ? "Version" : "<?= __('Latest version of the parent cluster') ?>" })
.html(function(d) { return d.Relation.Tag !== undefined ? d.Relation.Tag.name : ''; })
}
function drawLabel(gEnter, options) {
var defaultOptions = {
text: '',
x: '',
dx: '',
y: '',
dy: '',
textAnchor: 'start',
fontWeight: ''
}
options = $.extend(defaultOptions, options);
var svgText = gEnter.append("text")
.attr("dy", options.dy)
.attr("dx", options.dx)
.attr("x", options.x)
.attr("y", options.y)
.attr("text-anchor", options.textAnchor)
if (Array.isArray(options.text)) {
options.text.forEach(function(text, i) {
svgText.append('tspan')
.attr('font-weight', i == 0 ? 'bold' : '')
.attr('font-style', i != 0 ? 'italic' : '')
.attr('x', options.x)
.attr('dy', i != 0 ? 16 : 0)
.text(text);
})
} else {
svgText
.attr("font-weight", options.fontWeight)
.text(options.text);
}
}
function getTextWidth(text, additionalStyle) {
var style = {visibility: 'hidden'};
if (additionalStyle !== undefined) {
style = $.extend(style, additionalStyle);
}
var tmp = $('<span></span>').text(text).css(style)
$('body').append(tmp);
var bcr = tmp[0].getBoundingClientRect()
tmp.remove();
return bcr.width;
}
function nodeDbclick(d) {
}
function nodeHover(d) {
}
function getId(d) {
var id = "";
if (d.GalaxyCluster !== undefined) {
id = d.GalaxyCluster.id;
} else if (d.Relation !== undefined) {
id = d.Relation.id;
}
return id;
}
</script>

View File

@ -420,7 +420,7 @@ function init<?= $seed ?>() { // variables and functions have their own scope (n
.attr("fill", "none")
.attr("stroke-width", 2.5);
series
.style("stroke", function(d) { ;return colors(d.name); })
.style("stroke", function(d) { return colors(d.name); })
.attr("d", function(d) { return value_line(d.values); });
series.exit().remove();

View File

@ -14,8 +14,8 @@ echo $this->element('genericElements/assetLoader', array(
<?= __('There are no relations in this Galaxy'); ?>
</div>
<?php else: ?>
<div style="border: 1px solid #ddd; margin-bottom: 15px;">
<div id="graphContainer" style="height: 70vh;"></div>
<div style="margin-bottom: 10px;">
<div id="graphContainer" style="height: 70vh; border: 1px solid #ddd; "></div>
</div>
<script>
@ -23,10 +23,12 @@ var graph = <?= json_encode($relations) ?>;
var nodes, links;
var width, height, margin;
var vis, svg, plotting_area, force, container, zoom;
var legendLabels, labels;
var graphElementScale = 1;
var graphElementTranslate = [0, 0];
var nodeHeight = 20;
var nodeWidth = 120;
var colors = d3.scale.category10();
$(document).ready( function() {
margin = {top: 5, right: 5, bottom: 5, left: 5},
@ -39,23 +41,22 @@ $(document).ready( function() {
function initGraph() {
var correctLink = [];
var groupDomain = {};
graph.links.forEach(function(link) {
var tmpNode = graph.nodes.filter(function(node) {
return node.id == link.source;
})
link.source = tmpNode[0]
if (tmpNode[0] === undefined) {
console.log(link);
}
tmpNode = graph.nodes.filter(function(node) {
return node.id == link.target;
})
if (tmpNode[0] === undefined) {
console.log(link);
}
link.target = tmpNode[0]
groupDomain[link.source.group] = 1;
groupDomain[link.target.group] = 1;
correctLink.push(link)
})
groupDomain = Object.keys(groupDomain);
colors.domain(groupDomain);
graph.links = correctLink;
force = d3.layout.force()
.size([width, height])
@ -75,6 +76,18 @@ function initGraph() {
.on("zoom", zoomHandler);
svg.call(zoom);
svg.append('g')
.classed('legendContainer', true)
.append('g')
.classed('legend', true);
legendLabels = groupDomain.map(function(domain) {
return {
text: domain,
color: colors(domain)
}
})
drawLabels();
update();
}
@ -124,9 +137,9 @@ function update() {
// })
nodesEnter.append("circle")
.attr("r", 5)
.style("fill", "lightsteelblue")
.style("stroke", "steelblue")
.style("stroke-width", "2px");
.style("fill", function(d) { return colors(d.group); })
.style("stroke", "black")
.style("stroke-width", "1px");
nodesEnter.append("text")
.attr("dy", "20px")
.attr("dx", "")
@ -179,5 +192,54 @@ function drag(force) {
.on("drag", dragmove)
.on("dragend", dragend)
}
function drawLabels() {
labels = svg.select('.legend')
.selectAll('.labels')
.data(legendLabels);
var label = labels.enter()
.append('g')
.attr('class', 'labels')
label.append('circle')
label.append('text')
labels.selectAll('circle')
.style('fill', function(d, i){ return d.color })
.style('stroke', function(d, i){ return d.color })
.attr('r', 5);
labels.selectAll('text')
.text(function(d) { return d.text })
.style('font-size', '16px')
.style('text-decoration', function(d) { return d.disabled ? 'line-through' : '' })
.attr('fill', function(d) { return d.disabled ? 'gray' : '' })
.attr('text', 'start')
.attr('dy', '.32em')
.attr('dx', '8');
labels.exit().remove();
var ypos = 10, newxpos = 20, xpos;
label
.attr('transform', function(d, i) {
var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
var xpos = newxpos;
if (width < (margin.left) + margin.right + xpos + length) {
newxpos = xpos = 20;
ypos += 20;
}
newxpos += length;
return 'translate(' + xpos + ',' + ypos + ')';
})
var legendBB = svg.select('.legend').node().getBBox();
var pad = 3;
svg.select('.legendContainer').insert('rect', ':first-child')
.style('fill', '#fff')
.attr('x', legendBB.x - pad)
.attr('y', legendBB.y - pad)
.attr('width', legendBB.width + pad)
.attr('height', legendBB.height + pad)
.style('stroke', '#eee');
}
</script>
<?php endif; ?>

View File

@ -1,6 +1,19 @@
<button class="btn btn-inverse" onclick="$('#references_div').toggle('blind', 300);"><span class="fa fa-eye-slash"> <?php echo __('Toggle Cluster relationships'); ?></span></button>
<button class="btn btn-inverse" onclick="toggleClusterRelations()"><span class="fa fa-eye-slash"> <?php echo __('Toggle Cluster relationships'); ?></span></button>
<div id="references_div" style="position: relative; border: solid 1px;" class="statistics_attack_matrix hidden">
<?php
echo $this->element('GalaxyClusters/view_relations');
?>
</div>
<script>
function toggleClusterRelations() {
$('#references_div').toggle({
effect: 'blind',
duration: 300,
complete: function() {
if (window.buildTree !== undefined) {
buildTree();
}
}
});
}
</script>