Skip to content

Commit

Permalink
working sort feature for proposal search
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlesC30 committed Mar 27, 2023
1 parent fbf46a0 commit 2ceddc5
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 57 deletions.
21 changes: 15 additions & 6 deletions app_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import plotly.graph_objects as go

from xas.tiled_io import group_node_by_metadata_key
from xas.tiled_io import group_node_by_metadata_key, sort_nodes_by_metadata_key

def build_scangroup_interactable(scangroup_node, group_label):
select_all = html.Div([
Expand All @@ -24,12 +24,21 @@ def build_scangroup_interactable(scangroup_node, group_label):
# return scan_labels


def build_nested_accordion(base_node, groupby_keys: list[str], _node_label=""):
def build_nested_accordion(base_node, groupby_keys: list[str], sort_key:str=None, reverse_order=False, _node_label=""):
current_key = groupby_keys[0]
next_nodes, next_labels = group_node_by_metadata_key(base_node, current_key, return_values=True)
next_level_keys = groupby_keys[1:]

next_nodes, next_labels = group_node_by_metadata_key(base_node, current_key, return_values=True)

if sort_key is not None:
next_nodes, next_labels = sort_nodes_by_metadata_key(next_nodes, sort_key, node_labels=next_labels)

if reverse_order:
next_nodes.reverse()
next_labels.reverse()


# reached final level of sorting
# reached final level of grouping
if len(next_level_keys) == 0:
accordion_items = [
dbc.AccordionItem(
Expand All @@ -52,8 +61,8 @@ def build_nested_accordion(base_node, groupby_keys: list[str], _node_label=""):
return dbc.Accordion(accordion_items, start_collapsed=True, always_open=True,)


def build_proposal_accordion(proposal_node, groupby_keys):
proposal_accordion = build_nested_accordion(proposal_node, groupby_keys)
def build_proposal_accordion(proposal_node, groupby_keys, sort_key=None, reverse_order=False):
proposal_accordion = build_nested_accordion(proposal_node, groupby_keys, sort_key=sort_key, reverse_order=reverse_order)
return html.Div(proposal_accordion, style={"max-height": "700px", "overflow-y": "scroll"})


Expand Down
46 changes: 38 additions & 8 deletions dash_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from itertools import compress # basically numpy bool array casting using python iterables

from xas import tiled_io
from xas.tiled_io import filter_node_by_metadata_key, filter_node_for_proposal
from xas.tiled_io import filter_node_by_metadata_key, filter_node_for_proposal, sort_nodes_by_metadata_key
from xas.analysis import check_scan

from app_components import build_proposal_accordion, build_filter_input, visualization_tab, normalization_scheme_panel
Expand Down Expand Up @@ -64,14 +64,28 @@
]),
dbc.Col([
dbc.Label("Sort by"),
dbc.Input(id="sort_input"),
html.Div([
dcc.Dropdown(
options = [
{"label": "alphabetical", "value": "default"},
{"label": "time", "value": "time"},
],
value = "scan_name",
id="sort_dropdown",
),
dbc.Checkbox(
id="reverse_sort_checkbox",
label="reverse",
value=False,
),
])
]),
dbc.Col(
dbc.Button("apply", id="apply_btn"),
# align="end",
width=2,
),
], align="end", class_name="mb-3"),
], align="start", class_name="mb-3"),
dbc.Row([
dbc.Col(dbc.Spinner(html.Div(id="accordion_loc"), color="primary")),
dbc.Col([
Expand Down Expand Up @@ -119,13 +133,26 @@
Input("search_btn", "n_clicks"),
Input("apply_btn", "n_clicks"),
State("groupby_dropdown", "value"),
State("sort_dropdown", "value"),
State("reverse_sort_checkbox", "value"),
State("year_input", "value"),
State("cycle_input", "value"),
State("proposal_input", "value"),
State({"type": "filter_key_input", "index": ALL}, "value"),
State({"type": "filter_value_input", "index": ALL}, "value"),
)
def show_proposal_accordion(n_search_clicks, n_apply_clicks, dropdown_choice, year, cycle, proposal, other_filter_keys, other_filter_values):
def show_proposal_accordion(
n_search_clicks,
n_apply_clicks,
groupby_dropdown_choice,
sort_dropdown_choice,
reverse_sort_checked,
year,
cycle,
proposal,
other_filter_keys,
other_filter_values,
):
proposal_node = filter_node_for_proposal(ISS_SANDBOX, year, cycle, proposal)

if other_filter_keys and other_filter_values:
Expand All @@ -135,10 +162,13 @@ def show_proposal_accordion(n_search_clicks, n_apply_clicks, dropdown_choice, ye

if n_search_clicks == 0:
return
if not dropdown_choice: # check if empty or None
dropdown_choice = ("sample_name", "monochromator_scan_uid", )
if not groupby_dropdown_choice: # check if empty or None
groupby_dropdown_choice = ("sample_name", "monochromator_scan_uid", )

return build_proposal_accordion(proposal_node, groupby_keys=dropdown_choice)
return build_proposal_accordion(proposal_node,
groupby_keys=groupby_dropdown_choice,
sort_key=sort_dropdown_choice,
reverse_order=reverse_sort_checked)


@app.callback(
Expand Down Expand Up @@ -293,7 +323,7 @@ def change_visible_channels(n_channel_clicks, selected_scans, scan_id_dicts, cur
if current_btn_text == "see more":
if any(selected for selected in selected_scans):
selected_uids = [id_dict["uid"] for id_dict in compress(scan_id_dicts, selected_scans)]
selected_scan_df_cols = [set(SANDBOX_READER[uid].read().keys()) for uid in selected_uids]
selected_scan_df_cols = [set(ISS_SANDBOX[uid].columns) for uid in selected_uids]

# flatten into set of all unique column names
other_channels = set.union(*selected_scan_df_cols)
Expand Down
97 changes: 54 additions & 43 deletions xas/tiled_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,6 @@
from collections import UserDict


class StoredTiledEntry:
def __init__(self, client_obj):
self.source_type = type(client_obj)
self.data = client_obj.read()
self.metadata = client_obj.metadata

def read(self):
"""implemented to closer mimic tiled client object funcionality"""
return self.data

def __repr__(self):
return f"Stored data and metadata from {self.source_type}"


class TiledReader(UserDict):
"""Custom dictionary for accessing data from a source tiled client node.
When data is pulled from the node then it will be saved in the `TiledReader.data`
attribute and pulled from there in the future."""

def __init__(self, source_node: Node):
self.data = {}
self.source_node = source_node

def __setitem__(self, *args):
raise RuntimeError("Cannot set uid values using TiledReader")

def __getitem__(self, uid: str):
if uid in self.data.keys():
return self.data[uid]
elif uid in self.source_node.keys():
self.data[uid] = StoredTiledEntry(self.source_node[uid])
return self.data[uid]
else:
raise KeyError("Could not find uid in locally stored data or source node")




def get_iss_sandbox():
username=input("BNL Username: ")
tiled_catalog = from_uri("https://localhost:8008", verify=False, username=username)
Expand All @@ -63,8 +25,25 @@ def filter_node_for_proposal(node, year, cycle, proposal):
return node.search(Key('year') == year).search(Key('cycle') == cycle).search(Key('PROPOSAL') == proposal)


def sort_nodes_by_metadata_key(list_of_nodes: list, sort_key):
return list_of_nodes.sort(key=(lambda node: node[sort_key]))
def min_value_for_metadata_key(node: Node, key):
return min([client_obj.metadata[key] for client_obj in node.values_indexer if key in client_obj.metadata.keys()], default=None)


def sort_nodes_by_metadata_key(list_of_nodes: list[Node], sort_key, node_labels:list=None, reverse_order=False):
"""Returns nodes sorted by the minimum value for the `sort_key` within each node.
If `node_labels` is provided then the labels are sorted in the same order as the sorted nodes
and also returned."""
if node_labels is None:
# weird lambda returning tuple is a hacky way to push `None`s to the end of sorted list
return sorted(list_of_nodes, key=lambda node: (min_value_for_metadata_key(node, sort_key) is None, min_value_for_metadata_key(node, sort_key)),
reverse=reverse_order)
else:
sorted_nodes_and_labels = sorted(
zip(list_of_nodes, node_labels), key=lambda node: (min_value_for_metadata_key(node[0], sort_key) is None, min_value_for_metadata_key(node[0], sort_key)),
reverse=reverse_order,
)
sorted_nodes, sorted_labels = zip(*sorted_nodes_and_labels)
return list(sorted_nodes), list(sorted_labels)


def group_node_by_metadata_key(node, key, return_values=False):
Expand All @@ -83,6 +62,38 @@ def get_df_for_uid(node, uid):
return dfc.read()


class TiledViewer:
def __init__(self, tiled_client):
self.tiled_client = tiled_client
class StoredTiledEntry:
def __init__(self, client_obj):
self.source_type = type(client_obj)
self.data = client_obj.read()
self.metadata = client_obj.metadata
# self.processed_data = {}

def read(self):
"""implemented to closer mimic tiled client object funcionality"""
return self.data

def __repr__(self):
return f"Stored data and metadata from {self.source_type}"


class TiledReader(UserDict):
"""Custom dictionary for accessing data from a source tiled client node.
When data is pulled from the node it will also be saved in the `TiledReader.data`
attribute and pulled from there in the future."""

def __init__(self, source_node: Node):
super().__init__()
self.source_node = source_node

def __setitem__(self, *args):
raise RuntimeError("Cannot set uid values using TiledReader")

def __getitem__(self, uid: str):
if uid in self.data.keys():
return self.data[uid]
elif uid in self.source_node.keys():
self.data[uid] = StoredTiledEntry(self.source_node[uid])
return self.data[uid]
else:
raise KeyError("Could not find uid in locally stored data or source node")

0 comments on commit 2ceddc5

Please sign in to comment.