ModDotPlot 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1755 @@
1
+ import plotly.express as px
2
+ from moddotplot.estimate_identity import (
3
+ getInteractiveColor,
4
+ getMatchingColors,
5
+ makeDifferencesEqual,
6
+ generateDictionaryFromList,
7
+ findValueInRange,
8
+ convertMatrixToBed,
9
+ )
10
+
11
+ import numpy as np
12
+ import dash
13
+ from dash import Input, Output, html, dcc, State
14
+ import math
15
+ import plotly.graph_objs as go
16
+ import logging
17
+ import os
18
+
19
+ # Prevent HTTP protocol requests from showing up in terminal
20
+ log = logging.getLogger("werkzeug")
21
+ log.setLevel(logging.ERROR)
22
+
23
+
24
+ def find_closest_elements(value, sorted_list):
25
+ # Initialize variables to store the indices of the closest elements
26
+ closest_index1 = None
27
+ closest_index2 = None
28
+
29
+ # Initialize a variable to store the minimum difference
30
+ min_difference = float("inf")
31
+
32
+ # Iterate through the sorted list
33
+ for i in range(len(sorted_list)):
34
+ # Calculate the absolute difference between the current element and the target value
35
+ current_difference = abs(sorted_list[i] - value)
36
+
37
+ # Update indices and minimum difference if a closer element is found
38
+ if current_difference < min_difference:
39
+ min_difference = current_difference
40
+ closest_index1 = i
41
+
42
+ # Check if there is a previous element to avoid index out of range
43
+ if i > 0:
44
+ closest_index2 = i - 1
45
+ if closest_index1 == None:
46
+ return closest_index2, closest_index2
47
+ elif closest_index2 == None:
48
+ return closest_index1, closest_index1
49
+ if closest_index1 > closest_index2:
50
+ return closest_index2, closest_index1
51
+ else:
52
+ return closest_index1, closest_index2
53
+
54
+
55
+ def run_dash(matrices, metadata, axes, sparsity, identity, port_number, output_dir):
56
+ # Run Dash app
57
+ app = dash.Dash(__name__, prevent_initial_callbacks="initial_duplicate")
58
+ app.title = "ModDotPlot"
59
+ print(
60
+ f"{app.title} interactive mode is successfully running on http://127.0.0.1:{port_number}/ \n"
61
+ )
62
+ # Initialize first sequence
63
+ image_pyramid = matrices[0]
64
+ current_metadata = metadata[0]
65
+ x_name = metadata[0]["x_name"]
66
+ y_name = metadata[0]["y_name"]
67
+
68
+ # Initialize color palette
69
+ palette = "Spectral_11"
70
+ palette_orientation = "+"
71
+ current_color = getInteractiveColor(palette, palette_orientation)
72
+
73
+ # Initialize axes and titles for all images
74
+ titles = []
75
+ for i in range(len(metadata)):
76
+ titles.append(metadata[i]["title"])
77
+
78
+ # Get zooming thresholds, adjust sparsity respectively.
79
+ def halving_sequence(size, start):
80
+ sequence = [start]
81
+ for _ in range(1, size):
82
+ start /= 2
83
+ sequence.append(start)
84
+ return sequence
85
+
86
+ # print(current_metadata)
87
+ mod_thresholds_list = halving_sequence(
88
+ len(current_metadata["sparsities"]), current_metadata["x_size"]
89
+ )
90
+ # print(mod_thresholds_list)
91
+
92
+ # print(current_metadata["min_window_size"]* current_metadata["resolution"])
93
+ # print(current_metadata["max_window_size"])
94
+ numo = round(
95
+ math.log2(
96
+ current_metadata["max_window_size"] / current_metadata["min_window_size"]
97
+ )
98
+ + 1
99
+ )
100
+ # print(numo)
101
+ important = generateDictionaryFromList(mod_thresholds_list)
102
+ # print(f"this is imprtant: {important}")
103
+
104
+ main_level = image_pyramid[0]
105
+ main_x_axis = axes[0][0]
106
+ main_y_axis = axes[0][1]
107
+ main_x_axis_np = np.array(main_x_axis)
108
+
109
+ # TODO: modify value here
110
+ main_x_axis_np += 3000
111
+
112
+ # Modify text so that hover shows interval format
113
+ # TODO: Add intervals in a way that's speedy
114
+ '''X, Y = np.meshgrid(main_x_axis, main_y_axis)
115
+ X = np.delete(X, -1, axis=1)
116
+ X, Y = np.meshgrid(main_x_axis, main_y_axis)
117
+ X = np.delete(X, -1, axis=1)
118
+ X_str = np.array([[f"{str(val)}-{str(val + current_metadata['max_window_size'])}" for val in row] for row in X])
119
+ Y = np.delete(Y, -1, axis=1)
120
+ Y_str = np.array([[f"{str(val)}-{str(val + current_metadata['max_window_size'])}" for val in row] for row in Y])
121
+ hover_template_text = "%{text}<br>%{customdata}<br>Identity: %{z:.1f}"'''
122
+ hover_template_text = "Identity: %{z:.2f}"
123
+ # Create initial heatmap
124
+ heatmap = go.Heatmap(
125
+ z=main_level,
126
+ zmin=identity,
127
+ zmax=100,
128
+ colorscale=current_color,
129
+ x=main_x_axis,
130
+ y=main_y_axis,
131
+ showscale=True,
132
+ colorbar=dict(title="Identity"),
133
+ hoverinfo="all",
134
+ hovertemplate=hover_template_text,
135
+ name="",
136
+ x0=0,
137
+ dx=current_metadata["max_window_size"],
138
+ xtype="scaled",
139
+ y0=0,
140
+ dy=current_metadata["max_window_size"],
141
+ ytype="scaled",
142
+ )
143
+
144
+ fig = go.Figure(data=[heatmap])
145
+ fig.update_xaxes(
146
+ showspikes=True,
147
+ spikemode="across",
148
+ ticks="outside",
149
+ showline=True,
150
+ linewidth=2,
151
+ linecolor="black",
152
+ mirror=True,
153
+ )
154
+ fig.update_xaxes(
155
+ nticks=10, title_text=metadata[0]["x_name"], title_font=dict(size=18)
156
+ )
157
+
158
+ # Update y-axis properties directly within the heatmap trace
159
+ fig.update_yaxes(
160
+ showspikes=True,
161
+ spikemode="across",
162
+ ticks="outside",
163
+ showline=True,
164
+ linewidth=2,
165
+ linecolor="black",
166
+ mirror=True,
167
+ )
168
+ fig.update_yaxes(title_text=metadata[0]["y_name"], title_font=dict(size=18))
169
+ fig_title = ""
170
+
171
+ # TODO: Fine tuning on the names
172
+ title_size = 28
173
+ if metadata[0]["self"]:
174
+ fig_title = f"Self-Identity Plot: {x_name}"
175
+ if len(x_name) > 22:
176
+ title_size = 20
177
+ else:
178
+ fig_title = f"Comparative Plot: {x_name} vs. {y_name}"
179
+ if len(x_name) + len(y_name) > 22:
180
+ title_size = 18
181
+
182
+ # Set layout properties
183
+ fig.update_layout(
184
+ height=800,
185
+ width=800,
186
+ hoverlabel=dict(bgcolor="white", font_size=16, font_family="Helvetica"),
187
+ yaxis_scaleanchor="x",
188
+ title=fig_title,
189
+ title_font=dict(size=title_size, family="Helvetica, Arial, sans-serif"),
190
+ title_x=0.5,
191
+ title_y=0.95,
192
+ )
193
+ colorscales = px.colors.named_colorscales()
194
+ colornames = px.colors.named_colorscales()
195
+
196
+ colorscales.insert(0, current_color)
197
+ colornames.insert(0, palette)
198
+
199
+ # HTML for plot webpage
200
+ app.layout = html.Div(
201
+ id="main_color",
202
+ style={
203
+ "alignItems": "center",
204
+ "backgroundColor": "white",
205
+ "color": "black",
206
+ "height": "100vh",
207
+ },
208
+ children=[
209
+ html.Link(rel="icon", href="/assets/favicon.png"),
210
+ html.Div(
211
+ [
212
+ dcc.Graph(id="dotplot", figure=fig),
213
+ html.Div(
214
+ dcc.RangeSlider(
215
+ id="threshold-slider",
216
+ min=identity,
217
+ max=100,
218
+ step=0.1,
219
+ value=[identity, 100],
220
+ marks=None,
221
+ vertical=True,
222
+ verticalHeight=580,
223
+ allowCross=False,
224
+ pushable=0.1,
225
+ tooltip={"placement": "bottom", "always_visible": False},
226
+ className="custom-slider",
227
+ ),
228
+ style={
229
+ "display": "flex",
230
+ "justifyContent": "center",
231
+ "paddingTop": "110px",
232
+ },
233
+ ),
234
+ html.Div(
235
+ [
236
+ html.Div(
237
+ [
238
+ dcc.Loading(
239
+ id="loading-2",
240
+ children=[html.Div(id="loading-output-2")],
241
+ type="circle",
242
+ )
243
+ ],
244
+ style={
245
+ "paddingBottom": "40px",
246
+ "paddingLeft": "40px",
247
+ "width": "100px",
248
+ "fontFamily": "Helvetica, Arial, sans-serif",
249
+ }, # Added padding to separate the content
250
+ ),
251
+ html.Div(
252
+ id="invisible-div",
253
+ children=0,
254
+ style={"display": "none"},
255
+ ),
256
+ html.Div(
257
+ [
258
+ html.Label(
259
+ "Currently shown plot:",
260
+ style={
261
+ "marginRight": "10px",
262
+ "padding-left": "20px",
263
+ },
264
+ ),
265
+ dcc.Dropdown(
266
+ id="matrix-dropdown",
267
+ options=[
268
+ {"label": f"{title}", "value": f"{title}"}
269
+ for title in titles # Iterate over each title
270
+ ],
271
+ value=(
272
+ titles[0] if titles else None
273
+ ), # Set default value based on the length of matrices
274
+ clearable=False, # Prevent dropdown from clearing values,
275
+ style={"width": "300px"},
276
+ ),
277
+ html.Div(id="output-container"),
278
+ dcc.Store(id="matrices-store", data=len(matrices)),
279
+ ],
280
+ style={
281
+ "display": "none" if len(titles) < 2 else "block",
282
+ "width": "fit-content"
283
+ * 2, # Set width to fit content
284
+ "fontFamily": "Helvetica, Arial, sans-serif",
285
+ "paddingTop": "10px",
286
+ "paddingLeft": "25px",
287
+ "paddingBottom": "30px",
288
+ "textAlign": "left",
289
+ },
290
+ ),
291
+ html.Div(
292
+ html.Button(
293
+ "Save Matrix to Bed File",
294
+ id="save-bed",
295
+ n_clicks=0,
296
+ disabled=False,
297
+ style={
298
+ "marginLeft": "45px",
299
+ },
300
+ ),
301
+ ),
302
+ html.Div(
303
+ children=[
304
+ f"Current Window Size: {current_metadata['max_window_size']}"
305
+ ],
306
+ id="window-div",
307
+ style={
308
+ "fontFamily": "Helvetica, Arial, sans-serif",
309
+ "paddingTop": "10px",
310
+ "paddingLeft": "45px",
311
+ "paddingBot": "30px",
312
+ "width": "220px",
313
+ "textAlign": "left",
314
+ },
315
+ ),
316
+ html.Div(
317
+ f"Minimum Window Size: {current_metadata['min_window_size']}",
318
+ style={
319
+ "fontFamily": "Helvetica, Arial, sans-serif",
320
+ "paddingTop": "10px",
321
+ "paddingLeft": "45px",
322
+ "paddingBot": "30px",
323
+ "width": "220px",
324
+ "textAlign": "left",
325
+ },
326
+ ),
327
+ dcc.Checklist(
328
+ id="gradient-toggle",
329
+ options=[
330
+ {
331
+ "label": " Readjust Gradient",
332
+ "value": "keep-original",
333
+ }
334
+ ],
335
+ value=[],
336
+ style={
337
+ "paddingTop": "10px",
338
+ "paddingLeft": "24px",
339
+ "paddingBottom": "30px",
340
+ "width": "260px",
341
+ "textAlign": "left",
342
+ "fontFamily": "Helvetica, Arial, sans-serif",
343
+ },
344
+ ),
345
+ html.Div(id="content"),
346
+ html.Div(
347
+ "Color Palette",
348
+ id="color-palette-text",
349
+ style={
350
+ "fontFamily": "Helvetica, Arial, sans-serif",
351
+ "paddingLeft": "45px", # Added padding to separate the content
352
+ },
353
+ ),
354
+ html.Div(
355
+ style={"display": "flex", "height": "10vh"},
356
+ children=[
357
+ html.Div(
358
+ [
359
+ dcc.Dropdown(
360
+ [
361
+ {
362
+ "label": html.Span(
363
+ [
364
+ html.Img(
365
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/Spectral_11_discrete.png",
366
+ height=10,
367
+ width=100,
368
+ ),
369
+ html.Span(
370
+ "Spectral",
371
+ style={
372
+ "fontSize": 15,
373
+ "paddingLeft": 10,
374
+ },
375
+ ),
376
+ ],
377
+ style={
378
+ "alignItems": "center",
379
+ "justifyContent": "center",
380
+ },
381
+ ),
382
+ "value": "Spectral",
383
+ },
384
+ {
385
+ "label": html.Span(
386
+ [
387
+ html.Span(
388
+ "Sequential Color Palettes",
389
+ style={
390
+ "fontSize": 15,
391
+ "paddingLeft": 10,
392
+ },
393
+ ),
394
+ ],
395
+ style={
396
+ "alignItems": "center",
397
+ "justifyContent": "center",
398
+ },
399
+ ),
400
+ "value": "Sequential",
401
+ "disabled": True,
402
+ },
403
+ {
404
+ "label": html.Span(
405
+ [
406
+ html.Img(
407
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/Blues_9_discrete.png",
408
+ height=10,
409
+ width=100,
410
+ ),
411
+ html.Span(
412
+ "Blues",
413
+ style={
414
+ "fontSize": 15,
415
+ "paddingLeft": 10,
416
+ },
417
+ ),
418
+ ],
419
+ style={
420
+ "alignItems": "center",
421
+ "justifyContent": "center",
422
+ },
423
+ ),
424
+ "value": "Blues",
425
+ },
426
+ {
427
+ "label": html.Span(
428
+ [
429
+ html.Img(
430
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/BuGn_9_discrete.png",
431
+ height=10,
432
+ width=100,
433
+ ),
434
+ html.Span(
435
+ "BuGn",
436
+ style={
437
+ "fontSize": 15,
438
+ "paddingLeft": 10,
439
+ },
440
+ ),
441
+ ],
442
+ style={
443
+ "alignItems": "center",
444
+ "justifyContent": "center",
445
+ },
446
+ ),
447
+ "value": "BuGn",
448
+ },
449
+ {
450
+ "label": html.Span(
451
+ [
452
+ html.Img(
453
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/BuPu_9_discrete.png",
454
+ height=10,
455
+ width=100,
456
+ ),
457
+ html.Span(
458
+ "BuPu",
459
+ style={
460
+ "fontSize": 15,
461
+ "paddingLeft": 10,
462
+ },
463
+ ),
464
+ ],
465
+ style={
466
+ "alignItems": "center",
467
+ "justifyContent": "center",
468
+ },
469
+ ),
470
+ "value": "BuPu",
471
+ },
472
+ {
473
+ "label": html.Span(
474
+ [
475
+ html.Img(
476
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/GnBu_9_discrete.png",
477
+ height=10,
478
+ width=100,
479
+ ),
480
+ html.Span(
481
+ "GnBu",
482
+ style={
483
+ "fontSize": 15,
484
+ "paddingLeft": 10,
485
+ },
486
+ ),
487
+ ],
488
+ style={
489
+ "alignItems": "center",
490
+ "justifyContent": "center",
491
+ },
492
+ ),
493
+ "value": "GnBu",
494
+ },
495
+ {
496
+ "label": html.Span(
497
+ [
498
+ html.Img(
499
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/Greens_9_discrete.png",
500
+ height=10,
501
+ width=100,
502
+ ),
503
+ html.Span(
504
+ "Greens",
505
+ style={
506
+ "fontSize": 15,
507
+ "paddingLeft": 10,
508
+ },
509
+ ),
510
+ ],
511
+ style={
512
+ "alignItems": "center",
513
+ "justifyContent": "center",
514
+ },
515
+ ),
516
+ "value": "Green",
517
+ },
518
+ {
519
+ "label": html.Span(
520
+ [
521
+ html.Img(
522
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/Greys_9_discrete.png",
523
+ height=10,
524
+ width=100,
525
+ ),
526
+ html.Span(
527
+ "Greys",
528
+ style={
529
+ "fontSize": 15,
530
+ "paddingLeft": 10,
531
+ },
532
+ ),
533
+ ],
534
+ style={
535
+ "alignItems": "center",
536
+ "justifyContent": "center",
537
+ },
538
+ ),
539
+ "value": "Greys",
540
+ },
541
+ {
542
+ "label": html.Span(
543
+ [
544
+ html.Img(
545
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/OrRd_9_discrete.png",
546
+ height=10,
547
+ width=100,
548
+ ),
549
+ html.Span(
550
+ "OrRd",
551
+ style={
552
+ "fontSize": 15,
553
+ "paddingLeft": 10,
554
+ },
555
+ ),
556
+ ],
557
+ style={
558
+ "alignItems": "center",
559
+ "justifyContent": "center",
560
+ },
561
+ ),
562
+ "value": "OrRd",
563
+ },
564
+ {
565
+ "label": html.Span(
566
+ [
567
+ html.Img(
568
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/Oranges_9_discrete.png",
569
+ height=10,
570
+ width=100,
571
+ ),
572
+ html.Span(
573
+ "Oranges",
574
+ style={
575
+ "fontSize": 15,
576
+ "paddingLeft": 10,
577
+ },
578
+ ),
579
+ ],
580
+ style={
581
+ "alignItems": "center",
582
+ "justifyContent": "center",
583
+ },
584
+ ),
585
+ "value": "Oranges",
586
+ },
587
+ {
588
+ "label": html.Span(
589
+ [
590
+ html.Img(
591
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/PuBu_9_discrete.png",
592
+ height=10,
593
+ width=100,
594
+ ),
595
+ html.Span(
596
+ "PuBu",
597
+ style={
598
+ "fontSize": 15,
599
+ "paddingLeft": 10,
600
+ },
601
+ ),
602
+ ],
603
+ style={
604
+ "alignItems": "center",
605
+ "justifyContent": "center",
606
+ },
607
+ ),
608
+ "value": "PuBu",
609
+ },
610
+ {
611
+ "label": html.Span(
612
+ [
613
+ html.Img(
614
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/PuBuGn_9_discrete.png",
615
+ height=10,
616
+ width=100,
617
+ ),
618
+ html.Span(
619
+ "PuBuGn",
620
+ style={
621
+ "fontSize": 15,
622
+ "paddingLeft": 10,
623
+ },
624
+ ),
625
+ ],
626
+ style={
627
+ "alignItems": "center",
628
+ "justifyContent": "center",
629
+ },
630
+ ),
631
+ "value": "PuBuGn",
632
+ },
633
+ {
634
+ "label": html.Span(
635
+ [
636
+ html.Img(
637
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/PuRd_9_discrete.png",
638
+ height=10,
639
+ width=100,
640
+ ),
641
+ html.Span(
642
+ "PuRd",
643
+ style={
644
+ "fontSize": 15,
645
+ "paddingLeft": 10,
646
+ },
647
+ ),
648
+ ],
649
+ style={
650
+ "alignItems": "center",
651
+ "justifyContent": "center",
652
+ },
653
+ ),
654
+ "value": "PuRd",
655
+ },
656
+ {
657
+ "label": html.Span(
658
+ [
659
+ html.Img(
660
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/Purples_9_discrete.png",
661
+ height=10,
662
+ width=100,
663
+ ),
664
+ html.Span(
665
+ "Purples",
666
+ style={
667
+ "fontSize": 15,
668
+ "paddingLeft": 10,
669
+ },
670
+ ),
671
+ ],
672
+ style={
673
+ "alignItems": "center",
674
+ "justifyContent": "center",
675
+ },
676
+ ),
677
+ "value": "Purples",
678
+ },
679
+ {
680
+ "label": html.Span(
681
+ [
682
+ html.Img(
683
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/RdPu_9_discrete.png",
684
+ height=10,
685
+ width=100,
686
+ ),
687
+ html.Span(
688
+ "RdPu",
689
+ style={
690
+ "fontSize": 15,
691
+ "paddingLeft": 10,
692
+ },
693
+ ),
694
+ ],
695
+ style={
696
+ "alignItems": "center",
697
+ "justifyContent": "center",
698
+ },
699
+ ),
700
+ "value": "RdPu",
701
+ },
702
+ {
703
+ "label": html.Span(
704
+ [
705
+ html.Img(
706
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/Reds_9_discrete.png",
707
+ height=10,
708
+ width=100,
709
+ ),
710
+ html.Span(
711
+ "Reds",
712
+ style={
713
+ "fontSize": 15,
714
+ "paddingLeft": 10,
715
+ },
716
+ ),
717
+ ],
718
+ style={
719
+ "alignItems": "center",
720
+ "justifyContent": "center",
721
+ },
722
+ ),
723
+ "value": "Reds",
724
+ },
725
+ {
726
+ "label": html.Span(
727
+ [
728
+ html.Img(
729
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/YlGn_9_discrete.png",
730
+ height=10,
731
+ width=100,
732
+ ),
733
+ html.Span(
734
+ "YlGn",
735
+ style={
736
+ "fontSize": 15,
737
+ "paddingLeft": 10,
738
+ },
739
+ ),
740
+ ],
741
+ style={
742
+ "alignItems": "center",
743
+ "justifyContent": "center",
744
+ },
745
+ ),
746
+ "value": "YlGn",
747
+ },
748
+ {
749
+ "label": html.Span(
750
+ [
751
+ html.Img(
752
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/YlGnBu_9_discrete.png",
753
+ height=10,
754
+ width=100,
755
+ ),
756
+ html.Span(
757
+ "YlGnBu",
758
+ style={
759
+ "fontSize": 15,
760
+ "paddingLeft": 10,
761
+ },
762
+ ),
763
+ ],
764
+ style={
765
+ "alignItems": "center",
766
+ "justifyContent": "center",
767
+ },
768
+ ),
769
+ "value": "YlGnBu",
770
+ },
771
+ {
772
+ "label": html.Span(
773
+ [
774
+ html.Img(
775
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/YlOrBr_9_discrete.png",
776
+ height=10,
777
+ width=100,
778
+ ),
779
+ html.Span(
780
+ "YlOrBr",
781
+ style={
782
+ "fontSize": 15,
783
+ "paddingLeft": 10,
784
+ },
785
+ ),
786
+ ],
787
+ style={
788
+ "alignItems": "center",
789
+ "justifyContent": "center",
790
+ },
791
+ ),
792
+ "value": "YlOrBr",
793
+ },
794
+ {
795
+ "label": html.Span(
796
+ [
797
+ html.Img(
798
+ src="https://jiffyclub.github.io/palettable/colorbrewer/sequential/img/YlOrRd_9_discrete.png",
799
+ height=10,
800
+ width=100,
801
+ ),
802
+ html.Span(
803
+ "YlOrRd",
804
+ style={
805
+ "fontSize": 15,
806
+ "paddingLeft": 10,
807
+ },
808
+ ),
809
+ ],
810
+ style={
811
+ "alignItems": "center",
812
+ "justifyContent": "center",
813
+ },
814
+ ),
815
+ "value": "YlOrRd",
816
+ },
817
+ {
818
+ "label": html.Span(
819
+ [
820
+ html.Span(
821
+ "Divergent Color Palettes",
822
+ style={
823
+ "fontSize": 15,
824
+ "paddingLeft": 10,
825
+ },
826
+ ),
827
+ ],
828
+ style={
829
+ "alignItems": "center",
830
+ "justifyContent": "center",
831
+ },
832
+ ),
833
+ "value": "Divergent",
834
+ "disabled": True,
835
+ },
836
+ {
837
+ "label": html.Span(
838
+ [
839
+ html.Img(
840
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/BrBG_11_discrete.png",
841
+ height=10,
842
+ width=100,
843
+ ),
844
+ html.Span(
845
+ "BrBG",
846
+ style={
847
+ "fontSize": 15,
848
+ "paddingLeft": 10,
849
+ },
850
+ ),
851
+ ],
852
+ style={
853
+ "alignItems": "center",
854
+ "justifyContent": "center",
855
+ },
856
+ ),
857
+ "value": "BrBG",
858
+ },
859
+ {
860
+ "label": html.Span(
861
+ [
862
+ html.Img(
863
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/PRGn_11_discrete.png",
864
+ height=10,
865
+ width=100,
866
+ ),
867
+ html.Span(
868
+ "PRGn",
869
+ style={
870
+ "fontSize": 15,
871
+ "paddingLeft": 10,
872
+ },
873
+ ),
874
+ ],
875
+ style={
876
+ "alignItems": "center",
877
+ "justifyContent": "center",
878
+ },
879
+ ),
880
+ "value": "PRGn",
881
+ },
882
+ {
883
+ "label": html.Span(
884
+ [
885
+ html.Img(
886
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/PiYG_11_discrete.png",
887
+ height=10,
888
+ width=100,
889
+ ),
890
+ html.Span(
891
+ "PiYG",
892
+ style={
893
+ "fontSize": 15,
894
+ "paddingLeft": 10,
895
+ },
896
+ ),
897
+ ],
898
+ style={
899
+ "alignItems": "center",
900
+ "justifyContent": "center",
901
+ },
902
+ ),
903
+ "value": "PiYG",
904
+ },
905
+ {
906
+ "label": html.Span(
907
+ [
908
+ html.Img(
909
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/PuOr_11_discrete.png",
910
+ height=10,
911
+ width=100,
912
+ ),
913
+ html.Span(
914
+ "PuOr",
915
+ style={
916
+ "fontSize": 15,
917
+ "paddingLeft": 10,
918
+ },
919
+ ),
920
+ ],
921
+ style={
922
+ "alignItems": "center",
923
+ "justifyContent": "center",
924
+ },
925
+ ),
926
+ "value": "PuOr",
927
+ },
928
+ {
929
+ "label": html.Span(
930
+ [
931
+ html.Img(
932
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/RdBu_11_discrete.png",
933
+ height=10,
934
+ width=100,
935
+ ),
936
+ html.Span(
937
+ "RdBu",
938
+ style={
939
+ "fontSize": 15,
940
+ "paddingLeft": 10,
941
+ },
942
+ ),
943
+ ],
944
+ style={
945
+ "alignItems": "center",
946
+ "justifyContent": "center",
947
+ },
948
+ ),
949
+ "value": "RdBu",
950
+ },
951
+ {
952
+ "label": html.Span(
953
+ [
954
+ html.Img(
955
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/RdGy_11_discrete.png",
956
+ height=10,
957
+ width=100,
958
+ ),
959
+ html.Span(
960
+ "RdGy",
961
+ style={
962
+ "fontSize": 15,
963
+ "paddingLeft": 10,
964
+ },
965
+ ),
966
+ ],
967
+ style={
968
+ "alignItems": "center",
969
+ "justifyContent": "center",
970
+ },
971
+ ),
972
+ "value": "RdGy",
973
+ },
974
+ {
975
+ "label": html.Span(
976
+ [
977
+ html.Img(
978
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/RdYlBu_11_discrete.png",
979
+ height=10,
980
+ width=100,
981
+ ),
982
+ html.Span(
983
+ "RdYlBu",
984
+ style={
985
+ "fontSize": 15,
986
+ "paddingLeft": 10,
987
+ },
988
+ ),
989
+ ],
990
+ style={
991
+ "alignItems": "center",
992
+ "justifyContent": "center",
993
+ },
994
+ ),
995
+ "value": "RdYlBu",
996
+ },
997
+ {
998
+ "label": html.Span(
999
+ [
1000
+ html.Img(
1001
+ src="https://jiffyclub.github.io/palettable/colorbrewer/diverging/img/RdYlGn_11_discrete.png",
1002
+ height=10,
1003
+ width=100,
1004
+ ),
1005
+ html.Span(
1006
+ "RdYlGn",
1007
+ style={
1008
+ "fontSize": 15,
1009
+ "paddingLeft": 10,
1010
+ },
1011
+ ),
1012
+ ],
1013
+ style={
1014
+ "alignItems": "center",
1015
+ "justifyContent": "center",
1016
+ },
1017
+ ),
1018
+ "value": "RdYlGn",
1019
+ },
1020
+ {
1021
+ "label": html.Span(
1022
+ [
1023
+ html.Span(
1024
+ "Qualitative Color Palettes",
1025
+ style={
1026
+ "fontSize": 15,
1027
+ "paddingLeft": 10,
1028
+ },
1029
+ ),
1030
+ ],
1031
+ style={
1032
+ "alignItems": "center",
1033
+ "justifyContent": "center",
1034
+ },
1035
+ ),
1036
+ "value": "Qualitative",
1037
+ "disabled": True,
1038
+ },
1039
+ {
1040
+ "label": html.Span(
1041
+ [
1042
+ html.Img(
1043
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Accent_8_discrete.png",
1044
+ height=10,
1045
+ width=100,
1046
+ ),
1047
+ html.Span(
1048
+ "Accent",
1049
+ style={
1050
+ "fontSize": 15,
1051
+ "paddingLeft": 10,
1052
+ },
1053
+ ),
1054
+ ],
1055
+ style={
1056
+ "alignItems": "center",
1057
+ "justifyContent": "center",
1058
+ },
1059
+ ),
1060
+ "value": "Accent",
1061
+ },
1062
+ {
1063
+ "label": html.Span(
1064
+ [
1065
+ html.Img(
1066
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Dark2_8_discrete.png",
1067
+ height=10,
1068
+ width=100,
1069
+ ),
1070
+ html.Span(
1071
+ "Dark2",
1072
+ style={
1073
+ "fontSize": 15,
1074
+ "paddingLeft": 10,
1075
+ },
1076
+ ),
1077
+ ],
1078
+ style={
1079
+ "alignItems": "center",
1080
+ "justifyContent": "center",
1081
+ },
1082
+ ),
1083
+ "value": "Dark2",
1084
+ },
1085
+ {
1086
+ "label": html.Span(
1087
+ [
1088
+ html.Img(
1089
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Paired_12_discrete.png",
1090
+ height=10,
1091
+ width=100,
1092
+ ),
1093
+ html.Span(
1094
+ "Paired",
1095
+ style={
1096
+ "fontSize": 15,
1097
+ "paddingLeft": 10,
1098
+ },
1099
+ ),
1100
+ ],
1101
+ style={
1102
+ "alignItems": "center",
1103
+ "justifyContent": "center",
1104
+ },
1105
+ ),
1106
+ "value": "Paired",
1107
+ },
1108
+ {
1109
+ "label": html.Span(
1110
+ [
1111
+ html.Img(
1112
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Pastel1_8_discrete.png",
1113
+ height=10,
1114
+ width=100,
1115
+ ),
1116
+ html.Span(
1117
+ "Pastel1",
1118
+ style={
1119
+ "fontSize": 15,
1120
+ "paddingLeft": 10,
1121
+ },
1122
+ ),
1123
+ ],
1124
+ style={
1125
+ "alignItems": "center",
1126
+ "justifyContent": "center",
1127
+ },
1128
+ ),
1129
+ "value": "Pastel1",
1130
+ },
1131
+ {
1132
+ "label": html.Span(
1133
+ [
1134
+ html.Img(
1135
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Pastel2_8_discrete.png",
1136
+ height=10,
1137
+ width=100,
1138
+ ),
1139
+ html.Span(
1140
+ "Pastel2",
1141
+ style={
1142
+ "fontSize": 15,
1143
+ "paddingLeft": 10,
1144
+ },
1145
+ ),
1146
+ ],
1147
+ style={
1148
+ "alignItems": "center",
1149
+ "justifyContent": "center",
1150
+ },
1151
+ ),
1152
+ "value": "Pastel2",
1153
+ },
1154
+ {
1155
+ "label": html.Span(
1156
+ [
1157
+ html.Img(
1158
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Set1_8_discrete.png",
1159
+ height=10,
1160
+ width=100,
1161
+ ),
1162
+ html.Span(
1163
+ "Set1",
1164
+ style={
1165
+ "fontSize": 15,
1166
+ "paddingLeft": 10,
1167
+ },
1168
+ ),
1169
+ ],
1170
+ style={
1171
+ "alignItems": "center",
1172
+ "justifyContent": "center",
1173
+ },
1174
+ ),
1175
+ "value": "Set1",
1176
+ },
1177
+ {
1178
+ "label": html.Span(
1179
+ [
1180
+ html.Img(
1181
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Set2_8_discrete.png",
1182
+ height=10,
1183
+ width=100,
1184
+ ),
1185
+ html.Span(
1186
+ "Set2",
1187
+ style={
1188
+ "fontSize": 15,
1189
+ "paddingLeft": 10,
1190
+ },
1191
+ ),
1192
+ ],
1193
+ style={
1194
+ "alignItems": "center",
1195
+ "justifyContent": "center",
1196
+ },
1197
+ ),
1198
+ "value": "Set2",
1199
+ },
1200
+ {
1201
+ "label": html.Span(
1202
+ [
1203
+ html.Img(
1204
+ src="https://jiffyclub.github.io/palettable/colorbrewer/qualitative/img/Set3_8_discrete.png",
1205
+ height=10,
1206
+ width=100,
1207
+ ),
1208
+ html.Span(
1209
+ "Set3",
1210
+ style={
1211
+ "fontSize": 15,
1212
+ "paddingLeft": 10,
1213
+ },
1214
+ ),
1215
+ ],
1216
+ style={
1217
+ "alignItems": "center",
1218
+ "justifyContent": "center",
1219
+ },
1220
+ ),
1221
+ "value": "Set3",
1222
+ },
1223
+ ],
1224
+ value=palette.split("_")[0],
1225
+ id="color-options",
1226
+ ),
1227
+ html.Div(id="output-div"),
1228
+ ],
1229
+ style={
1230
+ "paddingTop": "10px",
1231
+ "paddingLeft": "20px",
1232
+ "width": "250px",
1233
+ "fontFamily": "Helvetica, Arial, sans-serif",
1234
+ }, # Added padding to separate the content
1235
+ )
1236
+ ],
1237
+ ),
1238
+ html.Div(
1239
+ [
1240
+ html.Div(
1241
+ "Coordinate Log:",
1242
+ id="coordinate-text",
1243
+ style={
1244
+ "fontFamily": "Helvetica, Arial, sans-serif",
1245
+ "paddingLeft": "45px", # Added padding to separate the content
1246
+ "paddingBottom": "10px",
1247
+ },
1248
+ ),
1249
+ dcc.Loading(
1250
+ id="loading",
1251
+ type="default",
1252
+ children=[
1253
+ dcc.Textarea(
1254
+ id="text-input",
1255
+ value="",
1256
+ style={
1257
+ "padding": "10px",
1258
+ "height": "40px",
1259
+ "width": "62%",
1260
+ "margin-left": "22px",
1261
+ "fontFamily": "Helvetica, Arial, sans-serif",
1262
+ },
1263
+ ),
1264
+ html.Button(
1265
+ "Save Coordinates to Bed File",
1266
+ id="save-button",
1267
+ n_clicks=0,
1268
+ disabled=True,
1269
+ style={"margin": "20px"},
1270
+ ),
1271
+ dcc.Download(id="download"),
1272
+ ],
1273
+ ),
1274
+ ],
1275
+ style={
1276
+ "fontFamily": "Helvetica, Arial, sans-serif",
1277
+ },
1278
+ ),
1279
+ ],
1280
+ style={
1281
+ "paddingTop": "110px",
1282
+ "paddingLeft": "20px",
1283
+ "textAlign": "left",
1284
+ }, # Added paddingTop for the text and centered the text
1285
+ ),
1286
+ ],
1287
+ style={
1288
+ "display": "flex",
1289
+ "justifyContent": "center",
1290
+ "flex-wrap": "wrap",
1291
+ },
1292
+ ),
1293
+ ],
1294
+ )
1295
+
1296
+ @app.callback(
1297
+ Output("text-input", "value"),
1298
+ Input("dotplot", "clickData"),
1299
+ Input("invisible-div", "children"),
1300
+ Input("matrix-dropdown", "value"),
1301
+ )
1302
+ def update_text(click_data, zoom_level, timothy):
1303
+ updated_info = ""
1304
+ for i in range(len(titles)):
1305
+ if timothy == titles[i]:
1306
+ updated_info = metadata[i]
1307
+ global clicked_values
1308
+ tmp = updated_info["max_window_size"] // (2**zoom_level)
1309
+ if click_data:
1310
+ x_val = (
1311
+ click_data["points"][0]["x"] if "x" in click_data["points"][0] else ""
1312
+ )
1313
+ y_val = (
1314
+ click_data["points"][0]["y"] if "y" in click_data["points"][0] else ""
1315
+ )
1316
+ z_val = (
1317
+ click_data["points"][0]["z"] if "z" in click_data["points"][0] else ""
1318
+ )
1319
+
1320
+ # Create clicked_values list if it doesn't exist in the global scope
1321
+ if "clicked_values" not in globals():
1322
+ globals()["clicked_values"] = []
1323
+
1324
+ # Append x and y values to the list and keep only the last 5 values
1325
+ # TODO: I shouldn't have to round, but I do :/
1326
+ clicked_values.insert(
1327
+ 0,
1328
+ f'{updated_info["x_name"]}: {round(x_val)}-{round(x_val + tmp)}\n{updated_info["y_name"]}: {round(y_val)}-{round(y_val + tmp)}\nIdentity: {round(z_val,2)}\n~',
1329
+ )
1330
+ clicked_values = clicked_values[-100:]
1331
+ return "\n".join((clicked_values))
1332
+ else:
1333
+ return ""
1334
+
1335
+ @app.callback(Output("save-button", "disabled"), Input("text-input", "value"))
1336
+ def update_button_state(text_value):
1337
+ return text_value == ""
1338
+
1339
+ @app.callback(
1340
+ Output("text-input", "value", allow_duplicate=True),
1341
+ Output("save-bed", "n_clicks"),
1342
+ Input("save-bed", "n_clicks"),
1343
+ Input("dotplot", "figure"),
1344
+ )
1345
+ def save_bed(n_clicks, figure):
1346
+ global clicked_values
1347
+ if n_clicks > 0:
1348
+ window_size = figure["data"][0]["x"][1] - figure["data"][0]["x"][0]
1349
+ try:
1350
+ identity = round(
1351
+ min([val for val in figure["data"][0]["z"][0] if val > 0])
1352
+ )
1353
+ x_axis_name = figure["layout"]["xaxis"]["title"]["text"]
1354
+ y_axis_name = figure["layout"]["yaxis"]["title"]["text"]
1355
+ if x_axis_name == y_axis_name:
1356
+ selfy = True
1357
+ title_hi = x_axis_name + ".bed"
1358
+ else:
1359
+ selfy = False
1360
+ title_hi = x_axis_name + "-" + y_axis_name + ".bed"
1361
+ except:
1362
+ identity = 86
1363
+ x_axis_name = "x"
1364
+ y_axis_name = "y"
1365
+ selfy = False
1366
+ title_hi = "x-y.bed"
1367
+ pls = np.array(figure["data"][0]["z"])
1368
+ tr = convertMatrixToBed(
1369
+ pls, window_size, identity, x_axis_name, y_axis_name, selfy
1370
+ )
1371
+ if not output_dir:
1372
+ bedfile_output = os.path.join("./", title_hi)
1373
+ else:
1374
+ bedfile_output = os.path.join(output_dir, title_hi)
1375
+ if (output_dir) and not os.path.exists(output_dir):
1376
+ os.makedirs(output_dir)
1377
+ with open(bedfile_output, "w") as bedfile:
1378
+ for row in tr:
1379
+ bedfile.write("\t".join(map(str, row)) + "\n")
1380
+ msg = f"Saved bed file to {bedfile_output}\n"
1381
+ return msg, 0 # Make sure to return a tuple of values for the outputs
1382
+ else:
1383
+ return (
1384
+ dash.no_update,
1385
+ dash.no_update,
1386
+ ) # Return dash.no_update for outputs not being updated
1387
+
1388
+ @app.callback(
1389
+ Output("text-input", "value", allow_duplicate=True),
1390
+ Input("save-button", "n_clicks"),
1391
+ State("text-input", "value"),
1392
+ )
1393
+ def save_to_file(n_clicks, content):
1394
+ global clicked_values
1395
+ if n_clicks > 0 and content:
1396
+ content = content.replace("\n", ",")
1397
+ content = content.replace(": ", ", ")
1398
+ content = content.replace("~,", "\n")
1399
+ content = content.replace(" Identity,", "")
1400
+ content = content.replace("~", "")
1401
+ content = content.replace(",\n", "\n")
1402
+ msg = f"Saved coordinate history to coordinate_log.txt!"
1403
+ with open("coordinate_log.txt", "w") as file:
1404
+ file.write(str(content))
1405
+ return msg
1406
+
1407
+ # Callback activates when panning, zooming, or changing color
1408
+ @app.callback(
1409
+ Output("dotplot", "figure", allow_duplicate=True),
1410
+ Output("loading-output-2", "children"),
1411
+ Output("window-div", "children"),
1412
+ Output("invisible-div", "children"),
1413
+ Input("dotplot", "relayoutData"),
1414
+ Input("dotplot", "figure"),
1415
+ Input("color-options", "value"),
1416
+ Input("threshold-slider", "value"),
1417
+ Input("gradient-toggle", "value"),
1418
+ Input("matrix-dropdown", "value"),
1419
+ Input("invisible-div", "children"),
1420
+ prevent_initial_call=True,
1421
+ )
1422
+ def update_dotplot(
1423
+ relayoutData,
1424
+ figure,
1425
+ color,
1426
+ threshold_range,
1427
+ button_states,
1428
+ timothy,
1429
+ current_zoom,
1430
+ ):
1431
+ updated_info = ""
1432
+ updated_title = ""
1433
+ title_size = 28
1434
+ image_axes = []
1435
+ # Matrix dropdown value = Timothy
1436
+ for i in range(len(titles)):
1437
+ if timothy == titles[i]:
1438
+ image_pyramid = matrices[i]
1439
+ image_axes = axes[i]
1440
+ updated_info = metadata[i]
1441
+ if updated_info["self"]:
1442
+ updated_title = f"Self-Identity Plot: {updated_info['x_name']}"
1443
+ if len(updated_info["x_name"]) > 22:
1444
+ title_size = 20
1445
+ else:
1446
+ updated_title = f"Comparative Plot: {updated_info['x_name']} vs. {updated_info['y_name']}"
1447
+ if len(updated_info["x_name"]) + len(updated_info["y_name"]) > 22:
1448
+ title_size = 16
1449
+ new_main_level = image_pyramid[0]
1450
+ fig = go.Figure(data=[heatmap])
1451
+ current_color = getInteractiveColor(getMatchingColors(color), "+")
1452
+ new_heatmap = heatmap
1453
+ new_heatmap.update(dict(colorscale=current_color, z=new_main_level))
1454
+
1455
+ masked_data = np.where(
1456
+ (new_heatmap["z"] < threshold_range[0])
1457
+ | (new_heatmap["z"] > threshold_range[1]),
1458
+ 0,
1459
+ new_heatmap["z"],
1460
+ )
1461
+ fig = go.Figure(data=[new_heatmap])
1462
+ fig.update_traces(z=masked_data)
1463
+
1464
+ if "keep-original" in button_states:
1465
+ fig.update_traces(zmin=threshold_range[0])
1466
+ fig.update_traces(zmax=threshold_range[1])
1467
+ else:
1468
+ fig.update_traces(zmin=identity)
1469
+ fig.update_traces(zmax=100)
1470
+
1471
+ fig.update_xaxes(
1472
+ showspikes=True,
1473
+ spikemode="across",
1474
+ ticks="outside",
1475
+ showline=True,
1476
+ linewidth=2,
1477
+ linecolor="black",
1478
+ mirror=True,
1479
+ )
1480
+ # TODO: nticks not working
1481
+ fig.update_xaxes(
1482
+ nticks=10, title_text=updated_info["x_name"], title_font=dict(size=18)
1483
+ )
1484
+
1485
+ # Update y-axis properties directly within the heatmap trace
1486
+ fig.update_yaxes(
1487
+ showspikes=True,
1488
+ spikemode="across",
1489
+ ticks="outside",
1490
+ showline=True,
1491
+ linewidth=2,
1492
+ linecolor="black",
1493
+ mirror=True,
1494
+ )
1495
+ fig.update_yaxes(title_text=updated_info["y_name"], title_font=dict(size=18))
1496
+
1497
+ fig.update_layout(
1498
+ hoverlabel=dict(bgcolor="white", font_size=16, font_family="Helvetica"),
1499
+ title=updated_title, # Add your title here
1500
+ title_font=dict(
1501
+ size=title_size, family="Helvetica"
1502
+ ), # Adjust the title font size if needed
1503
+ title_x=0.5,
1504
+ title_y=0.95,
1505
+ )
1506
+
1507
+ fig.update_layout(yaxis_scaleanchor="x")
1508
+ if relayoutData is not None:
1509
+ # TODO: Pan mode should stay in pan mode
1510
+
1511
+ if "xaxis.range[0]" in relayoutData:
1512
+ x_start_range = relayoutData.get("xaxis.range[0]")
1513
+ y_start_range = relayoutData.get("yaxis.range[0]")
1514
+ x_end_range = relayoutData.get("xaxis.range[1]")
1515
+ y_end_range = relayoutData.get("yaxis.range[1]")
1516
+
1517
+ # Check that selected range is in bounds, snap to boundary otherwise
1518
+ # TODO: still broken here
1519
+ if x_start_range is not None:
1520
+ if x_start_range < 0:
1521
+ print("x axis out of bounds! Shifting to x=0")
1522
+ relayoutData["xaxis.range[0]"] = 0
1523
+ x_start_range = 0
1524
+ if y_start_range < 0:
1525
+ print("y axis out of bounds! Shifting to y=0")
1526
+ relayoutData["yaxis.range[0]"] = 0
1527
+ y_start_range = 0
1528
+ if x_end_range > updated_info["x_size"]:
1529
+ print(
1530
+ f"x axis out of bounds! Shifting to x={updated_info['x_size']}"
1531
+ )
1532
+ relayoutData["xaxis.range[1]"] = updated_info["x_size"]
1533
+ x_end_range = updated_info["x_size"]
1534
+ if y_end_range > updated_info["y_size"]:
1535
+ print(
1536
+ f"y axis out of bounds! Shifting to y={updated_info['y_size']}"
1537
+ )
1538
+ relayoutData["yaxis.range[1]"] = updated_info["y_size"]
1539
+ y_end_range = updated_info["y_size"]
1540
+
1541
+ if x_start_range >= 0 and y_start_range >= 0:
1542
+ x_begin = round(relayoutData["xaxis.range[0]"])
1543
+ x_end = round(relayoutData["xaxis.range[1]"])
1544
+
1545
+ y_begin = round(relayoutData["yaxis.range[0]"])
1546
+ y_end = round(relayoutData["yaxis.range[1]"])
1547
+
1548
+ amount = x_end - x_begin
1549
+
1550
+ # This function finds the correct level in the image pyramid
1551
+ try:
1552
+ zoom_factor = findValueInRange(amount, important)
1553
+ except ValueError as err:
1554
+ zoom_factor = 0
1555
+ # If zoom_factor is less than current_zoom, base tmp factors on the older amount
1556
+
1557
+ if zoom_factor > len(image_pyramid) - 1:
1558
+ zoom_factor = len(image_pyramid) - 1
1559
+ if (zoom_factor != 0) or (zoom_factor != current_zoom):
1560
+ using_matrix = image_pyramid[zoom_factor]
1561
+ # Get the coordinates for the better matrix
1562
+ tmp1 = find_closest_elements(
1563
+ x_start_range, image_axes[2 * zoom_factor]
1564
+ )[0]
1565
+ tmp2 = find_closest_elements(
1566
+ x_end_range, image_axes[2 * zoom_factor]
1567
+ )[1]
1568
+ tmp3 = find_closest_elements(
1569
+ y_start_range, image_axes[(2 * zoom_factor) + 1]
1570
+ )[0]
1571
+ tmp4 = find_closest_elements(
1572
+ y_end_range, image_axes[(2 * zoom_factor) + 1]
1573
+ )[1]
1574
+ x_start_updated = math.floor(tmp1)
1575
+ x_end_updated = math.ceil(tmp2)
1576
+ y_start_updated = math.floor(tmp3)
1577
+ y_end_updated = math.ceil(tmp4)
1578
+
1579
+ x_equi, y_equi = makeDifferencesEqual(
1580
+ x_start_updated,
1581
+ x_end_updated,
1582
+ y_start_updated,
1583
+ y_end_updated,
1584
+ )
1585
+ # TODO: flip axes here
1586
+ using_matrix = np.copy(
1587
+ using_matrix[y_start_updated:y_equi, x_start_updated:x_equi]
1588
+ )
1589
+ masked_matrix = np.where(
1590
+ (using_matrix < threshold_range[0])
1591
+ | (using_matrix > threshold_range[1]),
1592
+ 0,
1593
+ using_matrix,
1594
+ )
1595
+
1596
+ x_ax = image_axes[2 * zoom_factor][x_start_updated:x_equi]
1597
+ y_ax = image_axes[(2 * zoom_factor) + 1][y_start_updated:y_equi]
1598
+
1599
+ new_hover_template_text = "Identity: %{z:.1f}"
1600
+
1601
+ # Create figure
1602
+ new_heatmap = go.Heatmap(
1603
+ z=masked_matrix,
1604
+ zmin=identity,
1605
+ zmax=100,
1606
+ colorscale=current_color,
1607
+ x=x_ax,
1608
+ y=y_ax,
1609
+ showscale=True, # Shows color scale bar
1610
+ colorbar=dict(title="Identity"), # Color bar title
1611
+ hoverinfo="z", # Hover info to display
1612
+ hovertemplate=new_hover_template_text,
1613
+ name="",
1614
+ x0=x_ax[0],
1615
+ dx=updated_info["max_window_size"] // (2**zoom_factor),
1616
+ xtype="scaled",
1617
+ y0=y_ax[0],
1618
+ dy=updated_info["max_window_size"] // (2**zoom_factor),
1619
+ ytype="scaled",
1620
+ )
1621
+
1622
+ # Create the figure
1623
+ current_fig = go.Figure(data=[new_heatmap])
1624
+ current_fig.update_xaxes(
1625
+ showspikes=True,
1626
+ spikemode="across",
1627
+ ticks="outside",
1628
+ showline=True,
1629
+ linewidth=2,
1630
+ linecolor="black",
1631
+ mirror=True,
1632
+ )
1633
+ current_fig.update_xaxes(
1634
+ nticks=10,
1635
+ title_text=updated_info["x_name"],
1636
+ title_font=dict(size=18),
1637
+ )
1638
+
1639
+ # Update y-axis properties directly within the heatmap trace
1640
+ current_fig.update_yaxes(
1641
+ showspikes=True,
1642
+ spikemode="across",
1643
+ ticks="outside",
1644
+ showline=True,
1645
+ linewidth=2,
1646
+ linecolor="black",
1647
+ mirror=True,
1648
+ )
1649
+
1650
+ current_fig.update_yaxes(
1651
+ title_text=updated_info["y_name"], title_font=dict(size=18)
1652
+ )
1653
+ current_fig.update_layout(
1654
+ hoverlabel=dict(
1655
+ bgcolor="white", font_size=16, font_family="Helvetica"
1656
+ ),
1657
+ title=fig_title, # Add your title here
1658
+ title_font=dict(size=28, family="Helvetica"),
1659
+ title_x=0.5, # Center horizontally
1660
+ title_y=0.95, # Center vertically
1661
+ )
1662
+ current_fig.update_layout(yaxis_scaleanchor="x")
1663
+ return (
1664
+ current_fig,
1665
+ "",
1666
+ f"Current Window Size: {updated_info['max_window_size']//(2**zoom_factor)}",
1667
+ zoom_factor,
1668
+ )
1669
+ else:
1670
+ # Happens when panning or zooming in/out within threshold
1671
+ return (
1672
+ figure,
1673
+ "",
1674
+ f"Current Window Size: {updated_info['max_window_size']//(2**current_zoom)}",
1675
+ current_zoom,
1676
+ )
1677
+
1678
+ elif "xaxis.autorange" in relayoutData:
1679
+ # Triggers when double-clicked or reset axes
1680
+ return (
1681
+ fig,
1682
+ "",
1683
+ f"Current Window Size: {updated_info['max_window_size']}",
1684
+ 0,
1685
+ )
1686
+
1687
+ # TODO: disable Autosizing
1688
+ elif "autosize" in relayoutData:
1689
+ # Autosize should be disabled: if not it gets handled here
1690
+ return (
1691
+ figure,
1692
+ "",
1693
+ f"Current Window Size: {updated_info['max_window_size']}",
1694
+ zoom_factor,
1695
+ )
1696
+
1697
+ else:
1698
+ # Happens when user selects pan mode.
1699
+ # TODO: get correct window size
1700
+ return (
1701
+ figure,
1702
+ "",
1703
+ f"Current Window Size: {updated_info['max_window_size']//(2**current_zoom)}",
1704
+ current_zoom,
1705
+ )
1706
+ else:
1707
+ # This occurs at startup and when selecting color. Return the base figure with color
1708
+ # fig.update_layout(coloraxis=dict(colorscale=current_color))
1709
+ return (
1710
+ fig,
1711
+ "",
1712
+ f"Current Window Size: {updated_info['max_window_size']}",
1713
+ current_zoom,
1714
+ )
1715
+
1716
+ # -------DO NOT DELETE! CUSTOM CSS FOR IDENTITY THRESHOLD SLIDER--------
1717
+ app.index_string = """
1718
+ <!DOCTYPE html>
1719
+ <html>
1720
+ <head>
1721
+ {%metas%}
1722
+ <title>{%title%}</title>
1723
+ {%favicon%}
1724
+ {%css%}
1725
+ <style>
1726
+ /* Custom CSS for the RangeSlider */
1727
+ .custom-slider .rc-slider-track {
1728
+ background-color: gray; /* Change the highlight color to gray */
1729
+ }
1730
+
1731
+ .custom-slider .rc-slider-dot {
1732
+ display: none; /* Hide the dots */
1733
+ }
1734
+
1735
+ .custom-slider .rc-slider-handle {
1736
+ border: 2px solid black; /* Change the handle border color to gray */
1737
+ font-family: Helvetica, Arial, sans-serif;
1738
+ }
1739
+ #main_color {
1740
+ justify-content:center;
1741
+ }
1742
+ </style>
1743
+ </head>
1744
+ <body>
1745
+ {%app_entry%}
1746
+ <footer>
1747
+ {%config%}
1748
+ {%scripts%}
1749
+ {%renderer%}
1750
+ </footer>
1751
+ </body>
1752
+ </html>
1753
+ """
1754
+ # TODO: Change debug to False for production
1755
+ app.run(debug=False, use_reloader=False, port=port_number)