Ippo987 commited on
Commit
42e1062
·
verified ·
1 Parent(s): 4826925

Update venuAnalysis.py

Browse files
Files changed (1) hide show
  1. venuAnalysis.py +441 -533
venuAnalysis.py CHANGED
@@ -1,7 +1,7 @@
1
  import pandas as pd
2
  import plotly.express as px
3
  import plotly.graph_objects as go
4
- from dash import Dash, dcc, html, Input, Output, State
5
  import numpy as np
6
  import random
7
  import math
@@ -9,20 +9,14 @@ from collections import defaultdict
9
  import colorsys
10
  from fastapi import HTTPException
11
  from pydantic import BaseModel
12
- import threading
13
- import webbrowser
14
- import os
15
- import psutil
16
- import socket
17
  from fastapi import HTTPException, APIRouter, Request
18
- router = APIRouter()
19
 
20
- # Global variables to track dashboard state
21
- dashboard_port = 8050
22
- dashboard_process = None
23
 
24
  # MongoDB connection and data loader function
25
- async def load_data_from_mongodb(userId, topic, year, request:Request):
26
  query = {
27
  "userId": userId,
28
  "topic": topic,
@@ -82,15 +76,14 @@ def generate_vibrant_colors(n):
82
  # Knowledge map creator function (unchanged)
83
  def create_knowledge_map(filtered_df, view_type='host'):
84
  color_palette = {
85
- 'background': '#1E1E1E', # Dark background (almost black)
86
- 'card_bg': '#1A2238', # Bluish-black for cards (from your image)
87
- 'accent1': '#FF6A3D', # Orange for headings (keeping from original)
88
- 'accent2': '#4ECCA3', # Keeping teal for secondary elements
89
- 'accent3': '#9D84B7', # Keeping lavender for tertiary elements
90
- 'text_light': '#FFFFFF', # White text
91
- 'text_dark': '#E0E0E0', # Light grey text for dark backgrounds
92
- }
93
-
94
  if view_type == 'host':
95
  group_col = 'host_organization_name'
96
  id_col = 'host_organization_id'
@@ -198,56 +191,52 @@ def create_knowledge_map(filtered_df, view_type='host'):
198
  ))
199
  max_x = max([abs(cluster['center_x']) for cluster in cluster_metadata.values()]) + 150 if cluster_metadata else 500
200
  max_y = max([abs(cluster['center_y']) for cluster in cluster_metadata.values()]) + 150 if cluster_metadata else 500
201
- # Update knowledge_map_fig layout
202
  knowledge_map_fig.update_layout(
203
- title=dict(
204
- text=title,
205
- font=dict(size=22, family='"Poppins", sans-serif', color=color_palette['accent1']) # Orange title
206
- ),
207
- plot_bgcolor='rgba(26, 34, 56, 1)', # Bluish-black background
208
- paper_bgcolor='rgba(26, 34, 56, 0.7)',
209
- xaxis=dict(range=[-max(700, max_x), max(700, max_x)], showticklabels=False, showgrid=False),
210
- yaxis=dict(range=[-max(500, max_y), max(500, max_y)], showticklabels=False, showgrid=False),
211
- margin=dict(l=10, r=10, t=60, b=10),
212
- height=700,
213
- hovermode='closest',
214
- showlegend=False,
215
- font=dict(family='"Poppins", sans-serif', color=color_palette['text_light']), # Light text
216
- )
217
  return knowledge_map_fig, cluster_metadata
218
 
219
  # Other chart functions (unchanged)
220
  def create_oa_pie_fig(filtered_df):
221
  color_palette = {
222
- 'background': '#1A2238', # Dark blue background
223
- 'card_bg': '#1A2238', # Changed to match the other chart
224
- 'accent1': '#FF6A3D', # Vibrant orange for highlights
225
- 'accent2': '#4ECCA3', # Teal for secondary elements
226
- 'accent3': '#9D84B7', # Lavender for tertiary elements
227
- 'text_light': '#FFFFFF', # White text
228
- 'text_dark': '#FFFFFF', # Changed to white for better contrast
229
  }
230
-
231
  fig = px.pie(
232
  filtered_df, names='is_oa', title="Overall Open Access Status",
233
  labels={True: "Open Access", False: "Not Open Access"},
234
  color_discrete_sequence=[color_palette['accent2'], color_palette['accent1']]
235
  )
236
-
237
  fig.update_traces(
238
  textinfo='label+percent',
239
  textfont=dict(size=14, family='"Poppins", sans-serif'),
240
- marker=dict(line=dict(color='#1A2238', width=2)) # Match background color
241
  )
242
-
243
  fig.update_layout(
244
  title=dict(
245
  text="Overall Open Access Status",
246
- font=dict(size=18, family='"Poppins", sans-serif', color=color_palette['accent1']) # Orange title
247
  ),
248
  font=dict(family='"Poppins", sans-serif', color=color_palette['text_light']),
249
- paper_bgcolor=color_palette['background'], # Dark background
250
- plot_bgcolor=color_palette['background'], # Dark background
251
  margin=dict(t=50, b=20, l=20, r=20),
252
  legend=dict(
253
  orientation="h",
@@ -258,8 +247,8 @@ def create_oa_pie_fig(filtered_df):
258
  font=dict(size=12, color=color_palette['text_light'])
259
  )
260
  )
261
-
262
  return fig
 
263
  def create_oa_status_pie_fig(filtered_df):
264
  custom_colors = [
265
  "#9D84B7",
@@ -286,7 +275,7 @@ def create_oa_status_pie_fig(filtered_df):
286
  font=dict(size=18, family='"Poppins", sans-serif', color="#FF6A3D")
287
  ),
288
  font=dict(family='"Poppins", sans-serif', color='#FFFFFF'),
289
- paper_bgcolor='#1A2238', # Bluish-black background
290
  plot_bgcolor='#1A2238',
291
  margin=dict(t=50, b=20, l=20, r=20),
292
  legend=dict(
@@ -299,6 +288,7 @@ def create_oa_status_pie_fig(filtered_df):
299
  )
300
  )
301
  return fig
 
302
  def create_type_bar_fig(filtered_df):
303
  type_counts = filtered_df['type'].value_counts()
304
  vibrant_colors = [
@@ -313,88 +303,39 @@ def create_type_bar_fig(filtered_df):
313
  color=type_counts.index,
314
  color_discrete_sequence=vibrant_colors[:len(type_counts)]
315
  )
 
 
 
 
 
 
 
 
 
