diff --git a/protzilla/data_integration/di_plots.py b/protzilla/data_integration/di_plots.py index 6e3cceeb..8fad1790 100644 --- a/protzilla/data_integration/di_plots.py +++ b/protzilla/data_integration/di_plots.py @@ -15,9 +15,8 @@ def GO_enrichment_bar_plot( top_terms, cutoff, value, - gene_sets=[], + gene_sets={}, title="", - colors=PLOT_COLOR_SEQUENCE, figsize=None, ): """ @@ -73,8 +72,6 @@ def GO_enrichment_bar_plot( if not gene_sets: msg = "Please select at least one category to plot." return dict(messages=[dict(level=logging.ERROR, msg=msg)]) - if not isinstance(gene_sets, list): - gene_sets = [gene_sets] if value not in ["fdr", "p-value"]: msg = "Invalid value. Value must be either 'fdr' or 'p-value'." return dict(messages=[dict(level=logging.ERROR, msg=msg)]) @@ -108,9 +105,8 @@ def GO_enrichment_bar_plot( elif value == "p-value": column = "P-value" if restring_input else "Adjusted P-value" + colors = gene_sets.values() - if colors == "" or colors is None or len(colors) == 0: - colors = PLOT_COLOR_SEQUENCE size_y = top_terms * 0.5 * len(gene_sets) try: ax = gseapy.barplot( diff --git a/protzilla/methods/data_integration.py b/protzilla/methods/data_integration.py index bfed063e..6a7896e6 100644 --- a/protzilla/methods/data_integration.py +++ b/protzilla/methods/data_integration.py @@ -234,7 +234,6 @@ class PlotGOEnrichmentBarPlot(PlotStep): "top_terms", "cutoff", "title", - "colors", "figsize", ] # TODO: input figsize optional? diff --git a/tests/protzilla/data_integration/test_plots_data_integration.py b/tests/protzilla/data_integration/test_plots_data_integration.py index 044c7c5a..6388ed7c 100644 --- a/tests/protzilla/data_integration/test_plots_data_integration.py +++ b/tests/protzilla/data_integration/test_plots_data_integration.py @@ -25,7 +25,7 @@ def test_enrichment_bar_plot_restring(show_figures, helpers): top_terms=10, cutoff=0.05, value="fdr", - gene_sets=["KEGG", "Process"], + gene_sets={"KEGG" : "#E2A46D", "Process" : "#4A536A"}, ) if show_figures: helpers.open_graph_from_base64(bar_base64[0]) @@ -35,7 +35,7 @@ def test_enrichment_bar_plot_restring(show_figures, helpers): top_terms=10, cutoff=0.05, value="p_value", - gene_sets=["KEGG", "Process"], + gene_sets={"KEGG" : "#E2A46D", "Process" : "#4A536A"}, ) if show_figures: helpers.open_graph_from_base64(bar_base64[0]) @@ -50,7 +50,7 @@ def test_enrichment_bar_plot(show_figures, helpers, data_folder_tests): top_terms=10, cutoff=0.05, value="p_value", - gene_sets=["Reactome_2013"], + gene_sets={"Reactome_2013" : "#E2A46D"}, ) if show_figures: helpers.open_graph_from_base64(bar_base64[0]) @@ -65,7 +65,7 @@ def test_enrichment_bar_plot_wrong_value(data_folder_tests): top_terms=10, cutoff=0.05, value="fdr", - gene_sets=["Reactome_2013"], + gene_sets={"Reactome_2013" : "#E2A46D"}, ) assert "messages" in current_out assert any(("FDR is not available" in message["msg"]) for message in current_out["messages"]) @@ -78,7 +78,7 @@ def test_enrichment_bar_plot_empty_df(): top_terms=10, cutoff=0.05, value="p_value", - gene_sets=["Reactome_2013"], + gene_sets={"Reactome_2013" : "#E2A46D"}, ) assert "messages" in current_out assert any(("No data to plot" in message["msg"]) for message in current_out["messages"]) @@ -102,7 +102,7 @@ def test_enrichment_bar_plot_wrong_df(): top_terms=10, cutoff=0.05, value="p_value", - gene_sets=["KEGG"], + gene_sets={"KEGG" : "#E2A46D"}, ) assert "messages" in current_out assert any(("Please choose an enrichment result dataframe" in message["msg"]) for message in current_out["messages"]) @@ -115,7 +115,7 @@ def test_enrichment_bar_plot_cutoff(data_folder_tests): top_terms=10, cutoff=0, value="fdr", - gene_sets=["KEGG", "Process"], + gene_sets={"KEGG" : "#E2A46D", "Process" : "#4A536A"}, ) assert "messages" in current_out @@ -129,7 +129,7 @@ def test_enrichment_bar_plot_cutoff(data_folder_tests): top_terms=10, cutoff=0, value="p-value", - gene_sets=["Reactome_2013"], + gene_sets={"Reactome_2013" : "#E2A46D"}, ) assert "messages" in current_out assert any(("No data to plot when applying cutoff" in message["msg"]) for message in current_out["messages"]) diff --git a/ui/runs/forms/custom_fields.py b/ui/runs/forms/custom_fields.py index 7171f173..175ec22f 100644 --- a/ui/runs/forms/custom_fields.py +++ b/ui/runs/forms/custom_fields.py @@ -13,6 +13,7 @@ from django.forms.widgets import CheckboxInput, SelectMultiple from django.utils.html import format_html from django.utils.safestring import SafeText, mark_safe +from django.template.loader import render_to_string # Custom widgets @@ -48,7 +49,7 @@ def __init__(self, choices: Enum | list, initial=None, *args, **kwargs): super().__init__(choices=choices, initial=initial, *args, **kwargs) else: super().__init__( - choices=[(el.value, el.value) for el in choices], + choices=[(choice.value, choice.value) for choice in choices], initial=initial, *args, **kwargs, @@ -76,7 +77,7 @@ def __init__(self, choices: Enum | list, initial=None, *args, **kwargs): super().__init__(choices=choices, initial=initial, *args, **kwargs) else: super().__init__( - choices=[(el.value, el.value) for el in choices], + choices=[(choice.value, choice.value) for choice in choices], initial=initial, *args, **kwargs, @@ -85,7 +86,70 @@ def __init__(self, choices: Enum | list, initial=None, *args, **kwargs): self.widget.attrs.update({"class": "form-select mb-2"}) def clean(self, value: list[str] | None): - return [el for el in value if el != "hidden"] if value else None + return [element for element in value if element != "hidden"] if value else None + +class CustomCheckboxMultipleChoiceField(MultipleChoiceField): + def __init__(self, choices: Enum | list, colors: Enum | list, initial=None, *args, **kwargs): + if isinstance(choices, list): + super().__init__(choices=choices, initial=initial, *args, **kwargs) + else: + super().__init__( + choices=[(choice.value, choice.value) for choice in choices], + initial=initial, + *args, + **kwargs, + ) + self.widget = CustomCheckboxSelectMultipleWidget() + self.widget.colors = colors + self.widget.attrs.update({"class": "form-select mb-2"}) + + + def clean(self, value: list[str] | None): + if not value: + return None + + gen_sets = [] + colors = {} + result = {} + + for element in value: + if element.startswith("color_"): + _,gen_set,color = element.split('_', 2) + colors[gen_set] = color + else: + gen_sets.append(element) + + for gen_set in sorted(gen_sets): + if gen_set in colors: + result[gen_set] = colors[gen_set] + + return result + + +class CustomCheckboxSelectMultipleWidget(SelectMultiple): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.colors = [] + + def render(self, name, value, attrs=None, renderer=None) -> SafeText: + if isinstance(value, dict): + reformat_value = [] + for gen_set, color in value.items(): + reformat_value.append(gen_set) + reformat_value.append(f"color_{gen_set}_{color}") + value = reformat_value + + return mark_safe( + render_to_string( + "runs/field_component_color_selection.html", + context={ + "name": name, + "choices": self.choices, + "colors": self.colors, + "values": value, + } + ) + ) class CustomFileField(FileField): diff --git a/ui/runs/forms/data_integration.py b/ui/runs/forms/data_integration.py index 29b0f46f..e00a23d4 100644 --- a/ui/runs/forms/data_integration.py +++ b/ui/runs/forms/data_integration.py @@ -22,6 +22,7 @@ CustomFloatField, CustomMultipleChoiceField, CustomNumberField, + CustomCheckboxMultipleChoiceField, ) PROTEIN_DF = "protein_df" @@ -215,7 +216,7 @@ def fill_form(self, run: Run) -> None: self.toggle_visibility("gene_sets_enrichr", True) self.fields["gene_sets_enrichr"].choices = fill_helper.to_choices( gseapy.get_library_name() - ) # TODO check whether we need to pass the organism name here + ) # check whether we need to pass the organism name here else: self.toggle_visibility("gene_sets_path", True) @@ -324,7 +325,7 @@ class EnrichmentAnalysisWithGSEAForm(MethodForm): choices=GeneSetsField, label="How do you want to provide the gene sets? (reselect to show dynamic fields)", initial="Choose from Enrichr options" - # Todo: Dynamic parameters + # : Dynamic parameters ) gene_sets_path = CustomFileField( label="Upload gene sets with uppercase gene symbols (any of the following file " @@ -334,15 +335,15 @@ class EnrichmentAnalysisWithGSEAForm(MethodForm): "SetName2: [Gene2, Gene3, ...]})", initial=None, ) - # Todo: gene_sets_enrichr dynamic filling + # : gene_sets_enrichr dynamic filling gene_sets_enrichr = CustomChoiceField(choices=[], label="Gene sets") grouping = CustomChoiceField( choices=[], label="Grouping from metadata", initial=None - # Todo: Dynamic parameters + # : Dynamic parameters ) - # Todo: add dynamic filling to group1, group2 + # : add dynamic filling to group1, group2 group1 = CustomChoiceField(choices=[], label="Group1", initial=None) group2 = CustomChoiceField(choices=[], label="Group2", initial=None) @@ -402,7 +403,7 @@ def fill_form(self, run: Run) -> None: self.toggle_visibility("gene_sets_enrichr", True) self.fields["gene_sets_enrichr"].choices = fill_helper.to_choices( gseapy.get_library_name() - ) # TODO check whether we need to pass the organism name here + ) # check whether we need to pass the organism name here else: self.toggle_visibility("gene_sets_path", True) @@ -446,20 +447,20 @@ def fill_form(self, run: Run) -> None: class EnrichmentAnalysisWithPrerankedGSEAForm(MethodForm): - # Todo: protein_df - # Todo: ranking_column + # : protein_df + # : ranking_column ranking_direction = CustomChoiceField( choices=RankingDirectionField, label="Sort the ranking column (ascending - smaller values are better, " "descending - larger values are better)", initial=RankingDirectionField.ascending, ) - # Todo: gene_mapping + # : gene_mapping gene_sets_field = CustomChoiceField( choices=GeneSetsField, label="How do you want to provide the gene sets? (reselect to show dynamic fields)", initial=GeneSetsField.choose_from_enrichr_options - # Todo: Dynamic parameters + # : Dynamic parameters ) gene_sets_path = CustomFileField( label="Upload gene sets with uppercase gene symbols (any of the following file " @@ -469,7 +470,7 @@ class EnrichmentAnalysisWithPrerankedGSEAForm(MethodForm): "SetName2: [Gene2, Gene3, ...]})", initial=None, ) - # Todo: gene_sets_enrichr + # : gene_sets_enrichr min_size = CustomNumberField( label="Minimum number of genes from gene set also in data", initial=15 ) @@ -513,7 +514,7 @@ class DatabaseIntegrationByGeneMappingForm(MethodForm): ) dataframe = CustomChoiceField( choices=[], label="Step to use" - ) # TODO this looks and sounds very generic, be more specific, maybe it needs diffexp step + ) # this looks and sounds very generic, be more specific, maybe it needs diffexp step def fill_form(self, run: Run) -> None: self.fields["database_names"].choices = fill_helper.to_choices( @@ -525,8 +526,8 @@ def fill_form(self, run: Run) -> None: class DatabaseIntegrationByUniprotForm(MethodForm): - # Todo: uniprot - # Todo: Add dynamic fill for database name and fields + # : uniprot + # : Add dynamic fill for database name and fields database_name = CustomChoiceField( choices=[], label="Uniprot databases (offline)", @@ -535,11 +536,12 @@ class DatabaseIntegrationByUniprotForm(MethodForm): class PlotGOEnrichmentBarPlotForm(MethodForm): - # TODO: input:df fill dynamic with fill_forms + # : input:df fill dynamic with fill_forms input_df_step_instance = CustomChoiceField( choices=[], label="Choose dataframe to be plotted" ) - gene_sets = CustomMultipleChoiceField(choices=[], label="Sets to be plotted") + # TODO: after the color naming has been optimised in all filese, the underlying line can be updated: (color, color) for color in PLOT_COLOR_SEQUENCE + gene_sets = CustomCheckboxMultipleChoiceField(choices=[], colors=[(v, k[4:]) for k, v, in list(mcolors.TABLEAU_COLORS.items())], label="Sets to be plotted") value = CustomChoiceField( choices=GOEnrichmentBarPlotValue, label="Value (bars will be plotted as -log10(value)), fdr only for GO analysis with STRING, p_value is adjusted if available", @@ -561,15 +563,7 @@ class PlotGOEnrichmentBarPlotForm(MethodForm): ) title = CustomCharField(label="Title of the plot (optional)", required=False) - colors = CustomMultipleChoiceField( - choices=[], label="Colors for the plot (optional)" - ) # TODO this should not have to be set in fill_form - def fill_form(self, run: Run) -> None: - self.fields["colors"].choices = [ - (v, k) for k, v, in mcolors.CSS4_COLORS.items() - ] - self.fields["input_df_step_instance"].choices = fill_helper.get_choices( run, "enrichment_df" ) diff --git a/ui/runs/templates/runs/field_component_color_selection.html b/ui/runs/templates/runs/field_component_color_selection.html new file mode 100644 index 00000000..923a9ee7 --- /dev/null +++ b/ui/runs/templates/runs/field_component_color_selection.html @@ -0,0 +1,27 @@ +
+ {% for option_value, option_label in choices %} +
+
+ + +
+
+ +
+
+ {% endfor %} +