Refactoring some code see #1791

skala
Julio Montoya 13 years ago
parent 54a3788c72
commit 670d532845
  1. 6
      main/admin/skills.json.php
  2. 2
      main/admin/skills.php
  3. 28
      main/admin/skills_wheel.php
  4. 7
      main/inc/ajax/skill.ajax.php
  5. 200
      main/inc/lib/javascript/coffeewheel/wheel.js
  6. 0
      main/inc/lib/javascript/d3/d3.v2.min.js
  7. 73
      main/inc/lib/skill.lib.php
  8. 2
      main/social/skills_tree.php
  9. 254
      main/template/default/skill/skill_wheel.tpl

@ -1,6 +0,0 @@
<?php
require '../inc/global.inc.php';
$skills = new Skill();
//$all = $skills->get_all(false,false,null,0);
$all = $skills->get_skills_tree_json();
echo $all;

@ -27,7 +27,7 @@ $htmlHeadXtra[] = api_get_js('skills.js');
$skill = new Skill();
$type = 'edit'; //edit
$tree = $skill->get_skills_tree(null, true);
$tree = $skill->get_skills_tree(null, null, true);
$skill_visualizer = new SkillVisualizer($tree, $type);
$html = $skill_visualizer->return_html();

@ -10,7 +10,6 @@ $language_file = array('admin');
$cidReset = true;
require_once '../inc/global.inc.php';
require_once api_get_path(LIBRARY_PATH).'skill.lib.php';
$this_section = SECTION_PLATFORM_ADMIN;
@ -21,24 +20,25 @@ if (api_get_setting('allow_skills_tool') != 'true') {
}
//Adds the JS needed to use the jqgrid
$htmlHeadXtra[] = api_get_js('d3.v2.min.js');
$htmlHeadXtra[] = api_get_js('coffeewheel/wheel.js');
$htmlHeadXtra[] = api_get_js('d3/d3.v2.min.js');
$htmlHeadXtra[] = api_get_js('d3/colorbrewer.js');
//$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/d3/colorbrewer.css');
$skill = new Skill();
$type = 'edit'; //edit
//$tree = $skill->get_skills_tree_json(null, true);
//$html = $skill_visualizer->return_html();
//$html = $skill_visualizer->return_html();
//$url = api_get_path(WEB_AJAX_PATH).'skill.ajax.php?1=1';
$tpl = new Template(null, false, false);
$load_user = 0;
if (isset($_GET['load_user'])) {
$load_user = 1;
}
$skill_id = null;
if (isset($_GET['skill_id'])) {
$skill_id = intval($_GET['skill_id']);
}
$tpl = new Template(null, false, false);
$url = api_get_path(WEB_AJAX_PATH)."skill.ajax.php?a=get_skills_tree_json&load_user=$load_user&skill_id=$skill_id";
//$tpl->assign('url', $url);
//$tpl->assign('html', $html);
//$tpl->assign('js', $skill_visualizer->return_js());
$tpl->assign('wheel_url', $url);
$content = $tpl->fetch('default/skill/skill_wheel.tpl');
$tpl->assign('content', $content);

@ -133,6 +133,13 @@ switch ($action) {
echo 0;
}
break;
case 'get_skills_tree_json':
$user_id = isset($_REQUEST['load_user']) && $_REQUEST['load_user'] == 1 ? api_get_user_id() : 0;
$skill_id = isset($_REQUEST['skill_id']) ? $_REQUEST['skill_id'] : 0;
$all = $skill->get_skills_tree_json($user_id, $skill_id);
echo $all;
break;
default:
echo '';
}

