|
import pandas as pd |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
from dash import Dash, dcc, html, Input, Output, State, ALL, MATCH |
|
import numpy as np |
|
import random |
|
import math |
|
from collections import defaultdict |
|
import colorsys |
|
from fastapi import HTTPException |
|
from pydantic import BaseModel |
|
from dash import Dash |
|
import dash_bootstrap_components as dbc |
|
from fastapi import HTTPException, APIRouter, Request |
|
|
|
router = APIRouter() |
|
|
|
|
|
async def load_data_from_mongodb(userId, topic, year, request: Request): |
|
query = { |
|
"userId": userId, |
|
"topic": topic, |
|
"year": year |
|
} |
|
collection = request.app.state.collection2 |
|
document = await collection.find_one(query) |
|
if not document: |
|
raise ValueError(f"No data found for userId={userId}, topic={topic}, year={year}") |
|
|
|
metadata = document.get("metadata", []) |
|
df = pd.DataFrame(metadata) |
|
df['publication_date'] = pd.to_datetime(df['publication_date']) |
|
return df |
|
|
|
|
|
def filter_by_date_range(dataframe, start_idx, end_idx): |
|
start_date = date_range[start_idx] |
|
end_date = date_range[end_idx] |
|
return dataframe[(dataframe['publication_date'] >= start_date) & |
|
(dataframe['publication_date'] <= end_date)] |
|
|
|
def generate_vibrant_colors(n): |
|
base_colors = [] |
|
for i in range(n): |
|
hue = (i / n) % 1.0 |
|
saturation = random.uniform(0.7, 0.9) |
|
value = random.uniform(0.7, 0.9) |
|
r, g, b = colorsys.hsv_to_rgb(hue, saturation, value) |
|
vibrant_color = '#{:02x}{:02x}{:02x}'.format( |
|
int(r * 255), |
|
int(g * 255), |
|
int(b * 255) |
|
) |
|
end_color_r = min(255, int(r * 255 * 1.1)) |
|
end_color_g = min(255, int(g * 255 * 1.1)) |
|
end_color_b = min(255, int(b * 255 * 1.1)) |
|
gradient_end = '#{:02x}{:02x}{:02x}'.format(end_color_r, end_color_g, end_color_b) |
|
base_colors.append({ |
|
'start': vibrant_color, |
|
'end': gradient_end |
|
}) |
|
extended_colors = base_colors * math.ceil(n/10) |
|
final_colors = [] |
|
for i in range(n): |
|
color = extended_colors[i] |
|
jitter = random.uniform(0.9, 1.1) |
|
def jitter_color(hex_color): |
|
r, g, b = [min(255, max(0, int(int(hex_color[j:j+2], 16) * jitter))) for j in (1, 3, 5)] |
|
return f'rgba({r}, {g}, {b}, 0.9)' |
|
final_colors.append({ |
|
'start': jitter_color(color['start']), |
|
'end': jitter_color(color['end']).replace('0.9', '0.8') |
|
}) |
|
return final_colors |
|
|
|
|
|
def create_knowledge_map(filtered_df, view_type='host'): |
|
color_palette = { |
|
'background': '#1E1E1E', |
|
'card_bg': '#1A2238', |
|
'accent1': '#FF6A3D', |
|
'accent2': '#4ECCA3', |
|
'accent3': '#9D84B7', |
|
'text_light': '#FFFFFF', |
|
'text_dark': '#E0E0E0', |
|
} |
|
if view_type == 'host': |
|
group_col = 'host_organization_name' |
|
id_col = 'host_organization_id' |
|
title = "Host Organization Clusters" |
|
else: |
|
group_col = 'venue' |
|
id_col = 'venue_id' |
|
title = "Publication Venue Clusters" |
|
summary = filtered_df.groupby(group_col).agg( |
|
paper_count=('id', 'count'), |
|
is_oa=('is_oa', 'mean'), |
|
oa_status=('oa_status', lambda x: x.mode()[0] if not x.mode().empty else None), |
|
entity_id=(id_col, 'first') |
|
).reset_index() |
|
paper_count_groups = defaultdict(list) |
|
for _, row in summary.iterrows(): |
|
paper_count_groups[row['paper_count']].append(row) |
|
knowledge_map_fig = go.Figure() |
|
sorted_counts = sorted(paper_count_groups.keys(), reverse=True) |
|
vibrant_colors = generate_vibrant_colors(len(sorted_counts)) |
|
golden_angle = np.pi * (3 - np.sqrt(5)) |
|
spiral_coef = 150 |
|
cluster_metadata = {} |
|
max_x, max_y = 500, 500 |
|
for i, count in enumerate(sorted_counts): |
|
radius = np.sqrt(i) * spiral_coef |
|
theta = golden_angle * i |
|
cluster_x, cluster_y = radius * np.cos(theta), radius * np.sin(theta) |
|
label_offset_angle = theta + np.pi/4 |
|
label_offset_distance = 80 + 4 * np.sqrt(len(paper_count_groups[count])) |
|
label_x = cluster_x + label_offset_distance * np.cos(label_offset_angle) |
|
label_y = cluster_y + label_offset_distance * np.sin(label_offset_angle) |
|
cluster_metadata[count] = { |
|
'center_x': cluster_x, |
|
'center_y': cluster_y, |
|
'entities': paper_count_groups[count], |
|
'color': vibrant_colors[i] |
|
} |
|
entities = paper_count_groups[count] |
|
num_entities = len(entities) |
|
cluster_size = min(200, max(80, 40 + 8 * np.sqrt(num_entities))) |
|
color = vibrant_colors[i] |
|
knowledge_map_fig.add_shape( |
|
type="circle", |
|
x0=cluster_x - cluster_size/2, y0=cluster_y - cluster_size/2, |
|
x1=cluster_x + cluster_size/2, y1=cluster_y + cluster_size/2, |
|
fillcolor=color['end'].replace("0.8", "0.15"), |
|
line=dict(color=color['start'], width=1.5), |
|
opacity=0.7 |
|
) |
|
knowledge_map_fig.add_trace(go.Scatter( |
|
x=[cluster_x], y=[cluster_y], |
|
mode='markers', |
|
marker=dict(size=cluster_size, color=color['start'], opacity=0.3), |
|
customdata=[[count, "cluster"]], |
|
hoverinfo='skip' |
|
)) |
|
knowledge_map_fig.add_trace(go.Scatter( |
|
x=[cluster_x, label_x], y=[cluster_y, label_y], |
|
mode='lines', |
|
line=dict(color=color['start'], width=1, dash='dot'), |
|
hoverinfo='skip' |
|
)) |
|
knowledge_map_fig.add_annotation( |
|
x=label_x, y=label_y, |
|
text=f"{count} papers<br>{num_entities} {'orgs' if view_type == 'host' else 'venues'}", |
|
showarrow=False, |
|
font=dict(size=11, color='white'), |
|
bgcolor=color['start'], |
|
bordercolor='white', |
|
borderwidth=1, |
|
opacity=0.9 |
|
) |
|
entities_sorted = sorted(entities, key=lambda x: x[group_col]) |
|
inner_spiral_coef = 0.4 |
|
for j, entity_data in enumerate(entities_sorted): |
|
spiral_radius = np.sqrt(j) * cluster_size * inner_spiral_coef / np.sqrt(num_entities + 1) |
|
spiral_angle = golden_angle * j |
|
jitter_radius = random.uniform(0.9, 1.1) * spiral_radius |
|
jitter_angle = spiral_angle + random.uniform(-0.1, 0.1) |
|
entity_x = cluster_x + jitter_radius * np.cos(jitter_angle) |
|
entity_y = cluster_y + jitter_radius * np.sin(jitter_angle) |
|
node_size = min(18, max(8, np.sqrt(entity_data['paper_count']) * 1.5)) |
|
knowledge_map_fig.add_trace(go.Scatter( |
|
x=[entity_x], y=[entity_y], |
|
mode='markers', |
|
marker=dict( |
|
size=node_size, |
|
color=color['start'], |
|
line=dict(color='rgba(255, 255, 255, 0.9)', width=1.5) |
|
), |
|
customdata=[[ |
|
entity_data[group_col], |
|
entity_data['paper_count'], |
|
entity_data['is_oa'], |
|
entity_data['entity_id'], |
|
count, |
|
"entity" |
|
]], |
|
hovertemplate=( |
|
f"<b>{entity_data[group_col]}</b><br>" |
|
f"Papers: {entity_data['paper_count']}<br>" |
|
f"Open Access: {entity_data['is_oa']:.1%}<extra></extra>" |
|
) |
|
)) |
|
max_x = max([abs(cluster['center_x']) for cluster in cluster_metadata.values()]) + 150 if cluster_metadata else 500 |
|
max_y = max([abs(cluster['center_y']) for cluster in cluster_metadata.values()]) + 150 if cluster_metadata else 500 |
|
knowledge_map_fig.update_layout( |
|
title=dict( |
|
text=title, |
|
font=dict(size=22, family='"Poppins", sans-serif', color=color_palette['accent1']) |
|
), |
|
plot_bgcolor='rgba(26, 34, 56, 1)', |
|
paper_bgcolor='rgba(26, 34, 56, 0.7)', |
|
xaxis=dict(range=[-max(700, max_x), max(700, max_x)], showticklabels=False, showgrid=False), |
|
yaxis=dict(range=[-max(500, max_y), max(500, max_y)], showticklabels=False, showgrid=False), |
|
margin=dict(l=10, r=10, t=60, b=10), |
|
height=700, |
|
hovermode='closest', |
|
showlegend=False, |
|
font=dict(family='"Poppins", sans-serif', color=color_palette['text_light']), |
|
) |
|
return knowledge_map_fig, cluster_metadata |
|
|
|
|
|
def create_oa_pie_fig(filtered_df): |
|
color_palette = { |
|
'background': '#1A2238', |
|
'card_bg': '#1A2238', |
|
'accent1': '#FF6A3D', |
|
'accent2': '#4ECCA3', |
|
'accent3': '#9D84B7', |
|
'text_light': '#FFFFFF', |
|
'text_dark': '#FFFFFF', |
|
} |
|
fig = px.pie( |
|
filtered_df, names='is_oa', title="Overall Open Access Status", |
|
labels={True: "Open Access", False: "Not Open Access"}, |
|
color_discrete_sequence=[color_palette['accent2'], color_palette['accent1']] |
|
) |
|
fig.update_traces( |
|
textinfo='label+percent', |
|
textfont=dict(size=14, family='"Poppins", sans-serif'), |
|
marker=dict(line=dict(color='#1A2238', width=2)) |
|
) |
|
fig.update_layout( |
|
title=dict( |
|
text="Overall Open Access Status", |
|
font=dict(size=18, family='"Poppins", sans-serif', color=color_palette['accent1']) |
|
), |
|
font=dict(family='"Poppins", sans-serif', color=color_palette['text_light']), |
|
paper_bgcolor=color_palette['background'], |
|
plot_bgcolor=color_palette['background'], |
|
margin=dict(t=50, b=20, l=20, r=20), |
|
legend=dict( |
|
orientation="h", |
|
yanchor="bottom", |
|
y=-0.2, |
|
xanchor="center", |
|
x=0.5, |
|
font=dict(size=12, color=color_palette['text_light']) |
|
) |
|
) |
|
return fig |
|
|
|
def create_oa_status_pie_fig(filtered_df): |
|
custom_colors = [ |
|
"#9D84B7", |
|
'#4DADFF', |
|
'#FFD166', |
|
'#06D6A0', |
|
'#EF476F' |
|
] |
|
fig = px.pie( |
|
filtered_df, |
|
names='oa_status', |
|
title="Open Access Status Distribution", |
|
color_discrete_sequence=custom_colors |
|
) |
|
fig.update_traces( |
|
textinfo='label+percent', |
|
insidetextorientation='radial', |
|
textfont=dict(size=14, family='"Poppins", sans-serif'), |
|
marker=dict(line=dict(color='#FFFFFF', width=2)) |
|
) |
|
fig.update_layout( |
|
title=dict( |
|
text="Open Access Status Distribution", |
|
font=dict(size=18, family='"Poppins", sans-serif', color="#FF6A3D") |
|
), |
|
font=dict(family='"Poppins", sans-serif', color='#FFFFFF'), |
|
paper_bgcolor='#1A2238', |
|
plot_bgcolor='#1A2238', |
|
margin=dict(t=50, b=20, l=20, r=20), |
|
legend=dict( |
|
orientation="h", |
|
yanchor="bottom", |
|
y=-0.2, |
|
xanchor="center", |
|
x=0.5, |
|
font=dict(size=12, color='#FFFFFF') |
|
) |
|
) |
|
return fig |
|
|
|
def create_type_bar_fig(filtered_df): |
|
type_counts = filtered_df['type'].value_counts() |
|
vibrant_colors = [ |
|
'#4361EE', '#3A0CA3', '#4CC9F0', |
|
'#F72585', '#7209B7', '#B5179E', |
|
'#480CA8', '#560BAD', '#F77F00' |
|
] |
|
fig = px.bar( |
|
type_counts, |
|
title="Publication Types", |
|
labels={'value': 'Count', 'index': 'Type'}, |
|
color=type_counts.index, |
|
color_discrete_sequence=vibrant_colors[:len(type_counts)] |
|
) |
|
fig.update_traces( |
|
marker_line_width=1, |
|
marker_line_color='rgba(0, 0, 0, 0.5)', |
|
opacity=0.9, |
|
hovertemplate='%{y} publications<extra></extra>', |
|
texttemplate='%{y}', |
|
textposition='outside', |
|
textfont=dict(size=14, color='white') |
|
) |
|
fig.update_layout( |
|
title=dict( |
|
text="Publication Types", |
|
font=dict(size=20, family='"Poppins", sans-serif', color="#FF6A3D") |
|
), |
|
xaxis_title="Type", |
|
yaxis_title="Count", |
|
font=dict(family='"Poppins", sans-serif', color="#FFFFFF", size=14), |
|
paper_bgcolor='#1A2238', |
|
plot_bgcolor='#1A2238', |
|
margin=dict(t=70, b=60, l=60, r=40), |
|
xaxis=dict( |
|
tickfont=dict(size=14, color="#FFFFFF"), |
|
tickangle=-45, |
|
gridcolor='rgba(255, 255, 255, 0.1)' |
|
), |
|
yaxis=dict( |
|
tickfont=dict(size=14, color="#FFFFFF"), |
|
gridcolor='rgba(255, 255, 255, 0.1)' |
|
), |
|
bargap=0.3, |
|
) |
|
return fig |
|
|
|
|
|
class DashboardRequest(BaseModel): |
|
userId: str |
|
topic: str |
|
year: int |
|
|
|
@router.post("/load_and_display_dashboard/") |
|
async def load_and_display_dashboard(request: DashboardRequest, req: Request): |
|
try: |
|
|
|
df = await load_data_from_mongodb(request.userId, request.topic, request.year, req) |
|
|
|
global min_date, max_date, date_range, date_marks |
|
min_date = df['publication_date'].min() |
|
max_date = df['publication_date'].max() |
|
date_range = pd.date_range(start=min_date, end=max_date, freq='MS') |
|
date_marks = {i: date.strftime('%b %Y') for i, date in enumerate(date_range)} |
|
|
|
create_and_run_dashboard(df, request.topic) |
|
base_url = str(req.base_url) |
|
venue_redirect_url = f"{base_url}venue_redirect/{request.userId}/{request.topic}/{request.year}" |
|
|
|
return { |
|
"status": "success", |
|
"message": "Dashboard ready at /venues/", |
|
"redirect": "/venues/", |
|
"open_url": venue_redirect_url |
|
} |
|
except Exception as e: |
|
raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
venue_dash_app = None |
|
|
|
def create_and_run_dashboard(df, topic): |
|
global venue_dash_app |
|
from app import get_or_create_venue_dash_app |
|
venue_dash_app = get_or_create_venue_dash_app() |
|
|
|
if hasattr(venue_dash_app, 'cluster_metadata'): |
|
venue_dash_app.cluster_metadata.clear() |
|
|
|
color_palette = { |
|
'background': '#1A2238', |
|
'card_bg': '#F8F8FF', |
|
'accent1': '#FF6A3D', |
|
'accent2': '#4ECCA3', |
|
'accent3': '#9D84B7', |
|
'text_light': '#FFFFFF', |
|
'text_dark': '#2D3748', |
|
} |
|
container_style = { |
|
'padding': '5px', |
|
'backgroundColor': color_palette['text_dark'], |
|
'borderRadius': '12px', |
|
'boxShadow': '0 4px 12px rgba(0, 0, 0, 0.15)', |
|
'marginBottom': '25px', |
|
'border': f'1px solid rgba(255, 255, 255, 0.2)', |
|
} |
|
hidden_style = {**container_style, 'display': 'none'} |
|
visible_style = {**container_style} |
|
|
|
venue_dash_app.layout = html.Div([ |
|
html.Div([ |
|
html.H1(topic.capitalize() + " Analytics Dashboard", style={ |
|
'textAlign': 'center', |
|
'marginBottom': '10px', |
|
'color': color_palette['accent1'], |
|
'fontSize': '2.5rem', |
|
'fontWeight': '700', |
|
'letterSpacing': '0.5px', |
|
}), |
|
html.Div([ |
|
html.P("Research Publication Analysis & Knowledge Mapping", style={ |
|
'textAlign': 'center', |
|
'color': color_palette['text_light'], |
|
'opacity': '0.8', |
|
'fontSize': '1.2rem', |
|
'marginTop': '0', |
|
}) |
|
]) |
|
], style={ |
|
'background': f'linear-gradient(135deg, {color_palette["background"]}, #364156)', |
|
'padding': '30px 20px', |
|
'borderRadius': '12px', |
|
'marginBottom': '25px', |
|
'boxShadow': '0 4px 20px rgba(0, 0, 0, 0.2)', |
|
}), |
|
|
|
html.Div([ |
|
html.Div([ |
|
html.Button( |
|
id='view-toggle', |
|
children='Switch to Venue View', |
|
style={ |
|
'padding': '12px 20px', |
|
'fontSize': '1rem', |
|
'borderRadius': '8px', |
|
'border': 'none', |
|
'backgroundColor': color_palette['accent1'], |
|
'color': 'white', |
|
'cursor': 'pointer', |
|
'boxShadow': '0 2px 5px rgba(0, 0, 0, 0.1)', |
|
'transition': 'all 0.3s ease', |
|
'marginRight': '20px', |
|
'fontWeight': '500', |
|
} |
|
), |
|
html.H3("Filter by Publication Date", style={ |
|
'marginBottom': '15px', |
|
'color': color_palette['text_dark'], |
|
'fontSize': '1.3rem', |
|
'fontWeight': '600', |
|
}), |
|
], style={'display': 'flex', 'alignItems': 'center', 'marginBottom': '15px'}), |
|
dcc.RangeSlider( |
|
id='date-slider', |
|
min=0, |
|
max=len(date_range) - 1, |
|
value=[0, len(date_range) - 1], |
|
marks=date_marks if len(date_marks) <= 12 else { |
|
i: date_marks[i] for i in range(0, len(date_range), max(1, len(date_range) // 12)) |
|
}, |
|
step=1, |
|
tooltip={"placement": "bottom", "always_visible": True}, |
|
updatemode='mouseup' |
|
), |
|
html.Div(id='date-range-display', style={ |
|
'textAlign': 'center', |
|
'marginTop': '12px', |
|
'fontSize': '1.1rem', |
|
'fontWeight': '500', |
|
'color': color_palette['accent1'], |
|
}) |
|
], style={**container_style, 'marginBottom': '25px'}), |
|
|
|
html.Div([ |
|
dcc.Graph( |
|
id='knowledge-map', |
|
style={'width': '100%', 'height': '700px'}, |
|
config={'scrollZoom': True, 'displayModeBar': True, 'responsive': True} |
|
) |
|
], style={ |
|
**container_style, |
|
'height': '750px', |
|
'marginBottom': '25px', |
|
'background': f'linear-gradient(to bottom right, {color_palette["card_bg"]}, #F0F0F8)', |
|
}), |
|
|
|
html.Div([ |
|
html.H3(id='details-title', style={ |
|
'marginBottom': '15px', |
|
'color': color_palette['accent1'], |
|
'fontSize': '1.4rem', |
|
'fontWeight': '600', |
|
}), |
|
html.Div(id='details-content', style={ |
|
'maxHeight': '350px', |
|
'overflowY': 'auto', |
|
'padding': '10px', |
|
'borderRadius': '8px', |
|
'backgroundColor': 'rgba(255, 255, 255, 0.7)', |
|
}) |
|
], id='details-container', style=hidden_style), |
|
|
|
html.Div([ |
|
html.Div([ |
|
dcc.Graph( |
|
id='oa-pie-chart', |
|
style={'width': '100%', 'height': '350px'}, |
|
config={'displayModeBar': False, 'responsive': True} |
|
) |
|
], style={ |
|
'flex': 1, |
|
**container_style, |
|
'margin': '0 10px', |
|
'height': '400px', |
|
'transition': 'transform 0.3s ease', |
|
':hover': {'transform': 'translateY(-5px)'}, |
|
}), |
|
html.Div([ |
|
dcc.Graph( |
|
id='oa-status-pie-chart', |
|
style={'width': '100%', 'height': '350px'}, |
|
config={'displayModeBar': False, 'responsive': True} |
|
) |
|
], style={ |
|
'flex': 1, |
|
**container_style, |
|
'margin': '0 10px', |
|
'height': '400px', |
|
'transition': 'transform 0.3s ease', |
|
':hover': {'transform': 'translateY(-5px)'}, |
|
}) |
|
], style={'display': 'flex', 'marginBottom': '25px', 'height': '420px'}), |
|
|
|
html.Div([ |
|
dcc.Graph( |
|
id='type-bar-chart', |
|
style={'width': '100%', 'height': '50vh'}, |
|
config={'displayModeBar': False, 'responsive': True} |
|
) |
|
], style={ |
|
**container_style, |
|
'height': '500px', |
|
'background': 'rgba(26, 34, 56, 1)', |
|
'marginBottom': '10px', |
|
}), |
|
|
|
dcc.Store(id='filtered-df-info'), |
|
dcc.Store(id='current-view', data='host'), |
|
html.Div(id='load-trigger', children=f"trigger-{pd.Timestamp.now().timestamp()}", style={'display': 'none'}) |
|
], style={ |
|
'fontFamily': '"Poppins", "Segoe UI", Arial, sans-serif', |
|
'backgroundColor': '#121212', |
|
'padding': '30px', |
|
'maxWidth': '1800px', |
|
'margin': '0 auto', |
|
'minHeight': '100vh', |
|
'color': color_palette['text_light'], |
|
'paddingBottom': '10px', |
|
}) |
|
|
|
@venue_dash_app.callback( |
|
[Output('current-view', 'data'), |
|
Output('view-toggle', 'children')], |
|
[Input('view-toggle', 'n_clicks')], |
|
[State('current-view', 'data')] |
|
) |
|
def toggle_view(n_clicks, current_view): |
|
if not n_clicks: |
|
return current_view, 'Switch to Venue View' if current_view == 'host' else 'Switch to Host View' |
|
new_view = 'venue' if current_view == 'host' else 'host' |
|
new_button_text = 'Switch to Host View' if new_view == 'venue' else 'Switch to Venue View' |
|
return new_view, new_button_text |
|
|
|
@venue_dash_app.callback( |
|
Output('date-range-display', 'children'), |
|
[Input('date-slider', 'value')] |
|
) |
|
def update_date_range_display(date_range_indices): |
|
start_date = date_range[date_range_indices[0]] |
|
end_date = date_range[date_range_indices[1]] |
|
return f"Selected period: {start_date.strftime('%b %Y')} to {end_date.strftime('%b %Y')}" |
|
|
|
@venue_dash_app.callback( |
|
[Output('knowledge-map', 'figure'), |
|
Output('oa-pie-chart', 'figure'), |
|
Output('oa-status-pie-chart', 'figure'), |
|
Output('type-bar-chart', 'figure'), |
|
Output('filtered-df-info', 'data'), |
|
Output('details-container', 'style')], |
|
[Input('date-slider', 'value'), |
|
Input('current-view', 'data'), |
|
Input('load-trigger', 'children')] |
|
) |
|
def update_visualizations(date_range_indices, current_view, _): |
|
|
|
filtered_df = filter_by_date_range(df, date_range_indices[0], date_range_indices[1]) |
|
|
|
knowledge_map_fig, cluster_metadata = create_knowledge_map(filtered_df, current_view) |
|
venue_dash_app.cluster_metadata = cluster_metadata |
|
|
|
filtered_info = { |
|
'start_idx': date_range_indices[0], |
|
'end_idx': date_range_indices[1], |
|
'start_date': date_range[date_range_indices[0]].strftime('%Y-%m-%d'), |
|
'end_date': date_range[date_range_indices[1]].strftime('%Y-%m-%d'), |
|
'record_count': len(filtered_df), |
|
'view_type': current_view |
|
} |
|
|
|
return ( |
|
knowledge_map_fig, |
|
create_oa_pie_fig(filtered_df), |
|
create_oa_status_pie_fig(filtered_df), |
|
create_type_bar_fig(filtered_df), |
|
filtered_info, |
|
hidden_style |
|
) |
|
|
|
@venue_dash_app.callback( |
|
[Output('details-container', 'style', allow_duplicate=True), |
|
Output('details-title', 'children'), |
|
Output('details-content', 'children')], |
|
[Input('knowledge-map', 'clickData')], |
|
[State('filtered-df-info', 'data')], |
|
prevent_initial_call=True |
|
) |
|
def display_details(clickData, filtered_info): |
|
if not clickData or not filtered_info: |
|
return hidden_style, "", [] |
|
customdata = clickData['points'][0]['customdata'] |
|
view_type = filtered_info['view_type'] |
|
entity_type = "Organization" if view_type == 'host' else "Venue" |
|
if len(customdata) >= 2 and customdata[-1] == "cluster": |
|
count = customdata[0] |
|
if count not in venue_dash_app.cluster_metadata: |
|
return hidden_style, "", [] |
|
entities = venue_dash_app.cluster_metadata[count]['entities'] |
|
color = venue_dash_app.cluster_metadata[count]['color']['start'] |
|
table_header = [ |
|
html.Thead(html.Tr([ |
|
html.Th(f"{entity_type} Name", style={'padding': '8px'}), |
|
html.Th(f"{entity_type} ID", style={'padding': '8px'}), |
|
html.Th("Papers", style={'padding': '8px', 'textAlign': 'center'}), |
|
html.Th("Open Access %", style={'padding': '8px', 'textAlign': 'center'}) |
|
], style={'backgroundColor': color_palette['accent1'], 'color': 'white'})) |
|
] |
|
rows = [] |
|
for entity in sorted(entities, key=lambda x: x['paper_count'], reverse=True): |
|
entity_name_link = html.A( |
|
entity[f"{view_type}_organization_name" if view_type == 'host' else "venue"], |
|
href=entity['entity_id'], |
|
target="_blank", |
|
style={'color': color, 'textDecoration': 'underline'} |
|
) |
|
entity_id_link = html.A( |
|
entity['entity_id'].split('/')[-1], |
|
href=entity['entity_id'], |
|
target="_blank", |
|
style={'color': color, 'textDecoration': 'underline'} |
|
) |
|
rows.append(html.Tr([ |
|
html.Td(entity_name_link, style={'padding': '8px'}), |
|
html.Td(entity_id_link, style={'padding': '8px'}), |
|
html.Td(entity['paper_count'], style={'padding': '8px', 'textAlign': 'center'}), |
|
html.Td(f"{entity['is_oa']:.1%}", style={'padding': '8px', 'textAlign': 'center'}) |
|
])) |
|
table = html.Table(table_header + [html.Tbody(rows)], style={ |
|
'width': '100%', |
|
'borderCollapse': 'collapse', |
|
'boxShadow': '0 1px 3px rgba(0,0,0,0.1)' |
|
}) |
|
return ( |
|
visible_style, |
|
f"{entity_type}s with {count} papers", |
|
[html.P(f"Showing {len(entities)} {entity_type.lower()}s during selected period"), table] |
|
) |
|
elif len(customdata) >= 6 and customdata[-1] == "entity": |
|
entity_name = customdata[0] |
|
entity_id = customdata[3] |
|
cluster_count = customdata[4] |
|
color = venue_dash_app.cluster_metadata[cluster_count]['color']['start'] |
|
if view_type == 'host': |
|
entity_papers = df[df['host_organization_name'] == entity_name].copy() |
|
else: |
|
entity_papers = df[df['venue'] == entity_name].copy() |
|
entity_papers = entity_papers[ |
|
(entity_papers['publication_date'] >= pd.to_datetime(filtered_info['start_date'])) & |
|
(entity_papers['publication_date'] <= pd.to_datetime(filtered_info['end_date'])) |
|
] |
|
entity_name_link = html.A( |
|
entity_name, |
|
href=entity_id, |
|
target="_blank", |
|
style={'color': color, 'textDecoration': 'underline', 'fontSize': '1.2em'} |
|
) |
|
entity_id_link = html.A( |
|
entity_id.split('/')[-1], |
|
href=entity_id, |
|
target="_blank", |
|
style={'color': color, 'textDecoration': 'underline'} |
|
) |
|
header = [ |
|
html.Div([ |
|
html.Span("Name: ", style={'fontWeight': 'bold'}), |
|
entity_name_link |
|
], style={'marginBottom': '10px'}), |
|
html.Div([ |
|
html.Span("ID: ", style={'fontWeight': 'bold'}), |
|
entity_id_link |
|
], style={'marginBottom': '10px'}), |
|
html.Div([ |
|
html.Span(f"Papers: {len(entity_papers)}", style={'marginRight': '20px'}), |
|
], style={'marginBottom': '20px'}) |
|
] |
|
table_header = [ |
|
html.Thead(html.Tr([ |
|
html.Th("Paper ID", style={'padding': '8px'}), |
|
html.Th("Type", style={'padding': '8px'}), |
|
html.Th("OA Status", style={'padding': '8px', 'textAlign': 'center'}), |
|
html.Th("Publication Date", style={'padding': '8px', 'textAlign': 'center'}) |
|
], style={'backgroundColor': color, 'color': 'white'})) |
|
] |
|
rows = [] |
|
for _, paper in entity_papers.sort_values('publication_date', ascending=False).iterrows(): |
|
paper_link = html.A( |
|
paper['id'], |
|
href=paper['id'], |
|
target="_blank", |
|
style={'color': color, 'textDecoration': 'underline'} |
|
) |
|
rows.append(html.Tr([ |
|
html.Td(paper_link, style={'padding': '8px'}), |
|
html.Td(paper['type'], style={'padding': '8px'}), |
|
html.Td(paper['oa_status'], style={'padding': '8px', 'textAlign': 'center'}), |
|
html.Td(paper['publication_date'].strftime('%Y-%m-%d'), style={'padding': '8px', 'textAlign': 'center'}) |
|
])) |
|
table = html.Table(table_header + [html.Tbody(rows)], style={ |
|
'width': '100%', |
|
'borderCollapse': 'collapse', |
|
'boxShadow': '0 1px 3px rgba(0,0,0,0.1)' |
|
}) |
|
return visible_style, f"{entity_type} Papers", header + [table] |
|
return hidden_style, "", [] |
|
|
|
return None |