diff --git a/.gitignore b/.gitignore
index ff0e7b3d..d94b703f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,9 @@
user_data/runs/*
user_data/workflows/*
user_data/external_data/*
+user_data/settings/*
+!user_data/settings/plots_default.yaml
+!user_data/settings/databases_default.yaml
user_data/debug/*
ui/static/admin/*
ui/uploads/*
diff --git a/protzilla/constants/paths.py b/protzilla/constants/paths.py
index 49364bda..177eef6c 100644
--- a/protzilla/constants/paths.py
+++ b/protzilla/constants/paths.py
@@ -4,6 +4,7 @@
USER_DATA_PATH = Path(PROJECT_PATH, "user_data")
RUNS_PATH = USER_DATA_PATH / "runs"
WORKFLOWS_PATH = USER_DATA_PATH / "workflows"
+SETTINGS_PATH = USER_DATA_PATH / "settings"
EXTERNAL_DATA_PATH = Path(PROJECT_PATH, "user_data/external_data")
WORKFLOW_META_PATH = Path(PROJECT_PATH, "protzilla/constants/workflow_meta.json")
UI_PATH = Path(PROJECT_PATH, "ui")
diff --git a/protzilla/data_preprocessing/plots.py b/protzilla/data_preprocessing/plots.py
index 2a487eb6..ef396e9e 100644
--- a/protzilla/data_preprocessing/plots.py
+++ b/protzilla/data_preprocessing/plots.py
@@ -7,7 +7,6 @@
from protzilla.data_preprocessing.plots_helper import generate_tics
from protzilla.utilities import default_intensity_column
-from protzilla.utilities.plot_template import *
from protzilla.constants.colors import *
def create_pie_plot(
diff --git a/protzilla/steps.py b/protzilla/steps.py
index eecf0d04..bf21942a 100644
--- a/protzilla/steps.py
+++ b/protzilla/steps.py
@@ -9,7 +9,8 @@
from pathlib import Path
import pandas as pd
-import plotly
+import plotly.io as pio
+import plotly.graph_objects as go
from PIL import Image
from protzilla.utilities import format_trace
@@ -277,33 +278,47 @@ def __repr__(self):
def empty(self) -> bool:
return len(self.plots) == 0
- def export(self, format_):
+ def export(self, settings: dict) -> list:
+ """
+ Converts all plots from this step to files according to the format and size in the Plotly template.
+ An exported plot is represented as BytesIO object containing binary image data.
+ :param settings: Dict containing the plot settings.
+ :return: List of all exported plots.
+ """
+ from ui.settings.plot_template import get_scale_factor
exports = []
+ format_ = settings["file_format"]
+
for plot in self.plots:
- if isinstance(plot, plotly.graph_objs.Figure):
- if format_ in ["eps", "tiff"]:
- png_binary = plotly.io.to_image(plot, format="png", scale=4)
- img = Image.open(BytesIO(png_binary)).convert("RGB")
+ scale_factor = get_scale_factor(plot, settings)
+ # For Plotly GO Figure
+ if isinstance(plot, go.Figure):
+ if format_ in ["tiff", "eps"]:
+ binary_png = pio.to_image(plot, format="png", scale=scale_factor)
+ img = Image.open(BytesIO(binary_png)).convert("RGB")
binary = BytesIO()
if format_ == "tiff":
img.save(binary, format="tiff", compression="tiff_lzw")
- else:
+ elif format_ == "eps":
img.save(binary, format=format_)
+ binary.seek(0)
exports.append(binary)
else:
- binary_string = plotly.io.to_image(plot, format=format_, scale=4)
- exports.append(BytesIO(binary_string))
+ binary_png = pio.to_image(plot, format=format_, scale=scale_factor)
+ exports.append(BytesIO(binary_png))
elif isinstance(plot, dict) and "plot_base64" in plot:
plot = plot["plot_base64"]
- if isinstance(plot, bytes): # base64 encoded plots
- if format_ in ["eps", "tiff"]:
+ # TO DO: Include scale_factor here
+ # For base64 encoded plot
+ if isinstance(plot, bytes):
+ if format_ in ["tiff", "eps"]:
img = Image.open(BytesIO(base64.b64decode(plot))).convert("RGB")
binary = BytesIO()
if format_ == "tiff":
img.save(binary, format="tiff", compression="tiff_lzw")
- else:
- img.save(binary, format=format_)
+ elif format_ == "eps":
+ img.save(binary, format="eps")
binary.seek(0)
exports.append(binary)
elif format_ in ["png", "jpg"]:
diff --git a/protzilla/utilities/plot_template.py b/protzilla/utilities/plot_template.py
deleted file mode 100644
index 9661a41f..00000000
--- a/protzilla/utilities/plot_template.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import plotly.io as pio
-import plotly.graph_objects as go
-
-from protzilla.constants.colors import PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR
-
-
-layout = go.Layout(
- title={
- "font": {
- "size": 16,
- "family": "Arial"
- },
- "y": 0.98,
- "x": 0.5,
- "xanchor": "center",
- "yanchor": "top"
- },
- font={
- "size": 14,
- "family": "Arial"
- },
- colorway=[PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR],
- plot_bgcolor="white",
- yaxis={
- "gridcolor": "lightgrey",
- "zerolinecolor": "lightgrey"
- },
- modebar={
- "remove": ["autoScale2d", "lasso", "lasso2d", "toImage", "select2d"],
- },
- dragmode="pan"
-)
-pio.templates["plotly_protzilla"] = go.layout.Template(layout=layout)
-pio.templates.default = "plotly_protzilla"
\ No newline at end of file
diff --git a/protzilla/utilities/utilities.py b/protzilla/utilities/utilities.py
index 7ab9e000..0f4b7fe1 100644
--- a/protzilla/utilities/utilities.py
+++ b/protzilla/utilities/utilities.py
@@ -11,6 +11,7 @@
import pandas as pd
import psutil
+from django.http import QueryDict
# recipie from https://docs.python.org/3/library/itertools.html
def unique_justseen(iterable, key=None):
@@ -136,3 +137,47 @@ def get_file_name_from_upload_path(upload_path: str) -> str:
base_name = file_name_randomized.split("_")[0]
file_extension = file_name_randomized.split(".")[-1]
return f"{base_name}.{file_extension}"
+
+
+def parameters_from_post(post: QueryDict) -> dict:
+ """
+ Removes token from dict and converts the remaining entries into suitable data formats.
+ :param post: Django dict containing POST data.
+ :return: Dict containing the parameters in suitable formats.
+ """
+ d = dict(post)
+ if "csrfmiddlewaretoken" in d:
+ del d["csrfmiddlewaretoken"]
+ parameters = {}
+ for k, v in d.items():
+ if len(v) > 1:
+ # only used for named_output parameters and multiselect fields
+ parameters[k] = v
+ else:
+ parameters[k] = convert_str_if_possible(v[0])
+ return parameters
+
+
+def convert_str_if_possible(s):
+ """
+ Converts an input value into suitable representation as a string.
+ :param s: Input value.
+ :return: Converted input value.
+ """
+ try:
+ f = float(s)
+ return int(f) if int(f) == f else f
+ except ValueError:
+ if s == "checked":
+ # s is a checkbox
+ return True
+ if re.fullmatch(r"\d+(\.\d+)?(\|\d+(\.\d+)?)*", s):
+ # s is a multi-numeric input e.g. 1-0.12-5
+ numbers_str = re.findall(r"\d+(?:\.\d+)?", s)
+ numbers = []
+ for num in numbers_str:
+ num = float(num)
+ num = int(num) if int(num) == num else num
+ numbers.append(num)
+ return numbers
+ return s
\ No newline at end of file
diff --git a/tests/ui/test_settings.py b/tests/ui/test_settings.py
new file mode 100644
index 00000000..de466a1c
--- /dev/null
+++ b/tests/ui/test_settings.py
@@ -0,0 +1,41 @@
+import pytest
+import plotly.graph_objects as go
+
+from protzilla.constants.colors import PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR
+from protzilla.constants.paths import SETTINGS_PATH
+from ui.settings.plot_template import (
+ determine_font,
+ resize_for_display,
+ get_scale_factor
+)
+
+
+@pytest.fixture
+def sample_params():
+ return {
+ "section_id": "plots",
+ "file_format": "png",
+ "width": 85,
+ "height": 60,
+ "custom_font": "Ubuntu Mono",
+ "font": "Arial",
+ "heading_size": 11,
+ "text_size": 8
+ }
+
+def test_determine_font(sample_params):
+ assert determine_font(sample_params) == "Arial"
+ sample_params["font"] = "Custom"
+ assert determine_font(sample_params) == "Ubuntu Mono"
+
+def test_resize_for_display(sample_params):
+ result = resize_for_display(sample_params)
+ assert result["display_width"] == 600 # SCALED_WIDTH
+ assert result["display_heading_size"] == 27
+
+def test_get_scale_factor(sample_params):
+ fig = go.Figure()
+ fig.update_layout(width=600)
+ scale = get_scale_factor(fig, sample_params)
+ assert isinstance(scale, float)
+ assert round(scale, 3) == 1.673
\ No newline at end of file
diff --git a/ui/main/settings.py b/ui/main/settings.py
index 58cbaca9..e2a6e9f8 100644
--- a/ui/main/settings.py
+++ b/ui/main/settings.py
@@ -47,6 +47,7 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"runs",
+ "settings",
]
MIDDLEWARE = [
@@ -127,7 +128,10 @@
STATIC_URL = "static/"
-STATICFILES_DIRS = [BASE_DIR / "static"]
+STATICFILES_DIRS = [
+ BASE_DIR / "static",
+ BASE_DIR / "settings/static"
+ ]
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
diff --git a/ui/main/urls.py b/ui/main/urls.py
index 54726696..0e2c0854 100644
--- a/ui/main/urls.py
+++ b/ui/main/urls.py
@@ -21,6 +21,7 @@
urlpatterns = [
path("", views.index),
path("runs/", include("runs.urls")),
+ path("settings/", include("settings.urls")),
path("databases", views.databases, name="databases"),
path("databases/upload", views.database_upload, name="database_upload"),
path("databases/delete", views.database_delete, name="database_delete"),
diff --git a/ui/main/views.py b/ui/main/views.py
index 14e36465..da0892d3 100644
--- a/ui/main/views.py
+++ b/ui/main/views.py
@@ -19,6 +19,7 @@ def index(request):
def databases(request):
+ request.session['last_view'] = "databases"
databases = uniprot_databases()
df_infos = {}
if database_metadata_path.exists():
diff --git a/ui/runs/static/runs/style.css b/ui/runs/static/runs/style.css
index ff1335d8..b75b144a 100644
--- a/ui/runs/static/runs/style.css
+++ b/ui/runs/static/runs/style.css
@@ -88,4 +88,10 @@ html, body {
padding: 10px 8%;
margin-top: 10px;
background-color: rgb(255, 255, 255);
+}
+.plot-wrapper {
+ width: fit-content;
+ height: fit-content;
+ border: 1px solid #e8edf3;
+ border-radius: .375rem;
}
\ No newline at end of file
diff --git a/ui/runs/templates/runs/details.html b/ui/runs/templates/runs/details.html
index e60a42d6..e2452e98 100644
--- a/ui/runs/templates/runs/details.html
+++ b/ui/runs/templates/runs/details.html
@@ -215,7 +215,7 @@
{{ display_name }}
{% if current_plots %}
{% for plot in current_plots %}
-
+
{{ plot|safe }}
{% endfor %}
@@ -225,15 +225,13 @@
{{ display_name }}
{% if current_plots %}
{% endif %}
@@ -278,11 +276,11 @@
Do you want back?
Do you want to proceed?
Back
-
+
+
-
{% endif %}
{% if not last_step %}
diff --git a/ui/runs/views.py b/ui/runs/views.py
index 56d0d264..accf7bdd 100644
--- a/ui/runs/views.py
+++ b/ui/runs/views.py
@@ -29,6 +29,7 @@
format_trace,
get_memory_usage,
name_to_title,
+ parameters_from_post,
)
from protzilla.workflow import get_available_workflow_names
from protzilla.constants.paths import WORKFLOWS_PATH
@@ -38,13 +39,14 @@
make_name_field,
make_sidebar,
)
-from ui.runs.views_helper import display_message, display_messages, parameters_from_post
+from ui.runs.views_helper import display_message, display_messages
from .form_mapping import (
get_empty_plot_form_by_method,
get_filled_form_by_method,
get_filled_form_by_request,
)
+from ui.settings.views import load_settings
active_runs: dict[str, Run] = {}
@@ -68,6 +70,9 @@ def detail(request: HttpRequest, run_name: str):
active_runs[run_name] = Run(run_name)
run: Run = active_runs[run_name]
+ request.session['last_view'] = "runs:detail"
+ request.session['run_name'] = run_name
+
# section, step, method = run.current_run_location()
# end_of_run = not step
@@ -173,6 +178,9 @@ def index(request: HttpRequest, index_error: bool = False):
:return: the rendered index page
:rtype: HttpResponse
"""
+
+ request.session['last_view'] = "runs:index"
+
return render(
request,
"runs/index.html",
@@ -447,11 +455,14 @@ def download_plots(request: HttpRequest, run_name: str):
if run_name not in active_runs:
active_runs[run_name] = Run(run_name)
run = active_runs[run_name]
- format_ = request.GET["format"]
+ settings = load_settings("plots")
+ format_ = settings["file_format"]
index = run.steps.current_step_index
section = run.current_step.section
operation = run.current_step.operation
- exported = run.current_plots.export(format_=format_)
+ exported = run.current_plots.export(settings)
+ if len(exported) == 0:
+ raise RuntimeError("List of exported plots is empty.")
if len(exported) == 1:
filename = f"{index}-{section}-{operation}.{format_}"
return FileResponse(exported[0], filename=filename, as_attachment=True)
diff --git a/ui/runs/views_helper.py b/ui/runs/views_helper.py
index b4a640f9..3beb9edf 100644
--- a/ui/runs/views_helper.py
+++ b/ui/runs/views_helper.py
@@ -8,40 +8,6 @@
from ui.runs.utilities.alert import build_trace_alert
-def parameters_from_post(post):
- d = dict(post)
- if "csrfmiddlewaretoken" in d:
- del d["csrfmiddlewaretoken"]
- parameters = {}
- for k, v in d.items():
- if len(v) > 1:
- # only used for named_output parameters and multiselect fields
- parameters[k] = v
- else:
- parameters[k] = convert_str_if_possible(v[0])
- return parameters
-
-
-def convert_str_if_possible(s):
- try:
- f = float(s)
- return int(f) if int(f) == f else f
- except ValueError:
- if s == "checked":
- # s is a checkbox
- return True
- if re.fullmatch(r"\d+(\.\d+)?(\|\d+(\.\d+)?)*", s):
- # s is a multi-numeric input e.g. 1-0.12-5
- numbers_str = re.findall(r"\d+(?:\.\d+)?", s)
- numbers = []
- for num in numbers_str:
- num = float(num)
- num = int(num) if int(num) == num else num
- numbers.append(num)
- return numbers
- return s
-
-
def get_displayed_steps(
steps: StepManager,
) -> list[dict]: # TODO i think this broke with the new naming scheme, should be redone
diff --git a/ui/settings/apps.py b/ui/settings/apps.py
new file mode 100644
index 00000000..2b122eed
--- /dev/null
+++ b/ui/settings/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class SettingsConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "settings"
\ No newline at end of file
diff --git a/ui/settings/plot_template.py b/ui/settings/plot_template.py
new file mode 100644
index 00000000..d8414c4c
--- /dev/null
+++ b/ui/settings/plot_template.py
@@ -0,0 +1,155 @@
+import plotly.io as pio
+import plotly.graph_objects as go
+import math
+
+from protzilla.disk_operator import YamlOperator
+from protzilla.constants.colors import PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR
+from protzilla.constants.paths import SETTINGS_PATH
+
+
+SCALED_WIDTH = 600
+PT_TO_INCH = 1 / 72
+INCH_TO_MM = 25.4
+DPI = 300
+
+template = None
+
+def load_settings(section_id: str) -> dict:
+ """
+ Loads the stored settings for a given settings section.
+ :param section_id: The ID of the section that should be loaded.
+ :return: Dict containing the loaded settings for given section.
+ """
+ op = YamlOperator()
+ path = SETTINGS_PATH / (section_id + ".yaml")
+ if path.exists():
+ settings = op.read(path)
+ else:
+ default_path = SETTINGS_PATH / (section_id + "_default.yaml")
+ settings = op.read(default_path)
+ save_settings(settings, section_id)
+ return settings
+
+def save_settings(params: dict, section_id: str):
+ """
+ Writes settings into settings section file.
+ :param params: Dict with parameter and values from this settings section.
+ :param section_id: The ID of the section.
+ """
+ op = YamlOperator()
+ path = SETTINGS_PATH / (section_id + ".yaml")
+ op.write(path, params)
+ if section_id == "plots" and isinstance(template, PlotTemplate):
+ template.update(params)
+ template.apply()
+
+def determine_font(params: dict) -> str:
+ """
+ Returns the selected or a given custom font.
+ :param params: Dict with parameter and values from this settings section.
+ :return: Selected font.
+ """
+ if(params["font"] == "Custom"):
+ font = params["custom_font"]
+ else:
+ font = params["font"]
+ return font
+
+def resize_for_display(params: dict) -> dict:
+ """
+ Scales the input sizes to sizes that can be easily displayed in a webbrowser.
+ :param params: Dict containing the plot settings.
+ :return: Dict containing plot settings with scaled sizes.
+ """
+ # Figure size
+ ratio = params["width"] / params["height"]
+ display_height = int(SCALED_WIDTH / ratio)
+
+ # Font size
+ ratio = SCALED_WIDTH / params["width"]
+ display_heading = int(params["heading_size"] * PT_TO_INCH * INCH_TO_MM * ratio)
+ display_text = int(params["text_size"] * PT_TO_INCH * INCH_TO_MM * ratio)
+
+ params["display_width"] = SCALED_WIDTH
+ params["display_height"] = display_height
+ params["display_heading_size"] = display_heading
+ params["display_text_size"] = display_text
+
+ return params
+
+def get_scale_factor(
+ fig: go.Figure,
+ params: dict
+ ) -> float:
+ """
+ Calculates the scale factor for downloading the plot in desired size and resolution.
+ :param fig: Plotly figure to be scaled.
+ :param params: Dict containing the plot settings.
+ :return: Scale factor to scale the whole plot to desired size.
+ """
+ current_width = fig.layout.width or SCALED_WIDTH
+ scale_factor = (params["width"] / INCH_TO_MM * DPI) / current_width
+
+ return scale_factor
+
+class PlotTemplate:
+ def __init__(self):
+ params = resize_for_display(load_settings("plots"))
+ font = determine_font(params)
+ self.layout = go.Layout(
+ title={
+ "font": {
+ "size": params["display_heading_size"],
+ "family": font
+ },
+ "y": 0.95,
+ "x": 0.5,
+ "xanchor": "center",
+ "yanchor": "top"
+ },
+ font={
+ "size": params["display_text_size"],
+ "family": font
+ },
+ colorway=[PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR],
+ plot_bgcolor="white",
+ yaxis={
+ "gridcolor": "lightgrey",
+ "zerolinecolor": "lightgrey"
+ },
+ modebar={
+ "remove": ["autoScale2d", "lasso", "lasso2d", "toImage", "select2d"],
+ },
+ dragmode="pan",
+ height=params["display_height"],
+ width=params["display_width"],
+ margin={
+ "t": 50,
+ "b": 50
+ }
+ )
+
+ def update(self, params: dict):
+ """
+ Updates all relevant parameters of this Plotly template.
+ :param params: Dict containing properties of the Plotly template.
+ """
+ params = resize_for_display(params)
+ font = determine_font(params)
+ self.layout.title.font.family = font
+ self.layout.font.family = font
+
+ self.layout.height = params["display_height"]
+ self.layout.width = params["display_width"]
+ self.layout.title.font.size = params["display_heading_size"]
+ self.layout.font.size = params["display_text_size"]
+
+ def apply(self):
+ """
+ Applies the current template as default template.
+ """
+ pio.templates["plotly_protzilla"] = go.layout.Template(layout=self.layout)
+
+template = PlotTemplate()
+template.apply()
+pio.templates.default = "plotly_protzilla"
\ No newline at end of file
diff --git a/ui/settings/static/settings.js b/ui/settings/static/settings.js
new file mode 100644
index 00000000..a460a278
--- /dev/null
+++ b/ui/settings/static/settings.js
@@ -0,0 +1,27 @@
+$( document ).ready(function () {
+ let formHasChanged = false;
+ const sectionId = document.getElementById('settings-form').dataset.sectionId;
+
+ $("#settings-form :input").on("change input", function () {
+ formHasChanged = true;
+ });
+
+ $("#cancel, #settings-icon").click(function (e) {
+ if(formHasChanged) {
+ e.preventDefault();
+ $("#unsavedChangesModal").modal("show");
+ }
+ });
+
+ $("#customFontInput").click(function () {
+ $("#customFontRadio").prop("checked", true);
+ });
+
+ $(".action-button").each(function () {
+ $(this).click(function () {
+ const form = document.getElementById("settings-form");
+ form.action = $(this).data("action");
+ form.submit();
+ });
+ });
+});
\ No newline at end of file
diff --git a/ui/settings/static/settings_style.css b/ui/settings/static/settings_style.css
new file mode 100644
index 00000000..9e616f4c
--- /dev/null
+++ b/ui/settings/static/settings_style.css
@@ -0,0 +1,50 @@
+.wrapper {
+ display: flex;
+ width: 100%;
+ align-items: stretch;
+}
+.sidebar {
+ width: 320px;
+ height: 100vh;
+}
+.selected {
+ background-color: #E8EDF3 !important;
+}
+.btn-section {
+ background-color: white;
+ width: 300px !important;
+ height: 60px;
+ align-items: center;
+}
+.content {
+ padding-top: 20px;
+ flex-grow: 1;
+ display: flex;
+ justify-content: center;
+}
+.footer {
+ position: sticky;
+ bottom: 0;
+ padding: 10px 8%;
+ margin-top: 10px;
+ background-color: rgb(255, 255, 255);
+}
+.custom-font {
+ justify-content: flex-start;
+ align-items: center;
+ gap: 20px;
+}
+#customFontInput.form-control {
+ display: inline-block !important;
+ width: 400px;
+ margin: 10px;
+}
+.plot-wrapper {
+ width: fit-content;
+ height: fit-content;
+ border: 1px solid #e8edf3;
+ border-radius: .375rem;
+}
+.field-wrapper {
+ margin-bottom: 10px;
+}
\ No newline at end of file
diff --git a/ui/settings/templates/settings_databases.html b/ui/settings/templates/settings_databases.html
new file mode 100644
index 00000000..cfe86d8c
--- /dev/null
+++ b/ui/settings/templates/settings_databases.html
@@ -0,0 +1,52 @@
+{% extends 'base.html' %}
+{% load static %}
+{# params: initials, sidebar, section_id, sections #}
+
+{% block css %}
+
+{% endblock %}
+
+{% block js %}
+
+
+{% endblock %}
+
+{% block title %}
+ PROTzilla - Settings
+{% endblock %}
+
+{% block content %}
+
+ {{ sidebar|safe }}
+
+
+
Manage Databases
+
In this section, you can manage your UniProt integration by uploading protein
+ data tables to enabling gene mapping.
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/ui/settings/templates/settings_footer.html b/ui/settings/templates/settings_footer.html
new file mode 100644
index 00000000..53fdc3ea
--- /dev/null
+++ b/ui/settings/templates/settings_footer.html
@@ -0,0 +1,34 @@
+{% load static %}
+
+{% block css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
There are changes that haven't been saved yet. Are you sure you want to discard all changes?
+
Discard Changes
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/ui/settings/templates/settings_plots.html b/ui/settings/templates/settings_plots.html
new file mode 100644
index 00000000..2773b357
--- /dev/null
+++ b/ui/settings/templates/settings_plots.html
@@ -0,0 +1,138 @@
+{% extends 'base.html' %}
+{% load static %}
+{# params: initials, sidebar, plot, sections, section_id #}
+
+{% block css %}
+{% endblock %}
+
+{% block js %}
+
+
+
+
+{% endblock %}
+
+{% block title %}
+ PROTzilla - Settings
+{% endblock %}
+
+{% block content %}
+
+ {{ sidebar|safe }}
+
+
+
Configurations for Plots
+
+ Configurations for plots can be entered here. This information is automatically applied to all plots in PROTzilla.
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/ui/settings/templates/settings_sidebar.html b/ui/settings/templates/settings_sidebar.html
new file mode 100644
index 00000000..68df5b56
--- /dev/null
+++ b/ui/settings/templates/settings_sidebar.html
@@ -0,0 +1,38 @@
+{% load static %}
+{# params: sections, selected_section #}
+
+{% block css %}
+
+{% endblock %}
+
+{% block js %}
+{% endblock %}
+
+{% block title %}
+ PROTzilla - Settings
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/ui/settings/urls.py b/ui/settings/urls.py
new file mode 100644
index 00000000..2fbd9a6a
--- /dev/null
+++ b/ui/settings/urls.py
@@ -0,0 +1,14 @@
+from django.contrib import admin
+from django.urls import include, path
+
+from . import views
+
+app_name = "settings"
+urlpatterns = [
+ path("runs/", include("runs.urls")),
+ path("save", views.save, name="save"),
+ path("last_view", views.last_view, name="last_view"),
+ path("plots", views.settings_plots, name="settings_plots"),
+ path("update_plot_preview", views.update_plot_preview, name="update_plot_preview"),
+ path("databases", views.settings_databases, name="settings_databases")
+]
\ No newline at end of file
diff --git a/ui/settings/views.py b/ui/settings/views.py
new file mode 100644
index 00000000..7f686ba5
--- /dev/null
+++ b/ui/settings/views.py
@@ -0,0 +1,119 @@
+import pandas
+
+import plotly.io as pio
+import plotly.graph_objects as go
+
+from django.contrib import messages
+from django.http import HttpResponseRedirect
+from django.shortcuts import redirect, render
+from django.urls import reverse, NoReverseMatch
+from django.template.loader import render_to_string
+
+from protzilla.utilities.utilities import parameters_from_post
+from protzilla.data_preprocessing.plots import create_bar_plot
+from ui.settings.plot_template import template, load_settings, save_settings
+
+
+SECTIONS = [
+ {
+ "id": "plots",
+ "name": "Plot Configurations"
+ },
+ {
+ "id": "databases",
+ "name": "Manage Databases"
+ },
+]
+
+
+def make_sidebar(request, section_id):
+ sidebar_template = "settings_sidebar.html"
+ return render_to_string(
+ sidebar_template,
+ context=dict(
+ sections=SECTIONS,
+ selected_section=section_id
+ )
+ )
+
+
+def settings_plots(request):
+ settings_content = load_settings("plots")
+ sidebar = make_sidebar(request, "plots")
+ plot = make_preview_plot(settings_content)
+ return render(
+ request,
+ "settings_plots.html",
+ context=dict(
+ initials=settings_content,
+ sidebar=sidebar,
+ plot=plot,
+ sections=SECTIONS,
+ section_id="plots"
+ )
+ )
+
+
+def make_preview_plot(params: dict):
+ template.update(params)
+ fig = create_bar_plot(
+ ["Example 1", "Example 2"],
+ [0.7, 0.3],
+ "Example plot",
+ "Example",
+ "Example"
+ )
+ pio.templates["plotly_protzilla_preview"] = go.layout.Template(layout=template.layout)
+ fig.update_layout(template="plotly_protzilla_preview")
+ plot = fig.to_html(include_plotlyjs=False, full_html=False)
+ return plot
+
+
+def update_plot_preview(request):
+ params = parameters_from_post(request.POST)
+ sidebar = make_sidebar(request, "plots")
+ plot = make_preview_plot(params)
+ return render(
+ request,
+ "settings_plots.html",
+ context=dict(
+ initials=params,
+ sidebar=sidebar,
+ plot=plot,
+ section_id="plots"
+ )
+ )
+
+
+def settings_databases(request):
+ settings_content = load_settings("databases")
+ sidebar = make_sidebar(request, "databases")
+ return render(
+ request,
+ "settings_databases.html",
+ context=dict(
+ initials=settings_content,
+ sidebar=sidebar,
+ sections=SECTIONS,
+ section_id="databases"
+ )
+ )
+
+
+def save(request):
+ params = parameters_from_post(request.POST)
+ section_id = request.POST.get("section_id")
+ save_settings(params, section_id)
+ return HttpResponseRedirect(reverse("settings:last_view"))
+
+
+def last_view(request):
+ view_name = request.session['last_view']
+ try:
+ if view_name=="runs:detail":
+ run_name = request.session['run_name']
+ return HttpResponseRedirect(reverse(view_name, args=(run_name,)))
+ else:
+ return HttpResponseRedirect(reverse(view_name))
+ except NoReverseMatch:
+ return HttpResponseRedirect(reverse("runs:index"))
\ No newline at end of file
diff --git a/ui/static/templates/navbar.html b/ui/static/templates/navbar.html
index e85afdff..13282165 100644
--- a/ui/static/templates/navbar.html
+++ b/ui/static/templates/navbar.html
@@ -16,6 +16,11 @@
+
+
+
\ No newline at end of file
diff --git a/user_data/settings/databases_default.yaml b/user_data/settings/databases_default.yaml
new file mode 100644
index 00000000..b07f1f9a
--- /dev/null
+++ b/user_data/settings/databases_default.yaml
@@ -0,0 +1,2 @@
+section_id: databases
+selected_database: Database 1
diff --git a/user_data/settings/plots_default.yaml b/user_data/settings/plots_default.yaml
new file mode 100644
index 00000000..a15e2b2c
--- /dev/null
+++ b/user_data/settings/plots_default.yaml
@@ -0,0 +1,8 @@
+custom_font: ''
+file_format: png
+font: Arial
+heading_size: 11
+height: 60
+section_id: plots
+text_size: 8
+width: 85