@ -1,200 +0,0 @@
/* Define constants and size of the wheel */
/** Total width of the wheel (also counts for the height) */
$(document).ready(function() {
var w = 800,
h = w,
r = w / 2,
/** x/y positionning of the center of the wheel */
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.1).domain([0, 1]).range([0, r]),
/** Padding in pixels before th string starts */
p = 3,
/** Duration of click animations */
duration = 1000,
/** Levels to show */
l = 3;
/* Locate the #div id element */
var div = d3.select("#vis");
/* Remove the image (make way for the dynamic stuff */
div.select("img").remove();
/* Append an element "svg" to the #vis section */
var vis = div.append("svg")
.attr("width", w + p * 2)
.attr("height", h + p * 2)
.append("g")
.attr("transform", "translate(" + (r + p) + "," + (r/1.5 + p) + ")");
/* ...update translate variables to change coordinates of wheel's center */
/* Add a small label to help the user */
div.append("p")
.attr("id", "intro")
.text("Click to zoom!");
/* Generate the partition layout */
var partition = d3.layout.partition()
.sort(null)
/** Value here seems to be a calculation of the size of the elements
depending on the level of depth they're at. Changing it makes
elements pass over the limits of others... */
.value(function(d) {
return 5.8 - d.depth;
});
/* Generate an arc which will define the whole wheel context */
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, d.y ? y(d.y) : d.y);
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
/* ...adding "+1" to "y" function's params is really funny */
/* Exexute the calculation based on a JSON file provided */
/*d3.json("wheel.json", function(json) {*/
/** Get the JSON list of skills and work on it */
d3.json("skills.json.php", function(json) {
/** Define the list of nodes based on the JSON */
var nodes = partition.nodes({
children: json
});
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) {
return "path-" + i;
})
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.on("click", click);
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d) {
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
/** Get the text details and define the rotation and general position */
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", click);
/** Managing text - alway maximum two words */
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) {
return d.depth ? d.name.split(" ")[0] : "";
});
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) {
return d.depth ? d.name.split(" ")[1] || "" : "";
});
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
// Somewhat of a hack as we rely on arcTween updating the scales.
text.style("visibility", function(e) {
return isParentOf(d, e) ? null : d3.select(this).style("visibility");
})
.transition().duration(duration)
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1;
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e) {
return isParentOf(d, e) ? 1 : 1e-6;
})
.each("end", function(e) {
d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
});
}
});
/* Returns whether p is parent of c */
function isParentOf(p, c) {
if (p === c) return true;
if (p.children) {
return p.children.some(function(d) {
return isParentOf(d, c);
});
}
return false;
}
/* Generated random colour */
function colour(d) {
if (d.children) {
// There is a maximum of two children!
var colours = d.children.map(colour),
a = d3.hsl(colours[0]),
b = d3.hsl(colours[1]);
// L*a*b* might be better here...
return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
}
return d.colour || "#fff";
}
/* Interpolate the scales! */
function arcTween(d) {
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, r]);
return function(d) {
return function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
/* */
function maxY(d) {
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
/* Use a formula for contrasting colour
http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
*/
function brightness(rgb) {
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
}
});

