File size: 23,675 Bytes
c71a7d8
 
 
 
 
 
 
 
 
 
 
 
f59ce12
 
1e1f917
6baf346
3f8ecd9
 
c71a7d8
a116d30
 
e072981
448861d
c71a7d8
 
 
 
261d935
c71a7d8
 
 
 
 
a116d30
 
261d935
 
c71a7d8
 
 
8edff27
c71a7d8
cffe818
 
 
 
 
 
a116d30
448861d
c71a7d8
a116d30
5d223db
a116d30
c71a7d8
 
5d223db
 
 
028d3cf
b8be8d2
2b5e2b5
3f8ecd9
 
f59ce12
 
 
 
 
 
 
 
 
 
 
d5000da
f59ce12
 
c71a7d8
 
 
 
 
428375d
3f8ecd9
 
 
 
 
 
 
 
d5000da
 
 
 
 
 
 
 
 
46e1655
d5000da
 
 
 
 
 
 
 
 
 
 
ecfd1f4
c71a7d8
f59ce12
ecfd1f4
 
f59ce12
 
 
ef641ab
 
c71a7d8
f59ce12
 
 
 
d5000da
f59ce12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3f8ecd9
f59ce12
 
 
 
 
3f8ecd9
f59ce12
 
3f8ecd9
 
 
321b6f5
3f8ecd9
6baf346
 
bb8f84b
 
7d2cacf
 
 
 
913a5f0
7d2cacf
3f8ecd9
 
 
 
7d2cacf
3f8ecd9
 
 
 
321b6f5
3f8ecd9
 
 
713ea62
d8ae036
3f8ecd9
 
 
 
 
 
 
 
7d2cacf
261d935
 
58515bc
261d935
c71a7d8
 
 
5187c1e
7d2cacf
c71a7d8
5b4a284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c727c4
5b4a284
 
 
 
 
bb7ce2d
 
5b4a284
 
 
 
 
 
 
786cf64
6baf346
5b4a284
6baf346
 
 
 
 
 
5b4a284
 
 
 
 
ca30736
5b4a284
 
428375d
5b4a284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e3742c4
5b4a284
3291a65
 
0323a61
 
 
3291a65
ecfd1f4
028d3cf
d5207f2
6baf346
d5000da
6baf346
 
 
 
 
 
 
 
09f5baa
 
 
 
 
ca30736
09f5baa
 
428375d
09f5baa
 
d5207f2
09f5baa
 
 
 
248b1ce
09f5baa
d5207f2
 
 
09f5baa
 
 
a77555e
a116d30
c71a7d8
6dcab67
6baf346
 
681c209
 
6baf346
 
 
 
 
681c209
 
6baf346
681c209
 
 
6baf346
681c209
6a7641c
6baf346
 
 
252036c
6baf346
 
 
 
 
9d87698
f57ee90
5cca2ac
 
681c209
 
6baf346
 
 
681c209
 
ca30736
681c209
 
6baf346
 
 
 
 
 
 
 
681c209
45e3c85
6baf346
 
681c209
 
 
 
 
 
 
1825a9f
681c209
 
c71a7d8
3291a65
02d71bd
3291a65
 
e841dd9
c72c122
3291a65
 
 
24e5e4e
5b4a284
 
 
0ad044a
5b4a284
e3742c4
02d71bd
e78a4d2
02d71bd
028d3cf
02d71bd
 
 
4a367b3
a77555e
681c209
 
 
 
 
 
3291a65
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
import gradio as gr
from pyvis.network import Network
import networkx as nx
import numpy as np
import pandas as pd
import os
from datasets import load_dataset
from datasets import Features
from datasets import Value
from datasets import Dataset
import matplotlib.pyplot as plt
import re
from collections import defaultdict
from huggingface_hub import hf_hub_download
import json        



pattern = r'"(.*?)"'
# this pattern captures anything in a double quotes. 

Secret_token = os.getenv('HF_token')

dataset = load_dataset('FDSRashid/hadith_info',data_files = 'Basic_Edge_Information.csv', token = Secret_token, split = 'train')

