Add mixed dataviz. CLI not operational yet.

This commit is contained in:
Jean-Christophe Dubacq 2024-10-27 20:50:51 +01:00
parent b33370daaf
commit 3b7a3df51c

124
get.py
View File

@ -97,7 +97,6 @@ def cli_check():
global depts global depts
parser = argparse.ArgumentParser(description="Process some departments.") 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("depts", nargs="*", help="List of departments")
parser.add_argument( parser.add_argument(
"--base", "--base",
@ -146,7 +145,6 @@ def cli_check():
args.depts.insert(0, args.optimize) args.depts.insert(0, args.optimize)
Options.base_year = args.base Options.base_year = args.base
Options.techno = args.techno
depts = args.depts depts = args.depts
orderkey = "_".join(depts) orderkey = "_".join(depts)
@ -181,9 +179,6 @@ def write_conf(key, obj):
conf = read_conf(orderkey) conf = read_conf(orderkey)
def conf_value(xkey: str):
"""Manage default values"""
defaults = { defaults = {
"spacing": 14, "spacing": 14,
"thickness": 6, "thickness": 6,
@ -198,15 +193,23 @@ def conf_value(xkey: str):
"diplome_separator": "", "diplome_separator": "",
"reuse": "yes", "reuse": "yes",
"optimize": "yes", "optimize": "yes",
"main_filter": 0,
"secondary_filter": 1,
} }
def conf_value(xkey: str):
"""Manage default values"""
if xkey in conf: if xkey in conf:
return conf[xkey] return conf[xkey]
if xkey in defaults: if xkey in defaults:
return defaults[xkey] return defaults[xkey]
if xkey[-9:] == "separator": if xkey[-9:] == "separator":
return " " return " "
if xkey == "nick" or xkey == "displayname": if xkey == "nick":
return "{diplome}{rank}{multidepartment}{modalite}{parcours}{year}" return "{diplome}{rank}{multidepartment}{modalite}{parcours}{year}"
if xkey == "displayname":
return "{diplome}{rank}{multidepartment}{modaliteshort}{parcours}"
if xkey == "extnick": if xkey == "extnick":
return "{ext}{rank}{multidepartment}{diplomenobut}{modaliteshort}" return "{ext}{rank}{multidepartment}{diplomenobut}{modaliteshort}"
if xkey == "orders": if xkey == "orders":
@ -588,12 +591,6 @@ def analyse_depts():
else: else:
studentsummary["civilite"] = "?" studentsummary["civilite"] = "?"
bacs.add(studentsummary["bac"]) 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"]: if bucket in studentsummary["cursus"]:
semestreerreur = int(bucket) + 1 semestreerreur = int(bucket) + 1
warning( warning(
@ -1007,7 +1004,9 @@ for etudid in student.keys():
goodred[ddd] += 1 goodred[ddd] += 1
output = finaloutput + " " + etud["nickshort"][lastyear] output = finaloutput + " " + etud["nickshort"][lastyear]
etud["nickshort"][lastyear + 1] = output etud["nickshort"][lastyear + 1] = output
displaynames[output] = finals[finaloutput] + etud["nickshort"][lastyear] displaynames[output] = (
finals[finaloutput] + displaynames[etud["nickshort"][lastyear]]
)
(firstsem, firstyear) = ( (firstsem, firstyear) = (
(etud["short"][1], 1) (etud["short"][1], 1)
if etud["short"][1] != None if etud["short"][1] != None
@ -1054,8 +1053,15 @@ for etudid in student.keys():
class Filter: 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 TECHNO = 1
WOMAN = 2 WOMAN = 2
PRIMO = 4
MAIN = 8
def bags_from_students(student, filter=0): 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"]: for j in ls["olayer"]:
lhi = 0 lhi = 0
lho = 0 lho = 0
slhi = 0
slho = 0
k = node_structure[j] k = node_structure[j]
for prev_node in k["prev"]: for prev_node in k["prev"]:
lhi += prev_node[1] lhi += prev_node[1]
slhi += prev_node[2]
for next_node in k["next"]: for next_node in k["next"]:
lho += next_node[1] lho += next_node[1]
slho += next_node[2]
k["size"] = max(lhi, lho) k["size"] = max(lhi, lho)
k["ssize"] = max(slhi, slho)
k["in"] = lhi k["in"] = lhi
k["out"] = lho k["out"] = lho
if lhi != lho and lhi * lho != 0: if lhi != lho and lhi * lho != 0:
print(f"BUG1: {j} {k} {lhi} {lho}") warning(f"BUG1: {j} {k} {lhi} {lho}")
print(json.dumps(layer_structure, indent=2))
print(json.dumps(node_structure, indent=2))
ls["inout"] += k["size"] ls["inout"] += k["size"]
layer_structure.append(ls) layer_structure.append(ls)
if height == 0: if height == 0:
debug("coucou")
minheight = 0 minheight = 0
for i in range(5): for i in range(5):
ls = layer_structure[i] ls = layer_structure[i]
new_minheight = spacing * (ls["num"] - 1) + 2 * hmargin + 2 * ls["inout"] new_minheight = spacing * (ls["num"] - 1) + 2 * hmargin + 2 * ls["inout"]
debug(f"{new_minheight} / {minheight}")
if new_minheight > minheight: if new_minheight > minheight:
minheight = new_minheight minheight = new_minheight
height = (1 + (minheight // 150)) * 150 height = (1 + (minheight // 150)) * 150
debug(f"{height}")
for i in range(5): for i in range(5):
ls = layer_structure[i] ls = layer_structure[i]
ls["density"] = ls["inout"] / ( ls["density"] = ls["inout"] / (
@ -1300,6 +1305,8 @@ def get_layer_structure(newls, node_structure, spacing, hmargin, height):
ns = node_structure[j] ns = node_structure[j]
ns["top"] = supp_spacing + spacing + cs ns["top"] = supp_spacing + spacing + cs
h = ns["size"] / realdensity h = ns["size"] / realdensity
sh = ns["ssize"] / realdensity
ns["middle"] = ns["top"] + sh
cs = ns["bottom"] = ns["top"] + h cs = ns["bottom"] = ns["top"] + h
return realdensity, height, layer_structure return realdensity, height, layer_structure
@ -1308,6 +1315,14 @@ def nodestructure_from_bags(bags, sbags=None):
node_structure = {} node_structure = {}
layers = [[], [], [], [], []] layers = [[], [], [], [], []]
edges = [] 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 layer, layernodes in enumerate(bags):
for startnode in layernodes: for startnode in layernodes:
# if startnode[-1] == "*": # if startnode[-1] == "*":
@ -1316,24 +1331,30 @@ def nodestructure_from_bags(bags, sbags=None):
# if endnode[-1] == "*": # if endnode[-1] == "*":
# continue # continue
weight = layernodes[startnode][endnode] 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: if endnode not in node_structure:
node_structure[endnode] = { node_structure[endnode] = {
"prev": [[startnode, weight]], "prev": [[startnode, weight, sweight]],
"next": [], "next": [],
"layer": layer + 1, "layer": layer + 1,
} }
layers[layer + 1].append(endnode) layers[layer + 1].append(endnode)
else: else:
node_structure[endnode]["prev"].append([startnode, weight]) node_structure[endnode]["prev"].append([startnode, weight, sweight])
if startnode not in node_structure: if startnode not in node_structure:
node_structure[startnode] = { node_structure[startnode] = {
"prev": [], "prev": [],
"next": [[endnode, weight]], "next": [[endnode, weight, sweight]],
"layer": layer, "layer": layer,
} }
layers[layer].append(startnode) layers[layer].append(startnode)
else: else:
node_structure[startnode]["next"].append([endnode, weight]) node_structure[startnode]["next"].append([endnode, weight, sweight])
edges.append([startnode, endnode, weight]) edges.append([startnode, endnode, weight])
return node_structure, layers, edges return node_structure, layers, edges
@ -1366,6 +1387,17 @@ def compute_svg(height, padding, realdensity, node_structure):
2 * thickness, 2 * thickness,
ns["bottom"] - ns["top"], ns["bottom"] - ns["top"],
fill=col, 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_width=0.2,
stroke="black", 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"]) ns["next"].sort(key=lambda x: node_structure[x[0]]["top"])
start = ns["top"] start = ns["top"]
for link in ns["prev"]: for link in ns["prev"]:
ysize = link[-1] ysize = link[1]
sysize = link[2]
link.append(start) link.append(start)
link.append(start + sysize / realdensity)
start += ysize / realdensity start += ysize / realdensity
link.append(start) link.append(start)
for n in node_structure: for n in node_structure:
ns = node_structure[n] ns = node_structure[n]
start = ns["top"] start = ns["top"]
for link in ns["next"]: for link in ns["next"]:
ysize = link[-1] ysize = link[1]
sysize = link[2]
link.append(start) link.append(start)
link.append(start + sysize / realdensity)
start += ysize / realdensity start += ysize / realdensity
link.append(start) link.append(start)
targets = node_structure[link[0]] targets = node_structure[link[0]]
@ -1446,7 +1482,7 @@ def compute_svg(height, padding, realdensity, node_structure):
if target == None: if target == None:
print(f"BUG: {n},{ns},{t}") print(f"BUG: {n},{ns},{t}")
sys.exit(5) sys.exit(5)
# At this point, link has values target_name, size, secondary_size, top, middle, bottom
posxa = columns[ns["layer"]] + thickness posxa = columns[ns["layer"]] + thickness
posxb = columns[targets["layer"]] - thickness posxb = columns[targets["layer"]] - thickness
posxc = (3 * posxa + posxb) / 4 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 = drawsvg.LinearGradient(posxa, 0, posxb, 0)
grad.add_stop(0, ns["color"], opacity=0.5) grad.add_stop(0, ns["color"], opacity=0.5)
grad.add_stop(1, targets["color"], opacity=0.5) grad.add_stop(1, targets["color"], opacity=0.5)
p = drawsvg.Path(fill=grad, stroke_width=0) posyat = link[3]
p.M(posxa, link[-2]) posyam = link[4]
p.C(posxc, link[-2], posxd, target[-2], posxb, target[-2]) posyab = link[5]
p.L(posxb, target[-1]) posybt = target[3]
p.C(posxd, target[-1], posxc, link[-1], posxa, link[-1]) 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.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) g2.append(p)
d.append(g2) d.append(g2)
d.append(g1) d.append(g1)
@ -1473,8 +1528,9 @@ def printsvg():
spacing = conf_value("spacing") spacing = conf_value("spacing")
height = conf_value("height") height = conf_value("height")
hmargin = conf_value("hmargin") hmargin = conf_value("hmargin")
bags = bags_from_students(student, 0) bags = bags_from_students(student, conf_value("main_filter"))
node_structure, layers, edges = nodestructure_from_bags(bags) sbags = bags_from_students(student, conf_value("secondary_filter"))
node_structure, layers, edges = nodestructure_from_bags(bags, sbags)
filename = "best-" + orderkey filename = "best-" + orderkey
if Options.restart: if Options.restart:
try: try: