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.
691 lines
25 KiB
691 lines
25 KiB
<template>
|
|
<div class="column">
|
|
<div id="FeatureGraph"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { EventBus } from '../main.js'
|
|
import * as greadability from '../greadability.js'
|
|
import * as d3Base from 'd3'
|
|
|
|
// attach all d3 plugins to the d3 library
|
|
const d3 = Object.assign(d3Base)
|
|
|
|
export default {
|
|
name: 'FeatureSpaceDetail',
|
|
data () {
|
|
return {
|
|
dataFS: [],
|
|
dataFSTrans: [],
|
|
quadrantNumber: 5,
|
|
jsonData: [],
|
|
corrMatrixComb: [],
|
|
corrMatrixCombTotal: [],
|
|
VIFRemaining: [],
|
|
MIRemaining: [],
|
|
computeNodes: 0
|
|
}
|
|
},
|
|
methods: {
|
|
initializeNetwork () {
|
|
|
|
var quadrantNumberLocal = this.quadrantNumber
|
|
var listofNodes = this.dataFS[0]
|
|
var dataLoc = JSON.parse(this.dataFS[quadrantNumberLocal+2])
|
|
var pushEachFinal = []
|
|
var pushEach
|
|
var oldVal
|
|
var newVal
|
|
var outcome
|
|
var countLoc
|
|
|
|
var corrMatrixCombLoc =[]
|
|
var corrMatrixCombTotalLoc = []
|
|
var VIFRemainingLoc = []
|
|
var MIRemainingLoc = []
|
|
|
|
this.dataFSTrans.forEach(function(element, index) {
|
|
var round = element.round
|
|
corrMatrixCombLoc.push(Object.values(JSON.parse(round[quadrantNumberLocal*2-1])))
|
|
corrMatrixCombTotalLoc.push(Object.values(JSON.parse(round[quadrantNumberLocal*3-1]))[0] * 100)
|
|
VIFRemainingLoc.push(Object.values(JSON.parse(round[quadrantNumberLocal*4-1]))[0])
|
|
MIRemainingLoc.push(JSON.parse(round[quadrantNumberLocal*5-1]))
|
|
round = JSON.parse(round[quadrantNumberLocal-1])
|
|
oldVal = 0
|
|
newVal = 0
|
|
outcome = 0
|
|
countLoc = 0
|
|
pushEach = []
|
|
Object.entries(round).forEach(
|
|
function ([feature, value]) {
|
|
var key = Object.keys(value)
|
|
var search = Object.values(dataLoc[key])
|
|
oldVal = Math.abs(search[countLoc]) + oldVal
|
|
newVal = Math.abs(Object.values(value)[0]) + newVal
|
|
countLoc++
|
|
})
|
|
oldVal = oldVal / listofNodes.length
|
|
newVal = newVal / listofNodes.length
|
|
outcome = oldVal - newVal
|
|
pushEach.push({keyIns: "round", valueIns: outcome})
|
|
|
|
var roundLogE = element.roundLogE
|
|
corrMatrixCombLoc.push(Object.values(JSON.parse(roundLogE[quadrantNumberLocal*2-1])))
|
|
corrMatrixCombTotalLoc.push(Object.values(JSON.parse(roundLogE[quadrantNumberLocal*3-1]))[0] * 100)
|
|
VIFRemainingLoc.push(Object.values(JSON.parse(roundLogE[quadrantNumberLocal*4-1]))[0])
|
|
MIRemainingLoc.push(JSON.parse(roundLogE[quadrantNumberLocal*5-1]))
|
|
roundLogE = JSON.parse(roundLogE[quadrantNumberLocal-1])
|
|
oldVal = 0
|
|
newVal = 0
|
|
outcome = 0
|
|
countLoc = 0
|
|
Object.entries(roundLogE).forEach(
|
|
function ([feature, value]) {
|
|
var key = Object.keys(value)
|
|
var search = Object.values(dataLoc[key])
|
|
oldVal = Math.abs(search[countLoc]) + oldVal
|
|
newVal = Math.abs(Object.values(value)[0]) + newVal
|
|
countLoc++
|
|
})
|
|
oldVal = oldVal / listofNodes.length
|
|
newVal = newVal / listofNodes.length
|
|
outcome = oldVal - newVal
|
|
pushEach.push({keyIns: "roundLogE", valueIns: outcome})
|
|
|
|
var roundLog2 = element.roundLog2
|
|
corrMatrixCombLoc.push(Object.values(JSON.parse(roundLog2[quadrantNumberLocal*2-1])))
|
|
corrMatrixCombTotalLoc.push(Object.values(JSON.parse(roundLog2[quadrantNumberLocal*3-1]))[0] * 100)
|
|
VIFRemainingLoc.push(Object.values(JSON.parse(roundLog2[quadrantNumberLocal*4-1]))[0])
|
|
MIRemainingLoc.push(JSON.parse(roundLog2[quadrantNumberLocal*5-1]))
|
|
roundLog2 = JSON.parse(roundLog2[quadrantNumberLocal-1])
|
|
oldVal = 0
|
|
newVal = 0
|
|
outcome = 0
|
|
countLoc = 0
|
|
Object.entries(roundLog2).forEach(
|
|
function ([feature, value]) {
|
|
var key = Object.keys(value)
|
|
var search = Object.values(dataLoc[key])
|
|
oldVal = Math.abs(search[countLoc]) + oldVal
|
|
newVal = Math.abs(Object.values(value)[0]) + newVal
|
|
countLoc++
|
|
})
|
|
oldVal = oldVal / listofNodes.length
|
|
newVal = newVal / listofNodes.length
|
|
outcome = oldVal - newVal
|
|
pushEach.push({keyIns: "roundLog2", valueIns: outcome})
|
|
|
|
var roundLog10 = element.roundLog10
|
|
corrMatrixCombLoc.push(Object.values(JSON.parse(roundLog10[quadrantNumberLocal*2-1])))
|
|
corrMatrixCombTotalLoc.push(Object.values(JSON.parse(roundLog10[quadrantNumberLocal*3-1]))[0] * 100)
|
|
VIFRemainingLoc.push(Object.values(JSON.parse(roundLog10[quadrantNumberLocal*4-1]))[0])
|
|
MIRemainingLoc.push(JSON.parse(roundLog10[quadrantNumberLocal*5-1]))
|
|
roundLog10 = JSON.parse(roundLog10[quadrantNumberLocal-1])
|
|
oldVal = 0
|
|
newVal = 0
|
|
outcome = 0
|
|
countLoc = 0
|
|
Object.entries(roundLog10).forEach(
|
|
function ([feature, value]) {
|
|
var key = Object.keys(value)
|
|
var search = Object.values(dataLoc[key])
|
|
oldVal = Math.abs(search[countLoc]) + oldVal
|
|
newVal = Math.abs(Object.values(value)[0]) + newVal
|
|
countLoc++
|
|
})
|
|
oldVal = oldVal / listofNodes.length
|
|
newVal = newVal / listofNodes.length
|
|
outcome = oldVal - newVal
|
|
pushEach.push({keyIns: "roundLog10", valueIns: outcome})
|
|
pushEachFinal.push({key: listofNodes[index], value: pushEach})
|
|
})
|
|
|
|
this.corrMatrixComb = [...corrMatrixCombLoc]
|
|
this.corrMatrixCombTotal = [...corrMatrixCombTotalLoc]
|
|
this.VIFRemaining = [...VIFRemainingLoc]
|
|
this.MIRemaining = [...MIRemainingLoc]
|
|
|
|
var nodes = []
|
|
var groupID = 0
|
|
listofNodes.forEach( function(element) {
|
|
groupID++
|
|
nodes.push({"name": element, "group": groupID+"_root"})
|
|
})
|
|
|
|
var links = []
|
|
|
|
Object.entries(dataLoc).forEach(
|
|
function ([feature, value]) {
|
|
Object.entries(value).forEach( function ([featureInside, value]) {
|
|
if (feature != featureInside) {
|
|
if (value >= 0) {
|
|
links.push({"source": listofNodes.indexOf(feature), "target": listofNodes.indexOf(featureInside), "value": Math.abs(value) * 30, "lin_color": "#D3D3D3"})
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
groupID = 0
|
|
listofNodes.forEach( function(element) {
|
|
groupID++
|
|
nodes.push(
|
|
{"name": element+"_r", "group": groupID},
|
|
{"name": element+"_r_E", "group": groupID},
|
|
{"name": element+"_r_2", "group": groupID},
|
|
{"name": element+"_r_10", "group": groupID},
|
|
)
|
|
})
|
|
|
|
Object.entries(pushEachFinal).forEach(
|
|
function ([index, feature]) {
|
|
feature.value.forEach(function (element, indexIns) {
|
|
if (element.valueIns > 0) {
|
|
links.push({"source": index, "target": index*feature.value.length+feature.value.length+indexIns, "value": Math.abs(element.valueIns) * 30, "lin_color": "#4393c3"})
|
|
}
|
|
else {
|
|
links.push({"source": index, "target": index*feature.value.length+feature.value.length+indexIns, "value": Math.abs(element.valueIns) * 30, "lin_color": "#d6604d"})
|
|
}
|
|
})
|
|
})
|
|
|
|
this.jsonData = {"nodes": nodes, "links": links};
|
|
|
|
this.graphVizualization()
|
|
|
|
},
|
|
graphVizualization () {
|
|
|
|
var computeNodesVar = this.computeNodes
|
|
var listofNodes = this.dataFS[0]
|
|
var corrTarget = JSON.parse(this.dataFS[this.quadrantNumber*2+2])
|
|
var corrGlob = JSON.parse(this.dataFS[this.quadrantNumber*3+2])
|
|
var uniqueTarget = JSON.parse(this.dataFS[this.quadrantNumber*4+2])
|
|
var VIFVar = JSON.parse(this.dataFS[this.quadrantNumber*5+2])
|
|
var MIVar = JSON.parse(this.dataFS[this.quadrantNumber*6+2])
|
|
MIVar = MIVar.concat(this.MIRemaining)
|
|
var colorCateg = d3.scaleOrdinal(d3.schemeAccent)
|
|
|
|
var corrTargetFormatted = []
|
|
for (let i = 0; i < Object.keys(corrTarget).length; i++) {
|
|
var corrTargetFormattedLoc = []
|
|
for (let j = 0; j < Object.keys(corrTarget[i]).length; j++) {
|
|
if (j > uniqueTarget.length - 1) {
|
|
corrTargetFormattedLoc.push(Math.round(Object.values(corrTarget[i])[j] * 100))
|
|
}
|
|
}
|
|
corrTargetFormatted.push(corrTargetFormattedLoc)
|
|
}
|
|
|
|
for (let j = 0; j < uniqueTarget.length; j++) {
|
|
for (let i = 0; i < Object.keys(this.corrMatrixComb).length; i++) {
|
|
if (Math.round(Object.values((Object.values(this.corrMatrixComb[i])[j]))[0] * 100) == null) {
|
|
corrTargetFormatted[j].push(0)
|
|
} else {
|
|
corrTargetFormatted[j].push(Math.round(Object.values((Object.values(this.corrMatrixComb[i])[j]))[0] * 100))
|
|
}
|
|
}
|
|
}
|
|
|
|
var corrGlobFormatted = []
|
|
for (let i = 0; i < Object.keys(corrGlob).length; i++) {
|
|
if (i != 0) {
|
|
corrGlobFormatted.push(Math.round(Object.values(corrGlob)[i]['0'] * 100))
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < this.corrMatrixCombTotal.length; i++) {
|
|
corrGlobFormatted.push(Math.round(this.corrMatrixCombTotal[i]))
|
|
}
|
|
|
|
var VIFVarFormatted = []
|
|
for (let i = 0; i < Object.keys(VIFVar).length; i++) {
|
|
if (i != 0) {
|
|
if (Object.values(VIFVar)[i] > 10) {
|
|
VIFVarFormatted.push(4)
|
|
} else if (Object.values(VIFVar)[i] > 5) {
|
|
VIFVarFormatted.push(2)
|
|
} else {
|
|
VIFVarFormatted.push(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < this.VIFRemaining.length; i++) {
|
|
if (this.VIFRemaining[i] > 10) {
|
|
VIFVarFormatted.push(4)
|
|
} else if (this.VIFRemaining[i] > 5) {
|
|
VIFVarFormatted.push(2)
|
|
} else {
|
|
VIFVarFormatted.push(0)
|
|
}
|
|
}
|
|
|
|
function min(input) {
|
|
if (toString.call(input) !== "[object Array]")
|
|
return false;
|
|
return Math.min.apply(null, input);
|
|
}
|
|
|
|
function max(input) {
|
|
if (toString.call(input) !== "[object Array]")
|
|
return false;
|
|
return Math.max.apply(null, input);
|
|
}
|
|
|
|
function normalize(min, max) {
|
|
var delta = max - min;
|
|
return function (val) {
|
|
return (val - min) / delta;
|
|
};
|
|
}
|
|
|
|
var MIMin = min(MIVar)
|
|
var MIMax = max(MIVar)
|
|
|
|
MIVar = MIVar.map(normalize(MIMin, MIMax))
|
|
|
|
var colorsScaleNodes = d3.scaleOrdinal()
|
|
.domain(d3.ticks([MIMin, MIMax, 10]))
|
|
.range(['#67001f','#b2182b','#d6604d','#f4a582','#fddbc7','#d1e5f0','#92c5de','#4393c3','#2166ac','#053061']);
|
|
|
|
var svg = d3.select("#FeatureGraph");
|
|
svg.selectAll("*").remove();
|
|
|
|
var width = 1000;
|
|
var height = 500;
|
|
|
|
var numTicks = 200;
|
|
|
|
var selectedParams;
|
|
var bestParams;
|
|
|
|
var dispatch = d3.dispatch('layoutend');
|
|
|
|
svg = d3.select("#FeatureGraph").append("svg")
|
|
.attr("width", width)
|
|
.attr("height", height);
|
|
|
|
var graph = this.jsonData
|
|
|
|
var link = svg.append('g')
|
|
.attr('class', 'links')
|
|
.selectAll('line')
|
|
.data(graph.links)
|
|
.enter().append('line')
|
|
.attr("stroke", function(d) { return d.lin_color; })
|
|
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
|
|
|
|
|
|
var node = svg.append('g')
|
|
.attr('class', 'nodes')
|
|
.selectAll('g')
|
|
.data(graph.nodes)
|
|
.enter().append('g')
|
|
.on("click", function(id, index) {
|
|
var groupLoc = index + 1
|
|
var items = document.getElementsByClassName(groupLoc)
|
|
items.forEach( function (it) {
|
|
if (it.style.visibility == "hidden") {
|
|
it.style.visibility = "visible";
|
|
} else {
|
|
it.style.visibility = "hidden";
|
|
}
|
|
})
|
|
});
|
|
|
|
var paramGroups = [
|
|
{name: 'chargeStrength', values: [-30, -80]},
|
|
{name: 'linkDistance', values: [30, -80]},
|
|
{name: 'linkStrength', values: [0, 1]},
|
|
{name: 'gravity', values: [0, 0.5]},
|
|
{name: 'iterations', values: [1, 2]},
|
|
{name: 'alphaDecay', values: [0, 0.0228, 0.05]},
|
|
{name: 'velocityDecay', values: [0.4, 0.8]}
|
|
];
|
|
|
|
var paramList = generateParams(paramGroups);
|
|
|
|
var bestSoFar = d3.select('.best').selectAll('li')
|
|
.data(paramGroups.map(function (d) { return d.name; }))
|
|
.enter().append('li')
|
|
.text(function (d) { return d; });
|
|
|
|
dispatch.on('layoutend', function (params, i) {
|
|
if (!bestParams || params.graphReadability > bestParams.graphReadability) {
|
|
bestParams = params;
|
|
selectedParams = bestParams;
|
|
|
|
bestSoFar
|
|
.data(d3.map(bestParams).keys().filter(function (d) { return d !== 'positions' && d !== 'graphReadability'; }))
|
|
.text(function (d) { return d + ' = ' + bestParams[d]; });
|
|
}
|
|
|
|
});
|
|
|
|
var i = 0;
|
|
var stepper = d3.timer(function () {
|
|
var p = paramList[i];
|
|
var forceSim = getForceSimFromParams(p);
|
|
|
|
// Reset node attributes.
|
|
graph.nodes.forEach(function (n) {
|
|
n.x = n.y = n.vx = n.vy = 0;
|
|
});
|
|
|
|
forceSim.nodes(graph.nodes)
|
|
.stop();
|
|
|
|
forceSim.force('link')
|
|
.links(graph.links);
|
|
|
|
for (var t = 0; t < numTicks; ++t) {
|
|
forceSim.tick();
|
|
}
|
|
|
|
p.graphReadability = greadability.greadability(graph.nodes, graph.links);
|
|
p.graphReadability = (p.graphReadability.crossing + p.graphReadability.crossingAngle +
|
|
p.graphReadability.angularResolutionMin + p.graphReadability.angularResolutionDev) / 4
|
|
|
|
p.positions = graph.nodes.map(function (n) { return {x: n.x, y: n.y}; });
|
|
|
|
dispatch.call('layoutend', forceSim, p, i);
|
|
|
|
++i;
|
|
if (i >= paramList.length) {
|
|
|
|
var widthLoc = 100;
|
|
var arcSize = (6 * widthLoc / 100);
|
|
var innerRadius = arcSize * 2;
|
|
|
|
var svgLoc = node.append('svg').attr('width', widthLoc).attr('height', widthLoc).attr("class", function(d, i) { return d.group; })
|
|
.attr("id", function(d, i) { return "svg" + (i+computeNodesVar); })
|
|
|
|
graph.nodes.forEach(function(itemVal, indexNode) {
|
|
|
|
var data = []
|
|
|
|
for(let k = 0; k < uniqueTarget.length; k++) {
|
|
data.push({value: corrTargetFormatted[k][indexNode], label: '', color: colorCateg(uniqueTarget[k]), lcolor: ''})
|
|
}
|
|
|
|
var length = data.length
|
|
data.push({value: 100, label: corrGlobFormatted[indexNode], color: '#000000', lcolor: colorsScaleNodes(MIVar[indexNode])})
|
|
|
|
|
|
var border = VIFVarFormatted[indexNode]
|
|
|
|
var arcs = data.map(function (obj, i) {
|
|
if (i == length) {
|
|
return d3.arc().innerRadius(i * arcSize + innerRadius).outerRadius((i + 1) * arcSize - (widthLoc / 100) + innerRadius + border);
|
|
} else {
|
|
return d3.arc().innerRadius(i * arcSize + innerRadius).outerRadius((i + 1) * arcSize - (widthLoc / 100) + innerRadius);
|
|
}
|
|
});
|
|
|
|
var arcsGrey = data.map(function (obj, i) {
|
|
return d3.arc().innerRadius(i * arcSize + (innerRadius + ((arcSize / 2) - 2))).outerRadius((i + 1) * arcSize - ((arcSize / 2)) + (innerRadius));
|
|
});
|
|
|
|
var pieData = data.map(function (obj, i) {
|
|
return [
|
|
{value: obj.value, arc: arcs[i], object: obj},
|
|
{value: (100 - obj.value), arc: arcsGrey[i], object: obj},
|
|
{value: 0, arc: arcs[i], object: obj}];
|
|
});
|
|
|
|
var pie = d3.pie().sort(null).value(function (d) {
|
|
return d.value;
|
|
});
|
|
|
|
var id = indexNode + computeNodesVar
|
|
var g = d3.select('#svg'+id).selectAll('g').data(pieData).enter()
|
|
.append('g')
|
|
.attr('transform', 'translate(' + widthLoc / 2 + ',' + widthLoc / 2 + ') rotate(180)');
|
|
var gText = d3.select('#svg'+id).selectAll('g.textClass').data([{}]).enter()
|
|
.append('g')
|
|
.classed('textClass', true)
|
|
.attr('transform', 'translate(' + widthLoc / 2 + ',' + widthLoc / 2 + ') rotate(180)');
|
|
|
|
g.selectAll('path').data(function (d) {
|
|
return pie(d);
|
|
}).enter().append('path')
|
|
.attr('id', function (d, i) {
|
|
if (i == 1) {
|
|
return "Text" + d.data.object.label
|
|
}
|
|
})
|
|
.attr('d', function (d) {
|
|
return d.data.arc(d);
|
|
}).attr('fill', function (d, i) {
|
|
return i == 0 ? d.data.object.color : i == 1 ? '#D3D3D3' : 'none';
|
|
});
|
|
|
|
g.each(function (d, index) {
|
|
|
|
var el = d3.select(this);
|
|
var path = el.selectAll('path').each(function (r, i) {
|
|
if (i === 1) {
|
|
var centroid = r.data.arc.centroid({
|
|
startAngle: r.startAngle + 0.05,
|
|
endAngle: r.startAngle + 0.001 + 0.05
|
|
});
|
|
var lableObj = r.data.object;
|
|
g.append('text')
|
|
.attr('font-size', ((2 * width) / 100))
|
|
.attr('dominant-baseline', 'central')
|
|
.append("textPath")
|
|
.attr("textLength", function (d, i) {
|
|
return 0;
|
|
})
|
|
.attr("startOffset", '5')
|
|
.attr("dy", '-3em')
|
|
.text(lableObj.value + '%');
|
|
}
|
|
if (i === 0) {
|
|
var centroidText = r.data.arc.centroid({
|
|
startAngle: r.startAngle,
|
|
endAngle: r.startAngle
|
|
});
|
|
var lableObj = r.data.object;
|
|
gText.append('text')
|
|
.attr('font-size', ((2 * width) / 100))
|
|
.text(lableObj.label)
|
|
.style('fill', lableObj.lcolor)
|
|
.attr('transform', "translate(" + (10) + "," + (0 + ") rotate(" + (180) + ")"))
|
|
.attr('dominant-baseline', 'central');
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
})
|
|
|
|
var drag_handler = d3.drag()
|
|
.on("start", drag_start)
|
|
.on("drag", drag_drag)
|
|
.on("end", drag_end);
|
|
|
|
drag_handler(node);
|
|
|
|
var labels = node.append("text")
|
|
.text(function(d) {
|
|
return d.name;
|
|
})
|
|
.attr('x', 20)
|
|
.attr('y', 8);
|
|
|
|
node.append('title').text(function (d) { return d.name; });
|
|
|
|
//add zoom capabilities
|
|
var zoom_handler = d3.zoom()
|
|
.on("zoom", zoom_actions);
|
|
|
|
zoom_handler(svg);
|
|
|
|
drawGraph();
|
|
|
|
for (let i = 0; i < listofNodes.length; i++) {
|
|
var numb = graph.nodes[i]['group'].match(/\d/g)
|
|
numb = parseInt(numb.join(""))
|
|
var items = document.getElementsByClassName(numb)
|
|
items.forEach( function (it) {
|
|
it.style.visibility = "hidden";
|
|
})
|
|
}
|
|
|
|
//Zoom functions
|
|
function zoom_actions(){
|
|
svg.attr("transform", d3.event.transform)
|
|
}
|
|
|
|
function drag_start(d) {
|
|
if (!d3.event.active) forceSim.alphaTarget(0.3).restart();
|
|
d.fx = d.x;
|
|
d.fy = d.y;
|
|
}
|
|
|
|
//make sure you can't drag the circle outside the box
|
|
function drag_drag(d) {
|
|
d.fx = d3.event.x;
|
|
d.fy = d3.event.y;
|
|
tickActions();
|
|
}
|
|
|
|
function drag_end(d) {
|
|
if (!d3.event.active) forceSim.alphaTarget(0);
|
|
d.fx = null;
|
|
d.fy = null;
|
|
}
|
|
|
|
stepper.stop();
|
|
}
|
|
|
|
});
|
|
|
|
function tickActions() {
|
|
link
|
|
.attr('x1', function (d) { return d.source.x; })
|
|
.attr('x2', function (d) { return d.target.x; })
|
|
.attr('y1', function (d) { return d.source.y; })
|
|
.attr('y2', function (d) { return d.target.y; });
|
|
|
|
node
|
|
.attr("id", function(d, i) { return "g" + (i+computeNodesVar); })
|
|
.attr("transform", function(d, i) {
|
|
d.x = d.x - 50
|
|
d.y = d.y - 50
|
|
return "translate(" + d.x + "," + d.y + ")";
|
|
})
|
|
};
|
|
|
|
function drawGraph () {
|
|
graph.nodes.forEach(function (n, i) {
|
|
n.x = selectedParams.positions[i].x;
|
|
n.y = selectedParams.positions[i].y;
|
|
});
|
|
|
|
var xDistance = d3.extent(graph.nodes, function (n) { return n.x; });
|
|
var xMin = xDistance[0];
|
|
xDistance = xDistance[1] - xDistance[0];
|
|
|
|
var yDistance = d3.extent(graph.nodes, function (n) { return n.y; });
|
|
var yMin = yDistance[0];
|
|
yDistance = yDistance[1] - yDistance[0];
|
|
|
|
graph.nodes.forEach(function (n, i) {
|
|
n.x = (height - 10) * (n.x - xMin) / Math.max(xDistance, yDistance);
|
|
n.y = (height - 10) * (n.y - yMin) / Math.max(xDistance, yDistance);
|
|
});
|
|
|
|
xDistance = d3.extent(graph.nodes, function (n) { return n.x; });
|
|
var xMid = (xDistance[1] + xDistance[0]) / 2;
|
|
yDistance = d3.extent(graph.nodes, function (n) { return n.y; });
|
|
var yMid = (yDistance[1] - yDistance[0]) / 2;
|
|
|
|
graph.nodes.forEach(function (n, i) {
|
|
n.x = n.x + width/2 - xMid;
|
|
n.y = n.y + height/2 - yMid;
|
|
});
|
|
|
|
tickActions();
|
|
}
|
|
|
|
function generateParams (paramGroups, paramList, currParam) {
|
|
var p = paramGroups[0];
|
|
|
|
if (!paramList) paramList = [];
|
|
|
|
if (!currParam) currParam = {};
|
|
|
|
p.values.forEach(function (v) {
|
|
var setting = {};
|
|
setting[p.name] = v;
|
|
if (paramGroups.length === 1) {
|
|
paramList.push(Object.assign(setting, currParam));
|
|
} else {
|
|
generateParams(paramGroups.slice(1), paramList, Object.assign(setting, currParam));
|
|
}
|
|
});
|
|
|
|
return paramList;
|
|
}
|
|
|
|
function getForceSimFromParams (params) {
|
|
var forceSim = d3.forceSimulation()
|
|
.force('link', d3.forceLink().iterations(params.iterations))
|
|
.force('charge', d3.forceManyBody().strength(params.chargeStrength))
|
|
.force('x', d3.forceX(0).strength(params.gravity))
|
|
.force('y', d3.forceY(0).strength(params.gravity))
|
|
.force('center', d3.forceCenter(0, 0))
|
|
.alphaDecay(params.alphaDecay)
|
|
.velocityDecay(params.velocityDecay);
|
|
|
|
if (params.linkStrength !== null) {
|
|
forceSim.force('link').strength(params.linkStrength);
|
|
}
|
|
|
|
return forceSim;
|
|
}
|
|
|
|
this.computeNodes = this.computeNodes + graph.nodes.length
|
|
|
|
EventBus.$emit('countNodes2', this.computeNodes)
|
|
|
|
},
|
|
reset () {
|
|
var svg = d3.select("#FeatureGraph");
|
|
svg.selectAll("*").remove();
|
|
},
|
|
},
|
|
mounted () {
|
|
EventBus.$on('quadTrans', data => { this.dataFSTrans = data })
|
|
EventBus.$on('quad', data => { this.dataFS = data })
|
|
EventBus.$on('quad', this.initializeNetwork)
|
|
EventBus.$on('countNodes1', data => { this.computeNodes = data })
|
|
|
|
EventBus.$on('reset', this.reset)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
|
|
text {
|
|
font-family: sans-serif;
|
|
}
|
|
|
|
svg {
|
|
display: block;
|
|
}
|
|
|
|
.links line {
|
|
stroke-opacity: 0.6;
|
|
}
|
|
|
|
.column {
|
|
float: left;
|
|
margin: 0 10px;
|
|
}
|
|
|
|
</style> |