Updating skill tree view and template.

This commit is contained in:
Gregory Trullemans 2022-01-12 14:48:59 +01:00
parent 706343cc6a
commit 83245b1216
3 changed files with 185 additions and 112 deletions

View File

@ -1,108 +1,179 @@
{% extends "base.html" %}
{% block header %}
<style>
svg {
width: 50%;
height: 50%;
}
</style>
{% endblock %}
{% block content %}
<div class="card mb-0">
<div class="card-header">
<h3 class="mb-0">{{ skill.short_label }}</h3>
<h4 class="card-title"> {{ skill.notation }}</h4>
</div>
<div class="card-body pb-0 mb-0">
<div class="row mr-1 ml-1 pb-0 mb-0">
<div class="col-md-6">
<!-- <h4 class="mb-1">Détails</h4> -->
<div class="row">
<div class="col-3 text-center">
<p>Notation : <a href="#">{{ skill.notation }}</a></p>
</div>
<div class="col-3 text-center">
<p>Difficulty : <a href="/skill/difficulty/exact/{{ skill.difficulty }}">{{ skill.difficulty }}</a></p>
</div>
<div class="col-3 text-center">
<p>Level : <a href="{% url 'skill_listing_by_key' 'level' 'exact' skill.level %}">{{ skill.level }}</a></p>
</div>
<div class="col-3 text-center">
<p>Rank : <a href="{% url 'skill_listing_by_key' 'rank' 'exact' skill.rank %}">{{ skill.level }}</a></p>
<!-- Age girl : <a href="/skill/difficulty/exact/{{ skill.difficulty }}">{{ skill.age_girl_masterised }}</a><br /> -->
<!-- Age boy : <a href="{% url 'skill_listing_by_key' 'level' 'exact' skill.level %}">{{ skill.age_boy_masterised }}</a> -->
</div>
</div>
<br />
<div class="row">
<div class="col-12">
<table class="table table-striped table-condensed" data-sort="table" id="skill_age_table">
<thead>
<th></th>
<th>With help</th>
<th>Without help</th>
<th>Chained</th>
<th>Masterised</th>
</thead>
<tbody>
<tr>
<td>Girl</td>
<td><a href="#">{{ skill.get_age_girl_with_help_display }}</a></td>
<td><a href="#">{{ skill.get_age_girl_without_help_display }}</a></td>
<td><a href="#">{{ skill.get_age_girl_chained_display }}</a></td>
<td><a href="#">{{ skill.get_age_girl_masterised_display }}</a></td>
</tr>
<tr>
<td>Boy</td>
<td><a href="#">{{ skill.get_age_boy_with_help_display }}</a></td>
<td><a href="#">{{ skill.get_age_boy_without_help_display }}</a></td>
<td><a href="#">{{ skill.get_age_boy_chained_display }}</a></td>
<td><a href="#">{{ skill.get_age_boy_masterised_display }}</a></td>
</tr>
</tbody>
</table>
<!-- Age girl : <a href="/skill/difficulty/exact/{{ skill.difficulty }}">{{ skill.age_girl }}</a><br /> -->
<!-- Age boy : <a href="{% url 'skill_listing_by_key' 'level' 'exact' skill.level %}">{{ skill.age_boy }}</a> -->
</div>
</div>
<br />
{% if skill.prerequisites.all or skill.educatives.all %}
<div class="row">
<div class="col-6">
<h4>Prerequisites</h4>
<ul>
{% if skill.prerequisites.all %}
{% for prerequisites in skill.prerequisites.all %}
<li><a href="{% url 'skill_details' prerequisites.id %}">{{ prerequisites.short_label }}</a></li>
{% endfor %}
{% else %}
<p class="text-muted">No prerequisites defined.</p>
{% endif %}
</ul>
</div>
<div class="col-6">
<h4>Educatives</h4>
<ul>
{% if skill.educatives.all %}
{% for educatives in skill.educatives.all %}
<li><a href="{% url 'skill_details' educatives.id %}">{{ educatives.short_label }}</a></li>
{% endfor %}
{% else %}
<p class="text-muted">No educative defined.</p>
{% endif %}
</ul>
</div>
</div>
{% endif %}
</div>
<div class="col-md-6 alert {% if request.session.template == 0 %}skill-info{% else %}alert-secondary{% endif %} mr-0">
<p>From <a href="#">{{ skill.departure }}</a>, {% if skill.rotation %} <a href="#">{{ skill.rotation }}</a> quart of <a href="#">{{ skill.get_rotation_type_display }}</a> rotation {% endif %}{% if skill.twist %} with <a href="#">{{ skill.twist }} half-twist</a> {% endif %} in a <a href="#">{{ skill.get_position_display }}</a> position, landing to <a href="#">{{ skill.landing }}</a></p>
<br />
{% if skill.informations %}
{{ skill.to_markdown | safe }}
{% else %}
<p class="text-muted">No more informations provided for this skill.</p>
{% endif %}
</div>
</div>
</div>
<svg></svg>
</div>
{% endblock %}
{% block footerscript %}
<script src="{% static "js/plugins/D3-dag/d3.min.js%}"></script>
<script src="{% static "js/plugins/D3-dag/d3-dag.0.8.2.min.js%}"></script>
<script>
(async () => {
// fetch data and render
// const resp = await fetch(
// "https://raw.githubusercontent.com/erikbrinkman/d3-dag/main/examples/grafo.json"
// );
const data = [{
// Rudy
"id": "43/",
"parentIds": ["-2", ".41/"]
},
{
// vrille
"id": "-2",
"parentIds": ["-1"]
},
{
// Barani
"id": ".41/",
"parentIds": ["-1", ".3-/"]
},
{
// 1/2 vrille
"id": "-1",
"parentIds": ["|"]
},
{
// Chandelle
"id": "|",
"parentIds": []
},
{
// 3/4 Avant Tendu
"id": ".3-/",
"parentIds": [".1"]
},
{
// Ventre
"id": ".1",
"parentIds": ["4p"]
},
{
// 4 pattes
"id": "4p",
"parentIds": []
}];
// const data = await resp.json();
const dag = d3.dagStratify()(data);
const nodeRadius = 20;
const layout = d3
//
// Base layout
.sugiyama()
//
// Layering
.layering(d3.layeringSimplex()) // Simplex (shortest edges)
// .layering(d3.layeringLongestPath()) // Longest Path (minimum height)
// .layering(d3.layeringCoffmanGraham()) // Coffman Graham (constrained width)
//
// Decrossing
.decross(d3.decrossOpt()) // Optimal (can be very slow)
// .decross(d3.decrossTwoLayer().order(d3.twolayerAgg())) // Two Layer Agg (fast)
// .decross(d3.decrossTwoLayer().order(d3.twolayerOpt())) // Two Layer Opt (can be very slow)
//
// Coords
// .coord(d3.coordCenter()) // Center (fast)
.coord(d3.coordGreedy()) // Greedy (fast)
// .coord(d3.coordQuad()) // Quadradtic (can be slow)
// .coord(d3.coordTopological()) //
.nodeSize(
(node) => [(node ? 3.6 : 0.25) * nodeRadius, 3 * nodeRadius]
); // set node size instead of constraining to fit
// .nodeSize((node) => {
// const size = node ? base : 5;
// return [1.2 * size, size];
// });
const { width, height } = layout(dag);
// --------------------------------
// This code only handles rendering
// --------------------------------
const svgSelection = d3.select("svg");
svgSelection.attr("viewBox", [0, 0, width, height].join(" "));
const defs = svgSelection.append("defs"); // For gradients
const steps = dag.size();
const interp = d3.interpolateRainbow;
const colorMap = new Map();
for (const [i, node] of dag.idescendants().entries()) {
colorMap.set(node.data.id, interp(i / steps));
}
// How to draw edges
const line = d3
.line()
.curve(d3.curveCatmullRom)
.x((d) => d.x)
.y((d) => d.y);
// Plot edges
svgSelection
.append("g")
.selectAll("path")
.data(dag.links())
.enter()
.append("path")
.attr("d", ({ points }) => line(points))
.attr("fill", "none")
.attr("stroke-width", 3)
.attr("stroke", ({ source, target }) => {
// encodeURIComponents for spaces, hope id doesn't have a `--` in it
const gradId = encodeURIComponent(`${source.data.id}--${target.data.id}`);
const grad = defs
.append("linearGradient")
.attr("id", gradId)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", source.x)
.attr("x2", target.x)
.attr("y1", source.y)
.attr("y2", target.y);
grad
.append("stop")
.attr("offset", "0%")
.attr("stop-color", colorMap.get(source.data.id));
grad
.append("stop")
.attr("offset", "100%")
.attr("stop-color", colorMap.get(target.data.id));
return `url(#${gradId})`;
});
// Select nodes
const nodes = svgSelection
.append("g")
.selectAll("g")
.data(dag.descendants())
.enter()
.append("g")
.attr("transform", ({ x, y }) => `translate(${x}, ${y})`);
// Plot node circles
nodes
.append("circle")
.attr("r", nodeRadius)
.attr("fill", (n) => colorMap.get(n.data.id));
// Add text to nodes
nodes
.append("text")
.text((d) => d.data.id)
.attr("font-weight", "bold")
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("fill", "white");
})();
</script>
{% endblock %}

