Skip to content

Commit

Permalink
Rework backend and make JS readable. Start on new graph type
Browse files Browse the repository at this point in the history
  • Loading branch information
supertom01 committed Jan 8, 2024
1 parent e7cdcaa commit fb76ef9
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 110 deletions.
192 changes: 117 additions & 75 deletions amelie/members/static/js/dogroup_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,15 @@ var state = {
hoveredNeighbours: null
};

/**
* A datastructure that holds for each dogroup its generations, parents and colours
*
* The structure looks as follows:
* dogroup_name: { # The name of a dogroup (allows for agreggration of generations)
* generation: { # The year that this dogroup was active
* id, # The id associated with this generation
* parents: { # A list of the parents of this dogroup
* id # The id of the generation from each parent
* },
* color, # The colour of this generation (by default the color of this dogroup)
* }
* }
*/
let data;
let dogroups;

/**
* All of the years that have at least one active generation of a dogroup
*/
let years;

/**
* A list of names from all the dogroups
*/
let dogroups;
const config = {
gap_x: 1,
gap_y: 1,
node_size: 5,
edge_size: 1,
};

/**
* Store nodes on a hover event.
Expand All @@ -54,67 +38,117 @@ function setHoveredNode(node) {
renderer.refresh();
}

window.addEventListener('DOMContentLoaded', async function() {
async function init() {
// Initialize our JS objects.
container = document.getElementById("do-group-tree");
graph = new graphology.Graph();

// Constants that determine the sizes and distances between nodes.
let gap_x = 1;
let gap_y = 1;
let node_size = 5;
let edge_size = 1;

// Load all the data objects.
// You want to do this asynchronous, since loading this data might take quite a while
let response = await fetch("/members/dogroups/data").then(res => res.json());
dogroups = response.dogroups;
years = response.years;
data = response.data;

// Give every dogroup its own x coordinate
let dogroup_x_coords = {};
let current_x_coord = 0;
for (let i in dogroups) {
dogroup_x_coords[dogroups[i]] = current_x_coord;
current_x_coord += gap_x;
}

// Give every Kick-In year its own y coordinate
let year_y_coords = {};
let current_y_coord = 0;
for (let i in years.reverse()) {
year_y_coords[years[i]] = current_y_coord;
current_y_coord += gap_y;
}
dogroups = response;
}

// Start adding in all the nodes.
for(let i in dogroups) {
let dogroup = dogroups[i];
for (let year in data[dogroup]) {
let generation = data[dogroup][year];
graph.addNode(generation.id, {x: dogroup_x_coords[dogroup],
y: year_y_coords[year],
label: dogroup + " " + year,
color: generation.color,
size: node_size});
}
}
function createDoGroupGraph() {
let graph = new graphology.Graph();

/**
* Stores the count of relations from a dogroup a to b.
* @type {{number: {number: {number}}}}
*/
let connections = {};
dogroups.map((dogroup) => {
if (!(dogroup.id in connections)) {
connections[dogroup.id] = {};
}
dogroup.generations.map((generation) => {
generation.parents.filter((parent) => parent.previous_dogroup !== null).map((parent) => {
if (!(parent.previous_dogroup in connections[dogroup.id])) {
connections[dogroup.id][parent.previous_dogroup] = 0;
}
connections[dogroup.id][parent.previous_dogroup] += 1;
});
});
});

// TODO: Figure out automated layouts: https://graphology.github.io/standard-library/layout-force.html

// Add a node for each dogroup.
dogroups.map((dogroup, i) => {
graph.addNode(dogroup.id, {
label: dogroup.name,
color: dogroup.color,
x: i,
y: i,
});
});

Object.keys(connections).map((source) => {
Object.keys(connections[source]).map((target) => {
graph.addEdge(source, target, { size: connections[source][target] });
});
});

return graph;
}

// Start adding in all the edges.
for (let i in dogroups) {
let dogroup = dogroups[i];
for (let year in data[dogroup]) {
let generation = data[dogroup][year];
let generation_id = generation['id'];
for (let _id in generation['parents']) {
let parent_id = generation['parents'][_id]['id'];
graph.addEdge(generation_id, parent_id, {size: edge_size})
}
}
}
function createTreeGraph() {
let graph = new graphology.Graph();
let years = dogroups
.map((dogroup) => dogroup.generations.map((generation) => generation.year))
.reduce((prev, curr) => {
curr.map((year) => prev.add(year));
return prev;
}, new Set())
years = [...years].sort((a, b) => b - a);

// Give every dogroup its own x coordinate
let dogroup_x_coords = {};
let current_x_coord = 0;
dogroups.map((dogroup) => {
dogroup_x_coords[dogroup.id] = current_x_coord;
current_x_coord += config.gap_x;
});

// Give every Kick-In year its own y coordinate
let year_y_coords = {};
let current_y_coord = 0;
years.map((year) => {
year_y_coords[year] = current_y_coord;
current_y_coord += config.gap_y;
});

// Start adding in all the nodes.
dogroups.map((dogroup) => {
dogroup.generations.map((generation) => {
graph.addNode(generation.id, {
x: dogroup_x_coords[dogroup.id],
y: year_y_coords[generation.year],
label: dogroup.name + " " + generation.year,
color: generation.color,
size: config.node_size,
});
});
});

// Start adding in all the edges.
dogroups.map((dogroup) => {
dogroup.generations.map((generation) => {
createdLinks = new Set();
generation.parents.filter((parent) => parent.previous_dogroup_generation !== null).map((parent) => {
if (!createdLinks.has(parent.previous_dogroup_generation)) {
graph.addEdge(generation.id, parent.previous_dogroup_generation, {size: config.edge_size});
createdLinks.add(parent.previous_dogroup_generation);
}
});
});
});

return graph;
}