@ -8,11 +8,6 @@
/**
* Code
*/
/**
* @package chamilo.library
*/
define ('SKILL_TYPE_REQUIREMENT', 'required');
define ('SKILL_TYPE_ACQUIRED', 'acquired');
define ('SKILL_TYPE_BOTH', 'both');
@ -329,19 +324,19 @@ class Skill extends Model {
function get_all($load_user_data = false, $user_id = false, $id = null, $parent_id = null) {
$id_condition = '';
if (isset($id)) {
if (isset($id) && !empty($id)) {
$id = intval($id);
$id_condition = " WHERE s.id = $id";
}
if (isset($parent_id)) {
if (isset($parent_id) && !empty($parent_id)) {
$parent_id = intval($parent_id);
if (empty($id_condition)) {
$id_condition = "WHERE ss.parent_id = $parent_id";
} else {
$id_condition = " AND ss.parent_id = $parent_id";
}
}
$sql = "SELECT s.id, s.name, s.description, ss.parent_id, ss.relation_type".
@ -546,29 +541,47 @@ class Skill extends Model {
return $clean_skill;
}
public function get_skills_tree($user_id = null, $return_flat_array = false) {
public function get_skills_tree($user_id = null, $skill_id = null, $return_flat_array = false, $add_root = false) {
if (isset($user_id) && !empty($user_id)) {
$skills = $this->get_all(true, $user_id);
$skills = $this->get_all(true, $user_id, null, $skill_id);
} else {
$skills = $this->get_all();
$skills = $this->get_all(false, false, null, $skill_id);
}
//Show 1 item
if ($add_root) {
if (!empty($skill_id)) {
$skills[1] = array('id' => '1', 'name' => get_lang('Root'), 'parent_id' => '0');
$skill_info = $this->get_skill_info($skill_id);
$skills[$skill_id] = $skill_info;
$skills[$skill_id]['parent_id'] = $skill_info['extra']['parent_id'];
}
}
$refs = array();
$skills_tree = null;
// Create references for all nodes
$flat_array = array();
$css_attributes = array('fill' => 'red');
//var_dump($skills);
if (!empty($skills)) {
foreach($skills as &$skill) {
foreach ($skills as &$skill) {
if ($skill['parent_id'] == 0) {
$skill['parent_id'] = 'root';
}
$skill['data'] = array('parent_id' => $skill['parent_id']); // because except main keys (id, name, children) others keys are not saved while in the space tree
$skill['data']['achieved'] = 'no';
if ($user_id) {
$skill['data']['achieved'] = $this->user_has_skill($user_id, $skill['id']);
$css_attributes = array('fill' => 'green');
$skill['data']['achieved'] = $this->user_has_skill($user_id, $skill['id']);
}
$skill['data']['css_attributes'] = $css_attributes;
$refs[$skill['id']] = &$skill;
$flat_array[$skill['id']] = &$skill;
}
@ -578,7 +591,7 @@ class Skill extends Model {
$refs[$skill['parent_id']]['children'][] = &$skill;
$flat_array[$skillInd] = $skill;
}
$skills_tree = array(
'name' => get_lang('SkillRootName'),
'id' => 'root',
@ -586,6 +599,8 @@ class Skill extends Model {
'data' => array()
);
}
//var_dump($flat_array);exit;
if ($return_flat_array) {
return $flat_array;
@ -598,18 +613,20 @@ class Skill extends Model {
* Get skills tree as a simplified JSON structure
*
*/
public function get_skills_tree_json($user_id = null, $return_flat_array = false) {
$tree = $this->get_skills_tree($user_id, $return_flat_array);
public function get_skills_tree_json($user_id = null, $skill_id = null, $return_flat_array = false) {
$tree = $this->get_skills_tree($user_id, $skill_id, $return_flat_array, true);
$simple_tree = array();
if (!empty($tree['children'])) {
foreach ($tree['children'] as $element) {
$simple_tree[] = array('name' => $element['name'], 'children' => $this->get_skill_json($element['children']));
$simple_tree[] = array( 'name' => $element['name'],
'children' => $this->get_skill_json($element['children']),
);
}
}
//$s = array('\/');
//return str_replace($s,'/',json_encode($simple_tree[0]['children']));
}
return json_encode($simple_tree[0]['children']);
}
/**
* Get JSON element
*/
@ -618,12 +635,22 @@ class Skill extends Model {
if (is_array($subtree)) {
foreach ($subtree as $elem) {
$tmp = array();
$tmp['name'] = $elem['name'];//.'<a href="">'.$elem['id'].'</a>';
$tmp['name'] = $elem['name'];
$tmp['id'] = $elem['id'];
if (is_array($elem['children'])) {
$tmp['children'] = $this->get_skill_json($elem['children'], $depth+1);
} else {
$tmp['colour'] = $this->colours[$depth][rand(0,3)];
}
$tmp['depth'] = $depth;
if (isset($elem['data']) && is_array($elem['data'])) {
foreach ($elem['data'] as $key => $item) {
$tmp[$key] = $item;
}
}
$simple_sub_tree[] = $tmp;
}
return $simple_sub_tree;

@ -29,7 +29,7 @@ $htmlHeadXtra[] = api_get_js('skills.js');
$skill = new Skill();
$type = 'read'; //edit
$tree = $skill->get_skills_tree(api_get_user_id(), true);
$tree = $skill->get_skills_tree(api_get_user_id(), null, true);
$skill_visualizer = new SkillVisualizer($tree, $type);
$url = api_get_path(WEB_AJAX_PATH).'skill.ajax.php?1=1';
$tpl = new Template(null, false, false);

@ -1,3 +1,257 @@
<script>
/* For licensing terms, see /license.txt */
$(document).ready(function() {
/** Define constants and size of the wheel */
/** Total width of the wheel (also counts for the height) */
var w = 800,
h = w,
r = w / 2,
/** x/y positionning of the center of the wheel */
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.1).domain([0, 1]).range([0, r]),
/** Padding in pixels before th string starts */
p = 3,
/** Duration of click animations */
duration = 1000,
/** Levels to show */
l = 3;
/* Locate the #div id element */
var div = d3.select("#vis");
/* Remove the image (make way for the dynamic stuff */
div.select("img").remove();
/* Append an element "svg" to the #vis section */
var vis = div.append("svg")
//.attr("class", "Blues")
.attr("width", w + p * 2)
.attr("height", h + p * 2)
.append("g")
.attr("transform", "translate(" + (r + p) + "," + (r/1.5 + p) + ")");
/* ...update translate variables to change coordinates of wheel's center */
/* Add a small label to help the user */
div.append("p")
.attr("id", "intro")
.text("Click to zoom!");
/* Generate the partition layout */
var partition = d3.layout.partition()
.sort(null)
/** Value here seems to be a calculation of the size of the elements
depending on the level of depth they're at. Changing it makes
elements pass over the limits of others... */
.value(function(d) {
return 5.8 - d.depth;
});
/* Generate an arc which will define the whole wheel context */
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, d.y ? y(d.y) : d.y);
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
/* ...adding "+1" to "y" function's params is really funny */
/* Exexute the calculation based on a JSON file provided */
/*d3.json("wheel.json", function(json) {*/
/** Get the JSON list of skills and work on it */
//var achieved_fill = d3.scale.category10().domain(["yes","no"]);
var normal_fill = d3.scale.category20c();
/*var colorScale = d3.scale.quantize()
.domain([1.0, 5.0])
.range(colorbrewer.YlGnBu[5]);
*/
var z = d3.scale.ordinal().domain(["1", "2", "3"]).range(colorbrewer.RdBu[9]);
function set_skill_style(d, attribute) {
return_fill = normal_fill;
return_fill = z(d.depth);
return_background = 'blue';
if (d.achieved) {
//return_fill = 'green';
return_background = 'blue';
} else {
//return_fill = colorScale;
return_background = '1px border #fff';
}
switch (attribute) {
case 'fill':
return return_fill;
break;
case 'background':
return return_background;
break;
}
}
d3.json("{{ wheel_url }}", function(json) {
/** Define the list of nodes based on the JSON */
var nodes = partition.nodes({
children: json
});
/* Setting all skills */
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) {
return "path-" + i;
})
.attr("class", function(d) { return "q" + "1-9"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
//.style("fill", colour)
.style("fill", function(d) {
return set_skill_style(d, 'fill');
})
.style("background", function(d) {
return set_skill_style(d, 'background');
})
.on("click", click);
/* End setting skills */
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d) {
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
/** Get the text details and define the rotation and general position */
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", click);
/** Managing text - always maximum two words */
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) {
return d.depth ? d.name.split(" ")[0] : "";
});
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) {
return d.depth ? d.name.split(" ")[1] || "" : "";
});
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
// Somewhat of a hack as we rely on arcTween updating the scales.
text.style("visibility", function(e) {
return isParentOf(d, e) ? null : d3.select(this).style("visibility");
})
.transition().duration(duration)
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1;
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e) {
return isParentOf(d, e) ? 1 : 1e-6;
})
.each("end", function(e) {
d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
});
}
});
/* Returns whether p is parent of c */
function isParentOf(p, c) {
if (p === c) return true;
if (p.children) {
return p.children.some(function(d) {
return isParentOf(d, c);
});
}
return false;
}
/* Generated random colour */
function colour(d) {
if (d.children) {
// There is a maximum of two children!
var colours = d.children.map(colour),
a = d3.hsl(colours[0]),
b = d3.hsl(colours[1]);
// L*a*b* might be better here...
return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
}
return d.colour || "#fff";
}
function set_attributes(d) {
console.log(d);
return d.style('fill', 'green');
}
/* Interpolate the scales! */
function arcTween(d) {
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, r]);
return function(d) {
return function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
/* */
function maxY(d) {
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
/* Use a formula for contrasting colour
http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
*/
function brightness(rgb) {
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
}
});
</script>
<div id="menu" class="well" style="top:20px; left:20px; width:380px; z-index: 9000; opacity: 0.9;">
<h3>{{ 'Skills'|get_lang }}</h3>
<div class="btn-group">

Loading…
Cancel
Save