edge_info = dataset.to_pandas()

features = Features({'Rawi ID': Value('int32'), 'Famous Name': Value('string'), 'Narrator Rank': Value('string'), 'Number of Narrations': Value('string'),  'Generation': Value('string')})
narrator_bios = load_dataset("FDSRashid/hadith_info", data_files = 'Teacher_Bios.csv', token = Secret_token,features=features )
narrator_bios = narrator_bios['train'].to_pandas()
narrator_bios.loc[49845, 'Narrator Rank'] = 'ุฑุณูˆู„ ุงู„ู„ู‡'
narrator_bios.loc[49845, 'Number of Narrations'] = 0
narrator_bios['Number of Narrations'] = narrator_bios['Number of Narrations'].astype(int)
narrator_bios.loc[49845, 'Number of Narrations'] = 327512
# 8125 Narrators have no Generation, listed in dataset as None
narrator_bios['Generation'] = narrator_bios['Generation'].replace([None], [-1])
narrator_bios['Generation'] = narrator_bios['Generation'].astype(int)

features = Features({'matn': Value('string'), 'taraf_ID': Value('string'), 'bookid_hadithid': Value('string')})

dataset = load_dataset("FDSRashid/hadith_info", data_files = 'All_Matns.csv',token = Secret_token, features = features)
matn_info = dataset['train'].to_pandas()
matn_info = matn_info.drop(97550)
matn_info = matn_info.drop(307206)
matn_info['taraf_ID'] = matn_info['taraf_ID'].replace('KeyAbsent', -1)

matn_info['taraf_ID'] = matn_info['taraf_ID'].astype(int)

# Isnad Info Hadiths column is structured like {"BookNum_HadithNum", ...} for each edge
isnad_info = load_dataset('FDSRashid/hadith_info',token = Secret_token, data_files = 'isnad_info.csv', split = 'train').to_pandas()
isnad_info['Hadiths Cleaned'] = isnad_info['Hadiths'].apply(lambda x: [re.findall(pattern, string)[0].split("_") for string in x[1:-1].split(',')])
# Hadiths Cleaned is a list of lists, each sub-list is Book Id, Hadith ID
taraf_max = np.max(matn_info['taraf_ID'].unique())
isnad_info['Tarafs Cleaned'] = isnad_info['Tarafs'].apply(lambda x: np.array([int(i.strip(' ')) for i in x[1:-1].split(',')]))

cmap = plt.colormaps['cool']

books = load_dataset('FDSRashid/Hadith_info', data_files='Books.csv', token = Secret_token)['train'].to_pandas()

matn_info['Book_ID'] = matn_info['bookid_hadithid'].apply(lambda x: int(x.split('_')[0]))
matn_info['Hadith Number'] = matn_info['bookid_hadithid'].apply(lambda x: int(x.split('_')[1]))
matn_info = pd.merge(matn_info, books, on='Book_ID')
# Preprocess narrator_bios into a dictionary
narrator_info = narrator_bios.set_index('Rawi ID').to_dict(orient='index')

# Download and read a file
file_path = hf_hub_download(
    repo_id="FDSRashid/hadith_info",  # read in fast lookup data structure
    filename="hadith_lookup.json",
    repo_type="dataset",
    token=Secret_token,
)

with open(file_path, 'r') as f:
    hadith_lookup_dict = json.load(f)
HADITH_LOOKUP = defaultdict(list, hadith_lookup_dict)

    
def value_to_hex(value):
    rgba_color = cmap(value)
    return "#{:02X}{:02X}{:02X}".format(int(rgba_color[0] * 255), int(rgba_color[1] * 255), int(rgba_color[2] * 255))


def get_node_info(node):
    node = int(node)  # Ensure node is an integer
    info = narrator_info.get(node, {})
    student_narrations = info.get('Number of Narrations', 1)
    student_gen = info.get('Generation', -1)
    student_rank = info.get('Narrator Rank', 'ูู„ุงู†')
    node_name = info.get('Famous Name', 'ูู„ุงู†')
    return info, student_narrations, student_gen, student_rank, node_name

