Update my_pages/multiverse.py
Browse files- my_pages/multiverse.py +72 -32
my_pages/multiverse.py
CHANGED
|
@@ -1,77 +1,115 @@
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import plotly.graph_objects as go
|
| 3 |
from utils import development_stages
|
| 4 |
|
| 5 |
-
def
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
node_labels = ["Start"]
|
| 8 |
-
node_positions = [(0, 0)]
|
| 9 |
node_stage = [0]
|
| 10 |
-
|
| 11 |
|
| 12 |
-
prev_nodes = [0] #
|
| 13 |
-
|
| 14 |
|
| 15 |
-
# Build
|
| 16 |
for stage_idx, stage in enumerate(development_stages, start=1):
|
| 17 |
options = stage["questions"]
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
for parent_idx in prev_nodes:
|
| 20 |
px, py = node_positions[parent_idx]
|
|
|
|
|
|
|
| 21 |
for opt_idx, opt in enumerate(options):
|
| 22 |
-
child_x = stage_idx
|
| 23 |
-
|
|
|
|
|
|
|
| 24 |
node_index = len(node_labels)
|
| 25 |
node_labels.append(opt)
|
| 26 |
node_positions.append((child_x, child_y))
|
| 27 |
node_stage.append(stage_idx)
|
| 28 |
-
node_index_map[(stage_idx, opt)] = node_index
|
| 29 |
edges.append((parent_idx, node_index))
|
| 30 |
-
|
| 31 |
-
prev_nodes = stage_nodes_map[stage_idx]
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
| 34 |
highlight_edges = set()
|
| 35 |
-
highlight_nodes = set([0])
|
| 36 |
current_node = 0
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
return node_labels, node_positions, edges, highlight_edges, highlight_nodes
|
| 47 |
|
|
|
|
| 48 |
def render():
|
| 49 |
-
st.title("Multiverse of Developer Decisions
|
| 50 |
|
| 51 |
-
# User
|
| 52 |
selected_path = []
|
| 53 |
for stage in development_stages:
|
| 54 |
-
|
|
|
|
|
|
|
| 55 |
selected_path.append(choice)
|
| 56 |
|
| 57 |
-
|
|
|
|
| 58 |
|
|
|
|
| 59 |
x_vals = [pos[0] for pos in positions]
|
| 60 |
y_vals = [pos[1] for pos in positions]
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
node_trace = go.Scatter(
|
| 64 |
x=x_vals, y=y_vals,
|
| 65 |
mode='markers+text',
|
| 66 |
text=labels,
|
| 67 |
textposition="top center",
|
| 68 |
-
marker=dict(size=
|
|
|
|
| 69 |
)
|
| 70 |
|
| 71 |
edge_traces = []
|
| 72 |
for src, dst in edges:
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
edge_traces.append(go.Scatter(
|
| 76 |
x=[positions[src][0], positions[dst][0]],
|
| 77 |
y=[positions[src][1], positions[dst][1]],
|
|
@@ -80,13 +118,15 @@ def render():
|
|
| 80 |
hoverinfo='none'
|
| 81 |
))
|
| 82 |
|
|
|
|
| 83 |
fig = go.Figure(data=edge_traces + [node_trace])
|
| 84 |
fig.update_layout(
|
| 85 |
showlegend=False,
|
| 86 |
xaxis=dict(visible=False),
|
| 87 |
yaxis=dict(visible=False),
|
| 88 |
plot_bgcolor='white',
|
| 89 |
-
margin=dict(l=
|
|
|
|
| 90 |
)
|
| 91 |
|
| 92 |
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
| 1 |
+
# pages/multiverse.py
|
| 2 |
import streamlit as st
|
| 3 |
import plotly.graph_objects as go
|
| 4 |
from utils import development_stages
|
| 5 |
|
| 6 |
+
def build_tree_and_trace_path(selected_path):
|
| 7 |
+
"""
|
| 8 |
+
Build tree nodes and edges. Then trace selected_path (one choice per stage)
|
| 9 |
+
by walking children of the current node to find the matching label at each stage.
|
| 10 |
+
Returns: node_labels, node_positions, edges, highlight_edges, highlight_nodes
|
| 11 |
+
"""
|
| 12 |
node_labels = ["Start"]
|
| 13 |
+
node_positions = [(0.0, 0.0)]
|
| 14 |
node_stage = [0]
|
| 15 |
+
edges = []
|
| 16 |
|
| 17 |
+
prev_nodes = [0] # nodes at previous stage (start)
|
| 18 |
+
y_spacing_base = 1.0
|
| 19 |
|
| 20 |
+
# Build nodes and edges stage by stage
|
| 21 |
for stage_idx, stage in enumerate(development_stages, start=1):
|
| 22 |
options = stage["questions"]
|
| 23 |
+
next_nodes = []
|
| 24 |
+
|
| 25 |
+
# For layout niceness: compute a span for children under each parent
|
| 26 |
for parent_idx in prev_nodes:
|
| 27 |
px, py = node_positions[parent_idx]
|
| 28 |
+
# Spread children vertically around the parent's y
|
| 29 |
+
span = max(1, len(options))
|
| 30 |
for opt_idx, opt in enumerate(options):
|
| 31 |
+
child_x = float(stage_idx)
|
| 32 |
+
# center children around px, offset by opt_idx
|
| 33 |
+
offset = (opt_idx - (len(options) - 1) / 2.0) * (y_spacing_base / (stage_idx))
|
| 34 |
+
child_y = py + offset
|
| 35 |
node_index = len(node_labels)
|
| 36 |
node_labels.append(opt)
|
| 37 |
node_positions.append((child_x, child_y))
|
| 38 |
node_stage.append(stage_idx)
|
|
|
|
| 39 |
edges.append((parent_idx, node_index))
|
| 40 |
+
next_nodes.append(node_index)
|
|
|
|
| 41 |
|
| 42 |
+
# After all parents, the next stage's parents are all next_nodes
|
| 43 |
+
prev_nodes = next_nodes
|
| 44 |
+
|
| 45 |
+
# Trace the single chosen path by walking children
|
| 46 |
highlight_edges = set()
|
| 47 |
+
highlight_nodes = set([0])
|
| 48 |
current_node = 0
|
| 49 |
+
|
| 50 |
+
for stage_idx, chosen_label in enumerate(selected_path, start=1):
|
| 51 |
+
# children of current_node
|
| 52 |
+
children = [dst for (src, dst) in edges if src == current_node]
|
| 53 |
+
# find a child among these whose label equals chosen_label
|
| 54 |
+
found_child = None
|
| 55 |
+
for c in children:
|
| 56 |
+
if node_labels[c] == chosen_label:
|
| 57 |
+
found_child = c
|
| 58 |
+
break
|
| 59 |
+
if found_child is None:
|
| 60 |
+
# chosen label not found under this parent — stop tracing
|
| 61 |
+
break
|
| 62 |
+
highlight_edges.add((current_node, found_child))
|
| 63 |
+
highlight_nodes.add(found_child)
|
| 64 |
+
current_node = found_child
|
| 65 |
|
| 66 |
return node_labels, node_positions, edges, highlight_edges, highlight_nodes
|
| 67 |
|
| 68 |
+
|
| 69 |
def render():
|
| 70 |
+
st.title("Multiverse of Developer Decisions — Tree View")
|
| 71 |
|
| 72 |
+
# --- User picks one choice per stage via dropdowns ---
|
| 73 |
selected_path = []
|
| 74 |
for stage in development_stages:
|
| 75 |
+
# use a stable key per stage to avoid conflicts
|
| 76 |
+
key = f"multiverse_choice_{stage['label']}"
|
| 77 |
+
choice = st.selectbox(f"{stage['icon']} {stage['label']}", stage["questions"], key=key)
|
| 78 |
selected_path.append(choice)
|
| 79 |
|
| 80 |
+
# --- Build tree and compute which edges/nodes to highlight ---
|
| 81 |
+
labels, positions, edges, highlight_edges, highlight_nodes = build_tree_and_trace_path(selected_path)
|
| 82 |
|
| 83 |
+
# --- Prepare node and edge traces for Plotly ---
|
| 84 |
x_vals = [pos[0] for pos in positions]
|
| 85 |
y_vals = [pos[1] for pos in positions]
|
| 86 |
+
|
| 87 |
+
node_colors = []
|
| 88 |
+
for idx in range(len(labels)):
|
| 89 |
+
if idx in highlight_nodes:
|
| 90 |
+
node_colors.append("rgba(34,139,34,0.95)") # green for selected path nodes
|
| 91 |
+
elif idx == 0:
|
| 92 |
+
node_colors.append("rgba(30,144,255,0.9)") # start node distinct
|
| 93 |
+
else:
|
| 94 |
+
node_colors.append("rgba(135,206,250,0.6)") # default skyblue
|
| 95 |
|
| 96 |
node_trace = go.Scatter(
|
| 97 |
x=x_vals, y=y_vals,
|
| 98 |
mode='markers+text',
|
| 99 |
text=labels,
|
| 100 |
textposition="top center",
|
| 101 |
+
marker=dict(size=18, color=node_colors, line=dict(width=1, color='black')),
|
| 102 |
+
hoverinfo="text"
|
| 103 |
)
|
| 104 |
|
| 105 |
edge_traces = []
|
| 106 |
for src, dst in edges:
|
| 107 |
+
if (src, dst) in highlight_edges:
|
| 108 |
+
color = "rgba(0,128,0,0.9)" # bright green
|
| 109 |
+
width = 4
|
| 110 |
+
else:
|
| 111 |
+
color = "rgba(120,120,120,0.4)"
|
| 112 |
+
width = 1.5
|
| 113 |
edge_traces.append(go.Scatter(
|
| 114 |
x=[positions[src][0], positions[dst][0]],
|
| 115 |
y=[positions[src][1], positions[dst][1]],
|
|
|
|
| 118 |
hoverinfo='none'
|
| 119 |
))
|
| 120 |
|
| 121 |
+
# --- Render figure ---
|
| 122 |
fig = go.Figure(data=edge_traces + [node_trace])
|
| 123 |
fig.update_layout(
|
| 124 |
showlegend=False,
|
| 125 |
xaxis=dict(visible=False),
|
| 126 |
yaxis=dict(visible=False),
|
| 127 |
plot_bgcolor='white',
|
| 128 |
+
margin=dict(l=10, r=10, t=10, b=10),
|
| 129 |
+
hovermode="closest"
|
| 130 |
)
|
| 131 |
|
| 132 |
st.plotly_chart(fig, use_container_width=True)
|