parent
19472c9fa4
commit
f29c758ac8
@ -1,270 +0,0 @@ |
|||||||
function kNearestNeighbors(k, points, points2d) { |
|
||||||
|
|
||||||
var averagekNN = 0; |
|
||||||
var Distances = []; |
|
||||||
var Distances2d = []; |
|
||||||
//var sortedDistances = [];
|
|
||||||
var point = points; |
|
||||||
var point2d = points2d; |
|
||||||
/* |
|
||||||
* Loop through our nodes and look for unknown types. |
|
||||||
*/ |
|
||||||
for (var i=0; i<point.length; i++){ |
|
||||||
/* |
|
||||||
if (this.nodes.hasOwnProperty(i)) { |
|
||||||
|
|
||||||
if ( ! this.nodes[i].type) {*/ |
|
||||||
/* |
|
||||||
* If the node is an unknown type, clone the nodes list and then measure distances. |
|
||||||
*/ |
|
||||||
|
|
||||||
/* Clone nodes *//* |
|
||||||
this.nodes[i].neighbors = []; |
|
||||||
|
|
||||||
for (var j in this.nodes) { |
|
||||||
if ( ! this.nodes[j].type) |
|
||||||
continue; |
|
||||||
this.nodes[i].neighbors.push( new KNN.Item(this.nodes[j]) ); |
|
||||||
}*/ |
|
||||||
|
|
||||||
/* Measure distances */ |
|
||||||
Distances[i] = measureDistances(point[i],points); |
|
||||||
Distances2d[i] = measureDistances(point2d[i],points2d); |
|
||||||
Distances[i].sort(); |
|
||||||
Distances2d[i].sort(); |
|
||||||
/* Sort by distance */ |
|
||||||
//sortByDistance();
|
|
||||||
/* Guess type */ |
|
||||||
//this.type = this.nodes[i].guessType(this.k);
|
|
||||||
} |
|
||||||
//console.log(Distances);
|
|
||||||
sum = LimitKNeighbor(Distances, k); |
|
||||||
sum2d = LimitKNeighbor(Distances2d, k); |
|
||||||
averagekNN = Math.round((sum2d / sum) * 100) / 100; |
|
||||||
return averagekNN; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
function measureDistances(point,points) { |
|
||||||
var neighborDistances = []; |
|
||||||
var checkForSame = []; |
|
||||||
for (var j=0; j<points.length; j++){ |
|
||||||
|
|
||||||
neighborDistances[j] = Math.sqrt( (point.x-points[j].x)*(point.x-points[j].x) + (point.y-points[j].y)*(point.y-points[j].y) ); |
|
||||||
} |
|
||||||
|
|
||||||
//neighborDistances = sortByDistance(neighborDistances);
|
|
||||||
return neighborDistances; |
|
||||||
} |
|
||||||
|
|
||||||
function LimitKNeighbor(Distances, k) { |
|
||||||
var DistancesSliced = []; |
|
||||||
var sum = 0; |
|
||||||
for (var i = 0; i < Distances.length; i++) { |
|
||||||
//for (var j = 0; j < Distances.length; j++) {
|
|
||||||
DistancesSliced[i] = Distances[i].slice(0,k); |
|
||||||
for (var j = 0 ; j < k ; j++){ |
|
||||||
sum = DistancesSliced[i][j] + sum; |
|
||||||
} |
|
||||||
//sum = DistancesSliced[i] + sum;
|
|
||||||
//}
|
|
||||||
} |
|
||||||
//console.log(sum);
|
|
||||||
return sum; |
|
||||||
//console.log(DistancesBetweenAllThePoints[0][0]);
|
|
||||||
//console.log(DistancesBetweenAllThePoints);
|
|
||||||
/* |
|
||||||
var guess = {type: false, count: 0}; |
|
||||||
|
|
||||||
for (var type in types) { |
|
||||||
if (types[type] > guess.count) { |
|
||||||
guess.type = type; |
|
||||||
guess.count = types[type]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.guess = guess; |
|
||||||
|
|
||||||
return types; |
|
||||||
}; |
|
||||||
|
|
||||||
calculateRanges = function() { |
|
||||||
this.areas = {min: 1000000, max: 0}; |
|
||||||
this.rooms = {min: 1000000, max: 0}; |
|
||||||
|
|
||||||
for (var i in this.nodes) { |
|
||||||
if (this.nodes.hasOwnProperty(i)) { |
|
||||||
|
|
||||||
if (this.nodes[i].rooms < this.rooms.min) { |
|
||||||
this.rooms.min = this.nodes[i].rooms; |
|
||||||
} |
|
||||||
|
|
||||||
if (this.nodes[i].rooms > this.rooms.max) { |
|
||||||
this.rooms.max = this.nodes[i].rooms; |
|
||||||
} |
|
||||||
|
|
||||||
if (this.nodes[i].area < this.areas.min) { |
|
||||||
this.areas.min = this.nodes[i].area; |
|
||||||
} |
|
||||||
|
|
||||||
if (this.nodes[i].area > this.areas.max) { |
|
||||||
this.areas.max = this.nodes[i].area; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
};*/ |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
function findNearest(point) { |
|
||||||
|
|
||||||
// TODO: make this more efficient by not recalculating quadtree at
|
|
||||||
// each call of findNearest()
|
|
||||||
|
|
||||||
// Extract points from the data array
|
|
||||||
//points = data.map(function(d) { return [x(d), y(d)]; });
|
|
||||||
|
|
||||||
// Add quadtree info to the points
|
|
||||||
//nodes = quadtreeify(points);
|
|
||||||
|
|
||||||
// Flag k-nearest points by adding `selected` property set to `true`
|
|
||||||
kNearest(new Array(nodes), [], point); |
|
||||||
|
|
||||||
// Return nearest points along with indices from origianl `data` array
|
|
||||||
return points |
|
||||||
.map(function(d, i) { |
|
||||||
var datum = [d[0], d[1]]; |
|
||||||
datum.i = i; |
|
||||||
return d.selected ? datum : null;
|
|
||||||
}) |
|
||||||
.filter(function(d) { return d !== null; }); |
|
||||||
} |
|
||||||
|
|
||||||
findNearest.extent = function(_) { |
|
||||||
if (!arguments.length) return extent; |
|
||||||
extent = _; |
|
||||||
//quadtree.extent(extent);
|
|
||||||
return findNearest; |
|
||||||
}; |
|
||||||
|
|
||||||
findNearest.data = function(_) { |
|
||||||
if (!arguments.length) return data; |
|
||||||
data = _; |
|
||||||
return findNearest; |
|
||||||
}; |
|
||||||
|
|
||||||
findNearest.k = function(_) { |
|
||||||
if (!arguments.length) return k; |
|
||||||
k = _; |
|
||||||
return findNearest; |
|
||||||
}; |
|
||||||
|
|
||||||
findNearest.x = function(_) { |
|
||||||
if (!arguments.length) return x; |
|
||||||
x = _; |
|
||||||
return findNearest; |
|
||||||
}; |
|
||||||
|
|
||||||
findNearest.y = function(_) { |
|
||||||
if (!arguments.length) return y; |
|
||||||
y = _; |
|
||||||
return findNearest; |
|
||||||
}; |
|
||||||
|
|
||||||
return findNearest; |
|
||||||
|
|
||||||
// Add quadtree information to each point (i.e., rectangles, depth, ...)
|
|
||||||
function quadtreeify(points) { |
|
||||||
var nodes = quadtree(points); |
|
||||||
nodes.depth = 0; |
|
||||||
nodes.visit(function(node, x1, y1, x2, y2) { |
|
||||||
node.x1 = x1; |
|
||||||
node.y1 = y1; |
|
||||||
node.x2 = x2; |
|
||||||
node.y2 = y2; |
|
||||||
for (var i = 0; i < 4; i++) { |
|
||||||
if (node.nodes[i]) node.nodes[i].depth = node.depth + 1; |
|
||||||
} |
|
||||||
}); |
|
||||||
return nodes; |
|
||||||
} |
|
||||||
|
|
||||||
// calculate the euclidean distance of two points with coordinates a(ax, ay) and b(bx, by)
|
|
||||||
function euclideanDistance(ax, ay, bx, by) { |
|
||||||
return Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2)); |
|
||||||
} |
|
||||||
|
|
||||||
// calculate minimum distance between search point rectangles
|
|
||||||
function minDistance(x, y, x1, y1, x2, y2) { |
|
||||||
var dx1 = x - x1, |
|
||||||
dx2 = x - x2, |
|
||||||
dy1 = y - y1, |
|
||||||
dy2 = y - y2; |
|
||||||
|
|
||||||
// x is between x1 and x2
|
|
||||||
if (dx1 * dx2 < 0) { |
|
||||||
// (x, y) is inside the rectangle
|
|
||||||
if (dy1 * dy2 < 0) { |
|
||||||
return 0; // return 0 as a point in the rectangle
|
|
||||||
} |
|
||||||
return Math.min(Math.abs(dy1), Math.abs(dy2)); |
|
||||||
} |
|
||||||
|
|
||||||
// y is between y1 and y2 (and not inside rectangle)
|
|
||||||
if (dy1 * dy2 < 0) { |
|
||||||
return Math.min(Math.abs(dx1), Math.abs(dx2)); |
|
||||||
} |
|
||||||
return Math.min(
|
|
||||||
Math.min(euclideanDistance(x,y,x1,y1), euclideanDistance(x,y,x2,y2)),
|
|
||||||
Math.min(euclideanDistance(x,y,x1,y2), euclideanDistance(x,y,x2,y1)) |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
// Find the nodes within the specified rectangle (used recursively)
|
|
||||||
function kNearest(bestQueue, resultQueue, point) { |
|
||||||
var x = point[0], |
|
||||||
y = point[1]; |
|
||||||
|
|
||||||
// sort children according to their minDistance/euclideanDistance to search point
|
|
||||||
bestQueue.sort(function(a, b) { |
|
||||||
// add minDistance to nodes if not there already
|
|
||||||
[a, b].forEach(function(d) { |
|
||||||
if (d.minDistance === undefined) { |
|
||||||
d.scanned = true; |
|
||||||
if (d.leaf) { |
|
||||||
d.point.scanned = true; |
|
||||||
d.minDistance = euclideanDistance(x, y, d.x, d.y); |
|
||||||
} |
|
||||||
else { |
|
||||||
d.minDistance = minDistance(x, y, d.x1, d.y1, d.x2, d.y2); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
return b.minDistance - a.minDistance; |
|
||||||
}); |
|
||||||
|
|
||||||
// add nearest leafs (if any)
|
|
||||||
for (var i = bestQueue.length - 1; i >= 0; i--) { |
|
||||||
var elem = bestQueue[i]; |
|
||||||
if (elem.leaf) { |
|
||||||
elem.point.selected = true; |
|
||||||
bestQueue.pop(); |
|
||||||
resultQueue.push(elem); |
|
||||||
} else { break; } |
|
||||||
if (resultQueue.length >= k) break; |
|
||||||
} |
|
||||||
|
|
||||||
// check if enough points found
|
|
||||||
if (resultQueue.length >= k || bestQueue.length == 0) { return; } |
|
||||||
else { |
|
||||||
// ...otherwise add child nodes to bestQueue and recurse
|
|
||||||
var visitedNode = bestQueue.pop(); |
|
||||||
visitedNode.visited = true; |
|
||||||
visitedNode.nodes.forEach(function(d) { |
|
||||||
bestQueue.push(d); |
|
||||||
}); |
|
||||||
kNearest(bestQueue, resultQueue, point); |
|
||||||
} |
|
||||||
} |
|
||||||
*/ |
|
@ -0,0 +1,2 @@ |
|||||||
|
node_modules |
||||||
|
|
@ -0,0 +1,299 @@ |
|||||||
|
d3v3.ringNote = function() { |
||||||
|
var draggable = false, |
||||||
|
controlRadius = 15; |
||||||
|
|
||||||
|
var dragCenter = d3v3.behavior.drag() |
||||||
|
.origin(function(d) { return { x: 0, y: 0}; }) |
||||||
|
.on("drag", dragmoveCenter); |
||||||
|
|
||||||
|
var dragRadius = d3v3.behavior.drag() |
||||||
|
.origin(function(d) { return { x: 0, y: 0 }; }) |
||||||
|
.on("drag", dragmoveRadius); |
||||||
|
|
||||||
|
var dragText = d3v3.behavior.drag() |
||||||
|
.origin(function(d) { return { x: 0, y: 0 }; }) |
||||||
|
.on("drag", dragmoveText); |
||||||
|
|
||||||
|
var path = d3v3.svg.line(); |
||||||
|
|
||||||
|
function draw(selection, annotation) { |
||||||
|
|
||||||
|
selection.selectAll(".ring-note").remove(); |
||||||
|
|
||||||
|
var gRingNote = selection.selectAll(".ring-note") |
||||||
|
.data(annotation) |
||||||
|
.enter().append("g") |
||||||
|
.attr("class", "ring-note") |
||||||
|
.attr("transform", function(d) { |
||||||
|
return "translate(" + d.cx + "," + d.cy + ")"; |
||||||
|
}); |
||||||
|
|
||||||
|
var gAnnotation = gRingNote.append("g") |
||||||
|
.attr("class", "annotation"); |
||||||
|
|
||||||
|
var circle = gAnnotation.append("circle") |
||||||
|
.attr("r", function(d) { return d.r; }); |
||||||
|
|
||||||
|
var line = gAnnotation.append("path") |
||||||
|
.call(updateLine); |
||||||
|
|
||||||
|
var text = gAnnotation.append("text") |
||||||
|
.call(updateText); |
||||||
|
|
||||||
|
if (draggable) { |
||||||
|
|
||||||
|
var gControls = gRingNote.append("g") |
||||||
|
.attr("class", "controls"); |
||||||
|
|
||||||
|
// Draggable circle that moves the circle's location
|
||||||
|
var center = gControls.append("circle") |
||||||
|
.attr("class", "center") |
||||||
|
.call(styleControl) |
||||||
|
.call(dragCenter); |
||||||
|
|
||||||
|
// Draggable circle that changes the circle's radius
|
||||||
|
var radius = gControls.append("circle") |
||||||
|
.attr("class", "radius") |
||||||
|
.attr("cx", function(d) { return d.r; }) |
||||||
|
.call(styleControl) |
||||||
|
.call(dragRadius);
|
||||||
|
|
||||||
|
// Make text draggble
|
||||||
|
text |
||||||
|
.style("cursor", "move") |
||||||
|
.call(dragText); |
||||||
|
} |
||||||
|
|
||||||
|
return selection; |
||||||
|
} |
||||||
|
|
||||||
|
draw.draggable = function(_) { |
||||||
|
if (!arguments.length) return draggable; |
||||||
|
draggable = _; |
||||||
|
return draw; |
||||||
|
}; |
||||||
|
|
||||||
|
// Region in relation to circle, e.g., N, NW, W, SW, etc.
|
||||||
|
function getRegion(x, y, r) { |
||||||
|
var px = r * Math.cos(Math.PI/4), |
||||||
|
py = r * Math.sin(Math.PI/4); |
||||||
|
|
||||||
|
var distance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); |
||||||
|
|
||||||
|
if (distance < r) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
else { |
||||||
|
if (x > px) { |
||||||
|
// East
|
||||||
|
if (y > py) return "SE";
|
||||||
|
if (y < -py) return "NE"; |
||||||
|
if (x > r) return "E"; |
||||||
|
return null; |
||||||
|
} |
||||||
|
else if (x < -px) { |
||||||
|
// West
|
||||||
|
if (y > py) return "SW"; |
||||||
|
if (y < -py) return "NW"; |
||||||
|
if (x < -r) return "W"; |
||||||
|
return null; |
||||||
|
} |
||||||
|
else { |
||||||
|
// Center
|
||||||
|
if (y > r) return "S"; |
||||||
|
if (y < -r) return "N"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function dragmoveCenter(d) { |
||||||
|
var gRingNote = d3v3.select(this.parentNode.parentNode); |
||||||
|
|
||||||
|
d.cx += d3v3.event.x; |
||||||
|
d.cy += d3v3.event.y; |
||||||
|
|
||||||
|
gRingNote |
||||||
|
.attr("transform", function(d) { |
||||||
|
return "translate(" + d.cx + "," + d.cy + ")"; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function dragmoveRadius(d) { |
||||||
|
var gRingNote = d3v3.select(this.parentNode.parentNode), |
||||||
|
gAnnotation = gRingNote.select(".annotation"), |
||||||
|
circle = gAnnotation.select("circle"), |
||||||
|
line = gAnnotation.select("path"), |
||||||
|
text = gAnnotation.select("text"), |
||||||
|
radius = d3v3.select(this); |
||||||
|
|
||||||
|
d.r += d3v3.event.dx; |
||||||
|
|
||||||
|
circle.attr("r", function(d) { return d.r; }); |
||||||
|
radius.attr("cx", function(d) { return d.r; }); |
||||||
|
line.call(updateLine); |
||||||
|
text.call(updateText); |
||||||
|
} |
||||||
|
|
||||||
|
function dragmoveText(d) { |
||||||
|
var gAnnotation = d3v3.select(this.parentNode), |
||||||
|
line = gAnnotation.select("path"), |
||||||
|
text = d3v3.select(this); |
||||||
|
|
||||||
|
d.textOffset[0] += d3v3.event.dx; |
||||||
|
d.textOffset[1] += d3v3.event.dy; |
||||||
|
|
||||||
|
text.call(updateText); |
||||||
|
line.call(updateLine); |
||||||
|
} |
||||||
|
|
||||||
|
function updateLine(selection) { |
||||||
|
return selection.attr("d", function(d) { |
||||||
|
var x = d.textOffset[0], |
||||||
|
y = d.textOffset[1], |
||||||
|
lineData = getLineData(x, y, d.r); |
||||||
|
return path(lineData); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function getLineData(x, y, r) { |
||||||
|
var region = getRegion(x, y, r); |
||||||
|
|
||||||
|
if (region == null) { |
||||||
|
// No line if text is inside the circle
|
||||||
|
return []; |
||||||
|
} |
||||||
|
else { |
||||||
|
// Cardinal directions
|
||||||
|
if (region == "N") return [[0, -r], [0, y]]; |
||||||
|
if (region == "E") return [[r, 0], [x, 0]]; |
||||||
|
if (region == "S") return [[0, r], [0, y]]; |
||||||
|
if (region == "W") return [[-r, 0],[x, 0]]; |
||||||
|
|
||||||
|
var d0 = r * Math.cos(Math.PI/4), |
||||||
|
d1 = Math.min(Math.abs(x), Math.abs(y)) - d0; |
||||||
|
|
||||||
|
// Intermediate directions
|
||||||
|
if (region == "NE") return [[ d0, -d0], [ d0 + d1, -d0 - d1], [x, y]]; |
||||||
|
if (region == "SE") return [[ d0, d0], [ d0 + d1, d0 + d1], [x, y]]; |
||||||
|
if (region == "SW") return [[-d0, d0], [-d0 - d1, d0 + d1], [x, y]]; |
||||||
|
if (region == "NW") return [[-d0, -d0], [-d0 - d1, -d0 - d1], [x, y]]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function updateText(selection) { |
||||||
|
return selection.each(function(d) { |
||||||
|
var x = d.textOffset[0], |
||||||
|
y = d.textOffset[1], |
||||||
|
region = getRegion(x, y, d.r), |
||||||
|
textCoords = getTextCoords(x, y, region); |
||||||
|
|
||||||
|
d3v3.select(this) |
||||||
|
.attr("x", textCoords.x) |
||||||
|
.attr("y", textCoords.y) |
||||||
|
.text(d.text) |
||||||
|
.each(function(d) { |
||||||
|
var x = d.textOffset[0], |
||||||
|
y = d.textOffset[1], |
||||||
|
textAnchor = getTextAnchor(x, y, region); |
||||||
|
|
||||||
|
var dx = textAnchor == "start" ? "0.33em" : |
||||||
|
textAnchor == "end" ? "-0.33em" : "0"; |
||||||
|
|
||||||
|
var dy = textAnchor !== "middle" ? ".33em" : |
||||||
|
["NW", "N", "NE"].indexOf(region) !== -1 ? "-.33em" : "1em"; |
||||||
|
|
||||||
|
var orientation = textAnchor !== "middle" ? undefined : |
||||||
|
["NW", "N", "NE"].indexOf(region) !== -1 ? "bottom" : "top"; |
||||||
|
|
||||||
|
d3v3.select(this) |
||||||
|
.style("text-anchor", textAnchor) |
||||||
|
.attr("dx", dx) |
||||||
|
.attr("dy", dy) |
||||||
|
.call(wrapText, d.textWidth || 960, orientation); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function getTextCoords(x, y, region) { |
||||||
|
if (region == "N") return { x: 0, y: y }; |
||||||
|
if (region == "E") return { x: x, y: 0 }; |
||||||
|
if (region == "S") return { x: 0, y: y }; |
||||||
|
if (region == "W") return { x: x, y: 0 }; |
||||||
|
return { x: x, y: y }; |
||||||
|
} |
||||||
|
|
||||||
|
function getTextAnchor(x, y, region) { |
||||||
|
if (region == null) { |
||||||
|
return "middle"; |
||||||
|
} |
||||||
|
else { |
||||||
|
// Cardinal directions
|
||||||
|
if (region == "N") return "middle"; |
||||||
|
if (region == "E") return "start"; |
||||||
|
if (region == "S") return "middle"; |
||||||
|
if (region == "W") return "end"; |
||||||
|
|
||||||
|
var xLonger = Math.abs(x) > Math.abs(y); |
||||||
|
|
||||||
|
// Intermediate directions`
|
||||||
|
if (region == "NE") return xLonger ? "start" : "middle"; |
||||||
|
if (region == "SE") return xLonger ? "start" : "middle"; |
||||||
|
if (region == "SW") return xLonger ? "end" : "middle"; |
||||||
|
if (region == "NW") return xLonger ? "end" : "middle";
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Adapted from: https://bl.ocks.org/mbostock/7555321
|
||||||
|
function wrapText(text, width, orientation) { |
||||||
|
text.each(function(d) { |
||||||
|
var text = d3v3.select(this), |
||||||
|
words = text.text().split(/\s+/).reverse(), |
||||||
|
word, |
||||||
|
line = [], |
||||||
|
lineNumber = 1, |
||||||
|
lineHeight = 1.1, // ems
|
||||||
|
x = text.attr("x"), |
||||||
|
dx = text.attr("dx"), |
||||||
|
tspan = text.text(null).append("tspan").attr("x", x).attr("dx", dx); |
||||||
|
while (word = words.pop()) { |
||||||
|
line.push(word); |
||||||
|
tspan.text(line.join(" ")); |
||||||
|
if (tspan.node().getComputedTextLength() > width) { |
||||||
|
line.pop(); |
||||||
|
tspan.text(line.join(" ")); |
||||||
|
line = [word]; |
||||||
|
tspan = text.append("tspan") |
||||||
|
.attr("x", x) |
||||||
|
.attr("dx", dx) |
||||||
|
.attr("dy", lineHeight + "em") |
||||||
|
.text(word); |
||||||
|
lineNumber++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var dy; |
||||||
|
if (orientation == "bottom") {
|
||||||
|
dy = -lineHeight * (lineNumber-1) - .33;
|
||||||
|
} |
||||||
|
else if (orientation == "top") {
|
||||||
|
dy = 1; |
||||||
|
} |
||||||
|
else {
|
||||||
|
dy = -lineHeight * ((lineNumber-1) / 2) + .33;
|
||||||
|
} |
||||||
|
text.attr("dy", dy + "em"); |
||||||
|
|
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function styleControl(selection) { |
||||||
|
selection |
||||||
|
.attr("r", controlRadius) |
||||||
|
.style("fill-opacity", "0") |
||||||
|
.style("stroke", "black") |
||||||
|
.style("stroke-dasharray", "3, 3") |
||||||
|
.style("cursor", "move"); |
||||||
|
} |
||||||
|
|
||||||
|
return draw; |
||||||
|
}; |
@ -0,0 +1,2 @@ |
|||||||
|
node_modules |
||||||
|
|
@ -0,0 +1,272 @@ |
|||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
/////////////// The Radar Chart Function ////////////////
|
||||||
|
/////////////// Written by Nadieh Bremer ////////////////
|
||||||
|
////////////////// VisualCinnamon.com ///////////////////
|
||||||
|
/////////// Inspired by the code of alangrafu ///////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function RadarChart(id, data, options) { |
||||||
|
var cfg = { |
||||||
|
w: 600, //Width of the circle
|
||||||
|
h: 600, //Height of the circle
|
||||||
|
margin: {top: 20, right: 20, bottom: 20, left: 20}, //The margins of the SVG
|
||||||
|
levels: 3, //How many levels or inner circles should there be drawn
|
||||||
|
maxValue: 0, //What is the value that the biggest circle will represent
|
||||||
|
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
|
||||||
|
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
|
||||||
|
opacityArea: 0.35, //The opacity of the area of the blob
|
||||||
|
dotRadius: 4, //The size of the colored circles of each blog
|
||||||
|
opacityCircles: 0.1, //The opacity of the circles of each blob
|
||||||
|
strokeWidth: 2, //The width of the stroke around each blob
|
||||||
|
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
|
||||||
|
color: d3v3.scale.category10() //Color function
|
||||||
|
}; |
||||||
|
|
||||||
|
//Put all of the options into a variable called cfg
|
||||||
|
if('undefined' !== typeof options){ |
||||||
|
for(var i in options){ |
||||||
|
if('undefined' !== typeof options[i]){ cfg[i] = options[i]; } |
||||||
|
}//for i
|
||||||
|
}//if
|
||||||
|
|
||||||
|
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
|
||||||
|
var maxValue = Math.max(cfg.maxValue, d3v3.max(data, function(i){return d3v3.max(i.map(function(o){return o.value;}))})); |
||||||
|
|
||||||
|
var allAxis = (data[0].map(function(i, j){return i.axis})), //Names of each axis
|
||||||
|
total = allAxis.length, //The number of different axes
|
||||||
|
radius = Math.min(cfg.w/2, cfg.h/2), //Radius of the outermost circle
|
||||||
|
Format = d3v3.format('%'), //Percentage formatting
|
||||||
|
angleSlice = Math.PI * 2 / total; //The width in radians of each "slice"
|
||||||
|
|
||||||
|
//Scale for the radius
|
||||||
|
var rScale = d3v3.scale.linear() |
||||||
|
.range([0, radius]) |
||||||
|
.domain([0, maxValue]); |
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
//////////// Create the container SVG and g /////////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Remove whatever chart with the same id/class was present before
|
||||||
|
d3v3.select(id).select("svg").remove(); |
||||||
|
|
||||||
|
//Initiate the radar chart SVG
|
||||||
|
var svg = d3v3.select(id).append("svg") |
||||||
|
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right) |
||||||
|
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom) |
||||||
|
.attr("class", "radar"+id); |
||||||
|
//Append a g element
|
||||||
|
var g = svg.append("g") |
||||||
|
.attr("transform", "translate(" + (cfg.w/2 + cfg.margin.left) + "," + (cfg.h/2 + cfg.margin.top) + ")"); |
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
////////// Glow filter for some extra pizzazz ///////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Filter for the outside glow
|
||||||
|
var filter = g.append('defs').append('filter').attr('id','glow'), |
||||||
|
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation','2.5').attr('result','coloredBlur'), |
||||||
|
feMerge = filter.append('feMerge'), |
||||||
|
feMergeNode_1 = feMerge.append('feMergeNode').attr('in','coloredBlur'), |
||||||
|
feMergeNode_2 = feMerge.append('feMergeNode').attr('in','SourceGraphic'); |
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
/////////////// Draw the Circular grid //////////////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Wrapper for the grid & axes
|
||||||
|
var axisGrid = g.append("g").attr("class", "axisWrapper"); |
||||||
|
|
||||||
|
//Draw the background circles
|
||||||
|
axisGrid.selectAll(".levels") |
||||||
|
.data(d3v3.range(1,(cfg.levels+1)).reverse()) |
||||||
|
.enter() |
||||||
|
.append("circle") |
||||||
|
.attr("class", "gridCircle") |
||||||
|
.attr("r", function(d, i){return radius/cfg.levels*d;}) |
||||||
|
.style("fill", "#CDCDCD") |
||||||
|
.style("stroke", "#CDCDCD") |
||||||
|
.style("fill-opacity", cfg.opacityCircles) |
||||||
|
.style("filter" , "url(#glow)"); |
||||||
|
|
||||||
|
//Text indicating at what % each level is
|
||||||
|
axisGrid.selectAll(".axisLabel") |
||||||
|
.data(d3v3.range(1,(cfg.levels+1)).reverse()) |
||||||
|
.enter().append("text") |
||||||
|
.attr("class", "axisLabel") |
||||||
|
.attr("x", 4) |
||||||
|
.attr("y", function(d){return -d*radius/cfg.levels;}) |
||||||
|
.attr("dy", "0.4em") |
||||||
|
.style("font-size", "10px") |
||||||
|
.attr("fill", "#737373") |
||||||
|
.text(function(d,i) { return Format(maxValue * d/cfg.levels); }); |
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
//////////////////// Draw the axes //////////////////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Create the straight lines radiating outward from the center
|
||||||
|
var axis = axisGrid.selectAll(".axis") |
||||||
|
.data(allAxis) |
||||||
|
.enter() |
||||||
|
.append("g") |
||||||
|
.attr("class", "axis"); |
||||||
|
//Append the lines
|
||||||
|
axis.append("line") |
||||||
|
.attr("x1", 0) |
||||||
|
.attr("y1", 0) |
||||||
|
.attr("x2", function(d, i){ return rScale(maxValue*1.1) * Math.cos(angleSlice*i - Math.PI/2); }) |
||||||
|
.attr("y2", function(d, i){ return rScale(maxValue*1.1) * Math.sin(angleSlice*i - Math.PI/2); }) |
||||||
|
.attr("class", "line") |
||||||
|
.style("stroke", "white") |
||||||
|
.style("stroke-width", "2px"); |
||||||
|
|
||||||
|
//Append the labels at each axis
|
||||||
|
axis.append("text") |
||||||
|
.attr("class", "legend") |
||||||
|
.style("font-size", "11px") |
||||||
|
.attr("text-anchor", "middle") |
||||||
|
.attr("dy", "0.35em") |
||||||
|
.attr("x", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.cos(angleSlice*i - Math.PI/2); }) |
||||||
|
.attr("y", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.sin(angleSlice*i - Math.PI/2); }) |
||||||
|
.text(function(d){return d}) |
||||||
|
.call(wrap, cfg.wrapWidth); |
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
///////////// Draw the radar chart blobs ////////////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//The radial line function
|
||||||
|
var radarLine = d3v3.svg.line.radial() |
||||||
|
.interpolate("linear-closed") |
||||||
|
.radius(function(d) { return rScale(d.value); }) |
||||||
|
.angle(function(d,i) { return i*angleSlice; }); |
||||||
|
|
||||||
|
if(cfg.roundStrokes) { |
||||||
|
radarLine.interpolate("cardinal-closed"); |
||||||
|
} |
||||||
|
|
||||||
|
//Create a wrapper for the blobs
|
||||||
|
var blobWrapper = g.selectAll(".radarWrapper") |
||||||
|
.data(data) |
||||||
|
.enter().append("g") |
||||||
|
.attr("class", "radarWrapper"); |
||||||
|
|
||||||
|
//Append the backgrounds
|
||||||
|
blobWrapper |
||||||
|
.append("path") |
||||||
|
.attr("class", "radarArea") |
||||||
|
.attr("d", function(d,i) { return radarLine(d); }) |
||||||
|
.style("fill", function(d,i) { return cfg.color(i); }) |
||||||
|
.style("fill-opacity", cfg.opacityArea) |
||||||
|
.on('mouseover', function (d,i){ |
||||||
|
//Dim all blobs
|
||||||
|
d3v3.selectAll(".radarArea") |
||||||
|
.transition().duration(200) |
||||||
|
.style("fill-opacity", 0.1);
|
||||||
|
//Bring back the hovered over blob
|
||||||
|
d3v3.select(this) |
||||||
|
.transition().duration(200) |
||||||
|
.style("fill-opacity", 0.7);
|
||||||
|
}) |
||||||
|
.on('mouseout', function(){ |
||||||
|
//Bring back all blobs
|
||||||
|
d3v3.selectAll(".radarArea") |
||||||
|
.transition().duration(200) |
||||||
|
.style("fill-opacity", cfg.opacityArea); |
||||||
|
}); |
||||||
|
|
||||||
|
//Create the outlines
|
||||||
|
blobWrapper.append("path") |
||||||
|
.attr("class", "radarStroke") |
||||||
|
.attr("d", function(d,i) { return radarLine(d); }) |
||||||
|
.style("stroke-width", cfg.strokeWidth + "px") |
||||||
|
.style("stroke", function(d,i) { return cfg.color(i); }) |
||||||
|
.style("fill", "none") |
||||||
|
.style("filter" , "url(#glow)");
|
||||||
|
|
||||||
|
//Append the circles
|
||||||
|
blobWrapper.selectAll(".radarCircle") |
||||||
|
.data(function(d,i) { return d; }) |
||||||
|
.enter().append("circle") |
||||||
|
.attr("class", "radarCircle") |
||||||
|
.attr("r", cfg.dotRadius) |
||||||
|
.attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2); }) |
||||||
|
.attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2); }) |
||||||
|
.style("fill", function(d,i,j) { return cfg.color(j); }) |
||||||
|
.style("fill-opacity", 0.8); |
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
//////// Append invisible circles for tooltip ///////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Wrapper for the invisible circles on top
|
||||||
|
var blobCircleWrapper = g.selectAll(".radarCircleWrapper") |
||||||
|
.data(data) |
||||||
|
.enter().append("g") |
||||||
|
.attr("class", "radarCircleWrapper"); |
||||||
|
|
||||||
|
//Append a set of invisible circles on top for the mouseover pop-up
|
||||||
|
blobCircleWrapper.selectAll(".radarInvisibleCircle") |
||||||
|
.data(function(d,i) { return d; }) |
||||||
|
.enter().append("circle") |
||||||
|
.attr("class", "radarInvisibleCircle") |
||||||
|
.attr("r", cfg.dotRadius*1.5) |
||||||
|
.attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2); }) |
||||||
|
.attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2); }) |
||||||
|
.style("fill", "none") |
||||||
|
.style("pointer-events", "all") |
||||||
|
.on("mouseover", function(d,i) { |
||||||
|
newX = parseFloat(d3v3.select(this).attr('cx')) - 10; |
||||||
|
newY = parseFloat(d3v3.select(this).attr('cy')) - 10; |
||||||
|
|
||||||
|
tooltip |
||||||
|
.attr('x', newX) |
||||||
|
.attr('y', newY) |
||||||
|
.text(Format(d.value)) |
||||||
|
.transition().duration(200) |
||||||
|
.style('opacity', 1); |
||||||
|
}) |
||||||
|
.on("mouseout", function(){ |
||||||
|
tooltip.transition().duration(200) |
||||||
|
.style("opacity", 0); |
||||||
|
}); |
||||||
|
|
||||||
|
//Set up the small tooltip for when you hover over a circle
|
||||||
|
var tooltip = g.append("text") |
||||||
|
.attr("class", "tooltip") |
||||||
|
.style("opacity", 0); |
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
/////////////////// Helper Function /////////////////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Taken from http://bl.ocks.org/mbostock/7555321
|
||||||
|
//Wraps SVG text
|
||||||
|
function wrap(text, width) { |
||||||
|
text.each(function() { |
||||||
|
var text = d3v3.select(this), |
||||||
|
words = text.text().split(/\s+/).reverse(), |
||||||
|
word, |
||||||
|
line = [], |
||||||
|
lineNumber = 0, |
||||||
|
lineHeight = 1.4, // ems
|
||||||
|
y = text.attr("y"), |
||||||
|
x = text.attr("x"), |
||||||
|
dy = parseFloat(text.attr("dy")), |
||||||
|
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em"); |
||||||
|
|
||||||
|
while (word = words.pop()) { |
||||||
|
line.push(word); |
||||||
|
tspan.text(line.join(" ")); |
||||||
|
if (tspan.node().getComputedTextLength() > width) { |
||||||
|
line.pop(); |
||||||
|
tspan.text(line.join(" ")); |
||||||
|
line = [word]; |
||||||
|
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}//wrap
|
||||||
|
|
||||||
|
}//RadarChart
|
Loading…
Reference in new issue