def lookup_hadith(taraf_hadith, hadith_lookup):
    """
    Returns a list of unique elements from the hadith_lookup for the given taraf_hadith.

    Parameters:
        taraf_hadith (str or list of str): A string or list of strings to look up.
        hadith_lookup (defaultdict): A defaultdict containing the hadith data.

    Returns:
        list: A list of the unique indices for isnad_info. so which edges for that matn
    """
    # Ensure taraf_hadith is always a list
    if isinstance(taraf_hadith, str):
        taraf_hadith = [taraf_hadith]
    
    # Create a set to accumulate unique elements
    unique_elements = {elem for key in taraf_hadith for elem in hadith_lookup[key]}

    # Convert the set to a list for consistency
    return list(unique_elements)


def visualize_isnad(taraf_num, yaxis):
    # Precompute filtered dataframes
    taraf = matn_info[matn_info['taraf_ID'] == taraf_num]
    taraf_hadith = taraf['bookid_hadithid'].to_list()
    
    
    # Precompute hadiths where taraf_num exists
    hadith_cleaned = isnad_info['Tarafs Cleaned'].apply(lambda x: taraf_num in x)
    isnad_hadith = isnad_info[hadith_cleaned]
    
    lst_hadith = []
    
    for i, hadith_parts in enumerate(taraf_hadith):
        # look up hadith for each bookid_hadithid
        isnad_hadith1 = isnad_info.iloc[lookup_hadith(taraf_hadith[i], HADITH_LOOKUP)][['Source', 'Destination']]
        
        # Create graph and find end nodes
        G = nx.from_pandas_edgelist(isnad_hadith1, source='Source', target='Destination', create_using=nx.DiGraph())
        nodes = [int(n) for n, d in G.out_degree() if d == 0]
        
        if nodes:
            # Batch fetch data from narrator_bios for efficiency
            bio_data = narrator_bios[narrator_bios['Rawi ID'].isin(nodes)]
            
            for n in nodes:
                gen_node = bio_data.loc[bio_data['Rawi ID'] == n, 'Generation'].squeeze()
                gen_node = gen_node if pd.notna(gen_node) else -1
                
                name_node = bio_data.loc[bio_data['Rawi ID'] == n, 'Famous Name'].squeeze()
                name_node = name_node if pd.notna(name_node) else 'ูู„ุงู†'
                
                # Append result for each node
                lst_hadith.append([
                    taraf.iloc[i]['matn'],
                    gen_node,
                    name_node,
                    taraf.iloc[i]['Book_Name'],
                    taraf.iloc[i]['Author'],
                    taraf.iloc[i]['Hadith Number'],
                    n,
                    i
                ])
    
    # Convert to DataFrame
    df = pd.DataFrame(lst_hadith, columns=['Matn', 'Generation', 'Name', 'Book_Name', 'Author', 'Book Hadith Number', 'End Transmitter ID', 'Hadith Number'])
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(int)
    

    # Merge isnad_hadith with narrator_bios for Teacher and Student
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Source', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Teacher'})
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Destination', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Student'})
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(str)
    # Fill missing values with 'ูู„ุงู†'
    # isnad_hadith['Teacher'].fillna('ูู„ุงู†', inplace=True)
    # isnad_hadith['Student'].fillna('ูู„ุงู†', inplace=True)

    end_nodes = df['End Transmitter ID'].tolist() 
    G = nx.from_pandas_edgelist(isnad_hadith, source = 'Source', target = 'Destination', create_using = nx.DiGraph())
    isnad_pos = nx.nx_agraph.graphviz_layout(G, prog='dot')
    x_stretch = 4
    y_stretch = 4
    net = Network(directed =True, select_menu=True, cdn_resources='remote')

    # Precompute end_matn_info for each end node
    end_node_data = df.groupby('End Transmitter ID').apply(lambda x: " ".join(x["Hadith Number"].astype("string"))).to_dict()
    
    # Loop over isnad_pos
    for node, pos in isnad_pos.items():
        node_info, student_narrations, student_gen, student_rank, node_name = get_node_info(node)
        label = f'{node_name} \n {student_rank} \n ID: {node} - Gen {student_gen}'
        size = 50
        font_color = 'red'
        if node == '99999':
            label = f'{node_name} \n ID: {node} - Gen {student_gen}'
            size = 70
            font_color = 'black'
        elif int(node) in end_nodes:
            hadith_numbers = end_node_data.get(int(node), '')
            label += f' \n Hadith {hadith_numbers}'
        net.add_node(node, font={'size': 30, 'color': font_color}, color=value_to_hex(student_narrations), label=label, x=pos[0] * x_stretch, y=-pos[1] * y_stretch, size=size)
    
    # Add edges efficiently
    edge_data = isnad_hadith[['Source', 'Destination', f'{yaxis} Count']].values
    for source, target, count in edge_data:
        net.add_edge(source, target, color=value_to_hex(int(count)), label=f"{count}")

    net.toggle_physics(False)    
    html = net.generate_html()
    html = html.replace("'", "\"")
    df = df.rename(columns = {'Generation': 'Gen.', 'Book Hadith Number': 'Hdth Num', 'End Transmitter ID': 'End Narrator ID', 'Hadith Number': 'Index', 'Book_Name': 'Book', 'Name':'Final Narrator'})
    return f"""<iframe style="width: 100%; height: 600px;margin:0 auto" name="result" allow="midi; geolocation; microphone; camera; 
  display-capture; encrypted-media;" sandbox="allow-modals allow-forms 
  allow-scripts allow-same-origin allow-popups 
  allow-top-navigation-by-user-activation allow-downloads" allowfullscreen="" 
  allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>""" , df.drop('Hdth Num', axis=1)
    

