mirror of
https://github.com/fhswf/aki_prj23_transparenzregister.git
synced 2025-04-22 22:12:53 +02:00
feat(ui): Include graph on person page and fix neighbor calculation (#533)
Includes the company/person relation graph on the persondetails page incl. the number of relations on lvl 1,2 and 3, the calculation of which was also fixed as part of the development. The graph displayed on the company details page now also includes **all** relations (company + person) until the 3rd lvl. --------- Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
This commit is contained in:
parent
04bcc3c458
commit
d55eeef87e
@ -15,10 +15,11 @@ from aki_prj23_transparenzregister.ui import (
|
||||
sentiment_elements,
|
||||
)
|
||||
from aki_prj23_transparenzregister.utils.networkx.network_2d import create_2d_graph
|
||||
from aki_prj23_transparenzregister.utils.networkx.network_base import initialize_network
|
||||
from aki_prj23_transparenzregister.utils.networkx.network_base import (
|
||||
initialize_network_without_metrics,
|
||||
)
|
||||
from aki_prj23_transparenzregister.utils.networkx.networkx_data import (
|
||||
create_edge_and_node_list_for_company,
|
||||
find_company_relations,
|
||||
get_relations_until_level_3,
|
||||
)
|
||||
|
||||
COLORS = {
|
||||
@ -414,20 +415,16 @@ def network_layout(selected_company_id: int) -> html.Div:
|
||||
Returns:
|
||||
The html div to create the network tab of the company page.
|
||||
"""
|
||||
person_relations, company_relations = find_company_relations(selected_company_id)
|
||||
|
||||
# Create Edge and Node List from data
|
||||
nodes, edges = create_edge_and_node_list_for_company(company_relations)
|
||||
nodes, edges = get_relations_until_level_3(f"c_{str(selected_company_id)}")
|
||||
# Initialize the Network and receive the Graph and a DataFrame with Metrics
|
||||
if nodes != {}:
|
||||
graph, metrics = initialize_network(nodes=nodes, edges=edges)
|
||||
metric = "None"
|
||||
graph = initialize_network_without_metrics(nodes=nodes, edges=edges)
|
||||
figure = create_2d_graph(
|
||||
graph,
|
||||
nodes,
|
||||
edges,
|
||||
metrics,
|
||||
metric,
|
||||
pd.DataFrame(),
|
||||
metric="None",
|
||||
layout="Spring",
|
||||
edge_annotation=True,
|
||||
edge_thickness=1,
|
||||
|
@ -31,12 +31,7 @@ def layout(value: str = "1") -> html.Div:
|
||||
# get all necessary data of the selected person
|
||||
selected_person_stats = data_elements.get_person_data(session).loc[person_id]
|
||||
selected_person_name = f"{selected_person_stats['person_firstname']} {selected_person_stats['person_lastname']}"
|
||||
return (
|
||||
person_elements.create_person_widgets(person_id, selected_person_name),
|
||||
html.Div(
|
||||
className="person-network", children=["Hier kann der Social Graph hin"]
|
||||
),
|
||||
)
|
||||
return person_elements.create_person_widgets(person_id, selected_person_name)
|
||||
|
||||
|
||||
@callback(
|
||||
|
@ -1,9 +1,15 @@
|
||||
"""Dash elements for person page."""
|
||||
import networkx as nx
|
||||
from dash import html
|
||||
import pandas as pd
|
||||
from dash import dcc, html
|
||||
|
||||
from aki_prj23_transparenzregister.utils.networkx.network_2d import create_2d_graph
|
||||
from aki_prj23_transparenzregister.utils.networkx.network_base import (
|
||||
initialize_network_without_metrics,
|
||||
)
|
||||
from aki_prj23_transparenzregister.utils.networkx.networkx_data import (
|
||||
get_relations_number_from_id,
|
||||
get_relations_until_level_3,
|
||||
)
|
||||
|
||||
|
||||
@ -44,7 +50,7 @@ def create_person_widgets(selected_person_id: int, selected_person_name: str) ->
|
||||
"""
|
||||
try:
|
||||
first_level, second_level, third_level = get_relations_number_from_id(
|
||||
selected_person_id
|
||||
f"p_{selected_person_id}"
|
||||
)
|
||||
|
||||
except nx.exception.NetworkXError:
|
||||
@ -67,13 +73,56 @@ def create_person_widgets(selected_person_id: int, selected_person_name: str) ->
|
||||
],
|
||||
),
|
||||
create_small_widget(
|
||||
["Anzahl Verbindungen", html.Br(), " - erste Ebene"], [first_level]
|
||||
["Anzahl Verbindungen", html.Br(), "1. Ebene"], [first_level]
|
||||
),
|
||||
create_small_widget(
|
||||
["Anzahl Verbindungen", html.Br(), " - dritte Ebene"], [second_level]
|
||||
["Anzahl Verbindungen", html.Br(), "2. Ebene"], [second_level]
|
||||
),
|
||||
create_small_widget(
|
||||
["Anzahl Verbindungen", html.Br(), " - dritte Ebene"], [third_level]
|
||||
["Anzahl Verbindungen", html.Br(), "3. Ebene"], [third_level]
|
||||
),
|
||||
network_layout(selected_person_id),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def network_layout(selected_person_id: int) -> html.Div:
|
||||
"""Create network tab.
|
||||
|
||||
Args:
|
||||
selected_person_id: Id of the chosen person in the dropdown.
|
||||
|
||||
Returns:
|
||||
The html div to create the network tab of the person page.
|
||||
"""
|
||||
nodes, edges = get_relations_until_level_3(f"p_{selected_person_id}")
|
||||
# Initialize the Network and receive the Graph and a DataFrame with Metrics
|
||||
if nodes != {}:
|
||||
graph = initialize_network_without_metrics(nodes=nodes, edges=edges)
|
||||
figure = create_2d_graph(
|
||||
graph,
|
||||
nodes,
|
||||
edges,
|
||||
pd.DataFrame([]),
|
||||
metric="None",
|
||||
layout="Spring",
|
||||
edge_annotation=True,
|
||||
edge_thickness=1,
|
||||
)
|
||||
return html.Div(
|
||||
children=[
|
||||
dcc.Graph(figure=figure, id="person-graph", className="graph-style")
|
||||
]
|
||||
)
|
||||
|
||||
return html.Div(
|
||||
className="choose-metric",
|
||||
children=[
|
||||
html.H3(
|
||||
className="metrics-title",
|
||||
children=[
|
||||
"Für diese Person wurden leider keine Verflechtungen gefunden."
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
@ -72,6 +72,8 @@ def create_2d_graph( # noqa PLR0913
|
||||
node_x.append(x)
|
||||
node_y.append(y)
|
||||
|
||||
edge_type_list = [row["type"] for row in edges]
|
||||
|
||||
# Add the Edges to the scatter plot according to their Positions.
|
||||
edge_trace = go.Scatter(
|
||||
x=edge_x,
|
||||
@ -80,7 +82,9 @@ def create_2d_graph( # noqa PLR0913
|
||||
hoverinfo="none",
|
||||
mode="lines",
|
||||
)
|
||||
|
||||
# Add the Edge description text to the scatter plot according to its Position.
|
||||
if edge_annotation is True:
|
||||
edge_weights_trace = go.Scatter(
|
||||
x=edge_weight_x,
|
||||
y=edge_weight_y,
|
||||
@ -88,7 +92,18 @@ def create_2d_graph( # noqa PLR0913
|
||||
marker_size=0.5,
|
||||
text=[0.45, 0.7, 0.34],
|
||||
textposition="top center",
|
||||
hovertemplate="Relation: %{text}<extra></extra>",
|
||||
hoverinfo="none",
|
||||
)
|
||||
else:
|
||||
edge_weights_trace = go.Scatter(
|
||||
x=edge_weight_x,
|
||||
y=edge_weight_y,
|
||||
mode="text",
|
||||
marker_size=0.5,
|
||||
text=[0.45, 0.7, 0.34],
|
||||
textposition="top center",
|
||||
hovertemplate="Relation: %{hovertext}<extra></extra>",
|
||||
hovertext=edge_type_list,
|
||||
)
|
||||
|
||||
# Add the Nodes to the scatter plot according to their Positions.
|
||||
@ -106,9 +121,7 @@ def create_2d_graph( # noqa PLR0913
|
||||
# # Set Color by using the nodes DataFrame with its Color Attribute. The sequence matters!
|
||||
colors = list(nx.get_node_attributes(graph, "color").values())
|
||||
node_names = list(nx.get_node_attributes(graph, "name").values())
|
||||
# ids = list(nx.get_node_attributes(graph, "id").values())
|
||||
|
||||
# # Get the Node Text
|
||||
node_trace.marker.color = colors
|
||||
node_trace.marker.line = {"color": "#2e2c2f", "width": 0.5}
|
||||
node_trace.text = node_names
|
||||
@ -118,10 +131,7 @@ def create_2d_graph( # noqa PLR0913
|
||||
node_trace.marker.size = list(np.sqrt(metrics[metric]) * 200)
|
||||
|
||||
# Add Relation_Type as a Description for the edges.
|
||||
if edge_annotation:
|
||||
edge_type_list = [
|
||||
row["type"] for row in edges
|
||||
] # this code be moved and used as hover data
|
||||
if edge_annotation is True: # this code be moved and used as hover data
|
||||
edge_weights_trace.text = edge_type_list
|
||||
|
||||
# Return the Plotly Figure
|
||||
|
@ -283,6 +283,52 @@ def find_company_relations(
|
||||
return pd.DataFrame(), company_relations # company_relations
|
||||
|
||||
|
||||
def find_person_relations(
|
||||
selected_person_id: int,
|
||||
) -> pd.DataFrame:
|
||||
"""Finds all Relations for the given Person id.
|
||||
|
||||
Args:
|
||||
selected_person_id: Id of the Person which Relations should be returned.
|
||||
|
||||
Returns:
|
||||
DataFrame
|
||||
"""
|
||||
session = SessionHandler.session
|
||||
assert session # noqa: S101
|
||||
relations_person_data = (
|
||||
session.query(
|
||||
entities.Company.id.label("id_company"),
|
||||
entities.Company.name.label("name_company"),
|
||||
entities.PersonRelation.relation.label("relation_type"),
|
||||
entities.Person.id.label("id_person"),
|
||||
entities.Person.lastname.label("lastname"),
|
||||
entities.Person.firstname.label("firstname"),
|
||||
entities.Person.date_of_birth.label("date_of_birth"),
|
||||
)
|
||||
.join(
|
||||
entities.PersonRelation,
|
||||
entities.PersonRelation.company_id == entities.Company.id,
|
||||
)
|
||||
.join(
|
||||
entities.Person,
|
||||
entities.PersonRelation.person_id == entities.Person.id,
|
||||
)
|
||||
.filter(entities.Person.id == selected_person_id)
|
||||
.all()
|
||||
)
|
||||
person_relations = pd.DataFrame(relations_person_data) # type: ignore
|
||||
|
||||
person_relations["id_company"] = person_relations["id_company"].apply(
|
||||
lambda x: f"c_{x}"
|
||||
)
|
||||
person_relations["id_person"] = person_relations["id_person"].apply(
|
||||
lambda x: f"p_{x}"
|
||||
)
|
||||
|
||||
return person_relations
|
||||
|
||||
|
||||
def create_edge_and_node_list_for_company(
|
||||
company_relations: pd.DataFrame,
|
||||
) -> tuple[dict, list]:
|
||||
@ -347,6 +393,18 @@ def get_all_metrics_from_id(company_id: int) -> pd.Series:
|
||||
return filtered_metrics.iloc[0]
|
||||
|
||||
|
||||
def filter_sets(sets: list[set]) -> set:
|
||||
"""Filters the list of sets given to only contain unique entries.
|
||||
|
||||
Args:
|
||||
sets (list[set]): List of sets to be filtered.
|
||||
|
||||
Returns:
|
||||
list[set]: List of sets with only unique entries.
|
||||
"""
|
||||
return set.union(*sets)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_relations_number_from_id(id: str) -> tuple[int, int, int]:
|
||||
"""Returns all Relation in 1, 2 and 3 lvl of one Node.
|
||||
@ -365,6 +423,7 @@ def get_relations_number_from_id(id: str) -> tuple[int, int, int]:
|
||||
nodes_tmp, edges_tmp = create_edge_and_node_list(person_df, company_df)
|
||||
graph = initialize_network_without_metrics(nodes=nodes_tmp, edges=edges_tmp)
|
||||
|
||||
try:
|
||||
neighbors = nx.all_neighbors(graph, id)
|
||||
|
||||
relations_lv1 = set(neighbors)
|
||||
@ -379,6 +438,65 @@ def get_relations_number_from_id(id: str) -> tuple[int, int, int]:
|
||||
for sub_node in relations_lv2:
|
||||
relations_lv3 |= set(nx.all_neighbors(graph, sub_node))
|
||||
|
||||
relations_lv2.difference(relations_lv3)
|
||||
unique_entires = filter_sets([relations_lv1, relations_lv2])
|
||||
relations_lv3 = {key for key in relations_lv3 if key not in unique_entires}
|
||||
|
||||
return len(relations_lv1), len(relations_lv2), len(relations_lv3)
|
||||
except nx.exception.NetworkXError:
|
||||
return 0, 0, 0
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_relations_until_level_3(id: str) -> tuple[dict, list]:
|
||||
"""Returns all Relation in 1, 2 and 3 lvl of one Node.
|
||||
|
||||
Args:
|
||||
id: String of the Company or Person Id.
|
||||
|
||||
Returns:
|
||||
tuple[dict, list]: nodes, edges
|
||||
"""
|
||||
# Get Data
|
||||
person_df = get_all_person_relations()
|
||||
company_df = get_all_company_relations()
|
||||
|
||||
# Create Edge and Node List from data
|
||||
nodes_tmp, edges_tmp = create_edge_and_node_list(person_df, company_df)
|
||||
graph = initialize_network_without_metrics(nodes=nodes_tmp, edges=edges_tmp)
|
||||
|
||||
try:
|
||||
neighbors = nx.all_neighbors(graph, id)
|
||||
|
||||
relations_lv1 = set(neighbors)
|
||||
relations_lv2 = set()
|
||||
relations_lv3 = set()
|
||||
|
||||
for node in relations_lv1:
|
||||
relations_lv2 |= set(nx.all_neighbors(graph, node))
|
||||
|
||||
relations_lv2.discard(id)
|
||||
|
||||
for sub_node in relations_lv2:
|
||||
relations_lv3 |= set(nx.all_neighbors(graph, sub_node))
|
||||
|
||||
unique_entires = filter_sets([relations_lv1, relations_lv2])
|
||||
relations_lv3 = {key for key in relations_lv3 if key not in unique_entires}
|
||||
|
||||
nodes = {
|
||||
key: value
|
||||
for key, value in nodes_tmp.items()
|
||||
if (
|
||||
key in relations_lv1
|
||||
or key in relations_lv2
|
||||
or key in relations_lv3
|
||||
or key == id
|
||||
)
|
||||
}
|
||||
edges = [
|
||||
edge
|
||||
for edge in edges_tmp
|
||||
if (edge["from"] in nodes and edge["to"] in nodes)
|
||||
]
|
||||
return nodes, edges
|
||||
except nx.exception.NetworkXError:
|
||||
return {}, []
|
||||
|
Loading…
x
Reference in New Issue
Block a user