wow-addons/z-BigBrother/bb/graph.py

130 lines
5.1 KiB
Python
Raw Permalink Normal View History

2020-07-10 03:56:08 +02:00
import base64
import io
2020-09-22 19:43:06 +02:00
from collections import defaultdict, Counter
2020-07-16 00:07:08 +02:00
from contextlib import suppress
2020-07-10 03:56:08 +02:00
from datetime import datetime, timedelta
from typing import List
import matplotlib.pyplot as plt
from . import config
def graph(raid: List[dict]) -> None:
2020-09-22 19:43:06 +02:00
# TODO: This entire function is shit.
2020-07-10 03:56:08 +02:00
figs = []
buff_ids = {
buff_id: buff_name
for buff_name, buff_data in config.buffs.items()
for buff_id in buff_data["ids"]
}
classes = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
2020-09-22 19:43:06 +02:00
player_buff_counts = defaultdict(Counter)
2020-07-10 03:56:08 +02:00
xticks = []
first_encounter_date = None
last_encounter_date = None
previous_encounter_date = None
for encounter in raid:
encounter_date = datetime.strptime(encounter["date"], "%Y-%m-%d %H:%M:%S")
if first_encounter_date is None:
first_encounter_date = encounter_date
previous_encounter_date = encounter_date - timedelta(minutes=5)
last_encounter_date = encounter_date
xticks.append((encounter_date, encounter["encounterName"]))
for class_name, players in encounter["buffs"].items():
for player_name, buffs in sorted(players.items()):
real_class_name = config.class_overrides.get(player_name, str.title(class_name))
2020-07-16 00:07:08 +02:00
classes[real_class_name][player_name] # ensure player is added, irregardless if they have any buffs
2020-09-22 19:43:06 +02:00
player_buff_names = set()
2020-07-10 03:56:08 +02:00
for buff_id in buffs:
2020-07-16 00:07:08 +02:00
with suppress(KeyError): # KeyError on buff_id => buff not tracked
2020-07-10 03:56:08 +02:00
buff_name = buff_ids[buff_id]
2020-09-22 19:43:06 +02:00
player_buff_names.add(buff_name)
2020-07-16 00:07:08 +02:00
classes[real_class_name][player_name][buff_name].append(
(previous_encounter_date, (encounter_date - previous_encounter_date))
)
2020-09-22 19:43:06 +02:00
player_buff_counts[player_name].update(player_buff_names)
2020-07-10 03:56:08 +02:00
previous_encounter_date = encounter_date
for class_name, players in sorted(classes.items()):
fig, ax = plt.subplots()
2020-09-22 19:43:06 +02:00
class_buffs = {
2020-07-10 03:56:08 +02:00
buff_name: buff_data
for buff_name, buff_data in config.buffs.items()
if any(x in buff_data["classes"] for x in ("ALL", class_name))
}
2020-09-22 19:43:06 +02:00
required_class_buffs = {
buff_name: buff_data
for buff_name, buff_data in class_buffs.items()
if buff_data["required"]
}
2020-07-10 03:56:08 +02:00
#legend_handles = []
2020-09-22 19:43:06 +02:00
bar_width = 1 / (len(class_buffs) + 1)
for b, (buff_name, buff) in enumerate(class_buffs.items()):
2020-07-10 03:56:08 +02:00
bar_color = "#{color}".format(**buff)
#legend_handles.append(Patch(label=buff_name, color=bar_color))
2020-07-16 00:07:08 +02:00
for y0, (player_name, player_buffs) in enumerate(players.items()):
2020-07-10 03:56:08 +02:00
ax.broken_barh(
player_buffs[buff_name],
(bar_width/2 + y0 + b * bar_width, bar_width),
color=bar_color
)
annotation_color = "black"
try:
if player_buffs[buff_name][1][0] == first_encounter_date:
annotation_color = contrast_color(*tuple(int(buff["color"][i:i+2], 16) for i in (0, 2, 4)))
except IndexError:
pass
ax.annotate(
f" {buff_name}",
xy=(first_encounter_date, bar_width + y0 + b * bar_width),
ha="left",
va="center",
color=annotation_color
)
2020-07-16 00:07:08 +02:00
for y0 in range(len(players) + 1):
2020-07-10 03:56:08 +02:00
ax.axhline(y=y0, linewidth=1.0, color="black")
plt.xticks(*zip(*xticks), rotation=30, ha="right")
2020-09-22 19:43:06 +02:00
required_buff_fraction = {
p: sum(
c
for b, c in player_buff_counts[p].items()
if b in required_class_buffs
) / (len(raid) * len(required_class_buffs))
for p in players
}
2020-07-10 03:56:08 +02:00
plt.yticks(
2020-07-16 00:07:08 +02:00
list(range(len(players))),
2020-09-22 19:43:06 +02:00
[f"{p}\n{required_buff_fraction[p]:.0%}" for p in players],
verticalalignment="top"
2020-07-10 03:56:08 +02:00
)
ax.invert_yaxis()
ax.grid(axis="x", linestyle="--")
ax.set_xlim(first_encounter_date - timedelta(minutes=10), last_encounter_date + timedelta(minutes=1))
plt.title(class_name, fontweight="bold", fontsize=16)
#plt.legend(handles=legend_handles, bbox_to_anchor=(1, 1))
2020-07-16 00:07:08 +02:00
fig.set_size_inches(15, 2 + len(players) * 2)
2020-07-10 03:56:08 +02:00
plt.tight_layout()
#plt.show()
buffer = io.BytesIO()
fig.savefig(buffer, format="svg")
figs.append(base64.b64encode(buffer.getbuffer()).decode("ascii"))
with open("{date:10.10}-{zone}.html".format(**raid[0]), "w") as f:
f.write("<html>")
f.writelines(
f"<img src='data:image/svg+xml;base64,{fig}'/>"
for fig in figs
)
f.write("</html>")
def contrast_color(r, g, b):
luma = ((0.299 * r) + (0.587 * g) + (0.114 * b)) / 255
return "black" if luma > 0.5 else "white"