def visualize_subTaraf(taraf_num, hadith_str, yaxis):
    hadith_list = hadith_str.split(',')
    hadith_list = [hadith.strip() for hadith in hadith_list]
    hadiths = np.array([], dtype=int)
    for hadith in hadith_list:
        if '-' in hadith:
            if hadith.count('-') > 1:
                #print('Please use only one Dash mark!')
                raise gr.Error('Please use only one Dash mark!')
            hadith_multi = hadith.strip().split('-')
            if any([not had.isnumeric() for had in hadith_multi]):
                #print('Invalid Begining')
                raise gr.Error('Invalid Begining')
            elif len(hadith_multi) != 2:
                #print('Two numbers for a range of Hadith numbers please!')
                raise gr.Error('Two numbers for a range of Hadith numbers please!')
            hadith_multi = [int(had) for had in hadith_multi]
            hadiths = np.append(hadiths, np.arange(hadith_multi[0], hadith_multi[1] +1))
        elif hadith.isnumeric():
            hadiths = np.append(hadiths, int(hadith))
        else:
            #print('Invalid Data format!')
            raise gr.Error("Invalid Data format!")

    hadiths= np.unique(hadiths)
    taraf = matn_info[matn_info['taraf_ID'] == taraf_num]
    num_hadith = taraf.shape[0]
    if np.max(hadiths) > num_hadith:
        raise gr.Error(f'Hadith index outside of range. Total Number of Hadith in this Taraf: {num_hadith}')
    taraf['Index'] = np.arange(num_hadith)
    sub_taraf = taraf[taraf['Index'].isin(hadiths)]

    isnad_hadith = isnad_info.iloc[lookup_hadith(sub_taraf['bookid_hadithid'].to_list(), HADITH_LOOKUP)]
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(int)
    
    # Merge isnad_hadith with narrator_bios for Teacher and Student
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Source', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Teacher'})
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Destination', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Student'})
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(str)
    # isnad_hadith['Teacher'].fillna('ูู„ุงู†', inplace=True)
    # isnad_hadith['Student'].fillna('ูู„ุงู†', inplace=True)

    G = nx.from_pandas_edgelist(isnad_hadith, source = 'Source', target = 'Destination', create_using = nx.DiGraph())
    isnad_pos = nx.nx_agraph.graphviz_layout(G, prog='dot')
    x_stretch = 4
    y_stretch = 4
    net = Network(directed =True, select_menu=True, cdn_resources='remote')

    for node, pos in isnad_pos.items():
        node_info,student_narrations,student_gen, student_rank, node_name = get_node_info(node)
        if node == '99999':
            net.add_node(node, font = {'size':50, 'color': 'black'}, color = '#000000', label = f'{node_name} \n ID: {node} - Gen {student_gen}', x= pos[0]*x_stretch, y= -1*pos[1]*y_stretch, size= 70)   
        else:
            net.add_node(node, font = {'size':30, 'color': 'red'}, color = value_to_hex(student_narrations), label = f'{node_name} \n {student_rank} \n ID: {node} - Gen {student_gen}', x= pos[0]*x_stretch, y= -1*pos[1]*y_stretch, size= 50)
    for _, row in isnad_hadith.iterrows():
        source = row['Source']
        target = row['Destination']
        net.add_edge(source, target, color = value_to_hex(int(row[f'{yaxis} Count'])), label = f"{row[f'{yaxis} Count']}")
    net.toggle_physics(False)    
    html = net.generate_html()
    html = html.replace("'", "\"")
    return f"""<iframe style="width: 100%; height: 600px;margin:0 auto" name="result" allow="midi; geolocation; microphone; camera; 
  display-capture; encrypted-media;" sandbox="allow-modals allow-forms 
  allow-scripts allow-same-origin allow-popups 
  allow-top-navigation-by-user-activation allow-downloads" allowfullscreen="" 
  allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>""", sub_taraf[['matn', 'Book_Name', 'Author', 'Book_ID', 'Hadith Number']]

