FeatureEnVi: Visual Analytics for Feature Engineering Using Stepwise Selection and Semi-Automatic Extraction Approaches https://doi.org/10.1109/TVCG.2022.3141040
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
FeatureEnVi/frontend/src/components/FeatureSpaceOverview.vue

1126 lines
37 KiB

<template>
<div>
<div id="tree-container" style="min-height: 820px"></div>
<div id="toolbar">
<div class="tool">
<div class="tlabel">Zoom</div>
<div class="tbuttons">
<div class="button" data-key="187" title="Zoom In">+</div>
<div class="button" data-key="189" title="Zoom Out">&minus;</div>
</div>
</div>
<div class="tool">
<div class="tlabel">Rotate</div>
<div class="tbuttons">
<div class="button" data-key="33" title="Rotate CCW" style="font-size:0.9em">&#8634;</div>
<div class="button" data-key="34" title="Rotate CW" style="font-size:0.9em">&#8635;</div>
</div>
</div>
<div class="tool">
<div class="tlabel">Select</div>
<div class="tbuttons">
<div class="button" data-key="-38" title="Select Previous" style="font-size:0.9em">&#8613;</div>
<div class="button" data-key="-40" title="Select Next" style="font-size:0.9em">&#8615;</div>
</div>
</div>
<div class="tool">
<div class="tlabel">View</div>
<div class="tbuttons">
<div class="button" data-key="36" title="Center Root">&#8962;</div>
<div class="button" data-key="35" title="Center Selected" style="font-size:0.8em">&#9673;</div>
</div>
</div>
<div class="tool">
<div class="tlabel">Toggle</div>
<div class="tbuttons">
<div class="button" data-key="32" title="Toggle Node">1</div>
<div class="button" data-key="13" title="Toggle Level">&oplus;</div>
<div class="button" data-key="191" title="Toggle Root">/</div>
</div>
</div>
<div class="tool">
<div class="tlabel" style="text-align:left" title="Change Root">&nbsp;Navigate</div>
<div id="selection" class="tlabel"></div>
</div>
<div class="tool">
<div id="help">Labels</div>
<div id="legendTarget" style="min-width: 492.7px; min-height: 50px; margin-top:-10px"></div>
</div>
</div>
<div id="contextmenu">
<div data-key="32"><span class="expcol">Expand</span> Node</div>
<div data-key="13">Expand 1 Level</div>
<div data-key="-13">Expand Full Tree</div>
<div data-key="35">Center This Node</div>
<div data-key="36">Center Root</div>
<div data-key="191">Set Root</div>
</div>
</div>
</template>
<script>
import { EventBus } from '../main.js'
import * as greadability from '../greadability.js'
import * as Plotly from 'plotly.js'
import * as d3Base from 'd3'
import $ from 'jquery'
// attach all d3 plugins to the d3 library
const d3v5 = Object.assign(d3Base)
export default {
name: 'FeatureSpaceOverview',
data () {
return {
colorsReceive: [],
activeLeaf: -1,
overallData: [],
keepRoot: 1
}
},
methods: {
reset () {
var svg = d3.select("#tree-container");
svg.selectAll("*").remove();
var svg = d3.select("#legendTarget")
svg.selectAll("*").remove();
},
// Get JSON data
initializeRadialTree() {
var svg = d3.select("#legendTarget")
svg.selectAll("*").remove();
var svg = d3.select("#tree-container");
svg.selectAll("*").remove();
var features = this.colorsReceive
var activeLeafLoc = this.activeLeaf
var listofNodes = this.overallData[34]
var featuresQuad1 = []
var featuresQuad2 = []
var featuresQuad3 = []
var featuresQuad4 = []
var featuresQuad5 = []
for (let i = 0; i < features[4].length; i++) {
featuresQuad1.push({"name": features[0][i].key,
"children": [
{"name": "r", "lin_color": features[0][i].value[0].valueIns},
{"name": "r_E", "lin_color": features[0][i].value[1].valueIns},
{"name": "r_2", "lin_color": features[0][i].value[2].valueIns},
{"name": "r_10", "lin_color": features[0][i].value[3].valueIns},
],
"lin_color": features[0][i].value[0].valueIns+features[0][i].value[1].valueIns+features[0][i].value[3].valueIns+features[0][i].value[3].valueIns
})
featuresQuad2.push({"name": features[1][i].key,
"children": [
{"name": "r", "lin_color": features[1][i].value[0].valueIns},
{"name": "r_E", "lin_color": features[1][i].value[1].valueIns},
{"name": "r_2", "lin_color": features[1][i].value[2].valueIns},
{"name": "r_10", "lin_color": features[1][i].value[3].valueIns},
],
"lin_color": features[1][i].value[0].valueIns+features[1][i].value[1].valueIns+features[1][i].value[3].valueIns+features[1][i].value[3].valueIns
})
featuresQuad3.push({"name": features[2][i].key,
"children": [
{"name": "r", "lin_color": features[2][i].value[0].valueIns},
{"name": "r_E", "lin_color": features[2][i].value[1].valueIns},
{"name": "r_2", "lin_color": features[2][i].value[2].valueIns},
{"name": "r_10", "lin_color": features[2][i].value[3].valueIns},
],
"lin_color": features[2][i].value[0].valueIns+features[2][i].value[1].valueIns+features[2][i].value[3].valueIns+features[2][i].value[3].valueIns
})
featuresQuad4.push({"name": features[3][i].key,
"children": [
{"name": "r", "lin_color": features[3][i].value[0].valueIns},
{"name": "r_E", "lin_color": features[3][i].value[1].valueIns},
{"name": "r_2", "lin_color": features[3][i].value[2].valueIns},
{"name": "r_10", "lin_color": features[3][i].value[3].valueIns},
],
"lin_color": features[3][i].value[0].valueIns+features[3][i].value[1].valueIns+features[3][i].value[3].valueIns+features[3][i].value[3].valueIns
})
featuresQuad5.push({"name": features[4][i].key,
"children": [
{"name": "r", "lin_color": features[4][i].value[0].valueIns},
{"name": "r_E", "lin_color": features[4][i].value[1].valueIns},
{"name": "r_2", "lin_color": features[4][i].value[2].valueIns},
{"name": "r_10", "lin_color": features[4][i].value[3].valueIns},
],
"lin_color": features[4][i].value[0].valueIns+features[4][i].value[1].valueIns+features[4][i].value[3].valueIns+features[4][i].value[3].valueIns
})
}
var spaceSlice1=0
var spaceSlice2 = 0
var spaceSlice3 = 0
var spaceSlice4 = 0
var spaceSlice5 = 0
for (let i = 0; i < featuresQuad5.length; i++) {
spaceSlice4 = spaceSlice4 + featuresQuad4[i].lin_color
spaceSlice5 = spaceSlice5 + featuresQuad5[i].lin_color
spaceSlice1 = spaceSlice1 + featuresQuad1[i].lin_color
spaceSlice2 = spaceSlice2 + featuresQuad2[i].lin_color
spaceSlice3 = spaceSlice3 + featuresQuad3[i].lin_color
}
var treeData = {
"name": "Data",
"children": [
{"name": "Worst",
"children": featuresQuad4,
"lin_color": spaceSlice4
},
{"name": "All",
"children": featuresQuad5,
"lin_color": spaceSlice5
},
{"name": "Best",
"children": featuresQuad1,
"lin_color": spaceSlice1
},
{"name": "Good",
"children": featuresQuad2,
"lin_color": spaceSlice2
},
{"name": "Bad",
"children": featuresQuad3,
"lin_color": spaceSlice3
},
]
}
var DURATION = 700; // d3 animation duration
var STAGGERN = 4; // delay for each node
var STAGGERD = 200; // delay for each depth
var NODE_DIAMETER = 8; // diameter of circular nodes
var MIN_ZOOM = 0.5; // minimum zoom allowed
var MAX_ZOOM = 10; // maximum zoom allowed
var HAS_CHILDREN_COLOR = 'lightsteelblue';
var SELECTED_COLOR = 'yellow'; // color of selected node
var ZOOM_INC = 0.04; // zoom factor per animation frame
var PAN_INC = 3; // pan per animation frame
var ROT_INC = 0.3; // rotation per animation frame
var counter = 0; // node ids
var curNode; // currently selected node
var curPath; // array of nodes in the path to the currently selected node
// size of the diagram
var width = 820;
var height = 820;
// current pan, zoom, and rotation
var curX = width / 2;
var curY = height / 2;
var curZ = 1.28; // current zoom
var curR = 270; // current rotation
// keyboard key codes
var KEY_PLUS = 187; // + (zoom in)
var KEY_MINUS = 189; // - (zoom out)
var KEY_SLASH = 191; // / (slash)
var KEY_PAGEUP = 33; // (rotate CCW)
var KEY_PAGEDOWN = 34; // (rotate CW)
var KEY_LEFT = 37; // left arrow
var KEY_UP = 38; // up arrow
var KEY_RIGHT = 39; // right arrow
var KEY_DOWN = 40; // down arrow
var KEY_SPACE = 32; // (expand node)
var KEY_RETURN = 13; // (expand tree)
var KEY_HOME = 36; // (center root)
var KEY_END = 35; // (center selection)
// d3 diagonal projection for use by the node paths
var diagonal= d3.svg.diagonal.radial()
.projection(function(d) {
return [d.y, d.x / 180 * Math.PI];
});
// d3 tree layout
var tree = d3.layout.tree()
// .nodeSize([4.5, 120])
.size([360, Math.min(width, height) / 2 - 120])
.separation(function(a, b) {
return a.depth === 0 ? 1 : (a.parent === b.parent ? 1 : 2) / a.depth;
});
// define the svgBase, attaching a class for styling and the zoomListener
var svgBase = d3.select('#tree-container').append('svg')
.attr('width', width)
.attr('height', height)
.on('mousedown', mousedown);
// Group which holds all nodes and manages pan, zoom, rotate
var svgGroup = svgBase.append('g')
.attr('transform', 'translate(' + curX + ',' + curY + ')');
d3.select(document) // set up document events
.on('wheel', wheel) // zoom, rotate
.on('keydown', keydown)
.on('keyup', keyup);
d3.select(window).on('resize', resize);
d3.selectAll('.button')
.on('mousedown', tooldown)
.on('mouseup', toolup);
d3.select('#selection').on('mousedown', switchroot);
d3.select('#contextmenu').on('mouseup', menuSelection);
// Define the data root
var root = treeData;
root.x0 = curY;
root.y0 = 0;
selectNode(treeData.children[this.keepRoot]); // current selected node
// Collapse all children of root's children before rendering
// if (root.children) {
// root.children.forEach(function(child) {
// collapseTree(child);
// });
// }
update(root, true); // Layout the tree initially and center on the root node
// update the tree
// source - source node of the update
// transition - whether to do a transition
function update(source, transition) {
var duration = transition ?
(d3.event && d3.event.altKey ? DURATION * 4 : DURATION) : 0;
// Compute the new tree layout.
var nodes = tree.nodes(root);
var links = tree.links(nodes);
// Update the view
svgGroup.transition().duration(duration)
.attr('transform',
'rotate(' + curR + ' ' + curX + ' ' + curY +
')translate(' + curX + ' ' + curY +
')scale(' + curZ + ')');
// Update the nodes…
var node = svgGroup.selectAll('g.node')
.data(nodes, function(d) {
return d.id || (d.id = ++counter);
});
// Enter any new nodes at the parent's previous position
var nodeEnter = node.enter().insert('g', ':first-child')
.attr('class', 'node')
.attr('transform', 'rotate(' + (source.x0 - 90) + ')translate(' + source.y0 + ')')
.on('click', click)
.on('dblclick', dblclick)
.on('contextmenu', showContextMenu);
// .on('mousedown', suppress);
//d3.select('#some-id').dispatch('click');
nodeEnter.append('circle')
.attr('r', 1e-6)
.style('fill', function(d) {
return d._children ? HAS_CHILDREN_COLOR : 'white';
});
nodeEnter.append('text')
.text(function(d) {
return d.name;
})
.style('opacity', 0.9)
.style('fill-opacity', 0)
.attr('transform', function() {
return ((source.x0 + curR) % 360 <= 180 ?
'translate(8)scale(' :
'rotate(180)translate(-8)scale('
) + reduceZ() + ')';
});
// update existing graph nodes
// Change the circle fill depending on whether it has children and is collapsed
node.select('circle')
.attr('r', NODE_DIAMETER * reduceZ())
.style('fill', function(d) {
if (d.name == 'Data') {
return d._children ? 'white' : 'white';
} else {
if (activeLeafLoc != -1) {
if (d.name == listofNodes[activeLeafLoc]) {
return d._children ? 'yellow' : 'yellow'
}
else {
return d._children ? '#D3D3D3' : '#D3D3D3'
}
} else {
return d._children ? '#D3D3D3' : '#D3D3D3'
}
}
}).attr('stroke', function(d) {
if(d.name == 'Data') {
return d.selected ? SELECTED_COLOR : 'white';
} else {
return d.selected ? SELECTED_COLOR : 'black';
}
}).attr('stroke-width', function(d) {
if(d.name == 'Data') {
return d.selected ? 0 : 0;
} else if (d.name == 'All' || d.name == 'Best' || d.name == 'Good' || d.name == 'Bad' || d.name == 'Worst'){
return d.selected ? 2 : 2;
} else {
return d.selected ? 0 : 0;
}
});
node.select('text')
.attr('text-anchor', function(d) {
return (d.x + curR) % 360 <= 180 ? 'start' : 'end';
}).attr('transform', function(d) {
return ((d.x + curR) % 360 <= 180 ?
'translate(8)scale(' :
'rotate(180)translate(-8)scale('
) + reduceZ() +')';
}).attr('fill', function(d) {
return d.selected ? 'black' : 'black';
}).attr('dy', '.35em');
var nodeUpdate = node.transition().duration(duration)
.delay( transition ? function(d, i) {
return i * STAGGERN +
Math.abs(d.depth - curNode.depth) * STAGGERD; } : 0)
.attr('transform', function(d) {
return 'rotate(' + (d.x - 90) + ')translate(' + d.y + ')';
});
nodeUpdate.select('circle')
.attr('r', NODE_DIAMETER * reduceZ());
// .style('fill', function(d) {
// return d._children ? HAS_CHILDREN_COLOR : 'white';
// });
nodeUpdate.select('text')
.style('fill-opacity', 1);
// Transition exiting nodes to the parent's new position and remove
var nodeExit = node.exit().transition().duration(duration)
.delay( transition ? function(d, i) {
return i * STAGGERN; } : 0)
.attr('transform', function() {
return 'rotate(' + (source.x - 90) +')translate(' + source.y + ')';
}).remove();
nodeExit.select('circle').attr('r', 0);
nodeExit.select('text').style('fill-opacity', 0);
// Update the links…
var link = svgGroup.selectAll('path.link')
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position
link.enter().insert('path', 'g')
.attr('class', 'link')
.attr('d', function() {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).style("stroke", function(d){
if (d.target.lin_color > 0) {
return '#33a02c'
} else if (d.target.lin_color < 0) {
return '#e31a1c'
} else {
return '#D3D3D3'
}
})
// Transition links to their new position
link.transition().duration(duration)
.delay( transition ? function(d, i) {
return i * STAGGERN +
Math.abs(d.source.depth - curNode.depth) * STAGGERD;
// Math.max(0, d.source.depth - curNode.depth) * STAGGERD;
} : 0)
.attr('d', diagonal);
// Transition exiting nodes to the parent's new position
link.exit().transition().duration(duration)
.attr('d', function() {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).remove();
// Stash the old positions for transition
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
} // end update
// Helper functions for collapsing and expanding nodes
// Toggle expand / collapse
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
d.children = d._children;
d._children = null;
}
}
function toggleTree(d) {
if (d.children) {
collapseTree(d);
} else {
expandTree(d);
}
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
}
}
// expand all children, whether expanded or collapsed
function expandTree(d) {
if (d._children) {
d.children = d._children;
d._children = null;
}
if (d.children) {
d.children.forEach(expandTree);
}
}
function collapse(d) {
if (d.children) {
d._children = d.children;
d.children = null;
}
}
// collapse all children
function collapseTree(d) {
if (d.children) {
d._children = d.children;
d.children = null;
}
if (d._children) {
d._children.forEach(collapseTree);
}
}
// expand one level of tree
function expand1Level(d) {
var q = [d]; // non-recursive
var cn;
var done = null;
while (q.length > 0) {
cn = q.shift();
if (done !== null && done < cn.depth) { return; }
if (cn._children) {
done = cn.depth;
cn.children = cn._children;
cn._children = null;
cn.children.forEach(collapse);
}
if (cn.children) { q = q.concat(cn.children); }
}
// no nodes to open
}
// highlight selected node
function selectNode(node) {
if (curNode) {
delete curNode.selected;
}
curNode = node;
curNode.selected = true;
curPath = []; // filled in by fullpath
d3.select('#selection').html(fullpath(node));
}
// for displaying full path of node in tree
function fullpath(d, idx) {
idx = idx || 0;
curPath.push(d);
return '/<span class="nodepath'+
'" data-sel="'+ idx +'" title="Set Root to '+ d.name +'">' +
d.name + '</span>';
}
// d3 event handlers
function switchroot() {
d3.event.preventDefault();
var pathelms = document.querySelectorAll('#selection .nodepath');
for (var i = 0; i < pathelms.length; i++) {
pathelms[i].classList.remove('highlight');
}
var target = d3.event.target;
var node = curPath[+target.dataset.sel];
if (d3.event.shiftKey) {
if (curNode !== node) {
selectNode(node);
}
} else {
root = node;
target.classList.add('highlight');
}
update(root, true);
}
function resize() { // window resize
var oldwidth = width;
var oldheight = height;
width = window.innerWidth - 20;
height = window.innerHeight - 20;
tree.size([360, Math.min(width, height) / 2 - 120]);
svgBase.attr('width', width).attr('height', height);
curX += (width - oldwidth) / 2;
curY += (height - oldheight) / 2;
svgGroup.attr('transform', 'rotate(' + curR + ' ' + curX + ' ' + curY +
')translate(' + curX + ' ' + curY + ')scale(' + curZ + ')');
update(root);
}
function click(d) { // select node
if (d3.event.defaultPrevented || d === curNode) { return; } // suppressed
d3.event.preventDefault();
selectNode(d);
update(d);
var sendSliceID = 4
var rootID = 1
if (d.name == "Best") {
sendSliceID = 0
rootID = 2
} else if (d.name == "Good") {
sendSliceID = 1
rootID = 3
} else if (d.name == "Bad") {
sendSliceID = 2
rootID = 4
} else if (d.name == "Worst") {
sendSliceID = 3
rootID = 0
} else {
sendSliceID = 4
rootID = 1
}
EventBus.$emit('keepRootFun', rootID)
EventBus.$emit('updateSlice', sendSliceID)
}
function dblclick(d) { // Toggle children of node
if (d3.event.defaultPrevented) { return; } // click suppressed
d3.event.preventDefault();
if (d3.event.shiftKey) {
expand1Level(d); // expand node by one level
} else {
toggle(d);
}
update(d, true);
}
function tooldown(d) { // tool button pressed
d3.event.preventDefault();
d3.select(d3.event.target).on('mouseout', toolup);
var key = +d3.event.target.dataset.key;
keydown(Math.abs(key), key < 0 || d3.event.shiftKey);
}
function toolup() { // tool button released
d3.event.preventDefault();
d3.select(d3.event.target).on('mouseout', null);
keyup(Math.abs(+d3.event.target.dataset.key));
}
// right click, show context menu and select this node
function showContextMenu(d) {
d3.event.preventDefault();
d3.selectAll('.expcol').text(d.children ? 'Collapse' : 'Expand');
d3.select('#contextmenu').style({
left: (d3.event.pageX + 3) + 'px',
top: (d3.event.pageY + 8) + 'px',
display: 'block'
});
d3.select(document).on('mouseup', hideContextMenu);
selectNode(d);
update(d);
}
function hideContextMenu() {
d3.select('#contextmenu').style('display', 'none');
d3.select(document).on('mouseup', null);
}
function menuSelection() {
d3.event.preventDefault();
var key = +d3.event.target.dataset.key;
keydown(Math.abs(key), key < 0 || d3.event.shiftKey);
}
var startposX, startposY; // initial position on mouse button down for pan
function mousedown() { // pan
d3.event.preventDefault();
if (d3.event.which !== 1 || d3.event.ctrlKey) { return; } // ingore other mouse buttons
startposX = curX - d3.event.clientX;
startposY = curY - d3.event.clientY;
d3.select(document).on('mousemove', mousemove, true);
d3.select(document).on('mouseup', mouseup, true);
}
function mousemove() {
d3.event.preventDefault();
curX = startposX + d3.event.clientX;
curY = startposY + d3.event.clientY;
setview();
}
function mouseup() {
d3.select(document).on('mousemove', null);
d3.select(document).on('mouseup', null);
}
var keysdown = []; // which keys are currently down
var moveX = 0, moveY = 0, moveZ = 0, moveR = 0; // animations
var aniRequest = null;
function wheel() { // mousewheel
var dz, newZ;
var slow = d3.event.altKey ? 0.25 : 1;
if (d3.event.wheelDeltaY !== 0) { // up-down
dz = Math.pow(1.2, d3.event.wheelDeltaY * 0.001 * slow);
newZ = limitZ(curZ * dz);
dz = newZ / curZ;
curZ = newZ;
curX -= (d3.event.clientX - curX) * (dz - 1);
curY -= (d3.event.clientY - curY) * (dz - 1);
setview();
}
if (d3.event.wheelDeltaX !== 0) { // left-right
curR = limitR(curR + d3.event.wheelDeltaX * 0.01 * slow);
update(root);
}
}
// keyboard shortcuts
function keydown(key, shift) {
if (!key) {
key = d3.event.which; // fake key
shift = d3.event.shiftKey;
}
var parch; // parent's children
var slow = d3.event.altKey ? 0.25 : 1;
if (keysdown.indexOf(key) >= 0) { return; } // defeat auto repeat
switch (key) {
case KEY_PLUS: // zoom in
moveZ = ZOOM_INC * slow;
break;
case KEY_MINUS: // zoom out
moveZ = -ZOOM_INC * slow;
break;
case KEY_SLASH: // toggle root to selection
root = root === curNode ? treeData : curNode;
update(root, true);
curPath = []; // filled in by fullpath
d3.select('#selection').html(fullpath(curNode));
return;
case KEY_PAGEUP: // rotate counterclockwise
moveR = -ROT_INC * slow;
break;
case KEY_PAGEDOWN: // zoom out
moveR = ROT_INC * slow; // rotate clockwise
break;
case KEY_LEFT: // left arrow
if (shift) { // move selection to parent
if (!curNode) {
selectNode(root);
} else if (curNode.parent) {
selectNode(curNode.parent);
}
update(curNode);
return;
}
moveX = -PAN_INC * slow;
break;
case KEY_UP: // up arrow
if (shift) { // move selection to previous child
if (!curNode) {
selectNode(root);
} else if (curNode.parent) {
parch = curNode.parent.children;
selectNode(parch[(parch.indexOf(curNode) +
parch.length - 1) % parch.length]);
}
update(curNode);
return;
}
moveY = -PAN_INC * slow;
break;
case KEY_RIGHT: // right arrow
if (shift) { // move selection to first/last child
if (!curNode) {
selectNode(root);
} else {
if (curNode.children) {
selectNode(curNode.children[d3.event.altKey ?
curNode.children.length - 1 : 0]);
}
}
update(curNode);
return;
}
moveX = PAN_INC * slow;
break;
case KEY_DOWN: // down arrow
if (shift) { // move selection to next child
if (!curNode) {
selectNode(root);
} else if (curNode.parent) {
parch = curNode.parent.children;
selectNode(parch[(parch.indexOf(curNode) + 1) % parch.length]);
}
update(curNode);
return;
}
moveY = PAN_INC * slow;
break;
case KEY_SPACE: // expand/collapse node
if (!curNode) {
selectNode(root);
}
toggle(curNode);
update(curNode, true);
return;
case KEY_RETURN: // expand/collapse tree
if (!curNode) {
selectNode(root);
}
if (shift) {
expandTree(curNode);
} else {
expand1Level(curNode);
}
update(curNode, true);
return;
case KEY_HOME: // reset transform
if (shift) {
root = treeData;
}
curX = width / 2;
curY = height / 2;
curR = limitR(90 - root.x);
curZ = 1;
update(root, true);
return;
case KEY_END: // zoom to selection
if (!curNode) { return; }
curX = width / 2 - curNode.y * curZ;
curY = height / 2;
curR = limitR(90 - curNode.x);
update(curNode, true);
return;
default: return; // ignore other keys
} // break jumps to here
keysdown.push(key);
// start animation if anything happening
if (keysdown.length > 0 && aniRequest === null) {
aniRequest = requestAnimationFrame(frame);
}
}
function keyup(key) {
key = key || d3.event.which;
var pos = keysdown.indexOf(key);
if (pos < 0) { return; }
switch (key) {
case KEY_PLUS: // zoom out
case KEY_MINUS: // zoom in
moveZ = 0;
break;
case KEY_PAGEUP: // rotate CCW
case KEY_PAGEDOWN: // rotate CW
moveR = 0;
break;
case KEY_LEFT: // left arrow
case KEY_RIGHT: // right arrow
moveX = 0;
break;
case KEY_UP: // up arrow
case KEY_DOWN: // down arrow
moveY = 0;
break;
}
keysdown.splice(pos, 1); // remove key
if (keysdown.length > 0 || aniRequest === null) { return; }
cancelAnimationFrame(aniRequest);
aniRequest = aniTime = null;
}
var aniTime = null;
// update animation frame
function frame(frametime) {
var diff = aniTime ? (frametime - aniTime) / 16 : 0;
aniTime = frametime;
var dz = Math.pow(1.2, diff * moveZ);
var newZ = limitZ(curZ * dz);
dz = newZ / curZ;
curZ = newZ;
curX += diff * moveX - (width / 2- curX) * (dz - 1);
curY += diff * moveY - (height / 2 - curY) * (dz - 1);
curR = limitR(curR + diff * moveR);
setview();
aniRequest = requestAnimationFrame(frame);
}
// enforce zoom extent
function limitZ(z) {
return Math.max(Math.min(z, MAX_ZOOM), MIN_ZOOM);
}
// keep rotation between 0 and 360
function limitR(r) {
return (r + 360) % 360;
}
// limit size of text and nodes as scale increases
function reduceZ() {
return Math.pow(1.1, -curZ);
}
// set view with no animation
function setview() {
svgGroup.attr('transform', 'rotate(' + curR + ' ' + curX + ' ' + curY +
')translate(' + curX + ' ' + curY + ')scale(' + curZ + ')');
svgGroup.selectAll('text')
.attr('text-anchor', function(d) {
return (d.x + curR) % 360 <= 180 ? 'start' : 'end';
})
.attr('transform', function(d) {
return ((d.x + curR) % 360 <= 180 ?
'translate(8)scale(' :
'rotate(180)translate(-8)scale('
) + reduceZ() +')';
});
svgGroup.selectAll('circle').attr('r', NODE_DIAMETER * reduceZ());
}
var legendRectSize = 14;
var legendSpacing = 3;
var color = d3v5.scaleOrdinal(d3v5.schemeDark2)
var labelsData = JSON.parse(this.overallData[1])
var svgLegend = d3v5.select('#legendTarget').append('svg')
.attr('width', 130)
.attr('height', 60)
var legend = svgLegend.selectAll('.legend') // NEW
.data(labelsData) // NEW
.enter() // NEW
.append('g') // NEW
.attr('class', 'legend') // NEW
.attr('transform', function(d, i) { // NEW
var height = legendRectSize + legendSpacing; // NEW
var offset = height * color.domain().length / 2;
var horz = 25 // NEW
var vert = i * height - offset; // NEW
return 'translate(' + horz + ',' + vert + ')'; // NEW
}); // NEW
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function (d) { return color(d) });
legend.append('text')
.attr('class', 'legendLab')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
}
},
mounted () {
EventBus.$on('keepRootFun', data => { this.keepRoot = data })
EventBus.$on('quad', data => { this.overallData = data })
EventBus.$on('overviewCall', data => { this.colorsReceive = data })
EventBus.$on('overviewCall', this.initializeRadialTree)
EventBus.$on('brushLink', data => { this.activeLeaf = data })
EventBus.$on('brushLink', this.initializeRadialTree)
EventBus.$on('reset', this.reset)
}
}
</script>
<style>
text {
font-family: sans-serif;
}
svg {
display: block;
}
#toolbar {
display: -webkit-box;
display: moz-box-flex;
display: -ms-flexbox;
display: -webkit-flex;
flex-wrap: wrap;
top: 10px;
margin-top: 25px;
left: 10px;
font-family: sans-serif;
box-shadow: 1px 1px 3px;
background-color: #ECEFF1;
}
.tool {
display: flex;
flex-direction: column;
border: solid black 1px;
padding: 1px;
}
.tlabel {
text-align: center;
padding: 3px 2px 1px 2px;
}
.tbuttons {
display: flex;
display: -webkit-box;
display: moz-box-flex;
display: -ms-flexbox;
display: -webkit-flex;
flex-wrap: wrap;
flex-direction: row;
}
.button {
border: outset gray 2px;
border-radius: 4px;
cursor: pointer;
padding: 1px 3px;
min-width: 12px;
text-align: center;
margin: 2px;
font-family: "Courier New", Courier, monospace;
background-color: white;
}
.button:hover {
background-color: yellow;
}
.button:active {
border: inset gray 2px;
}
#selection {
margin: 2px 5px;
vertical-align: middle;
font-family: "Courier New", Courier, monospace;
color: black;
background-color: white;
}
#selection span.nodepath:hover {
background-color: rgba(0, 0, 25, 0.1);
cursor: pointer;
}
#selection span.nodepath.highlight {
font-weight: bold;
}
#contextmenu {
display: none;
position: absolute;
border: solid black 1px;
box-shadow: 2px 2px 3px gray;
padding: 2px;
background-color: white;
font-family: Roboto, Helvetica, sans-serif;
font-size: 0.9em;
}
#contextmenu div {
padding: 4px 10px;
cursor: pointer;
}
#contextmenu div:hover {
background-color: rgba(0, 0, 25, 0.1);
}
#contextmenu span.expcol {
pointer-events: none;
}
#help {
position: relative;
bottom: -1.7em;
transform: rotate(270deg);
-webkit-transform: rotate(270deg);
-moz-transform: rotate(270deg);
-o-transform: rotate(270deg);
width: 100px;
text-align: center;
margin-top: -10px;
margin-left: -38px;
}
#help a {
color: black;
text-decoration: none;
background-color: white;
}
#help:hover a {
background-color: yellow;
}
#tree-container, svg {
overflow: hidden;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
}
.node text {
font-size: 12px;
font-family: Roboto, sans-serif;
text-shadow: 4px 4px 3px white, -4px -4px 3px white;
}
.node text:hover {
font-size: 1.2em;
transition: font-size 0.1s;
}
.node text:not(:hover) {
transition: font-size 1s;
transition-delay: 0.5s;
}
.link {
fill: none;
stroke: #D3D3D3;
}
.legendLab {
font-size: 14px !important;
}
</style>