View File

@ -10,7 +10,8 @@ skill_urlpatterns = [
),
path(r"lookup/", views.skill_lookup),
path(r"search/", views.skill_listing),
path(r"<int:skillid>/", views.skill_details, name="skill_details"),
path(r"<int:skill_id>/", views.skill_details, name="skill_details"),
path(r"<int:skill_id>/tree/", views.skill_tree, name="skill_tree"),
path(r"", views.skill_listing, name="skill_list"),
]

View File

@ -66,27 +66,32 @@ def skill_listing(request, field=None, expression=None, value=None, level=None):
@login_required
@require_http_methods(["GET"])
def skill_details(request, skillid):
def skill_tree(request, skill_id):
"""
"""
skill = get_object_or_404(Skill, pk=skill_id)
context = {"skill": skill}
return render(request, "objectives/skills/tree.html", context)
@login_required
@require_http_methods(["GET"])
def skill_details(request, skill_id):
"""Récupère toutes les informations d'un skill.
La méthode en profite pour vérifier les champs level, rank, age_boy et age_girl
par rapport aux pré-requis.
:param skillig: id d'un `skill`
:type skillid: int
:param skill_id: id d'un `skill`
:type skill_id: int
:return: skill
"""
skill = get_object_or_404(Skill, pk=skillid)
skill = get_object_or_404(Skill, pk=skill_id)
for prerequisite in skill.prerequisites.all():
skill.level = max(prerequisite.level + 1, skill.level)
# if prerequisite.level >= skill.level:
# skill.level = prerequisite.level + 1
skill.rank = max(prerequisite.rank + 1, skill.rank)
# if prerequisite.rank >= skill.rank:
# skill.rank = prerequisite.rank + 1
skill.age_boy_with_help = max(skill.age_boy_with_help, prerequisite.age_boy_with_help)
skill.age_boy_without_help = max(
@ -95,8 +100,6 @@ def skill_details(request, skillid):
)
skill.age_boy_chained = max(skill.age_boy_chained, prerequisite.age_boy_chained)
skill.age_boy_masterised = max(skill.age_boy_masterised, prerequisite.age_boy_masterised)
# if prerequisite.age_boy > skill.age_boy:
# skill.age_boy = prerequisite.age_boy
skill.age_girl_with_help = max(skill.age_girl_with_help, prerequisite.age_girl_with_help)
skill.age_girl_without_help = max(
@ -105,8 +108,6 @@ def skill_details(request, skillid):
)
skill.age_girl_chained = max(skill.age_girl_chained, prerequisite.age_girl_chained)
skill.age_girl_masterised = max(skill.age_girl_masterised, prerequisite.age_girl_masterised)
# if prerequisite.age_girl > skill.age_girl:
# skill.age_girl = prerequisite.age_girl
skill.save()