diff --git a/src/aki_prj23_transparenzregister/ui/company_elements.py b/src/aki_prj23_transparenzregister/ui/company_elements.py
index 938f42f..3859417 100644
--- a/src/aki_prj23_transparenzregister/ui/company_elements.py
+++ b/src/aki_prj23_transparenzregister/ui/company_elements.py
@@ -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,
diff --git a/src/aki_prj23_transparenzregister/ui/pages/person.py b/src/aki_prj23_transparenzregister/ui/pages/person.py
index dff55e3..2391ea6 100644
--- a/src/aki_prj23_transparenzregister/ui/pages/person.py
+++ b/src/aki_prj23_transparenzregister/ui/pages/person.py
@@ -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(
diff --git a/src/aki_prj23_transparenzregister/ui/person_elements.py b/src/aki_prj23_transparenzregister/ui/person_elements.py
index 0bfcb7c..ee13f7a 100644
--- a/src/aki_prj23_transparenzregister/ui/person_elements.py
+++ b/src/aki_prj23_transparenzregister/ui/person_elements.py
@@ -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."
+ ],
+ )
],
)
diff --git a/src/aki_prj23_transparenzregister/utils/networkx/network_2d.py b/src/aki_prj23_transparenzregister/utils/networkx/network_2d.py
index 8bd993d..c6ee701 100644
--- a/src/aki_prj23_transparenzregister/utils/networkx/network_2d.py
+++ b/src/aki_prj23_transparenzregister/utils/networkx/network_2d.py
@@ -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,16 +82,29 @@ def create_2d_graph( # noqa PLR0913
hoverinfo="none",
mode="lines",
)
+
# Add the Edge description text to the scatter plot according to its Position.
- 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: %{text}",
- )
+ if edge_annotation is True:
+ 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",
+ 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}",
+ hovertext=edge_type_list,
+ )
# Add the Nodes to the scatter plot according to their Positions.
node_trace = go.Scatter(
@@ -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
diff --git a/src/aki_prj23_transparenzregister/utils/networkx/networkx_data.py b/src/aki_prj23_transparenzregister/utils/networkx/networkx_data.py
index 19f3b9c..47aebd8 100644
--- a/src/aki_prj23_transparenzregister/utils/networkx/networkx_data.py
+++ b/src/aki_prj23_transparenzregister/utils/networkx/networkx_data.py
@@ -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,20 +423,80 @@ 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)
- neighbors = nx.all_neighbors(graph, id)
+ try:
+ neighbors = nx.all_neighbors(graph, id)
- relations_lv1 = set(neighbors)
- relations_lv2 = set()
- relations_lv3 = set()
+ relations_lv1 = set(neighbors)
+ relations_lv2 = set()
+ relations_lv3 = set()
- for node in relations_lv1:
- relations_lv2 |= set(nx.all_neighbors(graph, node))
+ for node in relations_lv1:
+ relations_lv2 |= set(nx.all_neighbors(graph, node))
- relations_lv2.discard(id)
+ relations_lv2.discard(id)
- for sub_node in relations_lv2:
- relations_lv3 |= set(nx.all_neighbors(graph, sub_node))
+ 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)
+ 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 {}, []