316
  fig.update_layout(
317
  title=dict(
318
  text="Publication Types",
319
- font=dict(size=20, family='"Poppins", sans-serif', color="#FF6A3D") # Larger font size
320
  ),
321
  xaxis_title="Type",
322
  yaxis_title="Count",
323
- font=dict(family='"Poppins", sans-serif', color="#FFFFFF", size=14), # Increased font size
324
- paper_bgcolor='#1A2238', # Consistent dark background
325
- plot_bgcolor='#1A2238', # Consistent dark background
326
- margin=dict(t=70, b=60, l=60, r=40), # Increased margins
327
  xaxis=dict(
328
- tickfont=dict(size=14, color="#FFFFFF"), # Increased tick font size
329
  tickangle=-45,
330
- gridcolor='rgba(255, 255, 255, 0.1)' # Lighter grid lines
331
  ),
332
  yaxis=dict(
333
- tickfont=dict(size=14, color="#FFFFFF"), # Increased tick font size
334
- gridcolor='rgba(255, 255, 255, 0.1)' # Lighter grid lines
335
  ),
336
- bargap=0.3, # Increased bar gap
337
- )
338
- fig.update_traces(
339
- marker_line_width=1,
340
- marker_line_color='rgba(0, 0, 0, 0.5)',
341
- opacity=0.9,
342
- hovertemplate='%{y} publications<extra></extra>',
343
- texttemplate='%{y}', # Add text labels
344
- textposition='outside', # Position labels outside bars
345
- textfont=dict(size=14, color='white') # Text label formatting
346
  )
347
  return fig
348
 
349
- # Function to check if port is in use
350
- def is_port_in_use(port):
351
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
352
- return s.connect_ex(('localhost', port)) == 0
353
-
354
- # Function to find a free port
355
- def find_free_port(start_port=7860):
356
- port = start_port
357
- while is_port_in_use(port):
358
- port += 1
359
- return port
360
-
361
- # Function to shutdown any existing dashboard
362
- def shutdown_existing_dashboard():
363
- global dashboard_process
364
-
365
- # First, check if our port is in use
366
- if is_port_in_use(dashboard_port):
367
- try:
368
- # Kill processes using the port
369
- for proc in psutil.process_iter(['pid', 'name', 'connections']):
370
- try:
371
- for conn in proc.connections():
372
- if conn.laddr.port == dashboard_port:
373
- print(f"Terminating process {proc.pid} using port {dashboard_port}")
374
- proc.terminate()
375
- proc.wait(timeout=3)
376
- except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
377
- pass
378
- except Exception as e:
379
- print(f"Error freeing port {dashboard_port}: {e}")
380
-
381
- # If we're tracking a dashboard process, try to terminate it
382
- if dashboard_process is not None:
383
- try:
384
- # Kill the process if it's still running
385
- if dashboard_process.is_alive():
386
- parent = psutil.Process(os.getpid())
387
- children = parent.children(recursive=True)
388
- for process in children:
389
- try:
390
- process.terminate()
391
- except:
392
- pass
393
- dashboard_process = None
394
- except Exception as e:
395
- print(f"Error terminating dashboard process: {e}")
396
- dashboard_process = None # Reset the reference anyway
397
-
398
  # Pydantic model for request validation
399
  class DashboardRequest(BaseModel):
400
  userId: str
@@ -402,436 +343,403 @@ class DashboardRequest(BaseModel):
402
  year: int
403
 
404
  @router.post("/load_and_display_dashboard/")
405
- async def load_and_display_dashboard(request: DashboardRequest, req:Request):
406
- global dashboard_process, dashboard_port
407
-
408
- # Make sure any existing dashboard is shut down
409
- shutdown_existing_dashboard()
410
-
411
- # Find a free port
412
- dashboard_port = find_free_port()
413
-
414
  try:
415
  # Load data from MongoDB
416
  df = await load_data_from_mongodb(request.userId, request.topic, request.year, req)
417
-
418
  # Get date range for the slider
419
  global min_date, max_date, date_range, date_marks
420
  min_date = df['publication_date'].min()
421
  max_date = df['publication_date'].max()
422
  date_range = pd.date_range(start=min_date, end=max_date, freq='MS')
423
  date_marks = {i: date.strftime('%b %Y') for i, date in enumerate(date_range)}
424
-
425
- # Function to create and run the dashboard
426
- def create_and_run_dashboard():
427
- # Create a new app instance
428
- app = Dash(__name__, suppress_callback_exceptions=True)
429
- app.cluster_metadata = {}
430
- color_palette = {
431
- 'background': '#1A2238', # Dark blue background
432
- 'card_bg': '#F8F8FF', # Off-white for cards
433
- 'accent1': '#FF6A3D', # Vibrant orange for highlights
434
- 'accent2': '#4ECCA3', # Teal for secondary elements
435
- 'accent3': '#9D84B7', # Lavender for tertiary elements
436
- 'text_light': '#FFFFFF', # White text
437
- 'text_dark': '#2D3748', # Dark gray text
438
- }
439
-
440
- # Define modern styling for containers
441
- container_style = {
442
- 'padding': '5px',
443
- 'backgroundColor': color_palette['text_dark'],
444
- 'borderRadius': '12px',
445
- 'boxShadow': '0 4px 12px rgba(0, 0, 0, 0.15)',
446
- 'marginBottom': '25px',
447
- 'border': f'1px solid rgba(255, 255, 255, 0.2)',
448
-
449
- }
450
 
451
- hidden_style = {**container_style, 'display': 'none'}
452
- visible_style = {**container_style}
453
 
