Ultron/templates/objectives/skills/tree.html

160 lines
5.8 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block header %}
<style>
svg {
width: 25%;
height: 25%;
}
</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">
<svg></svg>
</div>
</div>
</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 () => {
const data = [
{% for key, value in node_dict.items %}
{
"id": "{{ key.short_label }}",
"parentIds": [
{% for prerequisite in value %}
"{{ prerequisite.short_label }}",
{% endfor %}
]
},
{% endfor %}
]
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];
// });
// .attr("r", function(d) {return d.name.length * 2.5;})
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-size", "8")
// .attr("font-size", function(d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; })
// .attr("font-size", function(d) {return (1 / d.data.id.length) * 60;})
.attr("font-size", function(d) {return (1 / d.data.id.replace(/ /g,'').length) * 60;})
.attr("font-weight", "bold")
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("fill", "white");
})();
</script>
{% endblock %}