arg-dashboard 0.1.19__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.
- arg_dashboard/__init__.py +934 -0
- arg_dashboard/arg.py +1019 -0
- arg_dashboard/arg_layout_force.py +365 -0
- arg_dashboard/arg_layout_json.py +269 -0
- arg_dashboard/arg_layout_mindist.py +412 -0
- arg_dashboard/assets/dashboard.css +139 -0
- arg_dashboard/assets/images/arg.png +0 -0
- arg_dashboard/assets/images/placeholder286x180.png +0 -0
- arg_dashboard/assets/tabs.css +34 -0
- arg_dashboard/index.py +6 -0
- arg_dashboard-0.1.19.dist-info/METADATA +88 -0
- arg_dashboard-0.1.19.dist-info/RECORD +16 -0
- arg_dashboard-0.1.19.dist-info/WHEEL +5 -0
- arg_dashboard-0.1.19.dist-info/entry_points.txt +2 -0
- arg_dashboard-0.1.19.dist-info/licenses/LICENSE +674 -0
- arg_dashboard-0.1.19.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ARG Layout - Minimum Distance Constrained
|
|
3
|
+
==========================================
|
|
4
|
+
Parents stay within children's x-range UNLESS moving outside is needed
|
|
5
|
+
to maintain minimum Euclidean distance (default 0.5) between nodes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def compute_arg_xpos(arg_data, min_distance=None, n_iterations=300):
|
|
13
|
+
"""
|
|
14
|
+
Compute x-positions for an ARG with minimum distance constraint.
|
|
15
|
+
|
|
16
|
+
Parents stay within children's x-range unless they need to move
|
|
17
|
+
outside to maintain minimum Euclidean distance from other nodes.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
arg_data : dict
|
|
22
|
+
ARG data with 'Leaf', 'Coalescent', 'Recombination', and 'Lineage' keys
|
|
23
|
+
min_distance : float, optional
|
|
24
|
+
Minimum allowed Euclidean distance between any two nodes.
|
|
25
|
+
Default: 1/(2*n_samples) where n_samples is the number of leaves.
|
|
26
|
+
n_iterations : int
|
|
27
|
+
Number of iterations for constraint satisfaction
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
dict
|
|
32
|
+
The input arg_data with updated 'xpos' values for all nodes
|
|
33
|
+
"""
|
|
34
|
+
# Extract all nodes and heights
|
|
35
|
+
all_nodes = []
|
|
36
|
+
heights = {}
|
|
37
|
+
|
|
38
|
+
for leaf in arg_data['Leaf']:
|
|
39
|
+
nid = leaf['nodeid']
|
|
40
|
+
all_nodes.append(nid)
|
|
41
|
+
heights[nid] = leaf['height']
|
|
42
|
+
|
|
43
|
+
for coal in arg_data['Coalescent']:
|
|
44
|
+
nid = coal['nodeid']
|
|
45
|
+
all_nodes.append(nid)
|
|
46
|
+
heights[nid] = coal['height']
|
|
47
|
+
|
|
48
|
+
for recomb in arg_data['Recombination']:
|
|
49
|
+
nid = recomb['nodeid']
|
|
50
|
+
all_nodes.append(nid)
|
|
51
|
+
heights[nid] = recomb['height']
|
|
52
|
+
|
|
53
|
+
# Build edges from Lineage data
|
|
54
|
+
edges = []
|
|
55
|
+
for lineage in arg_data['Lineage']:
|
|
56
|
+
down = lineage['down']
|
|
57
|
+
up = lineage['up']
|
|
58
|
+
if up is not None:
|
|
59
|
+
edges.append((up, down))
|
|
60
|
+
|
|
61
|
+
# Build adjacency lists
|
|
62
|
+
children_map = defaultdict(list)
|
|
63
|
+
for parent, child in edges:
|
|
64
|
+
children_map[parent].append(child)
|
|
65
|
+
|
|
66
|
+
# Identify leaves
|
|
67
|
+
leaves = [n for n in all_nodes if heights[n] == 0]
|
|
68
|
+
n_leaves = len(leaves)
|
|
69
|
+
|
|
70
|
+
# x-range is [0, 1]
|
|
71
|
+
x_min, x_max = 0.0, 1.0
|
|
72
|
+
|
|
73
|
+
# Default min_distance = 1/(2*n_samples)
|
|
74
|
+
if min_distance is None:
|
|
75
|
+
min_distance = 1.0 / (2 * n_leaves)
|
|
76
|
+
|
|
77
|
+
# Group nodes by height
|
|
78
|
+
layers = defaultdict(list)
|
|
79
|
+
for node in all_nodes:
|
|
80
|
+
layers[heights[node]].append(node)
|
|
81
|
+
sorted_heights = sorted(layers.keys())
|
|
82
|
+
|
|
83
|
+
x_pos = {}
|
|
84
|
+
|
|
85
|
+
# Use original leaf positions if available (assuming they're in [0,1])
|
|
86
|
+
original_leaf_xpos = {}
|
|
87
|
+
for leaf in arg_data['Leaf']:
|
|
88
|
+
if 'xpos' in leaf and leaf['xpos'] is not None:
|
|
89
|
+
original_leaf_xpos[leaf['nodeid']] = leaf['xpos']
|
|
90
|
+
|
|
91
|
+
if original_leaf_xpos and len(original_leaf_xpos) == n_leaves:
|
|
92
|
+
for node in leaves:
|
|
93
|
+
x_pos[node] = original_leaf_xpos[node]
|
|
94
|
+
else:
|
|
95
|
+
# Default: space leaves evenly from 0 to 1
|
|
96
|
+
leaves_sorted = sorted(leaves)
|
|
97
|
+
for i, node in enumerate(leaves_sorted):
|
|
98
|
+
if n_leaves > 1:
|
|
99
|
+
x_pos[node] = i / (n_leaves - 1)
|
|
100
|
+
else:
|
|
101
|
+
x_pos[node] = 0.5
|
|
102
|
+
|
|
103
|
+
# Initialize internal nodes at children centroid
|
|
104
|
+
def get_child_centroid(node):
|
|
105
|
+
if children_map[node]:
|
|
106
|
+
child_xs = [x_pos[c] for c in children_map[node] if c in x_pos]
|
|
107
|
+
if child_xs:
|
|
108
|
+
return np.mean(child_xs)
|
|
109
|
+
return (x_min + x_max) / 2
|
|
110
|
+
|
|
111
|
+
def get_child_range(node):
|
|
112
|
+
"""Get the x-range of immediate children."""
|
|
113
|
+
if children_map[node]:
|
|
114
|
+
child_xs = [x_pos[c] for c in children_map[node] if c in x_pos]
|
|
115
|
+
if child_xs:
|
|
116
|
+
return min(child_xs), max(child_xs)
|
|
117
|
+
return x_min, x_max
|
|
118
|
+
|
|
119
|
+
for h in sorted_heights:
|
|
120
|
+
if h == 0:
|
|
121
|
+
continue
|
|
122
|
+
for node in layers[h]:
|
|
123
|
+
x_pos[node] = get_child_centroid(node)
|
|
124
|
+
|
|
125
|
+
def euclidean_dist(n1, n2):
|
|
126
|
+
dx = x_pos[n1] - x_pos[n2]
|
|
127
|
+
dy = heights[n1] - heights[n2]
|
|
128
|
+
return np.sqrt(dx**2 + dy**2)
|
|
129
|
+
|
|
130
|
+
# Track which nodes are "released" to go outside children range
|
|
131
|
+
released = set()
|
|
132
|
+
|
|
133
|
+
# Iterative constraint satisfaction
|
|
134
|
+
for iteration in range(n_iterations):
|
|
135
|
+
lr = 0.4 * (1 - 0.5 * iteration / n_iterations)
|
|
136
|
+
|
|
137
|
+
# Find all distance violations
|
|
138
|
+
violations = []
|
|
139
|
+
for i, n1 in enumerate(all_nodes):
|
|
140
|
+
for n2 in all_nodes[i+1:]:
|
|
141
|
+
dist = euclidean_dist(n1, n2)
|
|
142
|
+
if dist < min_distance:
|
|
143
|
+
violations.append((n1, n2, dist))
|
|
144
|
+
|
|
145
|
+
if not violations:
|
|
146
|
+
break # All constraints satisfied
|
|
147
|
+
|
|
148
|
+
# Compute repulsion forces only for violating pairs
|
|
149
|
+
forces = defaultdict(float)
|
|
150
|
+
|
|
151
|
+
for n1, n2, dist in violations:
|
|
152
|
+
dx = x_pos[n1] - x_pos[n2]
|
|
153
|
+
|
|
154
|
+
# Deficit: how much more distance we need
|
|
155
|
+
deficit = min_distance - dist
|
|
156
|
+
|
|
157
|
+
# Direction to push
|
|
158
|
+
if abs(dx) < 0.001:
|
|
159
|
+
direction = 1 if n1 < n2 else -1
|
|
160
|
+
else:
|
|
161
|
+
direction = np.sign(dx)
|
|
162
|
+
|
|
163
|
+
# Force magnitude proportional to deficit
|
|
164
|
+
force_mag = deficit * 0.3
|
|
165
|
+
|
|
166
|
+
# Apply force to non-leaf nodes
|
|
167
|
+
if heights[n1] > 0:
|
|
168
|
+
forces[n1] += force_mag * direction
|
|
169
|
+
if heights[n2] > 0:
|
|
170
|
+
forces[n2] -= force_mag * direction
|
|
171
|
+
|
|
172
|
+
# Apply forces with conditional constraints
|
|
173
|
+
for node, force in forces.items():
|
|
174
|
+
if heights[node] == 0:
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
new_x = x_pos[node] + lr * force
|
|
178
|
+
child_lo, child_hi = get_child_range(node)
|
|
179
|
+
|
|
180
|
+
# Check if new position would be within children's range
|
|
181
|
+
if child_lo <= new_x <= child_hi:
|
|
182
|
+
# Stay within range - no problem
|
|
183
|
+
x_pos[node] = new_x
|
|
184
|
+
else:
|
|
185
|
+
# Would go outside - check if we NEED to
|
|
186
|
+
# Release node if it still has violations at the boundary
|
|
187
|
+
clipped_x = np.clip(new_x, child_lo, child_hi)
|
|
188
|
+
temp_x = x_pos[node]
|
|
189
|
+
x_pos[node] = clipped_x
|
|
190
|
+
|
|
191
|
+
# Check if any violations remain
|
|
192
|
+
still_violated = False
|
|
193
|
+
for n1, n2, _ in violations:
|
|
194
|
+
if node in (n1, n2):
|
|
195
|
+
if euclidean_dist(n1, n2) < min_distance:
|
|
196
|
+
still_violated = True
|
|
197
|
+
break
|
|
198
|
+
|
|
199
|
+
if still_violated:
|
|
200
|
+
# Need to go outside children's range
|
|
201
|
+
released.add(node)
|
|
202
|
+
x_pos[node] = np.clip(new_x, x_min, x_max)
|
|
203
|
+
else:
|
|
204
|
+
x_pos[node] = clipped_x
|
|
205
|
+
|
|
206
|
+
# For released nodes, allow them to move freely
|
|
207
|
+
for node in released:
|
|
208
|
+
if node in forces:
|
|
209
|
+
new_x = x_pos[node] + lr * forces[node]
|
|
210
|
+
x_pos[node] = np.clip(new_x, x_min, x_max)
|
|
211
|
+
|
|
212
|
+
# Weak centering force to children centroid (only for unreleased nodes)
|
|
213
|
+
for node in all_nodes:
|
|
214
|
+
if heights[node] > 0 and node not in released:
|
|
215
|
+
if children_map[node]:
|
|
216
|
+
centroid = get_child_centroid(node)
|
|
217
|
+
x_pos[node] += 0.02 * (centroid - x_pos[node])
|
|
218
|
+
|
|
219
|
+
# Update the JSON data
|
|
220
|
+
for leaf in arg_data['Leaf']:
|
|
221
|
+
leaf['xpos'] = float(x_pos[leaf['nodeid']])
|
|
222
|
+
|
|
223
|
+
for coal in arg_data['Coalescent']:
|
|
224
|
+
coal['xpos'] = float(x_pos[coal['nodeid']])
|
|
225
|
+
|
|
226
|
+
for recomb in arg_data['Recombination']:
|
|
227
|
+
recomb['xpos'] = float(x_pos[recomb['nodeid']])
|
|
228
|
+
|
|
229
|
+
return arg_data
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def count_crossings(arg_data):
|
|
233
|
+
"""Count edge crossings in the layout."""
|
|
234
|
+
heights = {}
|
|
235
|
+
x_pos = {}
|
|
236
|
+
|
|
237
|
+
for leaf in arg_data['Leaf']:
|
|
238
|
+
nid = leaf['nodeid']
|
|
239
|
+
heights[nid] = leaf['height']
|
|
240
|
+
x_pos[nid] = leaf['xpos']
|
|
241
|
+
|
|
242
|
+
for coal in arg_data['Coalescent']:
|
|
243
|
+
nid = coal['nodeid']
|
|
244
|
+
heights[nid] = coal['height']
|
|
245
|
+
x_pos[nid] = coal['xpos']
|
|
246
|
+
|
|
247
|
+
for recomb in arg_data['Recombination']:
|
|
248
|
+
nid = recomb['nodeid']
|
|
249
|
+
heights[nid] = recomb['height']
|
|
250
|
+
x_pos[nid] = recomb['xpos']
|
|
251
|
+
|
|
252
|
+
edges = []
|
|
253
|
+
for lineage in arg_data['Lineage']:
|
|
254
|
+
if lineage['up'] is not None:
|
|
255
|
+
edges.append((lineage['up'], lineage['down']))
|
|
256
|
+
|
|
257
|
+
crossings = 0
|
|
258
|
+
for i, (p1, c1) in enumerate(edges):
|
|
259
|
+
for p2, c2 in edges[i+1:]:
|
|
260
|
+
if p1 == p2 or c1 == c2 or p1 == c2 or p2 == c1:
|
|
261
|
+
continue
|
|
262
|
+
h1_top, h1_bot = heights[p1], heights[c1]
|
|
263
|
+
h2_top, h2_bot = heights[p2], heights[c2]
|
|
264
|
+
if max(h1_bot, h2_bot) >= min(h1_top, h2_top):
|
|
265
|
+
continue
|
|
266
|
+
if (x_pos[p1] - x_pos[p2]) * (x_pos[c1] - x_pos[c2]) < 0:
|
|
267
|
+
crossings += 1
|
|
268
|
+
|
|
269
|
+
return crossings
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def check_min_distances(arg_data):
|
|
273
|
+
"""Check minimum distances between all node pairs."""
|
|
274
|
+
heights = {}
|
|
275
|
+
x_pos = {}
|
|
276
|
+
|
|
277
|
+
for leaf in arg_data['Leaf']:
|
|
278
|
+
nid = leaf['nodeid']
|
|
279
|
+
heights[nid] = leaf['height']
|
|
280
|
+
x_pos[nid] = leaf['xpos']
|
|
281
|
+
|
|
282
|
+
for coal in arg_data['Coalescent']:
|
|
283
|
+
nid = coal['nodeid']
|
|
284
|
+
heights[nid] = coal['height']
|
|
285
|
+
x_pos[nid] = coal['xpos']
|
|
286
|
+
|
|
287
|
+
for recomb in arg_data['Recombination']:
|
|
288
|
+
nid = recomb['nodeid']
|
|
289
|
+
heights[nid] = recomb['height']
|
|
290
|
+
x_pos[nid] = recomb['xpos']
|
|
291
|
+
|
|
292
|
+
all_nodes = list(x_pos.keys())
|
|
293
|
+
min_dist = float('inf')
|
|
294
|
+
min_pair = None
|
|
295
|
+
|
|
296
|
+
for i, n1 in enumerate(all_nodes):
|
|
297
|
+
for n2 in all_nodes[i+1:]:
|
|
298
|
+
dx = x_pos[n1] - x_pos[n2]
|
|
299
|
+
dy = heights[n1] - heights[n2]
|
|
300
|
+
dist = np.sqrt(dx**2 + dy**2)
|
|
301
|
+
if dist < min_dist:
|
|
302
|
+
min_dist = dist
|
|
303
|
+
min_pair = (n1, n2)
|
|
304
|
+
|
|
305
|
+
return {'min_distance': min_dist, 'min_pair': min_pair}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def check_parents_outside_children(arg_data):
|
|
309
|
+
"""Find parents positioned outside their children's x-range."""
|
|
310
|
+
x_pos = {}
|
|
311
|
+
for leaf in arg_data['Leaf']:
|
|
312
|
+
x_pos[leaf['nodeid']] = leaf['xpos']
|
|
313
|
+
for coal in arg_data['Coalescent']:
|
|
314
|
+
x_pos[coal['nodeid']] = coal['xpos']
|
|
315
|
+
for recomb in arg_data['Recombination']:
|
|
316
|
+
x_pos[recomb['nodeid']] = recomb['xpos']
|
|
317
|
+
|
|
318
|
+
children_map = defaultdict(list)
|
|
319
|
+
for lineage in arg_data['Lineage']:
|
|
320
|
+
if lineage['up'] is not None:
|
|
321
|
+
children_map[lineage['up']].append(lineage['down'])
|
|
322
|
+
|
|
323
|
+
outside = []
|
|
324
|
+
for node, children in children_map.items():
|
|
325
|
+
if children:
|
|
326
|
+
child_xs = [x_pos[c] for c in children]
|
|
327
|
+
lo, hi = min(child_xs), max(child_xs)
|
|
328
|
+
node_x = x_pos[node]
|
|
329
|
+
if node_x < lo - 0.001 or node_x > hi + 0.001:
|
|
330
|
+
outside.append({
|
|
331
|
+
'node': node,
|
|
332
|
+
'x': node_x,
|
|
333
|
+
'children_range': (lo, hi)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
return outside
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == '__main__':
|
|
340
|
+
import copy
|
|
341
|
+
|
|
342
|
+
arg_json = {
|
|
343
|
+
"Coalescent": [
|
|
344
|
+
{"nodeid": 5, "height": 0.137, "xpos": 0.125},
|
|
345
|
+
{"nodeid": 7, "height": 0.390, "xpos": 0.5125},
|
|
346
|
+
{"nodeid": 8, "height": 0.550, "xpos": 0.606},
|
|
347
|
+
{"nodeid": 10, "height": 0.737, "xpos": 0.578},
|
|
348
|
+
{"nodeid": 11, "height": 0.991, "xpos": 0.312},
|
|
349
|
+
{"nodeid": 12, "height": 1.0, "xpos": 0.445}
|
|
350
|
+
],
|
|
351
|
+
"Recombination": [
|
|
352
|
+
{"nodeid": 6, "height": 0.332, "xpos": 0.125},
|
|
353
|
+
{"nodeid": 9, "height": 0.593, "xpos": 0.5}
|
|
354
|
+
],
|
|
355
|
+
"Leaf": [
|
|
356
|
+
{"nodeid": 0, "height": 0.0, "xpos": 0.5},
|
|
357
|
+
{"nodeid": 1, "height": 0.0, "xpos": 0.0},
|
|
358
|
+
{"nodeid": 2, "height": 0.0, "xpos": 0.25},
|
|
359
|
+
{"nodeid": 3, "height": 0.0, "xpos": 1.0},
|
|
360
|
+
{"nodeid": 4, "height": 0.0, "xpos": 0.75}
|
|
361
|
+
],
|
|
362
|
+
"Lineage": [
|
|
363
|
+
{"lineageid": 0, "down": 0, "up": 9},
|
|
364
|
+
{"lineageid": 1, "down": 1, "up": 5},
|
|
365
|
+
{"lineageid": 2, "down": 2, "up": 5},
|
|
366
|
+
{"lineageid": 3, "down": 3, "up": 8},
|
|
367
|
+
{"lineageid": 4, "down": 4, "up": 7},
|
|
368
|
+
{"lineageid": 5, "down": 5, "up": 6},
|
|
369
|
+
{"lineageid": 6, "down": 6, "up": 11},
|
|
370
|
+
{"lineageid": 7, "down": 6, "up": 7},
|
|
371
|
+
{"lineageid": 8, "down": 7, "up": 8},
|
|
372
|
+
{"lineageid": 9, "down": 8, "up": 10},
|
|
373
|
+
{"lineageid": 10, "down": 9, "up": 10},
|
|
374
|
+
{"lineageid": 11, "down": 9, "up": 11},
|
|
375
|
+
{"lineageid": 12, "down": 10, "up": 12},
|
|
376
|
+
{"lineageid": 13, "down": 11, "up": 12},
|
|
377
|
+
{"lineageid": 14, "down": 12, "up": None}
|
|
378
|
+
]
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
n_samples = len(arg_json['Leaf'])
|
|
382
|
+
default_min_dist = 1.0 / (2 * n_samples)
|
|
383
|
+
|
|
384
|
+
print(f"=== Minimum Distance Layout ===")
|
|
385
|
+
print(f"n_samples = {n_samples}")
|
|
386
|
+
print(f"x-range = [0, 1]")
|
|
387
|
+
print(f"default min_distance = 1/(2*{n_samples}) = {default_min_dist:.3f}")
|
|
388
|
+
print()
|
|
389
|
+
|
|
390
|
+
# With default min_distance
|
|
391
|
+
result = compute_arg_xpos(copy.deepcopy(arg_json))
|
|
392
|
+
result_info = check_min_distances(result)
|
|
393
|
+
outside = check_parents_outside_children(result)
|
|
394
|
+
|
|
395
|
+
print(f"Result with default min_distance={default_min_dist:.3f}:")
|
|
396
|
+
print(f" Actual min distance: {result_info['min_distance']:.3f} (nodes {result_info['min_pair']})")
|
|
397
|
+
print(f" Crossings: {count_crossings(result)}")
|
|
398
|
+
print(f" Parents outside children: {len(outside)}")
|
|
399
|
+
for o in outside:
|
|
400
|
+
print(f" Node {o['node']}: x={o['x']:.3f}, children=[{o['children_range'][0]:.3f}, {o['children_range'][1]:.3f}]")
|
|
401
|
+
|
|
402
|
+
print(f"\nLeaf positions: ", end="")
|
|
403
|
+
for leaf in sorted(result['Leaf'], key=lambda x: x['nodeid']):
|
|
404
|
+
print(f"{leaf['nodeid']}:{leaf['xpos']:.2f}", end=" ")
|
|
405
|
+
print()
|
|
406
|
+
|
|
407
|
+
print(f"Internal positions: ", end="")
|
|
408
|
+
for coal in sorted(result['Coalescent'], key=lambda x: x['nodeid']):
|
|
409
|
+
print(f"{coal['nodeid']}:{coal['xpos']:.2f}", end=" ")
|
|
410
|
+
for recomb in sorted(result['Recombination'], key=lambda x: x['nodeid']):
|
|
411
|
+
print(f"{recomb['nodeid']}:{recomb['xpos']:.2f}", end=" ")
|
|
412
|
+
print()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-size: 0.85rem;
|
|
3
|
+
/* background-color: #f9f9f9; */
|
|
4
|
+
/* background-color: #f1f1f1; */
|
|
5
|
+
/* background-color: #e7eff6; */
|
|
6
|
+
background-color: white;
|
|
7
|
+
/* background-color: #f9fcfe; */
|
|
8
|
+
font-family: "Asap", sans-serif;
|
|
9
|
+
/* margin: .5rem; */
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.rc-slider-track {
|
|
13
|
+
background-color: #007bff;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.rc-slider-dot-active {
|
|
17
|
+
border-color: #007bff;
|
|
18
|
+
border: solid 2px #007bff;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rc-slider-handle {
|
|
22
|
+
background-color: #007bff;
|
|
23
|
+
border-color: #007bff;
|
|
24
|
+
}
|
|
25
|
+
window_data.to_hdf('window_data.h5', 'df', format='table', mode='w')
|
|
26
|
+
.rc.slider-handle:hover {
|
|
27
|
+
border-color: #007bff;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.rc.slider-handle-active:active {
|
|
31
|
+
border-color: red;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.pretty_container {
|
|
35
|
+
|
|
36
|
+
border-radius: 0px;
|
|
37
|
+
background-color: white;
|
|
38
|
+
/* margin: 1.5rem; */
|
|
39
|
+
padding: 1rem;
|
|
40
|
+
/* position: relative; */
|
|
41
|
+
/* border: 1px solid #f1f1f1; */
|
|
42
|
+
/* border: 1px solid #e1e1e1; */
|
|
43
|
+
/* border: 1px solid #999999; */
|
|
44
|
+
border: 1px solid #999999;
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
padding: 1rem;
|
|
48
|
+
border: 1px solid #ffffff;
|
|
49
|
+
-webkit-box-shadow: 0px 1px 5px 1px #ccc;
|
|
50
|
+
-moz-box-shadow: 0px 1px 5px 1px #ccc;
|
|
51
|
+
box-shadow: 0px 1px 5px 1px #ccc;
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/* Buttons
|
|
56
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
57
|
+
/* .button,
|
|
58
|
+
button,
|
|
59
|
+
input[type="submit"],
|
|
60
|
+
input[type="reset"],
|
|
61
|
+
input[type="button"] {
|
|
62
|
+
display: inline-block;
|
|
63
|
+
height: 35px;
|
|
64
|
+
padding: 0 15px;
|
|
65
|
+
color: #555;
|
|
66
|
+
text-align: center;
|
|
67
|
+
font-size: 11px;
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
line-height: 35px;
|
|
70
|
+
letter-spacing: .1rem;
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
text-decoration: none;
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
background-color: transparent;
|
|
75
|
+
border-radius: 4px;
|
|
76
|
+
border: 1px solid #bbb;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
box-sizing: border-box; }
|
|
79
|
+
.button:hover,
|
|
80
|
+
button:hover,
|
|
81
|
+
input[type="submit"]:hover,
|
|
82
|
+
input[type="reset"]:hover,
|
|
83
|
+
input[type="button"]:hover,
|
|
84
|
+
.button:focus,
|
|
85
|
+
button:focus,
|
|
86
|
+
input[type="submit"]:focus,
|
|
87
|
+
input[type="reset"]:focus,
|
|
88
|
+
input[type="button"]:focus {
|
|
89
|
+
color: #333;
|
|
90
|
+
border-color: #888;
|
|
91
|
+
outline: 0; }
|
|
92
|
+
.button.button-primary,
|
|
93
|
+
button.button-primary,
|
|
94
|
+
input[type="submit"].button-primary,
|
|
95
|
+
input[type="reset"].button-primary,
|
|
96
|
+
input[type="button"].button-primary {
|
|
97
|
+
color: rgb(145, 9, 9);
|
|
98
|
+
background-color: #33C3F0;
|
|
99
|
+
border-color: #33C3F0; }
|
|
100
|
+
.button.button-primary:hover,
|
|
101
|
+
button.button-primary:hover,
|
|
102
|
+
input[type="submit"].button-primary:hover,
|
|
103
|
+
input[type="reset"].button-primary:hover,
|
|
104
|
+
input[type="button"].button-primary:hover,
|
|
105
|
+
.button.button-primary:focus,
|
|
106
|
+
button.button-primary:focus,
|
|
107
|
+
input[type="submit"].button-primary:focus,
|
|
108
|
+
input[type="reset"].button-primary:focus,
|
|
109
|
+
input[type="button"].button-primary:focus {
|
|
110
|
+
color: #FFF;
|
|
111
|
+
background-color: #1EAEDB;
|
|
112
|
+
border-color: #1EAEDB; } */
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
/* @import "bootstrap";
|
|
116
|
+
|
|
117
|
+
$mynewcolor:teal;
|
|
118
|
+
|
|
119
|
+
.btn-primary {
|
|
120
|
+
@include button-variant($mynewcolor, darken($mynewcolor, 7.5%), darken($mynewcolor, 10%), lighten($mynewcolor,5%), lighten($mynewcolor, 10%), darken($mynewcolor,30%));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.btn-outline-primary {
|
|
124
|
+
@include button-outline-variant($mynewcolor, #222222, lighten($mynewcolor,5%), $mynewcolor);
|
|
125
|
+
} */ */
|
|
126
|
+
/*
|
|
127
|
+
.btn-primary{color:#fff;background-color:teal;border-color:#005a5a}
|
|
128
|
+
|
|
129
|
+
.btn-primary:hover{color:#fff;background-color:#004d4d;border-color:#009a9a}
|
|
130
|
+
.btn-primary:focus,.btn-primary.focus{box-shadow:0 0 0 .2rem rgba(0,90,90,0.5)}
|
|
131
|
+
.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:teal;border-color:#005a5a}
|
|
132
|
+
.btn-primary:not(:disabled):not(.disabled):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#00b3b3;border-color:#000}
|
|
133
|
+
.btn-primary:not(:disabled):not(.disabled):active:focus,.btn-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,90,90,0.5)}
|
|
134
|
+
|
|
135
|
+
.btn-outline-primary{color:teal;background-color:transparent;background-image:none;border-color:teal}.btn-outline-primary:hover{color:#222;background-color:#009a9a;border-color:teal}
|
|
136
|
+
.btn-outline-primary:focus,.btn-outline-primary.focus{box-shadow:0 0 0 .2rem rgba(0,128,128,0.5)}
|
|
137
|
+
.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:teal;background-color:transparent}
|
|
138
|
+
.btn-outline-primary:not(:disabled):not(.disabled):active,.btn-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#009a9a;border-color:teal}
|
|
139
|
+
.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,128,128,0.5)} */
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.custom-tabs-container {
|
|
2
|
+
width: 85%;
|
|
3
|
+
}
|
|
4
|
+
.custom-tabs {
|
|
5
|
+
border-top-left-radius: 3px;
|
|
6
|
+
background-color: #f9f9f9;
|
|
7
|
+
padding: 0px 24px;
|
|
8
|
+
border-bottom: 1px solid #d6d6d6;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.custom-tab {
|
|
12
|
+
width: 20px;
|
|
13
|
+
color:#586069;
|
|
14
|
+
border-top-left-radius: 15px;
|
|
15
|
+
border-top-right-radius: 5px;
|
|
16
|
+
border-top: 3px solid transparent !important;
|
|
17
|
+
border-left: 0px !important;
|
|
18
|
+
border-right: 0px !important;
|
|
19
|
+
border-bottom: 0px !important;
|
|
20
|
+
background-color: #fafbfc;
|
|
21
|
+
padding: 12px !important;
|
|
22
|
+
font-family: "system-ui";
|
|
23
|
+
display: flex !important;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
}
|
|
27
|
+
.custom-tab--selected {
|
|
28
|
+
color: black;
|
|
29
|
+
box-shadow: 1px 1px 0px white;
|
|
30
|
+
border-left: 1px solid lightgrey !important;
|
|
31
|
+
border-right: 1px solid lightgrey !important;
|
|
32
|
+
border-top: 1px solid lightgrey !important;
|
|
33
|
+
|
|
34
|
+
}
|
arg_dashboard/index.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arg-dashboard
|
|
3
|
+
Version: 0.1.19
|
|
4
|
+
Summary: Project description
|
|
5
|
+
Author-email: Your Name <your@email.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/munch-group/arg-dashboard
|
|
8
|
+
Project-URL: Repository, https://github.com/munch-group/arg-dashboard
|
|
9
|
+
Project-URL: Documentation, https://munch-group.org/arg-dashboard
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: <3.14,>=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: ipykernel
|
|
16
|
+
Requires-Dist: jupyter
|
|
17
|
+
Requires-Dist: dash
|
|
18
|
+
Requires-Dist: dash-bootstrap-components
|
|
19
|
+
Requires-Dist: pandas
|
|
20
|
+
Requires-Dist: networkx
|
|
21
|
+
Requires-Dist: jupyter-dash
|
|
22
|
+
Requires-Dist: jupyter-server-proxy<5,>=4.4
|
|
23
|
+
Requires-Dist: plotly
|
|
24
|
+
Requires-Dist: seaborn
|
|
25
|
+
Requires-Dist: quartodoc
|
|
26
|
+
Requires-Dist: quarto
|
|
27
|
+
Requires-Dist: pip
|
|
28
|
+
Requires-Dist: pytest
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Template repository for a library project
|
|
33
|
+
|
|
34
|
+
## Initial set up
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pixi run init
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Get updates to upstream fork
|
|
41
|
+
|
|
42
|
+
Add upstream if not already added
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git remote add upstream https://github.com/munch-group/arg-dashboard.git
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Fetch upstream changes
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git fetch upstream
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Either rebase your changes on top of upstream (cleaner history)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git rebase upstream/main
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or, merge upstream into your fork (preserves history)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git merge upstream/main
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If you want to see what's changed upstream before applying:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git log HEAD..upstream/main
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
See the actual diff
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git diff HEAD...upstream/main
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then push your updated fork:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git push origin main
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If you rebased and need to force push
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git push origin main --force-with-lease
|
|
88
|
+
```
|