454
- # Create a modern, attractive layout
455
- app.layout = html.Div([
456
- # Header section with gradient background
457
- html.Div([
458
- html.H1(request.topic.capitalize() + " Analytics Dashboard", style={
459
- 'textAlign': 'center',
460
- 'marginBottom': '10px',
461
- 'color': color_palette['accent1'],
462
- 'fontSize': '2.5rem',
463
- 'fontWeight': '700',
464
- 'letterSpacing': '0.5px',
465
- }),
466
- html.Div([
467
- html.P("Research Publication Analysis & Knowledge Mapping", style={
468
- 'textAlign': 'center',
469
- 'color': color_palette['text_light'],
470
- 'opacity': '0.8',
471
- 'fontSize': '1.2rem',
472
- 'marginTop': '0',
473
- })
474
- ])
475
- ], style={
476
- 'background': f'linear-gradient(135deg, {color_palette["background"]}, #364156)',
477
- 'padding': '30px 20px',
478
- 'borderRadius': '12px',
479
- 'marginBottom': '25px',
480
- 'boxShadow': '0 4px 20px rgba(0, 0, 0, 0.2)',
481
- }),
482
-
483
- # Controls section
484
- html.Div([
485
- html.Div([
486
- html.Button(
487
- id='view-toggle',
488
- children='Switch to Venue View',
489
- style={
490
- 'padding': '12px 20px',
491
- 'fontSize': '1rem',
492
- 'borderRadius': '8px',
493
- 'border': 'none',
494
- 'backgroundColor': color_palette['accent1'],
495
- 'color': 'white',
496
- 'cursor': 'pointer',
497
- 'boxShadow': '0 2px 5px rgba(0, 0, 0, 0.1)',
498
- 'transition': 'all 0.3s ease',
499
- 'marginRight': '20px',
500
- 'fontWeight': '500',
501
- }
502
- ),
503
- html.H3("Filter by Publication Date", style={
504
- 'marginBottom': '15px',
505
- 'color': color_palette['text_dark'],
506
- 'fontSize': '1.3rem',
507
- 'fontWeight': '600',
508
- }),
509
- ], style={'display': 'flex', 'alignItems': 'center', 'marginBottom': '15px'}),
510
-
511
- dcc.RangeSlider(
512
- id='date-slider',
513
- min=0,
514
- max=len(date_range) - 1,
515
- value=[0, len(date_range) - 1],
516
- marks=date_marks if len(date_marks) <= 12 else {
517
- i: date_marks[i] for i in range(0, len(date_range), max(1, len(date_range) // 12))
518
- },
519
- step=1,
520
- tooltip={"placement": "bottom", "always_visible": True},
521
- updatemode='mouseup'
522
- ),
523
- html.Div(id='date-range-display', style={
524
- 'textAlign': 'center',
525
- 'marginTop': '12px',
526
- 'fontSize': '1.1rem',
527
- 'fontWeight': '500',
528
- 'color': color_palette['accent1'],
529
- })
530
- ], style={**container_style, 'marginBottom': '25px'}),
531
-
532
- # Knowledge map - main visualization
533
- html.Div([
534
- dcc.Graph(
535
- id='knowledge-map',
536
- style={'width': '100%', 'height': '700px'},
537
- config={'scrollZoom': True, 'displayModeBar': True, 'responsive': True}
538
- )
539
- ], style={
540
- **container_style,
541
- 'height': '750px',
542
- 'marginBottom': '25px',
543
- 'background': f'linear-gradient(to bottom right, {color_palette["card_bg"]}, #F0F0F8)',
544
- }),
545
-
546
- # Details container - appears when clicking elements
547
- html.Div([
548
- html.H3(id='details-title', style={
549
- 'marginBottom': '15px',
550
- 'color': color_palette['accent1'],
551
- 'fontSize': '1.4rem',
552
- 'fontWeight': '600',
553
- }),
554
- html.Div(id='details-content', style={
555
- 'maxHeight': '350px',
556
- 'overflowY': 'auto',
557
- 'padding': '10px',
558
- 'borderRadius': '8px',
559
- 'backgroundColor': 'rgba(255, 255, 255, 0.7)',
560
- })
561
- ], id='details-container', style=hidden_style),
562
-
563
- # Charts in flex container
564
- html.Div([
565
- html.Div([
566
- dcc.Graph(
567
- id='oa-pie-chart',
568
- style={'width': '100%', 'height': '350px'},
569
- config={'displayModeBar': False, 'responsive': True}
570
- )
571
- ], style={
572
- 'flex': 1,
573
- **container_style,
574
- 'margin': '0 10px',
575
- 'height': '400px',
576
- 'transition': 'transform 0.3s ease',
577
- ':hover': {'transform': 'translateY(-5px)'},
578
- }),
579
- html.Div([
580
- dcc.Graph(
581
- id='oa-status-pie-chart',
582
- style={'width': '100%', 'height': '350px'},
583
- config={'displayModeBar': False, 'responsive': True}
584
- )
585
- ], style={
586
- 'flex': 1,
587
- **container_style,
588
- 'margin': '0 10px',
589
- 'height': '400px',
590
- 'transition': 'transform 0.3s ease',
591
- ':hover': {'transform': 'translateY(-5px)'},
592
- })
593
- ], style={'display': 'flex', 'marginBottom': '25px', 'height': '420px'}),
594
-
595
- # Bar chart container
596
- # Increase bar chart height and improve visibility
597
- html.Div([
598
- dcc.Graph(
599
- id='type-bar-chart',
600
- style={'width': '100%', 'height': '50vh'}, # Reduced from 60vh
601
- config={'displayModeBar': False, 'responsive': True}
602
- )
603
- ], style={
604
- **container_style,
605
- 'height': '500px', # Decreased from 650px
606
- 'background': 'rgba(26, 34, 56, 1)',
607
- 'marginBottom': '10px', # Added smaller bottom margin
608
- }),
609
- # Store components for state
610
- dcc.Store(id='filtered-df-info'),
611
- dcc.Store(id='current-view', data='host'),
612
- html.Div(id='load-trigger', children='trigger-initial-load', style={'display': 'none'})
613
- ], style={
614
- 'fontFamily': '"Poppins", "Segoe UI", Arial, sans-serif',
615
- 'backgroundColor': '#121212', # Dark background
616
- 'backgroundImage': 'none', # Remove gradient
617
- 'padding': '30px',
618
- 'maxWidth': '1800px',
619
- 'margin': '0 auto',
620
- 'minHeight': '100vh',
621
  'color': color_palette['text_light'],
622
- 'paddingBottom': '10px',
 
 
623
  })
624
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
 
626
-
627
- @app.callback(
628
- [Output('current-view', 'data'),
629
- Output('view-toggle', 'children')],
630
- [Input('view-toggle', 'n_clicks')],
631
- [State('current-view', 'data')]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
  )
633
- def toggle_view(n_clicks, current_view):
634
- if not n_clicks:
635
- return current_view, 'Switch to Venue View' if current_view == 'host' else 'Switch to Host View'
636
- new_view = 'venue' if current_view == 'host' else 'host'
637
- new_button_text = 'Switch to Host View' if new_view == 'venue' else 'Switch to Venue View'
638
- return new_view, new_button_text
639
-
640
- @app.callback(
641
- Output('date-range-display', 'children'),
642
- [Input('date-slider', 'value')]
 
 
 
 
 
 
 
 
643
  )
644
- def update_date_range_display(date_range_indices):
645
- start_date = date_range[date_range_indices[0]]
646
- end_date = date_range[date_range_indices[1]]
647
- return f"Selected period: {start_date.strftime('%b %Y')} to {end_date.strftime('%b %Y')}"
648
-
649
- @app.callback(
650
- [Output('knowledge-map', 'figure'),
651
- Output('oa-pie-chart', 'figure'),
652
- Output('oa-status-pie-chart', 'figure'),
653
- Output('type-bar-chart', 'figure'),
654
- Output('filtered-df-info', 'data'),
655
- Output('details-container', 'style')],
656
- [Input('date-slider', 'value'),
657
- Input('current-view', 'data'),
658
- Input('load-trigger', 'children')] # Added trigger
659
  )
660
- def update_visualizations(date_range_indices, current_view, _):
661
- filtered_df = filter_by_date_range(df, date_range_indices[0], date_range_indices[1])
662
- knowledge_map_fig, cluster_metadata = create_knowledge_map(filtered_df, current_view)
663
- app.cluster_metadata = cluster_metadata
664
- filtered_info = {
665
- 'start_idx': date_range_indices[0],
666
- 'end_idx': date_range_indices[1],
667
- 'start_date': date_range[date_range_indices[0]].strftime('%Y-%m-%d'),
668
- 'end_date': date_range[date_range_indices[1]].strftime('%Y-%m-%d'),
669
- 'record_count': len(filtered_df),
670
- 'view_type': current_view
671
- }
672
- return (
673
- knowledge_map_fig,
674
- create_oa_pie_fig(filtered_df),
675
- create_oa_status_pie_fig(filtered_df),
676
- create_type_bar_fig(filtered_df),
677
- filtered_info,
678
- hidden_style
 
 
 
 
 
 
 
 
 
679
  )
680
-
681
- @app.callback(
682
- [Output('details-container', 'style', allow_duplicate=True),
683
- Output('details-title', 'children'),
684
- Output('details-content', 'children')],
685
- [Input('knowledge-map', 'clickData')],
686
- [State('filtered-df-info', 'data')],
687
- prevent_initial_call=True
688
- )
689
- def display_details(clickData, filtered_info):
690
- if not clickData or not filtered_info:
691
- return hidden_style, "", []
692
- customdata = clickData['points'][0]['customdata']
693
- view_type = filtered_info['view_type']
694
- entity_type = "Organization" if view_type == 'host' else "Venue"
695
- if len(customdata) >= 2 and customdata[-1] == "cluster":
696
- count = customdata[0]
697
- if count not in app.cluster_metadata:
698
- return hidden_style, "", []
699
- entities = app.cluster_metadata[count]['entities']
700
- color = app.cluster_metadata[count]['color']['start']
701
- table_header = [
702
- html.Thead(html.Tr([
703
- html.Th(f"{entity_type} Name", style={'padding': '8px'}),
704
- html.Th(f"{entity_type} ID", style={'padding': '8px'}),
705
- html.Th("Papers", style={'padding': '8px', 'textAlign': 'center'}),
706
- html.Th("Open Access %", style={'padding': '8px', 'textAlign': 'center'})
707
- ], style={'backgroundColor': color_palette['accent1'], 'color': 'white'}))
708
- ]
709
 
710
- # Update row styles
711
- row_style = {'backgroundColor': '#232D42'} if i % 2 == 0 else {'backgroundColor': '#1A2238'}
712
- rows = []
713
- for i, entity in enumerate(sorted(entities, key=lambda x: x['paper_count'], reverse=True)):
714
- row_style = {'backgroundColor': '#f9f9f9'} if i % 2 == 0 else {'backgroundColor': 'white'}
715
- entity_name_link = html.A(
716
- entity[f"{view_type}_organization_name" if view_type == 'host' else "venue"],
717
- href=entity['entity_id'],
718
- target="_blank",
719
- style={'color': color, 'textDecoration': 'underline'}
720
- )
721
- entity_id_link = html.A(
722
- entity['entity_id'].split('/')[-1],
723
- href=entity['entity_id'],
724
- target="_blank",
725
- style={'color': color, 'textDecoration': 'underline'}
726
- )
727
- rows.append(html.Tr([
728
- html.Td(entity_name_link, style={'padding': '8px'}),
729
- html.Td(entity_id_link, style={'padding': '8px'}),
730
- html.Td(entity['paper_count'], style={'padding': '8px', 'textAlign': 'center'}),
731
- html.Td(f"{entity['is_oa']:.1%}", style={'padding': '8px', 'textAlign': 'center'})
732
- ], style=row_style))
733
- table = html.Table(table_header + [html.Tbody(rows)], style={
734
- 'width': '100%',
735
- 'borderCollapse': 'collapse',
736
- 'boxShadow': '0 1px 3px rgba(0,0,0,0.1)'
737
- })
738
- return (
739
- visible_style,
740
- f"{entity_type}s with {count} papers",
741
- [html.P(f"Showing {len(entities)} {entity_type.lower()}s during selected period"), table]
742
- )
743
- elif len(customdata) >= 6 and customdata[-1] == "entity":
744
- entity_name = customdata[0]
745
- entity_id = customdata[3]
746
- cluster_count = customdata[4]
747
- color = app.cluster_metadata[cluster_count]['color']['start']
748
- if view_type == 'host':
749
- entity_papers = df[df['host_organization_name'] == entity_name].copy()
750
- else:
751
- entity_papers = df[df['venue'] == entity_name].copy()
752
- entity_papers = entity_papers[
753
- (entity_papers['publication_date'] >= pd.to_datetime(filtered_info['start_date'])) &
754
- (entity_papers['publication_date'] <= pd.to_datetime(filtered_info['end_date']))
755
- ]
756
- entity_name_link = html.A(
757
- entity_name,
758
- href=entity_id,
759
- target="_blank",
760
- style={'color': color, 'textDecoration': 'underline', 'fontSize': '1.2em'}
761
- )
762
- entity_id_link = html.A(
763
- entity_id.split('/')[-1],
764
- href=entity_id,
765
- target="_blank",
766
- style={'color': color, 'textDecoration': 'underline'}
767
- )
768
- header = [
769
- html.Div([
770
- html.Span("Name: ", style={'fontWeight': 'bold'}),
771
- entity_name_link
772
- ], style={'marginBottom': '10px'}),
773
- html.Div([
774
- html.Span("ID: ", style={'fontWeight': 'bold'}),
775
- entity_id_link
776
- ], style={'marginBottom': '10px'}),
777
- html.Div([
778
- html.Span(f"Papers: {len(entity_papers)}", style={'marginRight': '20px'}),
779
- ], style={'marginBottom': '20px'})
780
- ]
781
- table_header = [
782
- html.Thead(html.Tr([
783
- html.Th("Paper ID", style={'padding': '8px'}),
784
- html.Th("Type", style={'padding': '8px'}),
785
- html.Th("OA Status", style={'padding': '8px', 'textAlign': 'center'}),
786
- html.Th("Publication Date", style={'padding': '8px', 'textAlign': 'center'})
787
- ], style={'backgroundColor': color, 'color': 'white'}))
788
- ]
789
- rows = []
790
- for i, (_, paper) in enumerate(entity_papers.sort_values('publication_date', ascending=False).iterrows()):
791
- row_style = {'backgroundColor': '#232D42'} if i % 2 == 0 else {'backgroundColor': '#1A2238'}
792
- paper_link = html.A(
793
- paper['id'],
794
- href=paper['id'],
795
- target="_blank",
796
- style={'color': color, 'textDecoration': 'underline'}
797
- )
798
- rows.append(html.Tr([
799
- html.Td(paper_link, style={'padding': '8px'}),
800
- html.Td(paper['type'], style={'padding': '8px'}),
801
- html.Td(paper['oa_status'], style={'padding': '8px', 'textAlign': 'center'}),
802
- html.Td(paper['publication_date'].strftime('%Y-%m-%d'), style={'padding': '8px', 'textAlign': 'center'})
803
- ], style=row_style))
804
- table = html.Table(table_header + [html.Tbody(rows)], style={
805
- 'width': '100%',
806
- 'borderCollapse': 'collapse',
807
- 'boxShadow': '0 1px 3px rgba(0,0,0,0.1)'
808
- })
809
- with open("dashboard.html", "w") as f:
810
- f.write(app.index())
811
- print("yup saved!!")
812
- return visible_style, f"{entity_type} Papers", header + [table]
813
- return hidden_style, "", []
814
-
815
- # Start the Dash app
816
- app.run_server(debug=False, port=dashboard_port, use_reloader=False)
817
-
818
- # Run the dashboard in a separate process
819
- dashboard_process = threading.Thread(target=create_and_run_dashboard)
820
- dashboard_process.daemon = True
821
- dashboard_process.start()
822
-
823
- # Open the browser after a delay
824
- def open_browser():
825
- try:
826
- webbrowser.open_new(f"http://127.0.0.1:{dashboard_port}/")
827
- except:
828
- pass
829
-
830
- threading.Timer(1.5, open_browser).start()
831
-
832
- return {"status": "success", "message": f"Dashboard loaded successfully on port {dashboard_port}."}
833
-
834
- except Exception as e:
835
- # Clean up in case of failure
836
- shutdown_existing_dashboard()
837
- raise HTTPException(status_code=400, detail=str(e))
 
1
  import pandas as pd
2
  import plotly.express as px
3
  import plotly.graph_objects as go
4
+ from dash import Dash, dcc, html, Input, Output, State, ALL, MATCH
5
  import numpy as np
6
  import random
7
  import math
 
9
  import colorsys
10
  from fastapi import HTTPException
11
  from pydantic import BaseModel
12
+ from dash import Dash
13
+ import dash_bootstrap_components as dbc
 
 
 
14
  from fastapi import HTTPException, APIRouter, Request
 
15
 
16
+ router = APIRouter()
 
 
17
 
18
  # MongoDB connection and data loader function
19
+ async def load_data_from_mongodb(userId, topic, year, request: Request):
20
  query = {
21
  "userId": userId,
22
  "topic": topic,
 
76
  # Knowledge map creator function (unchanged)
77
  def create_knowledge_map(filtered_df, view_type='host'):
78
  color_palette = {
79
+ 'background': '#1E1E1E',
80
+ 'card_bg': '#1A2238',
81
+ 'accent1': '#FF6A3D',
82
+ 'accent2': '#4ECCA3',
83
+ 'accent3': '#9D84B7',
84
+ 'text_light': '#FFFFFF',
85
+ 'text_dark': '#E0E0E0',
86
+ }
 
87
  if view_type == 'host':
88
  group_col = 'host_organization_name'
89
  id_col = 'host_organization_id'
 
191
  ))
192
  max_x = max([abs(cluster['center_x']) for cluster in cluster_metadata.values()]) + 150 if cluster_metadata else 500
193
  max_y = max([abs(cluster['center_y']) for cluster in cluster_metadata.values()]) + 150 if cluster_metadata else 500
 
194
  knowledge_map_fig.update_layout(
195
+ title=dict(
196
+ text=title,
197
+ font=dict(size=22, family='"Poppins", sans-serif', color=color_palette['accent1'])
198
+ ),
199
+ plot_bgcolor='rgba(26, 34, 56, 1)',
200
+ paper_bgcolor='rgba(26, 34, 56, 0.7)',
201
+ xaxis=dict(range=[-max(700, max_x), max(700, max_x)], showticklabels=False, showgrid=False),
202
+ yaxis=dict(range=[-max(500, max_y), max(500, max_y)], showticklabels=False, showgrid=False),
203
+ margin=dict(l=10, r=10, t=60, b=10),
204
+ height=700,
205
+ hovermode='closest',
206
+ showlegend=False,
207
+ font=dict(family='"Poppins", sans-serif', color=color_palette['text_light']),
208
+ )
209
  return knowledge_map_fig, cluster_metadata
210
 
211
  # Other chart functions (unchanged)
212
  def create_oa_pie_fig(filtered_df):
213
  color_palette = {
214
+ 'background': '#1A2238',
215
+ 'card_bg': '#1A2238',
216
+ 'accent1': '#FF6A3D',
217
+ 'accent2': '#4ECCA3',
218
+ 'accent3': '#9D84B7',
219
+ 'text_light': '#FFFFFF',
220
+ 'text_dark': '#FFFFFF',
221
  }
 
222
  fig = px.pie(
223
  filtered_df, names='is_oa', title="Overall Open Access Status",
224
  labels={True: "Open Access", False: "Not Open Access"},
225
  color_discrete_sequence=[color_palette['accent2'], color_palette['accent1']]
226
  )
 
227
  fig.update_traces(
228
  textinfo='label+percent',
229
  textfont=dict(size=14, family='"Poppins", sans-serif'),
230
+ marker=dict(line=dict(color='#1A2238', width=2))
231
  )
 
232
  fig.update_layout(
233
  title=dict(
234
  text="Overall Open Access Status",
235
+ font=dict(size=18, family='"Poppins", sans-serif', color=color_palette['accent1'])
236
  ),
237
  font=dict(family='"Poppins", sans-serif', color=color_palette['text_light']),
238
+ paper_bgcolor=color_palette['background'],
239
+ plot_bgcolor=color_palette['background'],
240
  margin=dict(t=50, b=20, l=20, r=20),
241
  legend=dict(
242
  orientation="h",
 
247
  font=dict(size=12, color=color_palette['text_light'])
248
  )
249
  )
 
250
  return fig
251
+
252
  def create_oa_status_pie_fig(filtered_df):
253
  custom_colors = [
254
  "#9D84B7",
 
275
  font=dict(size=18, family='"Poppins", sans-serif', color="#FF6A3D")
276
  ),
277
  font=dict(family='"Poppins", sans-serif', color='#FFFFFF'),
278
+ paper_bgcolor='#1A2238',
279
  plot_bgcolor='#1A2238',
280
  margin=dict(t=50, b=20, l=20, r=20),
281
  legend=dict(
 
288
  )
289
  )
