parent
877575a375
commit
ba8ddb2831
Binary file not shown.
@ -1 +1 @@ |
||||
{"duration": 4.569021940231323, "input_args": {}} |
||||
{"duration": 7.80327582359314, "input_args": {}} |
@ -0,0 +1,99 @@ |
||||
<template> |
||||
<div> |
||||
<div class="row align-items-center"> |
||||
<div class="col-lg-2"><p>Positive</p></div> |
||||
<div class="col-lg-8"><div id="slider-stepPos"></div></div> |
||||
<div class="col-lg-2"><p id="value-stepPos"></p></div> |
||||
</div> |
||||
<div class="row align-items-center"> |
||||
<div class="col-lg-2"><p>Negative</p></div> |
||||
<div class="col-lg-8"><div id="slider-stepNeg"></div></div> |
||||
<div class="col-lg-2"><p id="value-stepNeg"></p></div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { EventBus } from '../main.js' |
||||
import { sliderBottom } from 'd3-simple-slider' |
||||
import * as d3Base from 'd3' |
||||
|
||||
// attach all d3 plugins to the d3 library |
||||
const d3 = Object.assign(d3Base, { sliderBottom }) |
||||
|
||||
export default { |
||||
name: 'Slider', |
||||
data () { |
||||
return { |
||||
} |
||||
}, |
||||
methods: { |
||||
InitSliders () { |
||||
var dataCorrect = [55.0, 60.0, 65.0, 70.0, 75.0, 80.0, 85.0, 90.0, 95.0]; |
||||
var dataWrong = [5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0]; |
||||
|
||||
var sliderStepPos = d3 |
||||
.sliderBottom() |
||||
.min(d3.min(dataCorrect)) |
||||
.max(d3.max(dataCorrect)) |
||||
.width(300) |
||||
.tickFormat(d3.format(".0f")) |
||||
.ticks(9) |
||||
.step(5) |
||||
.default(75.0) |
||||
.on('onchange', val => { |
||||
d3.select('p#value-stepPos').text(d3.format(".0f")(val)); |
||||
EventBus.$emit('SendtheChangeinRangePos', d3.format(".0f")(val)) |
||||
}); |
||||
|
||||
var gStepPos = d3 |
||||
.select('div#slider-stepPos') |
||||
.append('svg') |
||||
.attr('width', 500) |
||||
.attr('height', 100) |
||||
.append('g') |
||||
.attr('transform', 'translate(30,30)'); |
||||
|
||||
gStepPos.call(sliderStepPos); |
||||
|
||||
d3.select('p#value-stepPos').text(d3.format(".0f")(sliderStepPos.value())); |
||||
|
||||
var sliderStepNeg = d3 |
||||
.sliderBottom() |
||||
.min(d3.min(dataWrong)) |
||||
.max(d3.max(dataWrong)) |
||||
.width(300) |
||||
.tickFormat(d3.format(".0f")) |
||||
.ticks(9) |
||||
.step(5) |
||||
.default(25.0) |
||||
.on('onchange', val => { |
||||
d3.select('p#value-stepNeg').text(d3.format(".0f")(val)); |
||||
EventBus.$emit('SendtheChangeinRangeNeg', d3.format(".0f")(val)) |
||||
}); |
||||
|
||||
var gStepNeg = d3 |
||||
.select('div#slider-stepNeg') |
||||
.append('svg') |
||||
.attr('width', 500) |
||||
.attr('height', 100) |
||||
.append('g') |
||||
.attr('transform', 'translate(30,30)'); |
||||
|
||||
gStepNeg.call(sliderStepNeg); |
||||
|
||||
d3.select('p#value-stepNeg').text(d3.format(".0f")(sliderStepNeg.value())); |
||||
}, |
||||
reset () { |
||||
EventBus.$emit('reset') |
||||
EventBus.$emit('alternateFlagLock') |
||||
}, |
||||
initialize () { |
||||
EventBus.$emit('ConfirmDataSet') |
||||
} |
||||
}, |
||||
mounted () { |
||||
this.InitSliders() |
||||
}, |
||||
} |
||||
</script> |
@ -0,0 +1,379 @@ |
||||
(function (global, factory) { |
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
||||
(factory((global.greadability = global.greadability || {}))); |
||||
}(this, (function (exports) { 'use strict'; |
||||
|
||||
var greadability = function (nodes, links, id) { |
||||
var i, |
||||
j, |
||||
n = nodes.length, |
||||
m, |
||||
degree = new Array(nodes.length), |
||||
cMax, |
||||
idealAngle = 70, |
||||
dMax; |
||||
|
||||
/* |
||||
* Tracks the global graph readability metrics. |
||||
*/ |
||||
var graphStats = { |
||||
crossing: 0, // Normalized link crossings
|
||||
crossingAngle: 0, // Normalized average dev from 70 deg
|
||||
angularResolutionMin: 0, // Normalized avg dev from ideal min angle
|
||||
angularResolutionDev: 0, // Normalized avg dev from each link
|
||||
}; |
||||
|
||||
var getSumOfArray = function (numArray) { |
||||
var i = 0, n = numArray.length, sum = 0; |
||||
for (; i < n; ++i) sum += numArray[i]; |
||||
return sum; |
||||
}; |
||||
|
||||
var initialize = function () { |
||||
var i, j, link; |
||||
var nodeById = {}; |
||||
// Filter out self loops
|
||||
links = links.filter(function (l) { |
||||
return l.source !== l.target; |
||||
}); |
||||
|
||||
m = links.length; |
||||
|
||||
if (!id) { |
||||
id = function (d) { return d.index; }; |
||||
} |
||||
|
||||
for (i = 0; i < n; ++i) { |
||||
nodes[i].index = i; |
||||
degree[i] = []; |
||||
nodeById[id(nodes[i], i, nodeById)] = nodes[i]; |
||||
} |
||||
|
||||
// Make sure source and target are nodes and not indices.
|
||||
for (i = 0; i < m; ++i) { |
||||
link = links[i]; |
||||
if (typeof link.source !== "object") link.source = nodeById[link.source]; |
||||
if (typeof link.target !== "object") link.target = nodeById[link.target]; |
||||
} |
||||
|
||||
// Filter out duplicate links
|
||||
var filteredLinks = []; |
||||
links.forEach(function (l) { |
||||
var s = l.source, t = l.target; |
||||
if (s.index > t.index) { |
||||
filteredLinks.push({source: t, target: s}); |
||||
} else { |
||||
filteredLinks.push({source: s, target: t}); |
||||
} |
||||
}); |
||||
links = filteredLinks; |
||||
links.sort(function (a, b) { |
||||
if (a.source.index < b.source.index) return -1; |
||||
if (a.source.index > b.source.index) return 1; |
||||
if (a.target.index < b.target.index) return -1; |
||||
if (a.target.index > b.target.index) return 1; |
||||
return 0; |
||||
}); |
||||
i = 1; |
||||
while (i < links.length) { |
||||
if (links[i-1].source.index === links[i].source.index && |
||||
links[i-1].target.index === links[i].target.index) { |
||||
links.splice(i, 1); |
||||
} |
||||
else ++i; |
||||
} |
||||
|
||||
// Update length, if a duplicate was deleted.
|
||||
m = links.length; |
||||
|
||||
// Calculate degree.
|
||||
for (i = 0; i < m; ++i) { |
||||
link = links[i]; |
||||
link.index = i; |
||||
|
||||
degree[link.source.index].push(link); |
||||
degree[link.target.index].push(link); |
||||
}; |
||||
} |
||||
|
||||
// Assume node.x and node.y are the coordinates
|
||||
|
||||
function direction (pi, pj, pk) { |
||||
var p1 = [pk[0] - pi[0], pk[1] - pi[1]]; |
||||
var p2 = [pj[0] - pi[0], pj[1] - pi[1]]; |
||||
return p1[0] * p2[1] - p2[0] * p1[1]; |
||||
} |
||||
|
||||
// Is point k on the line segment formed by points i and j?
|
||||
// Inclusive, so if pk == pi or pk == pj then return true.
|
||||
function onSegment (pi, pj, pk) { |
||||
return Math.min(pi[0], pj[0]) <= pk[0] && |
||||
pk[0] <= Math.max(pi[0], pj[0]) && |
||||
Math.min(pi[1], pj[1]) <= pk[1] && |
||||
pk[1] <= Math.max(pi[1], pj[1]); |
||||
} |
||||
|
||||
function linesCross (line1, line2) { |
||||
var d1, d2, d3, d4; |
||||
|
||||
// CLRS 2nd ed. pg. 937
|
||||
d1 = direction(line2[0], line2[1], line1[0]); |
||||
d2 = direction(line2[0], line2[1], line1[1]); |
||||
d3 = direction(line1[0], line1[1], line2[0]); |
||||
d4 = direction(line1[0], line1[1], line2[1]); |
||||
|
||||
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && |
||||
((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) { |
||||
return true; |
||||
} else if (d1 === 0 && onSegment(line2[0], line2[1], line1[0])) { |
||||
return true; |
||||
} else if (d2 === 0 && onSegment(line2[0], line2[1], line1[1])) { |
||||
return true; |
||||
} else if (d3 === 0 && onSegment(line1[0], line1[1], line2[0])) { |
||||
return true; |
||||
} else if (d4 === 0 && onSegment(line1[0], line1[1], line2[1])) { |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
function linksCross (link1, link2) { |
||||
// Self loops are not intersections
|
||||
if (link1.index === link2.index || |
||||
link1.source === link1.target || |
||||
link2.source === link2.target) { |
||||
return false; |
||||
} |
||||
|
||||
// Links cannot intersect if they share a node
|
||||
if (link1.source === link2.source || |
||||
link1.source === link2.target || |
||||
link1.target === link2.source || |
||||
link1.target === link2.target) { |
||||
return false; |
||||
} |
||||
|
||||
var line1 = [ |
||||
[link1.source.x, link1.source.y], |
||||
[link1.target.x, link1.target.y] |
||||
]; |
||||
|
||||
var line2 = [ |
||||
[link2.source.x, link2.source.y], |
||||
[link2.target.x, link2.target.y] |
||||
]; |
||||
|
||||
return linesCross(line1, line2); |
||||
} |
||||
|
||||
function linkCrossings () { |
||||
var i, j, c = 0, d = 0, link1, link2, line1, line2;; |
||||
|
||||
// Sum the upper diagonal of the edge crossing matrix.
|
||||
for (i = 0; i < m; ++i) { |
||||
for (j = i + 1; j < m; ++j) { |
||||
link1 = links[i], link2 = links[j]; |
||||
|
||||
// Check if link i and link j intersect
|
||||
if (linksCross(link1, link2)) { |
||||
line1 = [ |
||||
[link1.source.x, link1.source.y], |
||||
[link1.target.x, link1.target.y] |
||||
]; |
||||
line2 = [ |
||||
[link2.source.x, link2.source.y], |
||||
[link2.target.x, link2.target.y] |
||||
]; |
||||
++c; |
||||
d += Math.abs(idealAngle - acuteLinesAngle(line1, line2)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return {c: 2*c, d: 2*d}; |
||||
} |
||||
|
||||
function linesegmentsAngle (line1, line2) { |
||||
// Finds the (counterclockwise) angle from line segement line1 to
|
||||
// line segment line2. Assumes the lines share one end point.
|
||||
// If both endpoints are the same, or if both lines have zero
|
||||
// length, then return 0 angle.
|
||||
// Param order matters:
|
||||
// linesegmentsAngle(line1, line2) != linesegmentsAngle(line2, line1)
|
||||
var temp, len, angle1, angle2, sLine1, sLine2; |
||||
|
||||
// Re-orient so that line1[0] and line2[0] are the same.
|
||||
if (line1[0][0] === line2[1][0] && line1[0][1] === line2[1][1]) { |
||||
temp = line2[1]; |
||||
line2[1] = line2[0]; |
||||
line2[0] = temp; |
||||
} else if (line1[1][0] === line2[0][0] && line1[1][1] === line2[0][1]) { |
||||
temp = line1[1]; |
||||
line1[1] = line1[0]; |
||||
line1[0] = temp; |
||||
} else if (line1[1][0] === line2[1][0] && line1[1][1] === line2[1][1]) { |
||||
temp = line1[1]; |
||||
line1[1] = line1[0]; |
||||
line1[0] = temp; |
||||
temp = line2[1]; |
||||
line2[1] = line2[0]; |
||||
line2[0] = temp; |
||||
} |
||||
|
||||
// Shift the line so that the first point is at (0,0).
|
||||
sLine1 = [ |
||||
[line1[0][0] - line1[0][0], line1[0][1] - line1[0][1]], |
||||
[line1[1][0] - line1[0][0], line1[1][1] - line1[0][1]] |
||||
]; |
||||
// Normalize the line length.
|
||||
len = Math.hypot(sLine1[1][0], sLine1[1][1]); |
||||
if (len === 0) return 0; |
||||
sLine1[1][0] /= len; |
||||
sLine1[1][1] /= len; |
||||
// If y < 0, angle = acos(x), otherwise angle = 360 - acos(x)
|
||||
angle1 = Math.acos(sLine1[1][0]) * 180 / Math.PI; |
||||
if (sLine1[1][1] < 0) angle1 = 360 - angle1; |
||||
|
||||
// Shift the line so that the first point is at (0,0).
|
||||
sLine2 = [ |
||||
[line2[0][0] - line2[0][0], line2[0][1] - line2[0][1]], |
||||
[line2[1][0] - line2[0][0], line2[1][1] - line2[0][1]] |
||||
]; |
||||
// Normalize the line length.
|
||||
len = Math.hypot(sLine2[1][0], sLine2[1][1]); |
||||
if (len === 0) return 0; |
||||
sLine2[1][0] /= len; |
||||
sLine2[1][1] /= len; |
||||
// If y < 0, angle = acos(x), otherwise angle = 360 - acos(x)
|
||||
angle2 = Math.acos(sLine2[1][0]) * 180 / Math.PI; |
||||
if (sLine2[1][1] < 0) angle2 = 360 - angle2; |
||||
|
||||
return angle1 <= angle2 ? angle2 - angle1 : 360 - (angle1 - angle2); |
||||
} |
||||
|
||||
function acuteLinesAngle (line1, line2) { |
||||
// Acute angle of intersection, in degrees. Assumes these lines
|
||||
// intersect.
|
||||
var slope1 = (line1[1][1] - line1[0][1]) / (line1[1][0] - line1[0][0]); |
||||
var slope2 = (line2[1][1] - line2[0][1]) / (line2[1][0] - line2[0][0]); |
||||
|
||||
// If these lines are two links incident on the same node, need
|
||||
// to check if the angle is 0 or 180.
|
||||
if (slope1 === slope2) { |
||||
// If line2 is not on line1 and line1 is not on line2, then
|
||||
// the lines share only one point and the angle must be 180.
|
||||
if (!(onSegment(line1[0], line1[1], line2[0]) && onSegment(line1[0], line1[1], line2[1])) || |
||||
!(onSegment(line2[0], line2[1], line1[0]) && onSegment(line2[0], line2[1], line1[1]))) |
||||
return 180; |
||||
else return 0; |
||||
} |
||||
|
||||
var angle = Math.abs(Math.atan(slope1) - Math.atan(slope2)); |
||||
|
||||
return (angle > Math.PI / 2 ? Math.PI - angle : angle) * 180 / Math.PI; |
||||
} |
||||
|
||||
function angularRes () { |
||||
var j, |
||||
resMin = 0, |
||||
resDev = 0, |
||||
nonZeroDeg, |
||||
node, |
||||
minAngle, |
||||
idealMinAngle, |
||||
incident, |
||||
line0, |
||||
line1, |
||||
line2, |
||||
incidentLinkAngles, |
||||
nextLink; |
||||
|
||||
nonZeroDeg = degree.filter(function (d) { return d.length >= 1; }).length; |
||||
|
||||
for (j = 0; j < n; ++j) { |
||||
node = nodes[j]; |
||||
line0 = [[node.x, node.y], [node.x+1, node.y]]; |
||||
|
||||
// Links that are incident to this node (already filtered out self loops)
|
||||
incident = degree[j]; |
||||
|
||||
if (incident.length <= 1) continue; |
||||
|
||||
idealMinAngle = 360 / incident.length; |
||||
|
||||
// Sort edges by the angle they make from an imaginary vector
|
||||
// emerging at angle 0 on the unit circle.
|
||||
// Necessary for calculating angles of incident edges correctly
|
||||
incident.sort(function (a, b) { |
||||
line1 = [ |
||||
[a.source.x, a.source.y], |
||||
[a.target.x, a.target.y] |
||||
]; |
||||
line2 = [ |
||||
[b.source.x, b.source.y], |
||||
[b.target.x, b.target.y] |
||||
]; |
||||
var angleA = linesegmentsAngle(line0, line1); |
||||
var angleB = linesegmentsAngle(line0, line2); |
||||
return angleA < angleB ? -1 : angleA > angleB ? 1 : 0; |
||||
}); |
||||
|
||||
incidentLinkAngles = incident.map(function (l, i) { |
||||
nextLink = incident[(i + 1) % incident.length]; |
||||
line1 = [ |
||||
[l.source.x, l.source.y], |
||||
[l.target.x, l.target.y] |
||||
]; |
||||
line2 = [ |
||||
[nextLink.source.x, nextLink.source.y], |
||||
[nextLink.target.x, nextLink.target.y] |
||||
]; |
||||
return linesegmentsAngle(line1, line2); |
||||
}); |
||||
|
||||
minAngle = Math.min.apply(null, incidentLinkAngles); |
||||
|
||||
resMin += Math.abs(idealMinAngle - minAngle) / idealMinAngle; |
||||
|
||||
resDev += getSumOfArray(incidentLinkAngles.map(function (angle) { |
||||
return Math.abs(idealMinAngle - angle) / idealMinAngle; |
||||
})) / (2 * incident.length - 2); |
||||
} |
||||
|
||||
// Divide by number of nodes with degree != 0
|
||||
resMin = resMin / nonZeroDeg; |
||||
|
||||
// Divide by number of nodes with degree != 0
|
||||
resDev = resDev / nonZeroDeg; |
||||
|
||||
return {resMin: resMin, resDev: resDev}; |
||||
} |
||||
|
||||
initialize(); |
||||
|
||||
cMax = (m * (m - 1) / 2) - getSumOfArray(degree.map(function (d) { return d.length * (d.length - 1); })) / 2; |
||||
|
||||
var crossInfo = linkCrossings(); |
||||
|
||||
dMax = crossInfo.c * idealAngle; |
||||
|
||||
graphStats.crossing = 1 - (cMax > 0 ? crossInfo.c / cMax : 0); |
||||
|
||||
graphStats.crossingAngle = 1 - (dMax > 0 ? crossInfo.d / dMax : 0); |
||||
|
||||
var angularResInfo = angularRes(); |
||||
|
||||
graphStats.angularResolutionMin = 1 - angularResInfo.resMin; |
||||
|
||||
graphStats.angularResolutionDev = 1 - angularResInfo.resDev; |
||||
|
||||
return graphStats; |
||||
}; |
||||
|
||||
exports.greadability = greadability; |
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true }); |
||||
|
||||
}))); |
Loading…
Reference in new issue