def taraf_booknum(taraf_num):
    taraf = matn_info[matn_info['taraf_ID'] == taraf_num]
    num_hadith = taraf.shape[0]
    taraf['Index'] = np.arange(num_hadith)
    return taraf[['matn', 'Book_ID', 'Hadith Number', 'Book_Name', 'Author', 'Index']]

def visualize_hadith_isnad(df, yaxis):
    df['bookid_hadithid'] = df['Book_ID'].astype(str) + '_' + df['Hadith Number'].astype(str)
    hadith = matn_info[matn_info['bookid_hadithid'].isin(df['bookid_hadithid'])]
    taraf_hadith = df['bookid_hadithid'].to_list()
    isnad_hadith = isnad_info.iloc[lookup_hadith(taraf_hadith, HADITH_LOOKUP)]
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(int)
    
    # Merge isnad_hadith with narrator_bios for Teacher and Student
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Source', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Teacher'})
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Destination', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Student'})
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(str)
    # isnad_hadith['Teacher'].fillna('ูู„ุงู†', inplace=True)
    # isnad_hadith['Student'].fillna('ูู„ุงู†', inplace=True)

    G = nx.from_pandas_edgelist(isnad_hadith, source = 'Source', target = 'Destination', create_using = nx.DiGraph())
    isnad_pos = nx.nx_agraph.graphviz_layout(G, prog='dot')
    x_stretch = 4
    y_stretch = 4
    net = Network(directed =True, select_menu=True, cdn_resources='remote')

    for node, pos in isnad_pos.items():
        node_info,student_narrations,student_gen, student_rank, node_name = get_node_info(node)
        if node == '99999':
            net.add_node(node, font = {'size':50, 'color': 'black'}, color = '#000000', label = f'{node_name} \n ID: {node} - Gen {student_gen}', x= pos[0]*x_stretch, y= -1*pos[1]*y_stretch, size= 70)   
        else:
            net.add_node(node, font = {'size':30, 'color': 'red'}, color = value_to_hex(student_narrations), label = f'{node_name} \n {student_rank} \n ID: {node} - Gen {student_gen}', x= pos[0]*x_stretch, y= -1*pos[1]*y_stretch, size= 50)
    for _, row in isnad_hadith.iterrows():
        source = row['Source']
        target = row['Destination']
        net.add_edge(source, target, color = value_to_hex(int(row[f'{yaxis} Count'])), label = f"{row[f'{yaxis} Count']}")
    net.toggle_physics(False)    
    html = net.generate_html()
    html = html.replace("'", "\"")
    return f"""<iframe style="width: 100%; height: 600px;margin:0 auto" name="result" allow="midi; geolocation; microphone; camera; 
  display-capture; encrypted-media;" sandbox="allow-modals allow-forms 
  allow-scripts allow-same-origin allow-popups 
  allow-top-navigation-by-user-activation allow-downloads" allowfullscreen="" 
  allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>""" , hadith[['matn', 'Book_ID', 'Hadith Number', 'Book_Name', 'Author', 'taraf_ID']]