290
  return fig
291
+
292
  def create_type_bar_fig(filtered_df):
293
  type_counts = filtered_df['type'].value_counts()
294
  vibrant_colors = [
 
303
  color=type_counts.index,
304
  color_discrete_sequence=vibrant_colors[:len(type_counts)]
305
  )
306
+ fig.update_traces(
307
+ marker_line_width=1,
308
+ marker_line_color='rgba(0, 0, 0, 0.5)',
309
+ opacity=0.9,
310
+ hovertemplate='%{y} publications<extra></extra>',
311
+ texttemplate='%{y}',
312
+ textposition='outside',
313
+ textfont=dict(size=14, color='white')
314
+ )
315
  fig.update_layout(
316
  title=dict(
317
  text="Publication Types",
318
+ font=dict(size=20, family='"Poppins", sans-serif', color="#FF6A3D")
319
  ),
320
  xaxis_title="Type",
321
  yaxis_title="Count",
322
+ font=dict(family='"Poppins", sans-serif', color="#FFFFFF", size=14),
323
+ paper_bgcolor='#1A2238',
324
+ plot_bgcolor='#1A2238',
325
+ margin=dict(t=70, b=60, l=60, r=40),
326
  xaxis=dict(
327
+ tickfont=dict(size=14, color="#FFFFFF"),
328
  tickangle=-45,
329
+ gridcolor='rgba(255, 255, 255, 0.1)'
330
  ),
331
  yaxis=dict(
332
+ tickfont=dict(size=14, color="#FFFFFF"),
333
+ gridcolor='rgba(255, 255, 255, 0.1)'
334
  ),
335
+ bargap=0.3,
 
 
 
 
 
 
 
 
 
336
  )
