(function () {
var app = angular.module('d3', []);
//directive to show a 'wheel' UI to view competetency tree structures
app.directive('competencyWheel', ['$window', function ($window) {
"use strict";
return {
restrict: "EA",
scope: {
competencies: '=', //the competencies to display in the wheel
skinName:'=',
onCompetencyClick: '&' //the function that is called when a competency is clicked in the wheel
},
link: link
}
function link(scope, element, attrs) {
//the colour of the labels and center text - passed as an attribute and defaults to white
var textColour = attrs.textColour || 'white';
//the duration (in milliseconds) of the transition when zooming - passed as an attribute but defaults to 750
var transitionDuration = attrs.transitionDuration || 750;
//the name of one of the preset skins - defaults to 'default'
var skinName = scope.skinName || 'leafy';
//the size factor of the wheel in the SVG
var sizeFactor = attrs.sizeFactor || 0.9;
//based on the name of the chosen skin get the skin data
var skinData = getSkinData(skinName);
//create the SVG element on the element that the directive represents
var svg = d3.select(element[0]).append("svg")
.attr("id","svg") //give it an id so we can get it again in the future
.append("g") //create a group that will hold the rest of the wheel
.attr("id","maingroup");
//build the tooltip using the d3 plugin
var tip = buildToolTip();
var competencies = [];
var current = [];
var partition, arc, center, centerCircle, path, labels, data, margin, radius, width, height, borderRadius;
//when the window is resized update the scope so the wheel is rendered at the appropriate size
window.onresize = function () {
scope.$apply();
};
//watch the inner width of the element that contains the directive, if it changes we need to rerender the wheel
scope.$watch(function () {
return angular.element($window)[0].innerWidth;
}, function () {
if(data)
scope.render(data);
});
////watch the competencies on the scope, if they change we need to rerender the wheel
scope.$watch('competencies', function (newCompetenciesValue, oldCompetenciesValue) {
if (newCompetenciesValue&& newCompetenciesValue.length > 0) {
data = parseCompetencies(newCompetenciesValue[0]);
scope.render(data);
}
});
//render the wheel
scope.render = function (comps) {
//remove all traces of the previous wheel - if there are any
svg.selectAll('*').remove();
//this is responsive - i.e. the wheel should change size if the browser window changes size
//get the width of the element that contains the directive
var containerWidth = d3.select(element[0]).node().offsetWidth;
if (containerWidth === 0)
containerWidth = 500;
//make the svg width a bit smaller than the maximum space
width = containerWidth * sizeFactor;
height = width; //height of the svg
radius = getRadius(width); //the radius of the wheel
margin = getMargin(width);
borderRadius = width; //radius of the svg
//set the size attributes on the svg
d3.select("#svg")
.attr("width", width + "px")
.attr("height", height + "px")
.style("border-radius", borderRadius + "px");
//move the main group to the center of the svg
d3.select("#maingroup")
.attr("transform", "translate(" + margin + "," + margin + ")");
current = comps;
competencies = comps;
//build the wheel partitions and arcs
partition = buildPartition();
arc = buildArc();
//build the central circle and set the appropiate text
center = buildCenter();
centerCircle = buildCenterCircle();
setInitialCentralCircleText(current.label);
path = buildPath();
//setup the tool tips
svg.call(tip);
//set up the labels
labels = buildLabels();
splitText(labels);
}
function getMargin(theWidth) {
return theWidth / 2;
}
function getRadius(theWidth) {
return (theWidth / 2) * sizeFactor;
}
// Zoom to the specified new root.
function zoom(root, competency) {
if (document.documentElement.__transition__)
return;
// Rescale outside angles to match the new layout.
var enterArc, exitArc;
var outsideAngle = d3.scale.linear().domain([0, 2 * Math.PI]);
function insideArc(d) {
return competency.key > d.key
? { depth: d.depth - 1, x: 0, dx: 0 } : competency.key < d.key
? { depth: d.depth - 1, x: 2 * Math.PI, dx: 0 }
: { depth: 0, x: 0, dx: 2 * Math.PI };
}
function outsideArc(d) {
return { depth: d.depth, x: outsideAngle(d.x), dx: outsideAngle(d.x + d.dx) - outsideAngle(d.x) };
}
center.datum(root);
// When zooming in, arcs enter from the outside and exit to the inside.
// Entering outside arcs start from the old layout.
if (root === competency)
enterArc = outsideArc, exitArc = insideArc, outsideAngle.range([competency.x, competency.x + competency.dx]);
path = path.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});
// When zooming out, arcs enter from the inside and exit to the outside.
// Exiting outside arcs transition to the new layout.
if (root !== competency)
enterArc = insideArc, exitArc = outsideArc, outsideAngle.range([competency.x, competency.x + competency.dx]);
d3.transition().duration(transitionDuration).each(function () {
path.exit().transition()
.style("fill-opacity", function (d) {
return d.depth === 1 + (root === competency) ? 1 : 0;
})
.attrTween("d", function (d) {
return arcTween.call(this, exitArc(d));
})
.remove();
path.enter().append("path")
.attr("class", "wheelcolor")
.attr("id", function (d) {
return d.id;
})
.style("fill-opacity", function (d) {
return d.depth === 2 - (root === competency) ? 1 : 0;
})
.style("fill", skinData.group)
.on("click", zoomIn)
.each(function (d) {
this._current = enterArc(d);
d.path = this;
})
.on('mouseenter', function (d) {
tip.show(d, d.path);
})
.on('mouseleave', tip.hide);
path.transition()
.style("fill-opacity", function (d) {
return 1 / d.depth;
})
.attrTween("d", function (d) {
return arcTween.call(this, updateArc(d));
});
labels = labels.data(partition.nodes(root).filter(function (d) {
return d.depth === 1;
}), function (d) {
return d.key;
});
labels.enter().append("text")
.attr("class", "label")
.attr("class", "label")
.style("opacity", 0)
.style("fill", textColour)
.style("text-anchor", "middle")
.each(function (d) {
d3.select(this).classed("cat" + d.category, true);
})
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.on("click", zoomIn)
.on('mouseenter', function (d) {
tip.show(d, d.path);
})
.on('mouseleave', tip.hide)
.text(function (d, i) {
return d.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[0];
});
splitText(labels);
});
}
function setInitialCentralCircleText(text) {
var backButtonHeight = 30;
var backButtonWidth = 30;
center.append("text")
.each(function (d) {
var arr = text.match(/\b[\w']+(?:[^\w\n]+[\w']+){0,1}\b/g);
if (arr != undefined) {
for (var i = 0; i < arr.length; i++) {
d3.select(this).append("tspan")
.text(arr[i])
.attr("y", -12)
.attr("dy", i + ".2em")
.attr("x", 0)
.attr("fill", textColour)
.attr("text-anchor", "middle")
.attr("id", "centertext")
.attr("class", "tspan" + i);
}
}
});
center.append("svg:image")
.attr("id", "centerbackbutton")
.attr("width", function(d, i) {
return backButtonWidth;
})
.attr("height", function(d, i) {
return backButtonHeight;
})
.attr("x", function(d, i) {
return 0 - (backButtonWidth / 2);
})
.attr("y", function(d, i) {
return 5;
})
.style("visibility", function(d) {
return "hidden";
})
.style("cursor","pointer")
.attr("xlink:href", "https://uiframework.mkmapps.com/latest/lib/products/myshowcase/images/512px-Undo_font_awesome.svg.png?version=270122");
//center.append("foreignObject")
// .attr("id", "centerbackbutton")
// .attr("width", function(d, i) {
// return backButtonWidth;
// })
// .attr("height", function(d, i) {
// return backButtonHeight;
// })
// .attr("x", function(d, i) {
// return 0 - (backButtonWidth / 2);
// })
// .attr("y", function(d, i) {
// return 5;
// })
// .style("visibility", function(d) {
// return "hidden";
// });
//.html("
");
}
function zoomIn(competency) {
tip.hide();
if (document.documentElement.__transition__)
return;
// Find previously selected, unselect
d3.select(".selected").classed("selected", false);
// Select current item
var id = "#competency" + competency.id;
d3.select(id).attr("class", "selected");
if (competency.depth > 1)
competency = competency.parent;
if (competency.children) {
svg.selectAll("text.label").data([]).exit().remove();
zoom(competency, competency);
changeCenter(competency);
}
d3.select("#centerbackbutton")
.style("visibility", function (d) {
return "visible";
})
.on("click", function () {
zoomOut(current);
});
scope.$apply(function () {
if (scope.onCompetencyClick())
scope.onCompetencyClick()(competency);
});
}
function key(d) {
var k = [], p = d;
while (p.depth)
k.push(p.name), p = p.parent;
return k.reverse().join(".");
}
function arcTween(b) {
var i = d3.interpolate(this._current, b);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
function parseCompetencies(data) {
var root = {
id: data.id,
name: data.name,
description: data.description,
children: parseChildren(data.branches)
};
return root;
}
function parseChildren(children) {
var wheelChildren = [];
if (children) {
children.forEach(function (child) {
var dynaChild = {
id: child.id,
name: child.name,
description: child.description,
children: parseChildren(child.branches)
};
wheelChildren.push(dynaChild);
});
}
return wheelChildren;
}
function buildLabels() {
return svg.selectAll("text.label")
.data(partition.nodes(competencies).filter(function (competency) {
return competency.depth === 1;
}))
.enter().append("text")
.attr("class", "label")
.style("fill", textColour)
.style("text-anchor", "middle")
.attr("transform", function (competency) {
return "translate(" + arc.centroid(competency) + ")";
})
.each(function (competency) {
d3.select(this).classed("cat" + competency.category, true);
})
.on("click", zoomIn)
.on('mouseenter', function (competency) {
tip.show(competency, competency.path);
})
.on('mouseleave', tip.hide)
.text(function (competency, i) {
return competency.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[0];
});
}
function splitText(newLabels) {
if (!newLabels)
return;
newLabels.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function (d, i) {
return d.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[1];
});
newLabels.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function (d, i) {
return d.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[2];
});
newLabels.transition().duration(1000).style("opacity", 1);
}
function zoomOut(competency) {
tip.hide();
if (document.documentElement.__transition__) return;
// Find previously selected, unselect
d3.select(".selected").classed("selected", false);
if (competency.parent) {
svg.selectAll("text.label").data([]).exit().remove();
changeCenter(competency.parent);
zoom(competency.parent, competency);
}
}
function changeCenter(competency) {
current = competency;
d3.select("#centertext")
.on("click", function () {
zoomOut(current);
})
.text(function (d) {
return current.label;
});
d3.select("#centercircle")
.on("click", function () {
zoomOut(current);
});
if (!competency.parent) {
d3.select("#centerbackbutton")
.style("visibility", function (d) {
return "hidden";
})
.on("click", function () {
zoomOut(current);
});
}
}
function buildPath() {
return svg.selectAll("path").data(partition.nodes(competencies).slice(1))
.enter().append("path")
.attr("d", arc)
.attr("class", "competency")
.attr("id", function (d) {
return "competency" + d.id;
})
.style("fill", skinData.group)
.style("fill-opacity", function (d) {
return 1 / d.depth;
})
.each(function (d) {
this._current = updateArc(d);
d.path = this;
})
.on("click", zoomIn)
.on('mouseenter', function (d) {
tip.show(d, d.path);
})
.on('mouseleave', tip.hide);
}
function updateArc(d) {
return { depth: d.depth, x: d.x, dx: d.dx };
}
// Partition is one of d3's built in layouts. See .... for documentation.
// Define a new partition layout. We'll sort the members alphabetically, but
// if you don't want sorting just do .sort(none)
function buildPartition() {
var part = d3.layout.partition().sort(function (a, b) {
return d3.ascending(a.name, b.name);
}).size([2 * Math.PI, radius]);
// We'll work out the sum sizes for each node - nodes in the main arc (ie
// those with a depth of 1) should be equal sizes. Because of the way the d3
// partition layout works this means that we'll have to work out how many
// children each competence has, and assign a value to the children of 1/by
// that number.
// Also compute the full name of each competence and stash the children so
// they can be restored as we descend.
part.nodes(competencies).forEach(function (competency) {
competency._children = competency.branches;
competency.sum = 1;
if (competency.depth > 1) competency.sum = 1 / Object.keys(competency.branches).length;
competency.label = competency.name;
competency.key = key(competency);
});
// Now redefine the value function to use the previously-computed sum.
part.children(function (competency, depth) {
return depth < 2 ? competency._children : null;
}).value(function (competency) {
return competency.sum;
});
return part;
}
function buildToolTip() {
return d3.tip().attr('class', 'd3-tip').offset([10, 0]).html(function (competency) {
if (!competency)
competency = current;
return "" + (competency.name || "") + "
" + (competency.description || "") + "";
});
}
function buildArc() {
return d3.svg.arc().startAngle(function (d) {
return d.x;
}).endAngle(function (d) {
return d.x + d.dx - .01 / (d.depth + .5);
}).innerRadius(function (d) {
return radius / 5 * (d.depth * 1.85);
}).outerRadius(function (d) {
return radius / 5 * (d.depth + 2.6);
});
}
function buildCenter() {
center = svg.append("g");
center.attr("transform", "translate(0,0)");
return center;
}
function buildCenterCircle() {
centerCircle = center.append("circle");
centerCircle.attr("r", (radius / 5 * 2) - 15)
.attr("id", "centercircle")
.style("fill", skinData.root);
return centerCircle;
}
}
function getSkinData(skinName) {
var skins = [
{ name: 'default', colors: { root: '#1C5288', group: '#2693FF', competency: '#2693FF' } },
{ name: 'dark', colors: { root: '#91ccff', group: '#74b3ea', competency: '#cccccc' } },
{ name: 'cloudy', colors: { root: '#909be6', group: '#9399b7', competency: '#aaaaaa' } },
{ name: 'leafy', colors: { root: '#719f6e', group: '#67b460', competency: '#7ec688' } },
{ name: 'merlot', colors: { root: '#5d0d0d', group: '#be5c5c', competency: '#b99a9a' } },
{ name: 'espresso', colors: { root: '#40341f', group: '#7c5a28', competency: '#a88a60' } },
{ name: 'latte', colors: { root: '#a88a60', group: '#b59c78', competency: '#c1ab8c' } },
{ name: 'nsa', colors: { root: '#0993CD', group: '#E67D00', competency: '#626250' } },
{ name: 'nw_metro', colors: { root: '#000000', group: '#766e67', competency: '#a3c6ca' } },
{ name: 'system', colors: { root: '#666666', group: '#b4ab0e', competency: '#666666' } },
{ name: 'apple', colors: { root: '#7784a5', group: '#a9b5d3', competency: '#444444' } },
{ name: 'manager', colors: { root: '#1e206b', group: '#ec008b', competency: '#00a9e0' } },
{ name: 'database', colors: { root: '#000000', group: '#454545', competency: '#999999' } },
{ name: 'bars', colors: { root: '#666666', group: '#888888', competency: '#aaaaaa' } },
{ name: 'blocks', colors: { root: '#777777', group: '#999999', competency: '#bbbbbb' } },
{ name: 'blocks_wd', colors: { root: '#777777', group: '#999999', competency: '#bbbbbb' } },
{ name: 'blocks_wd2', colors: { root: '#777777', group: '#999999', competency: '#bbbbbb' } }
];
var skinData = skins[0].colors;
for (var a = 0; a < skins.length; a++) {
if (skins[a].name == skinName) {
skinData = skins[a].colors;
break;
}
}
return skinData;
}
}]);
//directive to show a 'wheel' UI to view competetency tree structures
app.directive('frameworkWheel', ['$window', function ($window) {
"use strict";
return {
restrict: "EA",
scope: {
competencies: '=', //the competencies to display in the wheel
skinName: '=',
onCompetencyClick: '&' //the function that is called when a competency is clicked in the wheel
},
link: link
}
function link(scope, element, attrs) {
//the colour of the labels and center text - passed as an attribute and defaults to white
var textColour = attrs.textColour || 'white';
//the duration (in milliseconds) of the transition when zooming - passed as an attribute but defaults to 750
var transitionDuration = attrs.transitionDuration || 750;
//the name of one of the preset skins - defaults to 'default'
var skinName = scope.skinName || 'leafy';
//the size factor of the wheel in the SVG
var sizeFactor = attrs.sizeFactor || 0.9;
//based on the name of the chosen skin get the skin data
var skinData = getSkinData(skinName);
//create the SVG element on the element that the directive represents
var svg = d3.select(element[0]).append("svg")
.attr("id", "svg") //give it an id so we can get it again in the future
.append("g") //create a group that will hold the rest of the wheel
.attr("id", "maingroup");
var competencies = [];
var current = [];
var partition, arc, center, centerCircle, path, labels, data, margin, radius, width, height, borderRadius;
//render the wheel
scope.render = function (comps) {
//remove all traces of the previous wheel - if there are any
svg.selectAll('*').remove();
//this is responsive - i.e. the wheel should change size if the browser window changes size
//get the width of the element that contains the directive
var containerWidth = d3.select(element[0]).node().offsetWidth;
if (containerWidth === 0)
containerWidth = 500;
//make the svg width a bit smaller than the maximum space
width = containerWidth * sizeFactor;
height = width; //height of the svg
radius = getRadius(width); //the radius of the wheel
margin = getMargin(width);
borderRadius = width; //radius of the svg
//set the size attributes on the svg
d3.select("#svg")
.attr("width", width + "px")
.attr("height", height + "px")
.style("border-radius", borderRadius + "px");
//move the main group to the center of the svg
d3.select("#maingroup")
.attr("transform", "translate(" + margin + "," + margin + ")");
current = comps;
competencies = comps;
//build the wheel partitions and arcs
partition = buildPartition();
arc = buildArc();
//build the central circle and set the appropiate text
center = buildCenter();
centerCircle = buildCenterCircle();
setInitialCentralCircleText(current.label);
path = buildPath();
//setup the tool tips
svg.call(tip);
//set up the labels
labels = buildLabels();
splitText(labels);
}
function getMargin(theWidth) {
return theWidth / 2;
}
function getRadius(theWidth) {
return (theWidth / 2) * sizeFactor;
}
// Zoom to the specified new root.
function zoom(root, competency) {
if (document.documentElement.__transition__)
return;
// Rescale outside angles to match the new layout.
var enterArc, exitArc;
var outsideAngle = d3.scale.linear().domain([0, 2 * Math.PI]);
function insideArc(d) {
return competency.key > d.key
? { depth: d.depth - 1, x: 0, dx: 0 } : competency.key < d.key
? { depth: d.depth - 1, x: 2 * Math.PI, dx: 0 }
: { depth: 0, x: 0, dx: 2 * Math.PI };
}
function outsideArc(d) {
return { depth: d.depth, x: outsideAngle(d.x), dx: outsideAngle(d.x + d.dx) - outsideAngle(d.x) };
}
center.datum(root);
// When zooming in, arcs enter from the outside and exit to the inside.
// Entering outside arcs start from the old layout.
if (root === competency)
enterArc = outsideArc, exitArc = insideArc, outsideAngle.range([competency.x, competency.x + competency.dx]);
path = path.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});
// When zooming out, arcs enter from the inside and exit to the outside.
// Exiting outside arcs transition to the new layout.
if (root !== competency)
enterArc = insideArc, exitArc = outsideArc, outsideAngle.range([competency.x, competency.x + competency.dx]);
d3.transition().duration(transitionDuration).each(function () {
path.exit().transition()
.style("fill-opacity", function (d) {
return d.depth === 1 + (root === competency) ? 1 : 0;
})
.attrTween("d", function (d) {
return arcTween.call(this, exitArc(d));
})
.remove();
path.enter().append("path")
.attr("class", "wheelcolor")
.attr("id", function (d) {
return d.id;
})
.style("fill-opacity", function (d) {
return d.depth === 2 - (root === competency) ? 1 : 0;
})
.style("fill", skinData.group)
.on("click", zoomIn)
.each(function (d) {
this._current = enterArc(d);
d.path = this;
})
.on('mouseenter', function (d) {
tip.show(d, d.path);
})
.on('mouseleave', tip.hide);
path.transition()
.style("fill-opacity", function (d) {
return 1 / d.depth;
})
.attrTween("d", function (d) {
return arcTween.call(this, updateArc(d));
});
labels = labels.data(partition.nodes(root).filter(function (d) {
return d.depth === 1;
}), function (d) {
return d.key;
});
labels.enter().append("text")
.attr("class", "label")
.attr("class", "label")
.style("opacity", 0)
.style("fill", textColour)
.style("text-anchor", "middle")
.each(function (d) {
d3.select(this).classed("cat" + d.category, true);
})
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.on("click", zoomIn)
.on('mouseenter', function (d) {
tip.show(d, d.path);
})
.on('mouseleave', tip.hide)
.text(function (d, i) {
return d.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[0];
});
splitText(labels);
});
}
function setInitialCentralCircleText(text) {
var backButtonHeight = 30;
var backButtonWidth = 30;
center.append("text")
.each(function (d) {
var arr = text.match(/\b[\w']+(?:[^\w\n]+[\w']+){0,1}\b/g);
if (arr != undefined) {
for (var i = 0; i < arr.length; i++) {
d3.select(this).append("tspan")
.text(arr[i])
.attr("y", -12)
.attr("dy", i + ".2em")
.attr("x", 0)
.attr("fill", textColour)
.attr("text-anchor", "middle")
.attr("id", "centertext")
.attr("class", "tspan" + i);
}
}
});
center.append("svg:image")
.attr("id", "centerbackbutton")
.attr("width", function (d, i) {
return backButtonWidth;
})
.attr("height", function (d, i) {
return backButtonHeight;
})
.attr("x", function (d, i) {
return 0 - (backButtonWidth / 2);
})
.attr("y", function (d, i) {
return 5;
})
.style("visibility", function (d) {
return "hidden";
})
.style("cursor", "pointer")
.attr("xlink:href", "https://uiframework.mkmapps.com/latest/lib/products/myshowcase/images/512px-Undo_font_awesome.svg.png?version=270122");
//center.append("foreignObject")
// .attr("id", "centerbackbutton")
// .attr("width", function(d, i) {
// return backButtonWidth;
// })
// .attr("height", function(d, i) {
// return backButtonHeight;
// })
// .attr("x", function(d, i) {
// return 0 - (backButtonWidth / 2);
// })
// .attr("y", function(d, i) {
// return 5;
// })
// .style("visibility", function(d) {
// return "hidden";
// });
//.html("
");
}
function zoomIn(competency) {
tip.hide();
if (document.documentElement.__transition__)
return;
// Find previously selected, unselect
d3.select(".selected").classed("selected", false);
// Select current item
var id = "#competency" + competency.id;
d3.select(id).attr("class", "selected");
if (competency.depth > 1)
competency = competency.parent;
if (competency.children) {
svg.selectAll("text.label").data([]).exit().remove();
zoom(competency, competency);
changeCenter(competency);
}
d3.select("#centerbackbutton")
.style("visibility", function (d) {
return "visible";
})
.on("click", function () {
zoomOut(current);
});
scope.$apply(function () {
if (scope.onCompetencyClick())
scope.onCompetencyClick()(competency);
});
}
function key(d) {
var k = [], p = d;
while (p.depth)
k.push(p.name), p = p.parent;
return k.reverse().join(".");
}
function arcTween(b) {
var i = d3.interpolate(this._current, b);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
function parseCompetencies(data) {
var root = {
id: data.id,
name: data.name,
description: data.description,
category: data.category,
children: parseChildren(data.childCompetencies)
};
return root;
}
function parseChildren(children) {
var wheelChildren = [];
if (children) {
children.forEach(function (child) {
var dynaChild = {
id: child.id,
category: child.category,
name: child.name,
description: child.description,
children: parseChildren(child.childCompetencies)
};
wheelChildren.push(dynaChild);
});
}
return wheelChildren;
}
function buildLabels() {
return svg.selectAll("text.label")
.data(partition.nodes(competencies).filter(function (competency) {
return competency.depth === 1;
}))
.enter().append("text")
.attr("class", "label")
.style("fill", textColour)
.style("text-anchor", "middle")
.attr("transform", function (competency) {
return "translate(" + arc.centroid(competency) + ")";
})
.each(function (competency) {
d3.select(this).classed("cat" + competency.category, true);
})
.on("click", zoomIn)
.on('mouseenter', function (competency) {
tip.show(competency, competency.path);
})
.on('mouseleave', tip.hide)
.text(function (competency, i) {
return competency.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[0];
});
}
function splitText(newLabels) {
if (!newLabels)
return;
newLabels.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function (d, i) {
return d.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[1];
});
newLabels.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function (d, i) {
return d.label.replace(/.{10}\S*\s+/g, "$&@@").split(/\s+@@/)[2];
});
newLabels.transition().duration(1000).style("opacity", 1);
}
function zoomOut(competency) {
tip.hide();
if (document.documentElement.__transition__) return;
// Find previously selected, unselect
d3.select(".selected").classed("selected", false);
if (competency.parent) {
svg.selectAll("text.label").data([]).exit().remove();
changeCenter(competency.parent);
zoom(competency.parent, competency);
}
}
function changeCenter(competency) {
current = competency;
d3.select("#centertext")
.on("click", function () {
zoomOut(current);
})
.text(function (d) {
return current.label;
});
d3.select("#centercircle")
.on("click", function () {
zoomOut(current);
});
if (!competency.parent) {
d3.select("#centerbackbutton")
.style("visibility", function (d) {
return "hidden";
})
.on("click", function () {
zoomOut(current);
});
}
}
function buildPath() {
return svg.selectAll("path").data(partition.nodes(competencies).slice(1))
.enter().append("path")
.attr("d", arc)
.attr("class", "competency")
.attr("id", function (d) {
return "competency" + d.id;
})
.style("fill", skinData.group)
.style("fill-opacity", function (d) {
return 1 / d.depth;
})
.each(function (d) {
this._current = updateArc(d);
d.path = this;
})
.on("click", zoomIn)
.on('mouseenter', function (d) {
tip.show(d, d.path);
})
.on('mouseleave', tip.hide);
}
function updateArc(d) {
return { depth: d.depth, x: d.x, dx: d.dx };
}
// Partition is one of d3's built in layouts. See .... for documentation.
// Define a new partition layout. We'll sort the members alphabetically, but
// if you don't want sorting just do .sort(none)
function buildPartition() {
var part = d3.layout.partition().sort(function (a, b) {
return d3.ascending(a.name, b.name);
}).size([2 * Math.PI, radius]);
// We'll work out the sum sizes for each node - nodes in the main arc (ie
// those with a depth of 1) should be equal sizes. Because of the way the d3
// partition layout works this means that we'll have to work out how many
// children each competence has, and assign a value to the children of 1/by
// that number.
// Also compute the full name of each competence and stash the children so
// they can be restored as we descend.
part.nodes(competencies).forEach(function (competency) {
competency._children = competency.children;
competency.sum = 1;
if (competency.depth > 1) competency.sum = 1 / Object.keys(competency.parent.children).length;
competency.label = competency.name;
competency.key = key(competency);
});
// Now redefine the value function to use the previously-computed sum.
part.children(function (competency, depth) {
return depth < 2 ? competency._children : null;
}).value(function (competency) {
return competency.sum;
});
return part;
}
function buildToolTip() {
return d3.tip().attr('class', 'd3-tip').offset([10, 0]).html(function (competency) {
if (!competency)
competency = current;
return "" + (competency.name || "") + "
" + (competency.description || "") + "";
});
}
function buildArc() {
return d3.svg.arc().startAngle(function (d) {
return d.x;
}).endAngle(function (d) {
return d.x + d.dx - .01 / (d.depth + .5);
}).innerRadius(function (d) {
return radius / 5 * (d.depth * 1.85);
}).outerRadius(function (d) {
return radius / 5 * (d.depth + 2.6);
});
}
function buildCenter() {
center = svg.append("g");
center.attr("transform", "translate(0,0)");
return center;
}
function buildCenterCircle() {
centerCircle = center.append("circle");
centerCircle.attr("r", (radius / 5 * 2) - 15)
.attr("id", "centercircle")
.style("fill", skinData.root);
return centerCircle;
}
}
function getSkinData(skinName) {
var skins = [
{ name: 'default', colors: { root: '#1C5288', group: '#2693FF', competency: '#2693FF' } },
{ name: 'dark', colors: { root: '#91ccff', group: '#74b3ea', competency: '#cccccc' } },
{ name: 'cloudy', colors: { root: '#909be6', group: '#9399b7', competency: '#aaaaaa' } },
{ name: 'leafy', colors: { root: '#719f6e', group: '#67b460', competency: '#7ec688' } },
{ name: 'merlot', colors: { root: '#5d0d0d', group: '#be5c5c', competency: '#b99a9a' } },
{ name: 'espresso', colors: { root: '#40341f', group: '#7c5a28', competency: '#a88a60' } },
{ name: 'latte', colors: { root: '#a88a60', group: '#b59c78', competency: '#c1ab8c' } },
{ name: 'nsa', colors: { root: '#0993CD', group: '#E67D00', competency: '#626250' } },
{ name: 'nw_metro', colors: { root: '#000000', group: '#766e67', competency: '#a3c6ca' } },
{ name: 'system', colors: { root: '#666666', group: '#b4ab0e', competency: '#666666' } },
{ name: 'apple', colors: { root: '#7784a5', group: '#a9b5d3', competency: '#444444' } },
{ name: 'manager', colors: { root: '#1e206b', group: '#ec008b', competency: '#00a9e0' } },
{ name: 'database', colors: { root: '#000000', group: '#454545', competency: '#999999' } },
{ name: 'bars', colors: { root: '#666666', group: '#888888', competency: '#aaaaaa' } },
{ name: 'blocks', colors: { root: '#777777', group: '#999999', competency: '#bbbbbb' } },
{ name: 'blocks_wd', colors: { root: '#777777', group: '#999999', competency: '#bbbbbb' } },
{ name: 'blocks_wd2', colors: { root: '#777777', group: '#999999', competency: '#bbbbbb' } }
];
var skinData = skins[0].colors;
for (var a = 0; a < skins.length; a++) {
if (skins[a].name == skinName) {
skinData = skins[a].colors;
break;
}
}
return skinData;
}
}]);
})();