def visualize_narrator_taraf(taraf_num, narrator, yaxis):
    taraf = matn_info[matn_info['taraf_ID'] == taraf_num].copy()
    taraf['Index'] = np.arange(len(taraf))
    hadith_cleaned = isnad_info['Tarafs Cleaned'].apply(lambda x: taraf_num in x)
    isnad_hadith = isnad_info[hadith_cleaned]
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(int)
    # Merge isnad_hadith with narrator_bios for Teacher and Student
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Source', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Teacher'})
    isnad_hadith = isnad_hadith.merge(narrator_bios[['Rawi ID', 'Famous Name']], left_on='Destination', right_on='Rawi ID', how='left').rename(columns={'Famous Name': 'Student'})
    isnad_hadith[['Source', 'Destination']] = isnad_hadith[['Source', 'Destination']].astype(str)
    taraf_hadith = taraf['bookid_hadithid'].to_list()

    # original graph of whole taraf
    G = nx.from_pandas_edgelist(isnad_hadith, source = 'Source', target = 'Destination', create_using = nx.DiGraph())
    if narrator not in G.nodes():
        raise gr.Error('Narrator not in Isnad of Taraf!')

    matns_with_narrator = []
    end_node = {}

    # Process each hadith in taraf_hadith_split
    for idx, split_hadith in enumerate(taraf_hadith):
        isnad_hadith1 = isnad_info.iloc[lookup_hadith(taraf_hadith[idx], HADITH_LOOKUP)]
        G1 = nx.from_pandas_edgelist(isnad_hadith1, source='Source', target='Destination', create_using=nx.DiGraph())
        if narrator in G1.nodes:
            matns_with_narrator.append(taraf_hadith[idx])
            for node in (n for n, d in G1.out_degree() if d == 0):
                end_node.setdefault(node, []).append(str(idx))

    isnad_hadith = isnad_info.iloc[lookup_hadith(matns_with_narrator, HADITH_LOOKUP)]
    G = nx.from_pandas_edgelist(isnad_hadith, source = 'Source', target = 'Destination', create_using = nx.DiGraph())
    isnad_pos = nx.nx_agraph.graphviz_layout(G, prog='dot')

    narrator_matn_info = taraf[taraf['bookid_hadithid'].isin(matns_with_narrator)]
    narrator_matn_info['Subset Index'] = np.arange(len(narrator_matn_info))

    # Visualization with pyvis
    x_stretch = 4
    y_stretch = 4
    net = Network(directed =True, select_menu=True, cdn_resources='remote')

    for node, pos in isnad_pos.items():
        node_info, student_narrations, student_gen, student_rank, node_name = get_node_info(node)
        label = f'{node_name} \n ID: {node} - Gen {student_gen}'
        size = 70 if node == '99999' else 50
        font_color = 'black' if node == '99999' else 'red'
        hadiths = f" \n Hadiths {', '.join(end_node[node])}" if node in end_node else ''
        net.add_node(node, font={'size': 30, 'color': font_color}, color=value_to_hex(student_narrations),
                     label=f"{label} {hadiths}", x=pos[0] * x_stretch, y=-pos[1] * y_stretch, size=size)

    for edge in G.edges:
        row = isnad_hadith[(isnad_hadith['Source'] == edge[0]) & (isnad_hadith['Destination'] == edge[1])].iloc[0]
        net.add_edge(edge[0], edge[1], color=value_to_hex(int(row[f'{yaxis} Count'])), label=f"{row[f'{yaxis} Count']}")
        
    net.toggle_physics(False)    
    html = net.generate_html()
    html = html.replace("'", "\"")
    return f"""<iframe style="width: 100%; height: 600px;margin:0 auto" name="result" allow="midi; geolocation; microphone; camera; 
  display-capture; encrypted-media;" sandbox="allow-modals allow-forms 
  allow-scripts allow-same-origin allow-popups 
  allow-top-navigation-by-user-activation allow-downloads" allowfullscreen="" 
  allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>""" , narrator_matn_info[['matn', 'Book_Name', 'Author', 'Book_ID', 'Hadith Number', 'Index', 'Subset Index']]