337
  return fig
338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  # Pydantic model for request validation
340
  class DashboardRequest(BaseModel):
341
  userId: str
 
343
  year: int
344
 
345
  @router.post("/load_and_display_dashboard/")
346
+ async def load_and_display_dashboard(request: DashboardRequest, req: Request):
 
 
 
 
 
 
 
 
347
  try:
348
  # Load data from MongoDB
349
  df = await load_data_from_mongodb(request.userId, request.topic, request.year, req)
 
350
  # Get date range for the slider
351
  global min_date, max_date, date_range, date_marks
352
  min_date = df['publication_date'].min()
353
  max_date = df['publication_date'].max()
354
  date_range = pd.date_range(start=min_date, end=max_date, freq='MS')
355
  date_marks = {i: date.strftime('%b %Y') for i, date in enumerate(date_range)}
356
+ # Create and run dashboard
357
+ create_and_run_dashboard(df, request.topic)
358
+ base_url = str(req.base_url)
359
+ venue_redirect_url = f"{base_url}venue_redirect/{request.userId}/{request.topic}/{request.year}"
360
+ # Return response with redirect info - but DON'T open browser here
361
+ return {
362
+ "status": "success",
363
+ "message": "Dashboard ready at /venues/",
364
+ "redirect": "/venues/",
365
+ "open_url": venue_redirect_url
366
+ }
367
+ except Exception as e:
368
+ raise HTTPException(status_code=400, detail=str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
+ venue_dash_app = None
 
371
 
372
+ def create_and_run_dashboard(df, topic):
373
+ global venue_dash_app
374
+ from app import get_or_create_venue_dash_app
375
+ venue_dash_app = get_or_create_venue_dash_app()
376
+ # Clear previous cluster metadata
377
+ if hasattr(venue_dash_app, 'cluster_metadata'):
378
+ venue_dash_app.cluster_metadata.clear()
379
+ # Define color palette and styles
380
+ color_palette = {
381
+ 'background': '#1A2238',
382
+ 'card_bg': '#F8F8FF',
383
+ 'accent1': '#FF6A3D',
384
+ 'accent2': '#4ECCA3',
385
+ 'accent3': '#9D84B7',
386
+ 'text_light': '#FFFFFF',
387
+ 'text_dark': '#2D3748',
388
+ }
389
+ container_style = {
390
+ 'padding': '5px',
391
+ 'backgroundColor': color_palette['text_dark'],
392
+ 'borderRadius': '12px',
393
+ 'boxShadow': '0 4px 12px rgba(0, 0, 0, 0.15)',
394
+ 'marginBottom': '25px',
395
+ 'border': f'1px solid rgba(255, 255, 255, 0.2)',
396
+ }
397
+ hidden_style = {**container_style, 'display': 'none'}
398
+ visible_style = {**container_style}
399
+ # Create the layout
400
+ venue_dash_app.layout = html.Div([
401
+ html.Div([
402
+ html.H1(topic.capitalize() + " Analytics Dashboard", style={
403
+ 'textAlign': 'center',
404
+ 'marginBottom': '10px',
405
+ 'color': color_palette['accent1'],
406
+ 'fontSize': '2.5rem',
407
+ 'fontWeight': '700',
408
+ 'letterSpacing': '0.5px',
409
+ }),
410
+ html.Div([
411
+ html.P("Research Publication Analysis & Knowledge Mapping", style={
412
+ 'textAlign': 'center',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  'color': color_palette['text_light'],
414
+ 'opacity': '0.8',
415
+ 'fontSize': '1.2rem',
416
+ 'marginTop': '0',
417
  })
418
+ ])
419
+ ], style={
420
+ 'background': f'linear-gradient(135deg, {color_palette["background"]}, #364156)',
421
+ 'padding': '30px 20px',
422
+ 'borderRadius': '12px',
423
+ 'marginBottom': '25px',
424
+ 'boxShadow': '0 4px 20px rgba(0, 0, 0, 0.2)',
425
+ }),
426
+ # Controls section
427
+ html.Div([
428
+ html.Div([
429
+ html.Button(
430
+ id='view-toggle',
431
+ children='Switch to Venue View',
432
+ style={
433
+ 'padding': '12px 20px',
434
+ 'fontSize': '1rem',
435
+ 'borderRadius': '8px',
436
+ 'border': 'none',
437
+ 'backgroundColor': color_palette['accent1'],
438
+ 'color': 'white',
439
+ 'cursor': 'pointer',
440
+ 'boxShadow': '0 2px 5px rgba(0, 0, 0, 0.1)',
441
+ 'transition': 'all 0.3s ease',
442
+ 'marginRight': '20px',
443
+ 'fontWeight': '500',
444
+ }
445
+ ),
446
+ html.H3("Filter by Publication Date", style={
447
+ 'marginBottom': '15px',
448
+ 'color': color_palette['text_dark'],
449
+ 'fontSize': '1.3rem',
450
+ 'fontWeight': '600',
451
+ }),
452
+ ], style={'display': 'flex', 'alignItems': 'center', 'marginBottom': '15px'}),
453
+ dcc.RangeSlider(
454
+ id='date-slider',
455
+ min=0,
456
+ max=len(date_range) - 1,
457
+ value=[0, len(date_range) - 1],
458
+ marks=date_marks if len(date_marks) <= 12 else {
459
+ i: date_marks[i] for i in range(0, len(date_range), max(1, len(date_range) // 12))
460
+ },
461
+ step=1,
462
+ tooltip={"placement": "bottom", "always_visible": True},
463
+ updatemode='mouseup'
464
+ ),
465
+ html.Div(id='date-range-display', style={
466
+ 'textAlign': 'center',
467
+ 'marginTop': '12px',
468
+ 'fontSize': '1.1rem',
469
+ 'fontWeight': '500',
470
+ 'color': color_palette['accent1'],
471
+ })
472
+ ], style={**container_style, 'marginBottom': '25px'}),
473
+ # Knowledge map
474
+ html.Div([
475
+ dcc.Graph(
476
+ id='knowledge-map',
477
+ style={'width': '100%', 'height': '700px'},
478
+ config={'scrollZoom': True, 'displayModeBar': True, 'responsive': True}
479
+ )
480
+ ], style={
481
+ **container_style,
482
+ 'height': '750px',
483
+ 'marginBottom': '25px',
484
+ 'background': f'linear-gradient(to bottom right, {color_palette["card_bg"]}, #F0F0F8)',
485
+ }),
486
+ # Details container
487
+ html.Div([
488
+ html.H3(id='details-title', style={
489
+ 'marginBottom': '15px',
490
+ 'color': color_palette['accent1'],
491
+ 'fontSize': '1.4rem',
492
+ 'fontWeight': '600',
493
+ }),
494
+ html.Div(id='details-content', style={
495
+ 'maxHeight': '350px',
496
+ 'overflowY': 'auto',
497
+ 'padding': '10px',
498
+ 'borderRadius': '8px',
499
+ 'backgroundColor': 'rgba(255, 255, 255, 0.7)',
500
+ })
501
+ ], id='details-container', style=hidden_style),
502
+ # Charts in flex container
503
+ html.Div([
504
+ html.Div([
505
+ dcc.Graph(
506
+ id='oa-pie-chart',
507
+ style={'width': '100%', 'height': '350px'},
508
+ config={'displayModeBar': False, 'responsive': True}
509
+ )
510
+ ], style={
511
+ 'flex': 1,
512
+ **container_style,
513
+ 'margin': '0 10px',
514
+ 'height': '400px',
515
+ 'transition': 'transform 0.3s ease',
516
+ ':hover': {'transform': 'translateY(-5px)'},
517
+ }),
518
+ html.Div([
519
+ dcc.Graph(
520
+ id='oa-status-pie-chart',
521
+ style={'width': '100%', 'height': '350px'},
522
+ config={'displayModeBar': False, 'responsive': True}
523
+ )
524
+ ], style={
525
+ 'flex': 1,
526
+ **container_style,
527
+ 'margin': '0 10px',
528
+ 'height': '400px',
529
+ 'transition': 'transform 0.3s ease',
530
+ ':hover': {'transform': 'translateY(-5px)'},
531
+ })
532
+ ], style={'display': 'flex', 'marginBottom': '25px', 'height': '420px'}),
533
+ # Bar chart
534
+ html.Div([
535
+ dcc.Graph(
536
+ id='type-bar-chart',
537
+ style={'width': '100%', 'height': '50vh'},
538
+ config={'displayModeBar': False, 'responsive': True}
539
+ )
540
+ ], style={
541
+ **container_style,
542
+ 'height': '500px',
543
+ 'background': 'rgba(26, 34, 56, 1)',
544
+ 'marginBottom': '10px',
545
+ }),
546
+ # Store components
547
+ dcc.Store(id='filtered-df-info'),
548
+ dcc.Store(id='current-view', data='host'),
549
+ html.Div(id='load-trigger', children=f"trigger-{pd.Timestamp.now().timestamp()}", style={'display': 'none'})
550
+ ], style={
551
+ 'fontFamily': '"Poppins", "Segoe UI", Arial, sans-serif',
552
+ 'backgroundColor': '#121212',
553
+ 'padding': '30px',
554
+ 'maxWidth': '1800px',
555
+ 'margin': '0 auto',
556
+ 'minHeight': '100vh',
557
+ 'color': color_palette['text_light'],
558
+ 'paddingBottom': '10px',
559
+ })
560
+ # Callbacks
561
+ @venue_dash_app.callback(
562
+ [Output('current-view', 'data'),
563
+ Output('view-toggle', 'children')],
564
+ [Input('view-toggle', 'n_clicks')],
565
+ [State('current-view', 'data')]
566
+ )
567
+ def toggle_view(n_clicks, current_view):
568
+ if not n_clicks:
569
+ return current_view, 'Switch to Venue View' if current_view == 'host' else 'Switch to Host View'
570
+ new_view = 'venue' if current_view == 'host' else 'host'
571
+ new_button_text = 'Switch to Host View' if new_view == 'venue' else 'Switch to Venue View'
572
+ return new_view, new_button_text
573
 
574
+ @venue_dash_app.callback(
575
+ Output('date-range-display', 'children'),
576
+ [Input('date-slider', 'value')]
577
+ )
578
+ def update_date_range_display(date_range_indices):
579
+ start_date = date_range[date_range_indices[0]]
580
+ end_date = date_range[date_range_indices[1]]
581
+ return f"Selected period: {start_date.strftime('%b %Y')} to {end_date.strftime('%b %Y')}"
582
+
583
+ @venue_dash_app.callback(
584
+ [Output('knowledge-map', 'figure'),
585
+ Output('oa-pie-chart', 'figure'),
586
+ Output('oa-status-pie-chart', 'figure'),
587
+ Output('type-bar-chart', 'figure'),
588
+ Output('filtered-df-info', 'data'),
589
+ Output('details-container', 'style')],
590
+ [Input('date-slider', 'value'),
591
+ Input('current-view', 'data'),
592
+ Input('load-trigger', 'children')] # Trigger updates
593
+ )
594
+ def update_visualizations(date_range_indices, current_view, _):
595
+ # Filter data based on date range
596
+ filtered_df = filter_by_date_range(df, date_range_indices[0], date_range_indices[1])
597
+ # Generate knowledge map
598
+ knowledge_map_fig, cluster_metadata = create_knowledge_map(filtered_df, current_view)
599
+ venue_dash_app.cluster_metadata = cluster_metadata
600
+ # Prepare metadata for storage
601
+ filtered_info = {
602
+ 'start_idx': date_range_indices[0],
603
+ 'end_idx': date_range_indices[1],
604
+ 'start_date': date_range[date_range_indices[0]].strftime('%Y-%m-%d'),
605
+ 'end_date': date_range[date_range_indices[1]].strftime('%Y-%m-%d'),
606
+ 'record_count': len(filtered_df),
607
+ 'view_type': current_view
608
+ }
609
+ # Return updated figures and metadata
610
+ return (
611
+ knowledge_map_fig,
612
+ create_oa_pie_fig(filtered_df),
613
+ create_oa_status_pie_fig(filtered_df),
614
+ create_type_bar_fig(filtered_df),
615
+ filtered_info,
616
+ hidden_style
617
+ )
618
+
619
+ @venue_dash_app.callback(
620
+ [Output('details-container', 'style', allow_duplicate=True),
621
+ Output('details-title', 'children'),
622
+ Output('details-content', 'children')],
623
+ [Input('knowledge-map', 'clickData')],
624
+ [State('filtered-df-info', 'data')],
625
+ prevent_initial_call=True
626
+ )
627
+ def display_details(clickData, filtered_info):
628
+ if not clickData or not filtered_info:
629
+ return hidden_style, "", []
630
+ customdata = clickData['points'][0]['customdata']
631
+ view_type = filtered_info['view_type']
632
+ entity_type = "Organization" if view_type == 'host' else "Venue"
633
+ if len(customdata) >= 2 and customdata[-1] == "cluster":
634
+ count = customdata[0]
635
+ if count not in venue_dash_app.cluster_metadata:
636
+ return hidden_style, "", []
637
+ entities = venue_dash_app.cluster_metadata[count]['entities']
638
+ color = venue_dash_app.cluster_metadata[count]['color']['start']
639
+ table_header = [
640
+ html.Thead(html.Tr([
641
+ html.Th(f"{entity_type} Name", style={'padding': '8px'}),
642
+ html.Th(f"{entity_type} ID", style={'padding': '8px'}),
643
+ html.Th("Papers", style={'padding': '8px', 'textAlign': 'center'}),
644
+ html.Th("Open Access %", style={'padding': '8px', 'textAlign': 'center'})
645
+ ], style={'backgroundColor': color_palette['accent1'], 'color': 'white'}))
646
+ ]
647
+ rows = []
648
+ for entity in sorted(entities, key=lambda x: x['paper_count'], reverse=True):
649
+ entity_name_link = html.A(
650
+ entity[f"{view_type}_organization_name" if view_type == 'host' else "venue"],
651
+ href=entity['entity_id'],
652
+ target="_blank",
653
+ style={'color': color, 'textDecoration': 'underline'}
654
+ )
655
+ entity_id_link = html.A(
656
+ entity['entity_id'].split('/')[-1],
657
+ href=entity['entity_id'],
658
+ target="_blank",
659
+ style={'color': color, 'textDecoration': 'underline'}
660
+ )
661
+ rows.append(html.Tr([
662
+ html.Td(entity_name_link, style={'padding': '8px'}),
663
+ html.Td(entity_id_link, style={'padding': '8px'}),
664
+ html.Td(entity['paper_count'], style={'padding': '8px', 'textAlign': 'center'}),
665
+ html.Td(f"{entity['is_oa']:.1%}", style={'padding': '8px', 'textAlign': 'center'})
666
+ ]))
667
+ table = html.Table(table_header + [html.Tbody(rows)], style={
668
+ 'width': '100%',
669
+ 'borderCollapse': 'collapse',
670
+ 'boxShadow': '0 1px 3px rgba(0,0,0,0.1)'
671
+ })
672
+ return (
673
+ visible_style,
674
+ f"{entity_type}s with {count} papers",
675
+ [html.P(f"Showing {len(entities)} {entity_type.lower()}s during selected period"), table]
676
  )
677
+ elif len(customdata) >= 6 and customdata[-1] == "entity":
678
+ entity_name = customdata[0]
679
+ entity_id = customdata[3]
680
+ cluster_count = customdata[4]
681
+ color = venue_dash_app.cluster_metadata[cluster_count]['color']['start']
682
+ if view_type == 'host':
683
+ entity_papers = df[df['host_organization_name'] == entity_name].copy()
684
+ else:
685
+ entity_papers = df[df['venue'] == entity_name].copy()
686
+ entity_papers = entity_papers[
687
+ (entity_papers['publication_date'] >= pd.to_datetime(filtered_info['start_date'])) &
688
+ (entity_papers['publication_date'] <= pd.to_datetime(filtered_info['end_date']))
689
+ ]
690
+ entity_name_link = html.A(
691
+ entity_name,
692
+ href=entity_id,
693
+ target="_blank",
694
+ style={'color': color, 'textDecoration': 'underline', 'fontSize': '1.2em'}
695
  )
696
+ entity_id_link = html.A(
697
+ entity_id.split('/')[-1],
698
+ href=entity_id,
699
+ target="_blank",
700
+ style={'color': color, 'textDecoration': 'underline'}
 
 
 
 
 
 
 
 
 
 
701
  )
702
+ header = [
703
+ html.Div([
704
+ html.Span("Name: ", style={'fontWeight': 'bold'}),
705
+ entity_name_link
706
+ ], style={'marginBottom': '10px'}),
707
+ html.Div([
708
+ html.Span("ID: ", style={'fontWeight': 'bold'}),
709
+ entity_id_link
710
+ ], style={'marginBottom': '10px'}),
711
+ html.Div([
712
+ html.Span(f"Papers: {len(entity_papers)}", style={'marginRight': '20px'}),
713
+ ], style={'marginBottom': '20px'})
714
+ ]
715
+ table_header = [
716
+ html.Thead(html.Tr([
717
+ html.Th("Paper ID", style={'padding': '8px'}),
718
+ html.Th("Type", style={'padding': '8px'}),
719
+ html.Th("OA Status", style={'padding': '8px', 'textAlign': 'center'}),
720
+ html.Th("Publication Date", style={'padding': '8px', 'textAlign': 'center'})
721
+ ], style={'backgroundColor': color, 'color': 'white'}))
722
+ ]
723
+ rows = []
724
+ for _, paper in entity_papers.sort_values('publication_date', ascending=False).iterrows():
725
+ paper_link = html.A(
726
+ paper['id'],
727
+ href=paper['id'],
728
+ target="_blank",
729
+ style={'color': color, 'textDecoration': 'underline'}
730
  )
731
+ rows.append(html.Tr([
732
+ html.Td(paper_link, style={'padding': '8px'}),
733
+ html.Td(paper['type'], style={'padding': '8px'}),
734
+ html.Td(paper['oa_status'], style={'padding': '8px', 'textAlign': 'center'}),
735
+ html.Td(paper['publication_date'].strftime('%Y-%m-%d'), style={'padding': '8px', 'textAlign': 'center'})
736
+ ]))
737
+ table = html.Table(table_header + [html.Tbody(rows)], style={
738
+ 'width': '100%',
739
+ 'borderCollapse': 'collapse',
740
+ 'boxShadow': '0 1px 3px rgba(0,0,0,0.1)'
741
+ })
742
+ return visible_style, f"{entity_type} Papers", header + [table]
743
+ return hidden_style, "", []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
 
745
+ return None