From 3b7a3df51c61c9372ebe89a3a0cf72293f7866a2 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Dubacq Date: Sun, 27 Oct 2024 20:50:51 +0100 Subject: [PATCH] Add mixed dataviz. CLI not operational yet. --- get.py | 148 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 46 deletions(-) diff --git a/get.py b/get.py index 283cd4e..6872ece 100755 --- a/get.py +++ b/get.py @@ -97,7 +97,6 @@ def cli_check(): global depts parser = argparse.ArgumentParser(description="Process some departments.") - parser.add_argument("--techno", action="store_true", help="Enable TECHNO mode") parser.add_argument("depts", nargs="*", help="List of departments") parser.add_argument( "--base", @@ -146,7 +145,6 @@ def cli_check(): args.depts.insert(0, args.optimize) Options.base_year = args.base - Options.techno = args.techno depts = args.depts orderkey = "_".join(depts) @@ -181,32 +179,37 @@ def write_conf(key, obj): conf = read_conf(orderkey) +defaults = { + "spacing": 14, + "thickness": 6, + "fontsize_name": 10, + "fontsize_count": 14, + "width": 1300, + "height": 0, + "hmargin": 20, + "parcours_separator": "/", + "year_separator": " ", + "rank_separator": "", + "diplome_separator": "", + "reuse": "yes", + "optimize": "yes", + "main_filter": 0, + "secondary_filter": 1, +} + def conf_value(xkey: str): """Manage default values""" - defaults = { - "spacing": 14, - "thickness": 6, - "fontsize_name": 10, - "fontsize_count": 14, - "width": 1300, - "height": 0, - "hmargin": 20, - "parcours_separator": "/", - "year_separator": " ", - "rank_separator": "", - "diplome_separator": "", - "reuse": "yes", - "optimize": "yes", - } if xkey in conf: return conf[xkey] if xkey in defaults: return defaults[xkey] if xkey[-9:] == "separator": return " " - if xkey == "nick" or xkey == "displayname": + if xkey == "nick": return "{diplome}{rank}{multidepartment}{modalite}{parcours}{year}" + if xkey == "displayname": + return "{diplome}{rank}{multidepartment}{modaliteshort}{parcours}" if xkey == "extnick": return "{ext}{rank}{multidepartment}{diplomenobut}{modaliteshort}" if xkey == "orders": @@ -588,12 +591,6 @@ def analyse_depts(): else: studentsummary["civilite"] = "?" bacs.add(studentsummary["bac"]) - # We skip non-techno students if we are in techno mode - # If we want a mixed reporting, maybe we should change this - if ( - Options.techno and studentsummary["bac"][:2] != "ST" - ): # TODO: change this - continue if bucket in studentsummary["cursus"]: semestreerreur = int(bucket) + 1 warning( @@ -1007,7 +1004,9 @@ for etudid in student.keys(): goodred[ddd] += 1 output = finaloutput + " " + etud["nickshort"][lastyear] etud["nickshort"][lastyear + 1] = output - displaynames[output] = finals[finaloutput] + etud["nickshort"][lastyear] + displaynames[output] = ( + finals[finaloutput] + displaynames[etud["nickshort"][lastyear]] + ) (firstsem, firstyear) = ( (etud["short"][1], 1) if etud["short"][1] != None @@ -1054,8 +1053,15 @@ for etudid in student.keys(): class Filter: + # Filter on students to be considered + # 1 consider only technological baccalaureates, statistics are always asked + # 2 consider only women, because gender statistics are frequently asked + # 4 consider only incoming students (primo-entrants) in first year of the cohort + # 8 consider only people having a first year, not parallel entries TECHNO = 1 WOMAN = 2 + PRIMO = 4 + MAIN = 8 def bags_from_students(student, filter=0): @@ -1253,32 +1259,31 @@ def get_layer_structure(newls, node_structure, spacing, hmargin, height): for j in ls["olayer"]: lhi = 0 lho = 0 + slhi = 0 + slho = 0 k = node_structure[j] for prev_node in k["prev"]: lhi += prev_node[1] + slhi += prev_node[2] for next_node in k["next"]: lho += next_node[1] + slho += next_node[2] k["size"] = max(lhi, lho) + k["ssize"] = max(slhi, slho) k["in"] = lhi k["out"] = lho if lhi != lho and lhi * lho != 0: - print(f"BUG1: {j} {k} {lhi} {lho}") - print(json.dumps(layer_structure, indent=2)) - print(json.dumps(node_structure, indent=2)) + warning(f"BUG1: {j} {k} {lhi} {lho}") ls["inout"] += k["size"] layer_structure.append(ls) if height == 0: - debug("coucou") minheight = 0 for i in range(5): ls = layer_structure[i] new_minheight = spacing * (ls["num"] - 1) + 2 * hmargin + 2 * ls["inout"] - debug(f"{new_minheight} / {minheight}") if new_minheight > minheight: minheight = new_minheight height = (1 + (minheight // 150)) * 150 - debug(f"{height}") - for i in range(5): ls = layer_structure[i] ls["density"] = ls["inout"] / ( @@ -1300,6 +1305,8 @@ def get_layer_structure(newls, node_structure, spacing, hmargin, height): ns = node_structure[j] ns["top"] = supp_spacing + spacing + cs h = ns["size"] / realdensity + sh = ns["ssize"] / realdensity + ns["middle"] = ns["top"] + sh cs = ns["bottom"] = ns["top"] + h return realdensity, height, layer_structure @@ -1308,6 +1315,14 @@ def nodestructure_from_bags(bags, sbags=None): node_structure = {} layers = [[], [], [], [], []] edges = [] + union_sbag = {} + if sbags is not None: + for layer, layernodes in enumerate(sbags): + for startnode in layernodes: + for endnode in layernodes[startnode]: + if startnode not in union_sbag: + union_sbag[startnode] = {} + union_sbag[startnode][endnode] = layernodes[startnode][endnode] for layer, layernodes in enumerate(bags): for startnode in layernodes: # if startnode[-1] == "*": @@ -1316,24 +1331,30 @@ def nodestructure_from_bags(bags, sbags=None): # if endnode[-1] == "*": # continue weight = layernodes[startnode][endnode] + if startnode in union_sbag and endnode in union_sbag[startnode]: + sweight = union_sbag[startnode][endnode] + elif sbags is None: + sweight = -1 + else: + sweight = 0 if endnode not in node_structure: node_structure[endnode] = { - "prev": [[startnode, weight]], + "prev": [[startnode, weight, sweight]], "next": [], "layer": layer + 1, } layers[layer + 1].append(endnode) else: - node_structure[endnode]["prev"].append([startnode, weight]) + node_structure[endnode]["prev"].append([startnode, weight, sweight]) if startnode not in node_structure: node_structure[startnode] = { "prev": [], - "next": [[endnode, weight]], + "next": [[endnode, weight, sweight]], "layer": layer, } layers[layer].append(startnode) else: - node_structure[startnode]["next"].append([endnode, weight]) + node_structure[startnode]["next"].append([endnode, weight, sweight]) edges.append([startnode, endnode, weight]) return node_structure, layers, edges @@ -1366,6 +1387,17 @@ def compute_svg(height, padding, realdensity, node_structure): 2 * thickness, ns["bottom"] - ns["top"], fill=col, + opacity=0.5, + stroke_width=0.1, + stroke="#808080", + ) + g1.append(r) + r = drawsvg.Rectangle( + xpos - thickness, + ns["top"], + 2 * thickness, + ns["middle"] - ns["top"], + fill=col, stroke_width=0.2, stroke="black", ) @@ -1426,16 +1458,20 @@ def compute_svg(height, padding, realdensity, node_structure): ns["next"].sort(key=lambda x: node_structure[x[0]]["top"]) start = ns["top"] for link in ns["prev"]: - ysize = link[-1] + ysize = link[1] + sysize = link[2] link.append(start) + link.append(start + sysize / realdensity) start += ysize / realdensity link.append(start) for n in node_structure: ns = node_structure[n] start = ns["top"] for link in ns["next"]: - ysize = link[-1] + ysize = link[1] + sysize = link[2] link.append(start) + link.append(start + sysize / realdensity) start += ysize / realdensity link.append(start) targets = node_structure[link[0]] @@ -1446,7 +1482,7 @@ def compute_svg(height, padding, realdensity, node_structure): if target == None: print(f"BUG: {n},{ns},{t}") sys.exit(5) - + # At this point, link has values target_name, size, secondary_size, top, middle, bottom posxa = columns[ns["layer"]] + thickness posxb = columns[targets["layer"]] - thickness posxc = (3 * posxa + posxb) / 4 @@ -1454,12 +1490,31 @@ def compute_svg(height, padding, realdensity, node_structure): grad = drawsvg.LinearGradient(posxa, 0, posxb, 0) grad.add_stop(0, ns["color"], opacity=0.5) grad.add_stop(1, targets["color"], opacity=0.5) - p = drawsvg.Path(fill=grad, stroke_width=0) - p.M(posxa, link[-2]) - p.C(posxc, link[-2], posxd, target[-2], posxb, target[-2]) - p.L(posxb, target[-1]) - p.C(posxd, target[-1], posxc, link[-1], posxa, link[-1]) + posyat = link[3] + posyam = link[4] + posyab = link[5] + posybt = target[3] + posybm = target[4] + posybb = target[5] + p = drawsvg.Path(fill=grad, stroke="#000", stroke_width=0, opacity=0.8) + p.M(posxa, posyab) + p.C(posxc, posyab, posxd, posybb, posxb, posybb) + p.L(posxb, posybt) + p.C(posxd, posybt, posxc, posyat, posxa, posyat) p.Z() + p.append_title( + f"{displaynames[n]}=>{displaynames[link[0]]}: {link[2]}/{link[1]}" + ) + g2.append(p) + p = drawsvg.Path(fill=grad, stroke="#000", stroke_width=0) + p.M(posxa, posyam) + p.C(posxc, posyam, posxd, posybm, posxb, posybm) + p.L(posxb, posybt) + p.C(posxd, posybt, posxc, posyat, posxa, posyat) + p.Z() + p.append_title( + f"{displaynames[n]}=>{displaynames[link[0]]}: {link[2]}/{link[1]}" + ) g2.append(p) d.append(g2) d.append(g1) @@ -1473,8 +1528,9 @@ def printsvg(): spacing = conf_value("spacing") height = conf_value("height") hmargin = conf_value("hmargin") - bags = bags_from_students(student, 0) - node_structure, layers, edges = nodestructure_from_bags(bags) + bags = bags_from_students(student, conf_value("main_filter")) + sbags = bags_from_students(student, conf_value("secondary_filter")) + node_structure, layers, edges = nodestructure_from_bags(bags, sbags) filename = "best-" + orderkey if Options.restart: try: