mirror of
https://github.com/fhswf/aki_prj23_transparenzregister.git
synced 2025-05-14 00:58:47 +02:00
Added multi relation dropdowns to dashbord (#363)
This change allows for a more complete combination of relation combinations to be filtered.
This commit is contained in:
parent
ad8f5d0fb1
commit
e5b61bc19c
2
.github/workflows/test-and-build-action.yaml
vendored
2
.github/workflows/test-and-build-action.yaml
vendored
@ -154,7 +154,7 @@ jobs:
|
||||
run: exit 0
|
||||
|
||||
- name: Exit workflow on not main branch
|
||||
if: ${{ github.ref != 'refs/heads/main'}}
|
||||
if: ${{ github.ref != 'refs/heads/main' }}
|
||||
run: exit 0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
|
@ -10,7 +10,7 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.top_companytable_style {
|
||||
.top_company_table_style {
|
||||
float: left;
|
||||
margin-top: 20px;
|
||||
margin-left: 20px;
|
||||
@ -91,7 +91,7 @@
|
||||
}
|
||||
|
||||
.networkx_style .graph-style {
|
||||
|
||||
|
||||
margin-top: 10px;
|
||||
padding-bottom: 0px;
|
||||
display: inline-block;
|
||||
|
@ -4,7 +4,6 @@ from functools import lru_cache
|
||||
import dash
|
||||
import dash_daq as daq
|
||||
import networkx as nx
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import plotly.graph_objects as go
|
||||
from cachetools import TTLCache, cached
|
||||
@ -40,50 +39,113 @@ dash.register_page(
|
||||
# ],
|
||||
)
|
||||
|
||||
metric = "None"
|
||||
switch_edge_annotaion_value = False
|
||||
egde_thickness = 1
|
||||
network = None
|
||||
|
||||
def person_relation_type_filter() -> list[str]:
|
||||
"""Returns a Numpy Array of String with Person relation types."""
|
||||
return get_all_person_relations()["relation_type"].unique().tolist()
|
||||
|
||||
|
||||
def person_relation_type_filter() -> np.ndarray:
|
||||
"""Returns an Numpy Array of String with Person telation types."""
|
||||
return get_all_person_relations()["relation_type"].unique()
|
||||
|
||||
|
||||
def company_relation_type_filter() -> np.ndarray:
|
||||
"""Returns an Numpy Array of String with Company relation types."""
|
||||
return get_all_company_relations()["relation_type"].unique()
|
||||
def company_relation_type_filter() -> list[str]:
|
||||
"""Returns a Numpy Array of String with Company relation types."""
|
||||
return get_all_company_relations()["relation_type"].unique().tolist()
|
||||
|
||||
|
||||
def update_table(
|
||||
metric_dropdown_value: str, metrics: pd.DataFrame
|
||||
) -> tuple[dict, list]:
|
||||
"""_summary_.
|
||||
|
||||
Args:
|
||||
metric_dropdown_value (str): _description_
|
||||
metrics (pd.DataFrame): _description_
|
||||
|
||||
Returns:
|
||||
tuple[dict, list]: _description_
|
||||
"""
|
||||
"""_summary_."""
|
||||
table_df = metrics.sort_values(metric_dropdown_value, ascending=False).head(10)
|
||||
table_df = table_df[["designation", "category", metric_dropdown_value]]
|
||||
columns = [{"name": i, "id": i} for i in table_df.columns]
|
||||
return table_df.to_dict("records"), columns # type: ignore
|
||||
|
||||
|
||||
def layout() -> list[html.Div]:
|
||||
@cached(TTLCache(20, ttl=600))
|
||||
def _update_figure( # noqa: PLR0913
|
||||
selected_metric: str,
|
||||
switch_value: bool,
|
||||
switch_edge_annotation_value: bool,
|
||||
c_relation_filter_value: frozenset[str],
|
||||
p_relation_filter_value: frozenset[str],
|
||||
layout: str,
|
||||
slider_value: float,
|
||||
metric_dropdown_value: str,
|
||||
) -> tuple[dict, list, go.Figure]:
|
||||
"""In this Callback the Value of the Dropdown is used to filter the Data.
|
||||
|
||||
In Addition, it takes the filter for the Graph metrics and creates a new graph, or switches between 3D and 2D.
|
||||
|
||||
Args:
|
||||
selected_metric: Selected Value
|
||||
switch_value: True if 2D, False if 3D
|
||||
switch_edge_annotation_value: True if Edge should have a description, False = No Description
|
||||
c_relation_filter_value: Variable with String value of Relation Type for Companies
|
||||
p_relation_filter_value: Variable with String value of Relation Type for Persons
|
||||
layout: String of the Layout Dropdown
|
||||
metric_dropdown_value: String of the Metric Dropdown
|
||||
slider_value: Sets the size of the Edge Connections
|
||||
|
||||
|
||||
Returns:
|
||||
Network Graph(Plotly Figure): Plotly Figure in 3 or 2D
|
||||
"""
|
||||
_ = c_relation_filter_value, p_relation_filter_value
|
||||
|
||||
graph, metrics, nodes, edges = update_graph_data(
|
||||
person_relation_type=p_relation_filter_value,
|
||||
company_relation_type=c_relation_filter_value,
|
||||
)
|
||||
|
||||
table_dict, table_columns = update_table(metric_dropdown_value, metrics)
|
||||
|
||||
if switch_value:
|
||||
return (
|
||||
table_dict,
|
||||
table_columns,
|
||||
create_2d_graph(
|
||||
graph,
|
||||
nodes,
|
||||
edges,
|
||||
metrics,
|
||||
selected_metric,
|
||||
layout,
|
||||
switch_edge_annotation_value,
|
||||
slider_value, # type: ignore
|
||||
),
|
||||
)
|
||||
|
||||
return (
|
||||
table_dict,
|
||||
table_columns,
|
||||
create_3d_graph(
|
||||
graph,
|
||||
nodes,
|
||||
edges,
|
||||
metrics,
|
||||
selected_metric,
|
||||
layout,
|
||||
switch_edge_annotation_value,
|
||||
slider_value, # type: ignore
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def layout() -> list[html]:
|
||||
"""Generates the Layout of the Homepage."""
|
||||
person_relation_types = person_relation_type_filter()
|
||||
company_relation_types = company_relation_type_filter()
|
||||
top_companies_dict, top_companies_columns, figure = update_figure(
|
||||
selected_company_relation_types: frozenset[str] = frozenset(
|
||||
{company_relation_types[1]}
|
||||
)
|
||||
selected_person_relation_types: frozenset[str] = frozenset(
|
||||
{}
|
||||
) # frozenset({person_relation_types[1]})
|
||||
top_companies_dict, top_companies_columns, figure = _update_figure(
|
||||
"None",
|
||||
False,
|
||||
False,
|
||||
company_relation_types[0],
|
||||
person_relation_types[0],
|
||||
selected_company_relation_types,
|
||||
selected_person_relation_types,
|
||||
"Spring",
|
||||
1,
|
||||
"degree",
|
||||
@ -92,7 +154,7 @@ def layout() -> list[html.Div]:
|
||||
children=html.Div(
|
||||
children=[
|
||||
html.Div(
|
||||
className="top_companytable_style",
|
||||
className="top_company_table_style",
|
||||
children=[
|
||||
html.H1(
|
||||
title="Top Ten Nodes in Graph by Metric",
|
||||
@ -143,9 +205,11 @@ def layout() -> list[html.Div]:
|
||||
),
|
||||
dcc.Dropdown(
|
||||
company_relation_types,
|
||||
company_relation_types[0],
|
||||
list(selected_company_relation_types),
|
||||
id="dropdown_company_relation_filter",
|
||||
className="dropdown_style",
|
||||
multi=True,
|
||||
persistence=True,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -159,9 +223,11 @@ def layout() -> list[html.Div]:
|
||||
),
|
||||
dcc.Dropdown(
|
||||
person_relation_types,
|
||||
person_relation_types[0],
|
||||
list(selected_person_relation_types),
|
||||
id="dropdown_person_relation_filter",
|
||||
className="dropdown_style",
|
||||
multi=True,
|
||||
persistence=True,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -282,7 +348,10 @@ def layout() -> list[html.Div]:
|
||||
],
|
||||
),
|
||||
dcc.Graph(
|
||||
figure=figure, id="my-graph", className="graph-style"
|
||||
figure=figure,
|
||||
id="my-graph",
|
||||
className="graph-style",
|
||||
config={"displaylogo": False},
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -293,8 +362,8 @@ def layout() -> list[html.Div]:
|
||||
|
||||
@lru_cache(200)
|
||||
def update_graph_data(
|
||||
person_relation_type: str = "HAFTENDER_GESELLSCHAFTER",
|
||||
company_relation_type: str = "GESCHAEFTSFUEHRER",
|
||||
person_relation_type: frozenset[str] | None = None,
|
||||
company_relation_type: frozenset[str] | None = None,
|
||||
) -> tuple[nx.Graph, pd.DataFrame, dict, list]:
|
||||
"""_summary_.
|
||||
|
||||
@ -305,7 +374,6 @@ def update_graph_data(
|
||||
Returns:
|
||||
tuple[nx.Graph, pd.DataFrame, dict, list]: _description_
|
||||
"""
|
||||
# Get Data
|
||||
person_df = get_all_person_relations()
|
||||
company_df = get_all_company_relations()
|
||||
|
||||
@ -339,71 +407,41 @@ def update_graph_data(
|
||||
prevent_initial_call=True,
|
||||
allow_duplicate=True,
|
||||
)
|
||||
# @lru_cache(20)
|
||||
@cached(cache=TTLCache(maxsize=100, ttl=500))
|
||||
def update_figure( # noqa: PLR0913
|
||||
selected_metric: str,
|
||||
switch_value: bool,
|
||||
# switch_node_annotaion_value: bool,
|
||||
switch_edge_annotaion_value: bool,
|
||||
c_relation_filter_value: str,
|
||||
p_relation_filter_value: str,
|
||||
switch_edge_annotation_value: bool,
|
||||
c_relation_filter_value: list[str],
|
||||
p_relation_filter_value: list[str],
|
||||
layout: str,
|
||||
slider_value: float,
|
||||
metric_dropdown_value: str,
|
||||
) -> go.Figure:
|
||||
"""In this Callback the Value of the Dropdown is used to filter the Data. In Addition it takes the filter for the Graph metrics and creates a new graph, or switches between 3D and 2D.
|
||||
) -> tuple[dict, list, go.Figure]:
|
||||
"""In this Callback the Value of the Dropdown is used to filter the Data.
|
||||
|
||||
In Addition, it takes the filter for the Graph metrics and creates a new graph, or switches between 3D and 2D.
|
||||
|
||||
Args:
|
||||
selected_metric (_type_): Selected Value
|
||||
switch_value (bool): True if 2D, False if 3D
|
||||
switch_edge_annotaion_value: True if Edge should have a description, Flase = No Descritpion
|
||||
c_relation_filter_value (_type_): Variable with String value of Relation Type for Companies
|
||||
p_relation_filter_value (_type_): Variable with String value of Relation Type for Persons
|
||||
selected_metric: Selected Value
|
||||
switch_value: True if 2D, False if 3D
|
||||
switch_edge_annotation_value: True if Edge should have a description, False = No Description
|
||||
c_relation_filter_value: Variable with String value of Relation Type for Companies
|
||||
p_relation_filter_value: Variable with String value of Relation Type for Persons
|
||||
layout: String of the Layout Dropdown
|
||||
metric_dropdown_value: String of the Metric Dropdown
|
||||
slider_value: Sets the size of the Edge Connections
|
||||
|
||||
|
||||
Returns:
|
||||
Network Graph(Plotly Figure): Plotly Figure in 3 or 2D
|
||||
Network Graph: Plotly Figure in 3D or 2D
|
||||
"""
|
||||
_ = c_relation_filter_value, p_relation_filter_value
|
||||
|
||||
graph, metrics, nodes, edges = update_graph_data(
|
||||
person_relation_type=p_relation_filter_value,
|
||||
company_relation_type=c_relation_filter_value,
|
||||
)
|
||||
|
||||
table_dict, table_columns = update_table(metric_dropdown_value, metrics)
|
||||
|
||||
if switch_value:
|
||||
return (
|
||||
table_dict,
|
||||
table_columns,
|
||||
create_2d_graph(
|
||||
graph,
|
||||
nodes,
|
||||
edges,
|
||||
metrics,
|
||||
selected_metric,
|
||||
layout,
|
||||
switch_edge_annotaion_value,
|
||||
slider_value, # type: ignore
|
||||
),
|
||||
)
|
||||
|
||||
return (
|
||||
table_dict,
|
||||
table_columns,
|
||||
create_3d_graph(
|
||||
graph,
|
||||
nodes,
|
||||
edges,
|
||||
metrics,
|
||||
selected_metric,
|
||||
layout,
|
||||
switch_edge_annotaion_value,
|
||||
slider_value, # type: ignore
|
||||
),
|
||||
return _update_figure(
|
||||
selected_metric,
|
||||
switch_value,
|
||||
switch_edge_annotation_value,
|
||||
frozenset(c_relation_filter_value),
|
||||
frozenset(p_relation_filter_value),
|
||||
layout,
|
||||
slider_value,
|
||||
metric_dropdown_value,
|
||||
)
|
||||
|
@ -18,11 +18,11 @@ def create_3d_graph( # noqa : PLR0913
|
||||
"""This Method creates a 3D Network in Plotly with a Scatter Graph and retuns it.
|
||||
|
||||
Args:
|
||||
graph (_type_): NetworkX Graph.
|
||||
nodes (_type_): List of Nodes
|
||||
edges (_type_): List of Edges
|
||||
metrics (_type_): DataFrame with the MEtrics
|
||||
metric (_type_): Selected Metric
|
||||
graph: NetworkX Graph.
|
||||
nodes: List of Nodes
|
||||
edges: List of Edges
|
||||
metrics: DataFrame with the Metrics
|
||||
metric: Selected Metric
|
||||
|
||||
Returns:
|
||||
_type_: Plotly Figure
|
||||
@ -180,7 +180,7 @@ def create_3d_graph( # noqa : PLR0913
|
||||
node_trace.marker.color = colors
|
||||
node_trace.text = node_names
|
||||
|
||||
# Highlight the Node Size in regards to the selected Metric.
|
||||
# Highlight the Node Size with regard to the selected Metric.
|
||||
if metric != "None":
|
||||
node_trace.marker.size = list(np.cbrt(metrics[metric]) * 200)
|
||||
|
||||
|
@ -23,7 +23,6 @@ def initialize_network(edges: list, nodes: dict) -> tuple[nx.Graph, pd.DataFrame
|
||||
# update node attributes from dataframe
|
||||
nx.set_node_attributes(graph, nodes)
|
||||
# Create a DataFrame with all Metrics
|
||||
# Create a DataFrame with all Metrics
|
||||
metrics = pd.DataFrame(
|
||||
columns=[
|
||||
"eigenvector",
|
||||
@ -36,7 +35,7 @@ def initialize_network(edges: list, nodes: dict) -> tuple[nx.Graph, pd.DataFrame
|
||||
"id",
|
||||
]
|
||||
)
|
||||
metrics["eigenvector"] = nx.eigenvector_centrality(graph).values()
|
||||
# metrics["eigenvector"] = nx.eigenvector_centrality(graph, 200).values()
|
||||
metrics["degree"] = nx.degree_centrality(graph).values()
|
||||
metrics["betweenness"] = nx.betweenness_centrality(graph).values()
|
||||
metrics["closeness"] = nx.closeness_centrality(graph).values()
|
||||
@ -54,8 +53,8 @@ def initialize_network_with_reduced_metrics(
|
||||
"""This Method creates a Network from the Framework NetworkX with the help of a Node and Edge List. Furthemore it creates a DataFrame with the most important Metrics.
|
||||
|
||||
Args:
|
||||
edges (list): List with the connections between Nodes.
|
||||
nodes (dict): Dict with all Nodes.
|
||||
edges: List with the connections between Nodes.
|
||||
nodes: Dict with all Nodes.
|
||||
|
||||
Returns:
|
||||
Graph: Plotly Figure
|
||||
|
@ -3,6 +3,7 @@ from functools import lru_cache
|
||||
|
||||
import networkx as nx
|
||||
import pandas as pd
|
||||
from loguru import logger
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from aki_prj23_transparenzregister.ui.session_handler import SessionHandler
|
||||
@ -103,7 +104,7 @@ def get_all_company_relations() -> pd.DataFrame:
|
||||
|
||||
|
||||
def get_all_person_relations() -> pd.DataFrame:
|
||||
"""This Methods makes a Database Request for all Persons and their relations, modifies the ID Column and returns the Result as an DataFrame.
|
||||
"""These method makes a Database Request for all Persons and their relations, modifies the ID Column and returns the Result as an DataFrame.
|
||||
|
||||
Returns:
|
||||
DataFrame: DataFrame with all Relations between Persons and Companies.
|
||||
@ -142,7 +143,7 @@ def get_all_person_relations() -> pd.DataFrame:
|
||||
|
||||
|
||||
def filter_relation_type(
|
||||
relation_dataframe: pd.DataFrame, selected_relation_type: str
|
||||
relation_dataframe: pd.DataFrame, selected_relation_type: frozenset[str] | None
|
||||
) -> pd.DataFrame:
|
||||
"""This Method filters the given DataFrame based on the selected Relation Type and returns it.
|
||||
|
||||
@ -153,9 +154,13 @@ def filter_relation_type(
|
||||
Returns:
|
||||
relation_dataframe (pd.DataFrame): The filtered DataFrame which now only contains entries with the selected Relation Type.
|
||||
"""
|
||||
return relation_dataframe.loc[
|
||||
relation_dataframe["relation_type"] == selected_relation_type
|
||||
]
|
||||
if selected_relation_type is not None:
|
||||
filtered = relation_dataframe.loc[
|
||||
relation_dataframe["relation_type"].isin(selected_relation_type)
|
||||
]
|
||||
logger.info(f"Filtered! {len(filtered.index) / len(relation_dataframe)}")
|
||||
return filtered
|
||||
return relation_dataframe
|
||||
|
||||
|
||||
def create_edge_and_node_list(
|
||||
|
@ -187,9 +187,8 @@ def _get_company_relations() -> Generator:
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.tim()
|
||||
def test_update_graph_data() -> None:
|
||||
graph_result, metrics_result, nodes_result, edges_result = home.update_graph_data(
|
||||
"HAFTENDER_GESELLSCHAFTER", "GESCHAEFTSFUEHRER"
|
||||
frozenset({"HAFTENDER_GESELLSCHAFTER"}), frozenset("GESCHAEFTSFUEHRER")
|
||||
)
|
||||
assert graph_result is not None
|
||||
|
@ -185,7 +185,7 @@ def test_filter_relation_type() -> None:
|
||||
relation_dataframe = networkx_data.get_all_company_relations()
|
||||
selected_relation_type = "HAFTENDER_GESELLSCHAFTER"
|
||||
company_relations_df = networkx_data.filter_relation_type(
|
||||
relation_dataframe, selected_relation_type
|
||||
relation_dataframe, frozenset({selected_relation_type})
|
||||
)
|
||||
assert type(company_relations_df) is pd.DataFrame
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user