(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; } }]); })();