194 lines
6.9 KiB
HTML
194 lines
6.9 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
{% block header %}
|
|
<style>
|
|
svg {
|
|
/* width: 50%;
|
|
height: 50%; */
|
|
width: 25%;
|
|
height: 25%;
|
|
display: block;
|
|
margin: auto;
|
|
}
|
|
</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_7.6.1.min.js" %}"></script>
|
|
<script src="{% static "js/plugins/D3-dag/d3-dag_0.11.1.min.js" %}"></script>
|
|
<script>
|
|
function compute_font_size(string)
|
|
{
|
|
number_of_spaces = string.split(/ /g).length - 1;
|
|
number_of_tiny_char = (string.split(/f/g).length - 1)
|
|
+ (string.split(/i/g).length - 1)
|
|
+ (string.split(/j/g).length - 1)
|
|
+ (string.split(/l/g).length - 1)
|
|
+ (string.split(/t/g).length - 1)
|
|
+ (string.split(/\//g).length - 1)
|
|
+ (string.split(/,/g).length - 1)
|
|
+ (string.split(/\(/g).length - 1)
|
|
+ (string.split(/\)/g).length - 1)
|
|
number_of_small_char = (string.split(/r/g).length - 1);
|
|
number_of_big_char = (string.split(/½/g).length - 1)
|
|
+ (string.split(/¾/g).length - 1)
|
|
+ (string.split(/B/g).length - 1)
|
|
+ (string.split(/H/g).length - 1)
|
|
+ (string.split(/O/g).length - 1)
|
|
+ (string.split(/R/g).length - 1)
|
|
+ (string.split(/T/g).length - 1)
|
|
+ (string.split(/</g).length - 1);
|
|
return string.length
|
|
+ (number_of_big_char / 2)
|
|
- (
|
|
number_of_spaces
|
|
+ (number_of_tiny_char / 1.5)
|
|
+ (number_of_small_char / 2)
|
|
);
|
|
}
|
|
|
|
function define_font_size(string)
|
|
{
|
|
return (1 / compute_font_size(string)) * 80;
|
|
}
|
|
|
|
(async () => {
|
|
const data = [
|
|
{% for key, value in node_dict.items %}
|
|
{
|
|
"id": "{{ key.id }}",
|
|
"label": "{% autoescape off %}{{ key.short_label }}{% endautoescape %}",
|
|
"parentIds": [
|
|
{% for prerequisite in value %}"{{ prerequisite.id }}", {% endfor %}
|
|
]
|
|
},{% endfor %}
|
|
]
|
|
|
|
const dag = d3.dagStratify()(data);
|
|
const nodeRadius = 30;
|
|
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].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.label)
|
|
.attr("font-size", function(d) {return Math.min(define_font_size(d.data.label), 24);})
|
|
.attr("font-weight", "bold")
|
|
.attr("font-family", "sans-serif")
|
|
.attr("text-anchor", "middle")
|
|
.attr("alignment-baseline", "middle")
|
|
.attr("fill", "white");
|
|
})();
|
|
</script>
|
|
{% endblock %}
|