with gr.Blocks() as demo:
    with gr.Tab("Whole Taraf Visualizer"):
        Yaxis = gr.Dropdown(choices = ['Taraf', 'Hadith', 'Isnad', 'Book'], value = 'Taraf', label = 'Variable to Display', info = 'Choose the variable to visualize.')  
        taraf_number = gr.Slider(1,taraf_max , value=10000, label="Taraf", info="Choose the Taraf to Input", step = 1)
        btn = gr.Button('Submit')
        #
        btn.click(fn = visualize_isnad, inputs = [taraf_number, Yaxis], outputs = [gr.HTML(), gr.DataFrame(wrap=True, column_widths=[43, 8, 11,11,10,8, 9])])
    with gr.Tab("Book and Hadith Number Retriever"):
        taraf_num = gr.Slider(1,taraf_max , value=10000, label="Taraf", info="Choose the Taraf to Input", step = 1)
        btn_num = gr.Button('Retrieve')
        btn_num.click(fn=taraf_booknum, inputs = [taraf_num], outputs= [gr.DataFrame(wrap=True)])
    with gr.Tab('Sub Taraf Visualizer'):
        taraf_num = gr.Slider(1,taraf_max , value=10000, label="Taraf", info="Choose the Taraf to Input", step = 1)
        Yaxis = gr.Dropdown(choices = ['Taraf', 'Hadith', 'Isnad', 'Book'], value = 'Taraf', label = 'Variable to Display', info = 'Choose the variable to visualize.') 
        hadith_str = gr.Textbox(label='Hadith Selection', info='Choose which range of Hadith you would like visualized from the Taraf (eg "1, 2, 4-7")')
        btn_sub = gr.Button('Visualize')
        btn_sub.click(fn=visualize_subTaraf, inputs = [taraf_num, hadith_str, Yaxis], outputs=[gr.HTML(), gr.DataFrame(wrap=True)])
    with gr.Tab('Select Hadith Isnad Visualizer'):
        yyaxis = gr.Dropdown(choices = ['Taraf', 'Hadith', 'Isnad', 'Book'], value = 'Taraf', label = 'Variable to Display', info = 'Choose the variable to visualize.')
        hadith_selection =  gr.Dataframe(
            headers=["Book_ID", "Hadith Number"],
            datatype=["number", "number"],
            row_count=5,
            col_count=(2, "fixed"))
        btn_hadith = gr.Button('Visualize')
        btn_hadith.click(fn=visualize_hadith_isnad, inputs=[hadith_selection, yyaxis], outputs=[gr.HTML(), gr.DataFrame(wrap=True)])
    with gr.Tab('Taraf Narrator Isnad Visualizer'):
        Yaxis = gr.Dropdown(choices = ['Taraf', 'Hadith', 'Isnad', 'Book'], value = 'Taraf', label = 'Variable to Display', info = 'Choose the variable to visualize.')  
        taraf_number = gr.Slider(1,taraf_max , value=10000, label="Taraf", info="Choose the Taraf to Input", step = 1)
        narr = gr.Textbox(label='Narrator', info='Choose a Narrator (Refer to full isnad from previous tab)')
        btn_narr = gr.Button('Visualize')
        btn_narr.click(fn=visualize_narrator_taraf, inputs=[taraf_number, narr, Yaxis], outputs=[gr.HTML(), gr.DataFrame(wrap=True)])
demo.launch()