// Empty the container of all of its child nodes.
function render(graph) {
// Empty the container of all of its child nodes.
while (container.hasChildNodes()) {
container.removeChild(container.firstChild);
}
Expand All @@ -126,7 +160,7 @@ window.addEventListener('DOMContentLoaded', async function() {
});
renderer.on("leaveNode", () => {
setHoveredNode(null);
})
});

renderer.setSetting("nodeReducer", (node, data) => {
const res = {...data};
Expand All @@ -150,4 +184,12 @@ window.addEventListener('DOMContentLoaded', async function() {

const layout = new ForceSupervisor(graph);
layout.start();
}

window.addEventListener('DOMContentLoaded', async function() {
await init();

// let graph = createTreeGraph();
let graph = createDoGroupGraph();
render(graph);
});
120 changes: 85 additions & 35 deletions amelie/members/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1592,40 +1592,90 @@ class DoGroupTreeView(TemplateView):
class DoGroupTreeViewData(View):

def get(self, request, *args, **kwargs):
# Create a datastructure that maps connects generations based on parents and children
generation_objects = DogroupGeneration.objects.select_related('dogroup').prefetch_related(
'studyperiod_set').all().order_by('dogroup', 'generation')

# A data structure
generations = dict()

# Aggregates all the years that have had dogroups
years = set()
for generation in generation_objects:
years.add(generation.generation)
prev = set()
for parent in generation.parents.all():
if parent.is_student():
parent_prev_dogroups = parent.student.studyperiod_set.filter(dogroup__isnull=False,
dogroup__generation__lt=generation.generation).order_by(
'dogroup__generation')
if parent_prev_dogroups.exists():
prev.add(parent_prev_dogroups.last().dogroup)
generations[generation] = prev

dogroups = dict()
for dogroup in Dogroup.objects.all():
dogroups[str(dogroup)] = {}
for generation in generations.keys():
dogroups[str(generation.dogroup)][generation.generation] = {
"id": generation.id,
"parents": [{"id": p.id} for p in generations[generation]],
"color": generation.color,
dogroups = []
for dogroup in Dogroup.objects.prefetch_related(
'dogroupgeneration_set__parents__student__studyperiod_set__dogroup__dogroup',
).all():
dogroup_obj = {
'id': dogroup.id,
'name': dogroup.name,
'color': dogroup.color,
'generations': [],
}

data = {
"years": sorted(list(years)),
"dogroups": list(dogroups.keys()),
"data": dogroups,
}
return JsonResponse(data)
for generation in dogroup.dogroupgeneration_set.all():
generation_obj = {
'id': generation.id,
'year': generation.generation,
'color': generation.color,
'parents': [],
}
for parent in generation.parents.all():
parent_obj = {
'id': parent.id,
'previous_dogroup': None,
'previous_dogroup_generation': None,
}
if parent.is_student():
previous_dogroups = (parent
.student
.studyperiod_set
.filter(
dogroup__isnull=False,
dogroup__generation__lte=generation.generation
)
.order_by('dogroup__generation'))
if previous_dogroups.exists():
parent_obj['previous_dogroup'] = previous_dogroups.last().dogroup.dogroup.id
parent_obj['previous_dogroup_generation'] = previous_dogroups.last().dogroup.id
generation_obj['parents'].append(parent_obj)

dogroup_obj['generations'].append(generation_obj)
dogroups.append(dogroup_obj)

return JsonResponse(dogroups, safe=False)
# Create a datastructure that maps connects generations based on parents and children
# generation_objects = (DogroupGeneration.objects
# .select_related('dogroup')
# .prefetch_related('studyperiod_set')
# .aggregate()
# .order_by('dogroup', 'generation'))

# # A data structure
# generations = dict()
#
# # Aggregates all the years that have had dogroups
# years = set()
# for generation in generation_objects:
# years.add(generation.generation)
# prev = set()
# for parent in generation.parents.all():
# if parent.is_student():
# parent_prev_dogroups = parent.student.studyperiod_set.filter(dogroup__isnull=False,
# dogroup__generation__lt=generation.generation).order_by(
# 'dogroup__generation')
# if parent_prev_dogroups.exists():
# prev.add(parent_prev_dogroups.last().dogroup)
# generations[generation] = prev
#
# data = dict()
# dogroups = []
# for dogroup in Dogroup.objects.all():
# data[str(dogroup)] = {}
# dogroups.append({
# 'id': dogroup.id,
# 'name': dogroup.name,
# })
# for generation in generations.keys():
# data[str(generation.dogroup)][generation.generation] = {
# "id": generation.id,
# "parents": [{"id": p.id} for p in generations[generation]],
# "color": generation.color,
# }
#
# data = {
# "years": sorted(list(years)),
# "dogroups": dogroups,
# "data": data,
# }
# return JsonResponse(data)

0 comments on commit fb76ef9

Please sign in to comment.