From 655d5a0fccb07da88cbab6c622eb2d5638a17a30 Mon Sep 17 00:00:00 2001 From: Matthew Chan Date: Tue, 4 Dec 2018 14:32:32 -0500 Subject: [PATCH 1/7] Modified circos-styles.css --- assets/circos-styles.css | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/assets/circos-styles.css b/assets/circos-styles.css index 67647fbd8..a4db90bc8 100644 --- a/assets/circos-styles.css +++ b/assets/circos-styles.css @@ -38,7 +38,7 @@ .circos-row-two { margin-top: 2.5%; border: 1px solid #2a3f5f; - padding: 1% 2% 1% 3%; + padding: 1% 2% 3% 3%; } .circos-select-data-set { @@ -70,7 +70,12 @@ .circos-text-area { width: 100%; - height: 12vh; + height: 20vh; +} + +.circos-text-area-explain { + height: 30vh; + width: 100%; } .circos-upload-data { @@ -93,7 +98,8 @@ .circos-column-one { max-height: 88.5vh; - overflow: hidden auto; + padding-left: 1%; + padding-top: 1%; } .circos-column-two { From 08ab1f0ce19922d87b5cdb5469b349b9dc0fa2d4 Mon Sep 17 00:00:00 2001 From: Matthew Chan Date: Tue, 4 Dec 2018 14:32:43 -0500 Subject: [PATCH 2/7] JP & PF issue changes to app_circos.py --- tests/dash/app_circos.py | 795 ++++++++++++++++++++++++--------------- 1 file changed, 484 insertions(+), 311 deletions(-) diff --git a/tests/dash/app_circos.py b/tests/dash/app_circos.py index 402a5b52a..41a38d482 100644 --- a/tests/dash/app_circos.py +++ b/tests/dash/app_circos.py @@ -59,16 +59,32 @@ def parse_contents(contents, filename, date): return +def header_colors(): + return { + 'bg_color': '#000', + 'font_color': '#FFF', + 'light_logo': True + } + + empty = dash_bio.DashCircos( id="main-circos", selectEvent={}, layout=[], size=800, config={}, tracks=[] ) upload_instructions = ( - "1. Set Circos Graph to Custom. \n" + - "2. Select Dataset (Press Download for Sample Data). \n" + - "3. Drag and Drop .CSV for each Dataset dropdown (layout -> layout.csv, etc) \n" + - "4. Press Render! \n" + - "5. Reselect Dataset to view data in table." + "1. Select your dataset or (press download for sample data). \n" + + "2. Drag and drop .CSV for each dataset dropdown (layout -> layout.csv, etc) \n" + + "3. Press Render! \n" + + "4. Go to 'View Dataset' tab to view data in table." +) + +circos_explain = ( + "Circos is a circular graph best used to show relationships between entities and periodical data. \n" + + "A Circos graph consists of two main parts, being the layout and tracks. \n\n" + + "The layout sets the basic parameters of the graph such as radius, ticks, labels, etc. \n\n" + + "The tracks are graph layouts that take in a series of data points. " + + "The tracks can be: heatmaps, chords, highlights, histograms, line, scatter, stack and text graphs. \n\n" + + "For a look into Circos and the API please go here: https://github.com/nicgirault/circosJS" ) @@ -79,251 +95,388 @@ def layout(): [ html.Div( [ - html.Div( - [ - dt.DataTable( - id="data-table", - row_selectable=True, - sorting=True, - filtering=True, - css=[{ - 'selector': '.dash-cell div.dash-cell-value', - 'rule': 'display: inline; white-space: inherit; overflow: inherit; text-overflow: inherit;', - }], - style_cell={ - 'whiteSpace': 'no-wrap', - 'overflow': 'hidden', - 'textOverflow': 'ellipsis', - 'maxWidth': 0, - }, - style_table={ - 'maxHeight': '30vh', - }, - n_fixed_rows=1, - ), - html.Div( - id="expected-index"), - ], className="circos-datatable" - ), - html.Div( - [ - html.Div( - [ - html.H5( - "Select Circos Graph"), - dcc.Dropdown( - id="circos-selector", - options=[ - { - "label": "Custom", - "value": "custom", - }, - { - "label": "Heatmap", - "value": "heatmap", - }, - { - "label": "Chords", - "value": "chords", - }, - { - "label": "Highlight", - "value": "highlight", - }, - { - "label": "Histogram", - "value": "histogram", - }, - { - "label": "Line", - "value": "line", - }, - { - "label": "Scatter", - "value": "scatter", - }, - { - "label": "Stack", - "value": "stack", - }, - { - "label": "Text", - "value": "text", - }, - { - "label": "Sample Parser Dataset", - "value": "parser_data", - }, + dcc.Tabs( + id="circos-tabs", + value="circos-tab-select", + children=[ + dcc.Tab( + label="Select", + value="circos-tab-select", + children=[ + html.Div( + [ + html.Div( + [ + html.H5( + "Select Circos Graph" + ), + dcc.Dropdown( + id="circos-selector", + options=[ + { + "label": "Custom", + "value": "custom", + }, + { + "label": "Heatmap", + "value": "heatmap", + }, + { + "label": "Chords", + "value": "chords", + }, + { + "label": "Highlight", + "value": "highlight", + }, + { + "label": "Histogram", + "value": "histogram", + }, + { + "label": "Line", + "value": "line", + }, + { + "label": "Scatter", + "value": "scatter", + }, + { + "label": "Stack", + "value": "stack", + }, + { + "label": "Text", + "value": "text", + }, + { + "label": "Sample Parser Dataset", + "value": "parser_data", + }, + ], + value="chords", + ), + ], + className="six columns", + ), + html.Div( + [ + html.H5( + "Size Slider"), + html.Div( + [ + dcc.Slider( + id="size-slider", + marks={ + 500: "Min", + 800: "Max", + }, + min=500, + max=800, + step=10, + value=600, + ) + ], + className="circos-size-slider", + ), + ], + className="six columns", + ), ], - value="chords", + className="circos-row-one row", ), - ], - className="six columns", - ), - html.Div( - [ - html.H5( - "Size Slider"), html.Div( [ - dcc.Slider( - id="size-slider", - marks={ - 500: "Min", - 800: "Max", - }, - min=500, - max=800, - step=10, - value=600, - ) - ], className="circos-size-slider" - ), - ], - className="six columns", - ), - ], - className="circos-row-one row", - ), - html.Div( - [ - html.Div( - [ - html.H5( - "Select Data Set" - ), - dcc.Dropdown( - id="data-selector", - options=[ - { - "label": "Layout", - "value": "layout", - } + html.Div( + [ + html.H5( + "Hover/Click Data"), + dcc.Textarea( + id="event-data-select", + placeholder="Hover or click on data to see it here.", + value="Hover or click on data to see it here.", + className="circos-event-data", + ), + ], + className="twelve columns", + ), ], - value="layout", - ), - ], className="six columns", - ), - html.Div( - [ - html.H5( - "Hover/Click Data" + className="circos-row-two row", ), - dcc.Textarea( - id="event-data", - placeholder="Hover or click on data to see it here.", - value="Hover or click on data to see it here.", - className="circos-event-data" + html.Div( + [ + html.Div( + [ + html.H5( + "What is Circos?"), + dcc.Textarea( + value=circos_explain, + className="circos-text-area-explain", + ), + ], + className="twelve columns", + ), + ], + className="circos-row-two row", ), ], - className="six columns", ), - ], - className="circos-row-two row", - ), - html.Div( - [ - html.Div( - [ - html.H5( - "Upload Data", - className="circos-select-data-set five columns", + dcc.Tab( + label="View Dataset", + value="circos-tab-dataset", + children=[ + html.Div( + [ + dt.DataTable( + id="data-table", + row_selectable=True, + sorting=True, + filtering=True, + css=[ + { + "selector": ".dash-cell div.dash-cell-value", + "rule": "display: inline; white-space: inherit; overflow: inherit; text-overflow: inherit;", + } + ], + style_cell={ + "whiteSpace": "no-wrap", + "overflow": "hidden", + "textOverflow": "ellipsis", + "maxWidth": 0, + }, + style_table={ + "maxHeight": "50vh" + }, + n_fixed_rows=1, + ), + html.Div( + id="expected-index"), + ], + className="circos-datatable", ), html.Div( [ html.Div( [ - html.A( - html.Button( - "Download", - className="circos-button-data five columns", - ), - href="/assets/sample_data/circos_sample_data.rar", - download="circos_sample_data.rar" - ), - html.Button( - "Render", - id="render-button", - className="circos-button-render five columns", + html.H5( + "Select Data Set to View"), + dcc.Dropdown( + id="data-selector", + options=[ + { + "label": "Layout", + "value": "layout", + } + ], + value="layout", ), - ], className="row", - ) - ], className="six columns" - ) + ], + className="six columns", + ), + ], + className="circos-row-two row", + ), ], - className="circos-row-three row", ), - html.Div( - [ - html.Div( - [ - dcc.Textarea( - value=upload_instructions, - className="circos-text-area" - ) - ], className="six columns" - ), + dcc.Tab( + label="Custom Graph", + value="circos-tab-custom", + children=[ html.Div( [ - dcc.Upload( - id="upload-data", - children=html.Div( - [ - "Drag and Drop .CSV file here!" - ], - ), - className="circos-upload-data", - multiple=True, - ) - ], className="six columns" + html.Div( + [ + html.H5( + "Upload Data", + className="circos-select-data-set five columns", + ), + html.Div( + [ + html.Div( + [ + html.A( + html.Button( + "Download", + className="circos-button-data five columns", + ), + href="/assets/sample_data/circos_sample_data.rar", + download="circos_sample_data.rar", + ), + html.Button( + "Render", + id="render-button", + className="circos-button-render five columns", + ), + ], + className="row", + ) + ], + className="six columns", + ), + ], + className="circos-row-three row", + ), + html.Div( + [ + html.Div( + [ + dcc.Textarea( + value=upload_instructions, + className="circos-text-area", + ) + ], + className="six columns", + ), + html.Div( + [ + dcc.Upload( + id="upload-data", + children=html.Div( + [ + "Drag and Drop .CSV file here!" + ] + ), + className="circos-upload-data", + multiple=True, + ) + ], + className="six columns", + ), + ], + className="circos-row-four row", + ), + html.Div( + [ + html.Div( + [ + html.H5( + "Select Upload Data"), + dcc.Dropdown( + id="data-selector-custom", + options=[ + { + "label": "Layout", + "value": 0, + }, + { + "label": "Track 1", + "value": 1, + }, + { + "label": "Track 2", + "value": 2, + }, + ], + value=0, + ), + ], + className="six columns", + ), + html.Div( + [ + html.H5( + "Size Slider"), + html.Div( + [ + dcc.Slider( + id="size-slider-custom", + marks={ + 500: "Min", + 800: "Max", + }, + min=500, + max=800, + step=10, + value=600, + ) + ], + className="circos-size-slider", + ), + ], + className="six columns", + ), + + ], + className="circos-row-two row", + ), + html.Div( + [ + html.H5( + "Hover/Click Data"), + dcc.Textarea( + id="event-data-custom", + placeholder="Hover or click on data to see it here.", + value="Hover or click on data to see it here.", + className="circos-event-data", + ), + ], + className="circos-row-two twelve columns", + ), + ], + className="row", ) ], - className="circos-row-four row", ), ], - className="row", ), ], className="circos-column-one five columns", ), html.Div( id="circos-hold", - children=[ - empty - ], + children=[empty], className="circos-column-two seven columns", ), - ], className="row", + ], + className="row", ), html.Div( [ html.Div(id="output-data-upload"), - dcc.Interval( - id="init", n_intervals=0, interval=100000000), - ], className="circos-display-none" + html.Div(id="previous-tab"), + html.Div(id="event-data-store"), + dcc.Interval(id="init", n_intervals=0, + interval=100000000), + ], + className="circos-display-none", ), ] ) def callbacks(app): - @app.callback( - Output("init", "interval"), - [Input("init", "n_intervals")] - ) + @app.callback(Output("init", "interval"), [Input("init", "n_intervals")]) def init_callbacks_on_start(init): if init >= 1: return 10000000000000 return 1000 + # Store Previous Tab LIFO + + + @app.callback( + Output("previous-tab", "children"), + [Input("circos-tabs", "value")], + [State("previous-tab", "children")] + ) + def store_previous_tab(tabs, prev_tab): + if prev_tab is None: + prev_tab = [None, None] + prev_tab.append(tabs) + prev_tab.pop(0) + return prev_tab + + @app.callback( Output("data-selector", "options"), [Input("circos-hold", "children"), - Input("circos-selector", "value")], - [State("main-circos", "tracks")], + Input("circos-selector", "value"), + Input("circos-tabs", "value")], + [State("main-circos", "tracks"), + State("previous-tab", "children")], ) - def event_dropdown(dropdown, circos_select, tracks): - if tracks is not None and circos_select != "custom": + def event_dropdown(dropdown, circos_select, tabs, tracks, prev_tab): + if tracks is not None and prev_tab[0] == "circos-tab-select": array = [] dropdown = [] @@ -333,33 +486,34 @@ def event_dropdown(dropdown, circos_select, tracks): for i in range(len(tracks)): if array[i] != "CHORDS": - dropdown.append( - {"label": "{}".format(array[i]), "value": i}) + dropdown.append({"label": "{}".format(array[i]), "value": i}) dropdown.append({"label": "LAYOUT", "value": "layout"}.copy()) return dropdown - elif circos_select == "custom": + elif prev_tab[0] == "circos-tab-custom": dropdown = [ - {"label": "Layout", "value": 0}, - {"label": "Track One", "value": 1}, - {"label": "Track Two", "value": 2}, + {"label": "LAYOUT", "value": "layout"}, + {"label": "HIGHLIGHT", "value": 0}, + {"label": "HIGHLIGHT", "value": 1}, ] return dropdown else: return ["blank"] + @app.callback( Output("output-data-upload", "children"), [Input("upload-data", "contents")], - [State("upload-data", "filename"), - State("upload-data", "last_modified"), - State("output-data-upload", "children"), - State("data-selector", "value"), - State("circos-selector", "value")] + [ + State("upload-data", "filename"), + State("upload-data", "last_modified"), + State("output-data-upload", "children"), + State("data-selector-custom", "value") + ], ) def update_output( - list_of_contents, list_of_names, list_of_dates, data, upload_select, circos_select + list_of_contents, list_of_names, list_of_dates, data, upload_select ): if data == None: array = [None, None, None] @@ -378,24 +532,29 @@ def update_output( return json.dumps(array) return + @app.callback( Output("circos-hold", "children"), - [Input("circos-selector", "value"), - Input("size-slider", "value"), - Input("init", "n_intervals"), - Input("render-button", "n_clicks")], + [ + Input("circos-tabs", "value"), + Input("circos-selector", "value"), + Input("size-slider", "value"), + Input("size-slider-custom", "value"), + Input("init", "n_intervals"), + Input("render-button", "n_clicks"), + ], [State("output-data-upload", "children")], ) - def init(circos_select, size, init_onstart, render_button, uploadData): - if circos_select == "custom" and uploadData != None: + def init(tabs, circos_select, size, size_custom, init_onstart, render_button, uploadData): + if (tabs == "circos-tab-custom" or tabs == "circos-tab-dataset") and uploadData != None: array = json.loads(uploadData) return dash_bio.DashCircos( id="main-circos", - selectEvent={"0": "hover", "1": "click"}, + selectEvent={"0": "both", "1": "both"}, layout=array[0], config={ - "innerRadius": size / 2 - 80, - "outerRadius": size / 2 - 40, + "innerRadius": size_custom / 2 - 80, + "outerRadius": size_custom / 2 - 40, "ticks": {"display": False, "labelDenominator": 1000000}, "labels": { "position": "center", @@ -410,8 +569,8 @@ def init(circos_select, size, init_onstart, render_button, uploadData): "type": "HIGHLIGHT", "data": array[1], "config": { - "innerRadius": size / 2 - 80, - "outerRadius": size / 2 - 40, + "innerRadius": size_custom / 2 - 80, + "outerRadius": size_custom / 2 - 40, "opacity": 0.3, "tooltipContent": {"name": "all"}, "color": {"name": "color"}, @@ -421,8 +580,8 @@ def init(circos_select, size, init_onstart, render_button, uploadData): "type": "HIGHLIGHT", "data": array[2], "config": { - "innerRadius": size / 2 - 80, - "outerRadius": size / 2 - 40, + "innerRadius": size_custom / 2 - 80, + "outerRadius": size_custom / 2 - 40, "opacity": 0.3, "tooltipContent": {"name": "all"}, "color": {"name": "color"}, @@ -432,7 +591,7 @@ def init(circos_select, size, init_onstart, render_button, uploadData): size=800, style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "parser_data": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "parser_data": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover", "1": "click"}, @@ -454,29 +613,29 @@ def init(circos_select, size, init_onstart, render_button, uploadData): "type": "HIGHLIGHT", "data": parsed_track_one, "config": { - "innerRadius": size / 2 - 80, - "outerRadius": size / 2 - 40, - "opacity": 0.3, - "tooltipContent": {"name": "block_id"}, - "color": {"name": "color"}, + "innerRadius": size / 2 - 80, + "outerRadius": size / 2 - 40, + "opacity": 0.3, + "tooltipContent": {"name": "block_id"}, + "color": {"name": "color"}, }, }, { "type": "HIGHLIGHT", "data": parsed_track_two, "config": { - "innerRadius": size / 2 - 80, - "outerRadius": size / 2 - 40, - "opacity": 0.3, - "tooltipContent": {"name": "block_id"}, - "color": {"name": "color"}, + "innerRadius": size / 2 - 80, + "outerRadius": size / 2 - 40, + "opacity": 0.3, + "tooltipContent": {"name": "block_id"}, + "color": {"name": "color"}, }, }, ], size=800, - style={"display": "flex", "justify-content": "center"} + style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "heatmap": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "heatmap": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover", "1": "hover"}, @@ -520,7 +679,7 @@ def init(circos_select, size, init_onstart, render_button, uploadData): size=800, style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "chords": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "chords": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "both", "1": "both"}, @@ -569,7 +728,7 @@ def init(circos_select, size, init_onstart, render_button, uploadData): size=800, style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "highlight": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "highlight": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover"}, @@ -597,7 +756,7 @@ def init(circos_select, size, init_onstart, render_button, uploadData): style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "histogram": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "histogram": return dash_bio.DashCircos( id="main-circos", layout=circos_graph_data["GRCh37"], @@ -634,22 +793,29 @@ def init(circos_select, size, init_onstart, render_button, uploadData): size=800, style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "line": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "line": return dash_bio.DashCircos( id="main-circos", - selectEvent={"0": "both", "1": "both", "2": "both", "3": "both", "4": "both" ,"5": "both", "6": "both", "7":"both"}, + selectEvent={ + "0": "both", + "1": "both", + "2": "both", + "3": "both", + "4": "both", + "5": "both", + "6": "both", + "7": "both", + }, layout=list( - filter(lambda d: d["id"] in [ - "chr1", "chr2", "chr3"], circos_graph_data["GRCh37"]) + filter( + lambda d: d["id"] in ["chr1", "chr2", "chr3"], + circos_graph_data["GRCh37"], + ) ), config={ "innerRadius": size / 2 - 150, "outerRadius": size / 2 - 130, - "ticks": { - "display": False, - "spacing": 1000000, - "labelSuffix": "", - }, + "ticks": {"display": False, "spacing": 1000000, "labelSuffix": ""}, "labels": { "position": "center", "display": False, @@ -689,8 +855,7 @@ def init(circos_select, size, init_onstart, render_button, uploadData): "targetEnd": "value", }, "axes": [ - {"spacing": 0.001, "thickness": 1, - "color": "#666666"} + {"spacing": 0.001, "thickness": 1, "color": "#666666"} ], "backgrounds": [ { @@ -795,22 +960,26 @@ def init(circos_select, size, init_onstart, render_button, uploadData): size=800, style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "scatter": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "scatter": return dash_bio.DashCircos( id="main-circos", - selectEvent={"0": "hover", "1": "both", "3": "both", "4": "both", "5": "both"}, + selectEvent={ + "0": "hover", + "1": "both", + "3": "both", + "4": "both", + "5": "both", + }, layout=list( - filter(lambda d: d["id"] in [ - "chr1", "chr2", "chr3"], circos_graph_data["GRCh37"]) + filter( + lambda d: d["id"] in ["chr1", "chr2", "chr3"], + circos_graph_data["GRCh37"], + ) ), config={ "innerRadius": size / 2 - 150, "outerRadius": size / 2 - 130, - "ticks": { - "display": False, - "spacing": 1000000, - "labelSuffix": "", - }, + "ticks": {"display": False, "spacing": 1000000, "labelSuffix": ""}, "labels": {"display": False}, }, tracks=[ @@ -834,8 +1003,10 @@ def init(circos_select, size, init_onstart, render_button, uploadData): { "type": "SCATTER", "data": list( - filter(lambda d: float( - d["value"]) > 0.007, circos_graph_data["snp250"]) + filter( + lambda d: float(d["value"]) > 0.007, + circos_graph_data["snp250"], + ) ), "config": { "innerRadius": 0.65, @@ -938,8 +1109,10 @@ def init(circos_select, size, init_onstart, render_button, uploadData): { "type": "SCATTER", "data": list( - filter(lambda d: float( - d["value"]) < 0.002, circos_graph_data["snp250"]) + filter( + lambda d: float(d["value"]) < 0.002, + circos_graph_data["snp250"], + ) ), "config": { "tooltipContent": { @@ -1043,27 +1216,18 @@ def init(circos_select, size, init_onstart, render_button, uploadData): size=800, style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "stack": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "stack": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover"}, layout=[ - {"id": "chr9", "len": 8000000, - "label": "chr9", "color": "#FFCC00"} + {"id": "chr9", "len": 8000000, "label": "chr9", "color": "#FFCC00"} ], config={ "innerRadius": size / 2 - 50, "outerRadius": size / 2 - 30, - "ticks": { - "display": False, - "labels": False, - "spacing": 10000, - }, - "labels": { - "display": False, - "labels": False, - "spacing": 10000, - }, + "ticks": {"display": False, "labels": False, "spacing": 10000}, + "labels": {"display": False, "labels": False, "spacing": 10000}, }, tracks=[ { @@ -1084,22 +1248,16 @@ def init(circos_select, size, init_onstart, render_button, uploadData): "end": "end", "start": "start", "value": [150000, 120000, 90000, 60000, 30000], - "color": [ - "red", - "black", - "#000000", - "#999", - "#BBB", - ], + "color": ["red", "black", "#000000", "#999", "#BBB"], } - } + }, }, } ], size=800, style={"display": "flex", "justify-content": "center"}, ) - elif circos_select == "text": + elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "text": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover", "1": "both"}, @@ -1115,7 +1273,8 @@ def init(circos_select, size, init_onstart, render_button, uploadData): "type": "HIGHLIGHT", "data": list( filter( - lambda d: d["block_id"] == circos_graph_data["GRCh37"][0]["id"], + lambda d: d["block_id"] + == circos_graph_data["GRCh37"][0]["id"], circos_graph_data["cytobands"], ) ), @@ -1137,7 +1296,8 @@ def init(circos_select, size, init_onstart, render_button, uploadData): "block_id": d["block_id"], }, filter( - lambda d: d["block_id"] == circos_graph_data["GRCh37"][0]["id"], + lambda d: d["block_id"] + == circos_graph_data["GRCh37"][0]["id"], circos_graph_data["cytobands"], ), ) @@ -1154,16 +1314,20 @@ def init(circos_select, size, init_onstart, render_button, uploadData): ) return empty + @app.callback( Output("data-table", "data"), - [Input("data-selector", "value"), - Input("render-button", "n_clicks"), - Input("data-selector", "options"), - Input("data-table", "selected_cells")], - [State("main-circos", "layout"), - State("main-circos", "tracks")], + [ + Input("data-selector", "value"), + Input("render-button", "n_clicks"), + Input("data-selector", "options"), + Input("data-table", "selected_cells"), + ], + [State("main-circos", "layout"), State("main-circos", "tracks")], ) - def update_table_rows(data_selector, render_button, circos_trigger, selected, layout, tracks): + def update_table_rows( + data_selector, render_button, circos_trigger, selected, layout, tracks + ): try: if data_selector == "layout": df = pd.DataFrame(layout) @@ -1171,32 +1335,41 @@ def update_table_rows(data_selector, render_button, circos_trigger, selected, la df = pd.DataFrame(tracks[data_selector]["data"]) return df.to_dict("records") except: - df = pd.DataFrame() + df = pd.DataFrame() return df + @app.callback( Output("data-table", "columns"), - [Input("data-selector", "value"), - Input("render-button", "n_clicks"), - Input("data-selector", "options"), - Input("data-table", "selected_cells")], - [State("main-circos", "layout"), - State("main-circos", "tracks")], + [ + Input("data-selector", "value"), + Input("render-button", "n_clicks"), + Input("data-selector", "options"), + Input("data-table", "selected_cells"), + ], + [State("main-circos", "layout"), State("main-circos", "tracks")], ) - def update_table_columns(data_selector, render_button, circos_trigger, selected, layout, tracks): + def update_table_columns( + data_selector, render_button, circos_trigger, selected, layout, tracks + ): try: if data_selector == "layout": df = pd.DataFrame(layout) else: df = pd.DataFrame(tracks[data_selector]["data"]) - return [{'id': i, 'name': i} for i in df.columns] + return [{"id": i, "name": i} for i in df.columns] except: - df = pd.DataFrame() + df = pd.DataFrame() return df - @app.callback( - Output("event-data", "value"), - [Input("main-circos", "eventDatum")] - ) - def event_data(eventDatum): + + + @app.callback(Output("event-data-select", "value"), [Input("main-circos", "eventDatum")]) + def event_data_select(eventDatum): return str(eventDatum) + + @app.callback(Output("event-data-custom", "value"), [ + Input("render-button", "n_clicks"), + Input("main-circos", "eventDatum")]) + def event_data_custom(n_clicks, eventDatum): + return str(eventDatum) From 52558bb6a82bf08f2bd1ceaa6090c51e58141cb0 Mon Sep 17 00:00:00 2001 From: Matthew Chan Date: Wed, 5 Dec 2018 16:56:53 -0500 Subject: [PATCH 3/7] Add new styles to circos-styles.css --- assets/circos-styles.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/circos-styles.css b/assets/circos-styles.css index a4db90bc8..1d486a1bb 100644 --- a/assets/circos-styles.css +++ b/assets/circos-styles.css @@ -21,6 +21,10 @@ overflowY: hidden; } +.circos-chords-text { + padding-top: 4%; +} + .circos-size-slider { padding-left: 3.5%; } @@ -110,4 +114,4 @@ .circos-display-none { display: none; -} \ No newline at end of file +} From be8ae440bbab29222479357efea0a9ec59a7165f Mon Sep 17 00:00:00 2001 From: Matthew Chan Date: Wed, 5 Dec 2018 16:57:21 -0500 Subject: [PATCH 4/7] Add highlight chord on data-table select --- tests/dash/app_circos.py | 94 +++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/tests/dash/app_circos.py b/tests/dash/app_circos.py index 41a38d482..e80c96800 100644 --- a/tests/dash/app_circos.py +++ b/tests/dash/app_circos.py @@ -9,6 +9,7 @@ import io import pandas as pd import json +from textwrap import dedent with open('./tests/dash/sample_data/circos_graph_data.json', 'r') as circos_graph_data: circos_graph_data = json.load(circos_graph_data) @@ -66,7 +67,19 @@ def header_colors(): 'light_logo': True } +def circos_explain(): + return dcc.Markdown(dedent(''' + Circos is a circular graph best used to show relationships between entities and periodical data. + A Circos graph consists of two main parts, being the layout and tracks.The layout sets the basic + parameters of the graph such as radius, ticks, labels, etc. + The tracks are graph layouts that take in a series of data points and can be one of: + heatmaps, chords, highlights, histograms, line, scatter, stack and text graphs. Tracks can be + place on and around the layout graph. + For a look into Circos and the API please go here: + [https://github.com/nicgirault/circosJS](https://github.com/nicgirault/circosJS") + ''')) + empty = dash_bio.DashCircos( id="main-circos", selectEvent={}, layout=[], size=800, config={}, tracks=[] ) @@ -78,13 +91,8 @@ def header_colors(): + "4. Go to 'View Dataset' tab to view data in table." ) -circos_explain = ( - "Circos is a circular graph best used to show relationships between entities and periodical data. \n" - + "A Circos graph consists of two main parts, being the layout and tracks. \n\n" - + "The layout sets the basic parameters of the graph such as radius, ticks, labels, etc. \n\n" - + "The tracks are graph layouts that take in a series of data points. " - + "The tracks can be: heatmaps, chords, highlights, histograms, line, scatter, stack and text graphs. \n\n" - + "For a look into Circos and the API please go here: https://github.com/nicgirault/circosJS" +circos_link = ( + "For a look into Circos and the API please go here: https://github.com/nicgirault/circosJS" ) @@ -208,11 +216,9 @@ def layout(): html.Div( [ html.H5( - "What is Circos?"), - dcc.Textarea( - value=circos_explain, - className="circos-text-area-explain", + "What is Circos?" ), + circos_explain() ], className="twelve columns", ), @@ -273,6 +279,13 @@ def layout(): ], className="six columns", ), + html.Div( + id="chords-text", + className="circos-chords-text six columns", + children=[ + "" + ] + ) ], className="circos-row-two row", ), @@ -451,9 +464,7 @@ def init_callbacks_on_start(init): return 10000000000000 return 1000 - # Store Previous Tab LIFO - - + # Store Previous Tab @app.callback( Output("previous-tab", "children"), [Input("circos-tabs", "value")], @@ -485,8 +496,7 @@ def event_dropdown(dropdown, circos_select, tabs, tracks, prev_tab): array.append(v) for i in range(len(tracks)): - if array[i] != "CHORDS": - dropdown.append({"label": "{}".format(array[i]), "value": i}) + dropdown.append({"label": "{}".format(array[i]), "value": i}) dropdown.append({"label": "LAYOUT", "value": "layout"}.copy()) return dropdown @@ -535,19 +545,20 @@ def update_output( @app.callback( Output("circos-hold", "children"), - [ - Input("circos-tabs", "value"), - Input("circos-selector", "value"), - Input("size-slider", "value"), - Input("size-slider-custom", "value"), - Input("init", "n_intervals"), - Input("render-button", "n_clicks"), - ], - [State("output-data-upload", "children")], + [Input("circos-tabs", "value"), + Input("circos-selector", "value"), + Input("size-slider", "value"), + Input("size-slider-custom", "value"), + Input("init", "n_intervals"), + Input("render-button", "n_clicks"), + Input("data-table", "selected_rows")], + [State("output-data-upload", "children"), + State("data-table", "data"), + State("data-selector", "value")], ) - def init(tabs, circos_select, size, size_custom, init_onstart, render_button, uploadData): - if (tabs == "circos-tab-custom" or tabs == "circos-tab-dataset") and uploadData != None: - array = json.loads(uploadData) + def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,selected_row, upload_data, table_data, data_selector): + if (tabs == "circos-tab-custom" or tabs == "circos-tab-dataset") and upload_data != None: + array = json.loads(upload_data) return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "both", "1": "both"}, @@ -680,6 +691,12 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button, up style={"display": "flex", "justify-content": "center"}, ) elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "chords": + if selected_row != None and data_selector == 1: + for i in list(range(len(circos_graph_data["chords"]))): + circos_graph_data["chords"][i]["color"] = "#ff5722" + for i in selected_row: + circos_graph_data["chords"][i]["color"] = "#00cc96" + return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "both", "1": "both"}, @@ -714,7 +731,7 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button, up "config": { "logScale": False, "opacity": 0.7, - "color": "#ff5722", + "color": {"name": "color"}, "tooltipContent": { "source": "source", "sourceID": "id", @@ -1315,6 +1332,15 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button, up return empty + @app.callback( + Output("chords-text", "children"), + [Input("circos-selector", "value")] + ) + def update_chords_text(circos_select): + if circos_select == "chords": + return "Select chords and select row in dash-table to highlight chords." + return "" + @app.callback( Output("data-table", "data"), [ @@ -1331,6 +1357,10 @@ def update_table_rows( try: if data_selector == "layout": df = pd.DataFrame(layout) + elif tracks[data_selector]['type'] == "CHORDS": + new_data = {"color": d.pop("color") for d in tracks[data_selector]["data"]} + new_chords = [{'{}_{}'.format(k, a): b for k, v in d.items() for a, b in v.items()} for d in tracks[data_selector]["data"]] + df = pd.DataFrame(new_chords) else: df = pd.DataFrame(tracks[data_selector]["data"]) return df.to_dict("records") @@ -1355,6 +1385,10 @@ def update_table_columns( try: if data_selector == "layout": df = pd.DataFrame(layout) + elif tracks[data_selector]['type'] == "CHORDS": + new_data = {"color": d.pop("color") for d in tracks[data_selector]["data"]} + new_chords = [{'{}_{}'.format(k, a): b for k, v in d.items() for a, b in v.items()} for d in tracks[data_selector]["data"]] + df = pd.DataFrame(new_chords) else: df = pd.DataFrame(tracks[data_selector]["data"]) return [{"id": i, "name": i} for i in df.columns] @@ -1373,3 +1407,5 @@ def event_data_select(eventDatum): Input("main-circos", "eventDatum")]) def event_data_custom(n_clicks, eventDatum): return str(eventDatum) + + \ No newline at end of file From 93404a829c0f8ddbde35c63ff3e2ee5bd3c587b6 Mon Sep 17 00:00:00 2001 From: Matthew Chan Date: Wed, 5 Dec 2018 16:57:39 -0500 Subject: [PATCH 5/7] Modify dataset to work with data-table --- tests/dash/sample_data/circos_graph_data.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/dash/sample_data/circos_graph_data.json b/tests/dash/sample_data/circos_graph_data.json index 6ed16c496..c4cd64604 100644 --- a/tests/dash/sample_data/circos_graph_data.json +++ b/tests/dash/sample_data/circos_graph_data.json @@ -23495,6 +23495,7 @@ "chords": [ { + "color": "#ff5722", "source": { "id": "chr19", "start": 22186054, @@ -23507,6 +23508,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr4", "start": 74807187, @@ -23519,6 +23521,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr12", "start": 32372614, @@ -23531,6 +23534,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr12", "start": 112372616, @@ -23543,6 +23547,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr2", "start": 131012108, @@ -23555,6 +23560,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr22", "start": 19265412, @@ -23567,6 +23573,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr1", "start": 89852783, @@ -23579,6 +23586,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr21", "start": 7827203, @@ -23591,6 +23599,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr21", "start": 7826936, @@ -23603,6 +23612,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr21", "start": 7827510, @@ -23615,6 +23625,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr1", "start": -1431493, @@ -23627,6 +23638,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 35360418, @@ -23639,6 +23651,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478116, @@ -23651,6 +23664,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478131, @@ -23663,6 +23677,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478111, @@ -23675,6 +23690,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478116, @@ -23687,6 +23703,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478116, @@ -23699,6 +23716,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478117, @@ -23711,6 +23729,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478115, @@ -23723,6 +23742,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478115, @@ -23735,6 +23755,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478117, @@ -23747,6 +23768,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478117, @@ -23759,6 +23781,7 @@ } }, { + "color": "#ff5722", "source": { "id": "chr17", "start": 31478114, From 25f1a7302fe54da1efdda6335604f663d22bf483 Mon Sep 17 00:00:00 2001 From: Matthew Chan Date: Wed, 5 Dec 2018 17:05:56 -0500 Subject: [PATCH 6/7] Remove custom from circos select dropdown --- tests/dash/app_circos.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/dash/app_circos.py b/tests/dash/app_circos.py index e80c96800..6ea508983 100644 --- a/tests/dash/app_circos.py +++ b/tests/dash/app_circos.py @@ -121,10 +121,6 @@ def layout(): dcc.Dropdown( id="circos-selector", options=[ - { - "label": "Custom", - "value": "custom", - }, { "label": "Heatmap", "value": "heatmap", From 997bcdc3f23c27451bd18bc8facb214248517ffa Mon Sep 17 00:00:00 2001 From: Matthew Chan Date: Wed, 5 Dec 2018 17:54:23 -0500 Subject: [PATCH 7/7] PEP8 and reduce redundant code for app_circos.py --- tests/dash/app_circos.py | 279 ++++++++++++++++++++++----------------- 1 file changed, 160 insertions(+), 119 deletions(-) diff --git a/tests/dash/app_circos.py b/tests/dash/app_circos.py index 6ea508983..f99b78c90 100644 --- a/tests/dash/app_circos.py +++ b/tests/dash/app_circos.py @@ -5,15 +5,18 @@ import dash_html_components as html import dash_table as dt from dash_bio.utils import circosParser as cp +from textwrap import dedent import base64 import io import pandas as pd import json -from textwrap import dedent -with open('./tests/dash/sample_data/circos_graph_data.json', 'r') as circos_graph_data: + +# Main dataset used for all graphs +with open("./tests/dash/sample_data/circos_graph_data.json", "r") as circos_graph_data: circos_graph_data = json.load(circos_graph_data) +# Parsed data using circosParser for parsed_dataset graph parsed_layout = cp.txt_to_layout( file_one_name="./tests/dash/sample_data/circos_GRCh37.txt", file_two_name="./tests/dash/sample_data/circos_GRCh38.txt", @@ -37,13 +40,41 @@ create_local=False, ) - +# Description for gallery def description(): - return 'Dash Circos is a library used to analyze and interpret data using a circular layout, \ + return "Dash Circos is a library used to analyze and interpret data using a circular layout, \ based on the popular Circos graph. Showcase relationships between data/datasets in a \ - beautiful way.' - + beautiful way." +# Dash table call back data +def update_dash_table(data_selector, layout, tracks, orientation): + try: + if data_selector == "layout": + df = pd.DataFrame(layout) + elif tracks[data_selector]["type"] == "CHORDS": + new_data = { + "color": d.pop("color") for d in tracks[data_selector]["data"] + } + new_chords = [ + { + "{}_{}".format(k, a): b + for k, v in d.items() + for a, b in v.items() + } + for d in tracks[data_selector]["data"] + ] + df = pd.DataFrame(new_chords) + else: + df = pd.DataFrame(tracks[data_selector]["data"]) + if orientation == "column": + return [{"id": i, "name": i} for i in df.columns] + elif orientation == "row": + return df.to_dict("records") + except: + df = pd.DataFrame() + return df + +# Content parser used for dcc.Upload def parse_contents(contents, filename, date): content_type, content_string = contents.split(",") @@ -59,16 +90,15 @@ def parse_contents(contents, filename, date): return html.Div(["There was an error processing this file."]) return - +# Header colors def header_colors(): - return { - 'bg_color': '#000', - 'font_color': '#FFF', - 'light_logo': True - } + return {"bg_color": "#000", "font_color": "#FFF", "light_logo": True} +# Circos explaination blurb def circos_explain(): - return dcc.Markdown(dedent(''' + return dcc.Markdown( + dedent( + """ Circos is a circular graph best used to show relationships between entities and periodical data. A Circos graph consists of two main parts, being the layout and tracks.The layout sets the basic parameters of the graph such as radius, ticks, labels, etc. @@ -78,12 +108,16 @@ def circos_explain(): For a look into Circos and the API please go here: [https://github.com/nicgirault/circosJS](https://github.com/nicgirault/circosJS") - ''')) - + """ + ) + ) + +# Empty Circos needed for circos graph callback empty = dash_bio.DashCircos( id="main-circos", selectEvent={}, layout=[], size=800, config={}, tracks=[] ) +# Upload text blurb upload_instructions = ( "1. Select your dataset or (press download for sample data). \n" + "2. Drag and drop .CSV for each dataset dropdown (layout -> layout.csv, etc) \n" @@ -91,10 +125,6 @@ def circos_explain(): + "4. Go to 'View Dataset' tab to view data in table." ) -circos_link = ( - "For a look into Circos and the API please go here: https://github.com/nicgirault/circosJS" -) - def layout(): return html.Div( @@ -165,8 +195,7 @@ def layout(): ), html.Div( [ - html.H5( - "Size Slider"), + html.H5("Size Slider"), html.Div( [ dcc.Slider( @@ -193,8 +222,7 @@ def layout(): [ html.Div( [ - html.H5( - "Hover/Click Data"), + html.H5("Hover/Click Data"), dcc.Textarea( id="event-data-select", placeholder="Hover or click on data to see it here.", @@ -203,7 +231,7 @@ def layout(): ), ], className="twelve columns", - ), + ) ], className="circos-row-two row", ), @@ -211,13 +239,11 @@ def layout(): [ html.Div( [ - html.H5( - "What is Circos?" - ), - circos_explain() + html.H5("What is Circos?"), + circos_explain(), ], className="twelve columns", - ), + ) ], className="circos-row-two row", ), @@ -251,8 +277,7 @@ def layout(): }, n_fixed_rows=1, ), - html.Div( - id="expected-index"), + html.Div(id="expected-index"), ], className="circos-datatable", ), @@ -261,7 +286,8 @@ def layout(): html.Div( [ html.H5( - "Select Data Set to View"), + "Select Data Set to View" + ), dcc.Dropdown( id="data-selector", options=[ @@ -278,10 +304,8 @@ def layout(): html.Div( id="chords-text", className="circos-chords-text six columns", - children=[ - "" - ] - ) + children=[""], + ), ], className="circos-row-two row", ), @@ -359,7 +383,8 @@ def layout(): html.Div( [ html.H5( - "Select Upload Data"), + "Select Upload Data" + ), dcc.Dropdown( id="data-selector-custom", options=[ @@ -384,7 +409,8 @@ def layout(): html.Div( [ html.H5( - "Size Slider"), + "Size Slider" + ), html.Div( [ dcc.Slider( @@ -404,18 +430,16 @@ def layout(): ], className="six columns", ), - ], className="circos-row-two row", ), html.Div( [ - html.H5( - "Hover/Click Data"), + html.H5("Hover/Click Data"), dcc.Textarea( id="event-data-custom", placeholder="Hover or click on data to see it here.", - value="Hover or click on data to see it here.", + value="", className="circos-event-data", ), ], @@ -427,7 +451,7 @@ def layout(): ], ), ], - ), + ) ], className="circos-column-one five columns", ), @@ -444,8 +468,7 @@ def layout(): html.Div(id="output-data-upload"), html.Div(id="previous-tab"), html.Div(id="event-data-store"), - dcc.Interval(id="init", n_intervals=0, - interval=100000000), + dcc.Interval(id="init", n_intervals=0, interval=100000000), ], className="circos-display-none", ), @@ -454,6 +477,7 @@ def layout(): def callbacks(app): + # Init all callbacks on start of application @app.callback(Output("init", "interval"), [Input("init", "n_intervals")]) def init_callbacks_on_start(init): if init >= 1: @@ -464,7 +488,7 @@ def init_callbacks_on_start(init): @app.callback( Output("previous-tab", "children"), [Input("circos-tabs", "value")], - [State("previous-tab", "children")] + [State("previous-tab", "children")], ) def store_previous_tab(tabs, prev_tab): if prev_tab is None: @@ -473,14 +497,15 @@ def store_previous_tab(tabs, prev_tab): prev_tab.pop(0) return prev_tab - + # Dynamically update data-selector drop down on graph change @app.callback( Output("data-selector", "options"), - [Input("circos-hold", "children"), - Input("circos-selector", "value"), - Input("circos-tabs", "value")], - [State("main-circos", "tracks"), - State("previous-tab", "children")], + [ + Input("circos-hold", "children"), + Input("circos-selector", "value"), + Input("circos-tabs", "value"), + ], + [State("main-circos", "tracks"), State("previous-tab", "children")], ) def event_dropdown(dropdown, circos_select, tabs, tracks, prev_tab): if tracks is not None and prev_tab[0] == "circos-tab-select": @@ -507,7 +532,7 @@ def event_dropdown(dropdown, circos_select, tabs, tracks, prev_tab): else: return ["blank"] - + # Take in and return uploaded .CSV data @app.callback( Output("output-data-upload", "children"), [Input("upload-data", "contents")], @@ -515,7 +540,7 @@ def event_dropdown(dropdown, circos_select, tabs, tracks, prev_tab): State("upload-data", "filename"), State("upload-data", "last_modified"), State("output-data-upload", "children"), - State("data-selector-custom", "value") + State("data-selector-custom", "value"), ], ) def update_output( @@ -538,22 +563,39 @@ def update_output( return json.dumps(array) return - + # Return Circos Graph with specified layout & dataset @app.callback( Output("circos-hold", "children"), - [Input("circos-tabs", "value"), - Input("circos-selector", "value"), - Input("size-slider", "value"), - Input("size-slider-custom", "value"), - Input("init", "n_intervals"), - Input("render-button", "n_clicks"), - Input("data-table", "selected_rows")], - [State("output-data-upload", "children"), - State("data-table", "data"), - State("data-selector", "value")], + [ + Input("circos-tabs", "value"), + Input("circos-selector", "value"), + Input("size-slider", "value"), + Input("size-slider-custom", "value"), + Input("init", "n_intervals"), + Input("render-button", "n_clicks"), + Input("data-table", "selected_rows"), + ], + [ + State("output-data-upload", "children"), + State("data-table", "data"), + State("data-selector", "value"), + ], ) - def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,selected_row, upload_data, table_data, data_selector): - if (tabs == "circos-tab-custom" or tabs == "circos-tab-dataset") and upload_data != None: + def init( + tabs, + circos_select, + size, + size_custom, + init_onstart, + render_button, + selected_row, + upload_data, + table_data, + data_selector, + ): + if ( + tabs == "circos-tab-custom" or tabs == "circos-tab-dataset" + ) and upload_data != None: array = json.loads(upload_data) return dash_bio.DashCircos( id="main-circos", @@ -598,7 +640,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "parser_data": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "parser_data": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover", "1": "click"}, @@ -642,7 +686,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "heatmap": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "heatmap": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover", "1": "hover"}, @@ -686,7 +732,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "chords": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "chords": if selected_row != None and data_selector == 1: for i in list(range(len(circos_graph_data["chords"]))): circos_graph_data["chords"][i]["color"] = "#ff5722" @@ -741,7 +789,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "highlight": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "highlight": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover"}, @@ -769,7 +819,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "histogram": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "histogram": return dash_bio.DashCircos( id="main-circos", layout=circos_graph_data["GRCh37"], @@ -806,7 +858,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "line": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "line": return dash_bio.DashCircos( id="main-circos", selectEvent={ @@ -842,8 +896,7 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se "type": "HIGHLIGHT", "data": list( filter( - lambda d: d["block_id"] in [ - "chr1", "chr2", "chr3"], + lambda d: d["block_id"] in ["chr1", "chr2", "chr3"], circos_graph_data["cytobands"], ) ), @@ -973,7 +1026,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "scatter": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "scatter": return dash_bio.DashCircos( id="main-circos", selectEvent={ @@ -1000,8 +1055,7 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se "type": "HIGHLIGHT", "data": list( filter( - lambda d: d["block_id"] in [ - "chr1", "chr2", "chr3"], + lambda d: d["block_id"] in ["chr1", "chr2", "chr3"], circos_graph_data["cytobands"], ) ), @@ -1229,7 +1283,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "stack": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "stack": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover"}, @@ -1261,7 +1317,13 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se "end": "end", "start": "start", "value": [150000, 120000, 90000, 60000, 30000], - "color": ["red", "black", "#000000", "#999", "#BBB"], + "color": [ + "red", + "black", + "#000000", + "#999", + "#BBB", + ], } }, }, @@ -1270,7 +1332,9 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se size=800, style={"display": "flex", "justify-content": "center"}, ) - elif (tabs == "circos-tab-select" or tabs == "circos-tab-dataset") and circos_select == "text": + elif ( + tabs == "circos-tab-select" or tabs == "circos-tab-dataset" + ) and circos_select == "text": return dash_bio.DashCircos( id="main-circos", selectEvent={"0": "hover", "1": "both"}, @@ -1327,16 +1391,16 @@ def init(tabs, circos_select, size, size_custom, init_onstart, render_button ,se ) return empty - + # If chords graph selected, output text blurb to let user know of highlight feature @app.callback( - Output("chords-text", "children"), - [Input("circos-selector", "value")] + Output("chords-text", "children"), [Input("circos-selector", "value")] ) def update_chords_text(circos_select): if circos_select == "chords": return "Select chords and select row in dash-table to highlight chords." return "" + # Return dataset to data table @app.callback( Output("data-table", "data"), [ @@ -1350,21 +1414,9 @@ def update_chords_text(circos_select): def update_table_rows( data_selector, render_button, circos_trigger, selected, layout, tracks ): - try: - if data_selector == "layout": - df = pd.DataFrame(layout) - elif tracks[data_selector]['type'] == "CHORDS": - new_data = {"color": d.pop("color") for d in tracks[data_selector]["data"]} - new_chords = [{'{}_{}'.format(k, a): b for k, v in d.items() for a, b in v.items()} for d in tracks[data_selector]["data"]] - df = pd.DataFrame(new_chords) - else: - df = pd.DataFrame(tracks[data_selector]["data"]) - return df.to_dict("records") - except: - df = pd.DataFrame() - return df - + return update_dash_table(data_selector, layout, tracks, "row") + # Return dataset to columns of data table @app.callback( Output("data-table", "columns"), [ @@ -1378,30 +1430,19 @@ def update_table_rows( def update_table_columns( data_selector, render_button, circos_trigger, selected, layout, tracks ): - try: - if data_selector == "layout": - df = pd.DataFrame(layout) - elif tracks[data_selector]['type'] == "CHORDS": - new_data = {"color": d.pop("color") for d in tracks[data_selector]["data"]} - new_chords = [{'{}_{}'.format(k, a): b for k, v in d.items() for a, b in v.items()} for d in tracks[data_selector]["data"]] - df = pd.DataFrame(new_chords) - else: - df = pd.DataFrame(tracks[data_selector]["data"]) - return [{"id": i, "name": i} for i in df.columns] - except: - df = pd.DataFrame() - return df - + return update_dash_table(data_selector, layout, tracks, "column") - @app.callback(Output("event-data-select", "value"), [Input("main-circos", "eventDatum")]) + # Hover/click event handler data for preset graph + @app.callback( + Output("event-data-select", "value"), [Input("main-circos", "eventDatum")] + ) def event_data_select(eventDatum): return str(eventDatum) - - @app.callback(Output("event-data-custom", "value"), [ - Input("render-button", "n_clicks"), - Input("main-circos", "eventDatum")]) + # Hover click event handler data for custom graph + @app.callback( + Output("event-data-custom", "value"), + [Input("render-button", "n_clicks"), Input("main-circos", "eventDatum")], + ) def event_data_custom(n_clicks, eventDatum): return str(eventDatum) - - \ No newline at end of file