diff --git a/scipost_django/graphs/templates/graphs/graphs.html b/scipost_django/graphs/templates/graphs/graphs.html index 1df059edee14d12ca939b69f0fa0a5095a565572..d05984c88deb69d0eafe2c706a3d94de960bbdb0 100644 --- a/scipost_django/graphs/templates/graphs/graphs.html +++ b/scipost_django/graphs/templates/graphs/graphs.html @@ -3,14 +3,18 @@ {% block content %} <h1>SciPost Graphs</h1> - <div id="plot-container" class="row" hx-include="this"> + <div id="plot-container" class="row h-100" hx-include="this"> <div class="col-12 col-lg-3 d-flex flex-column" hx-post="{% url "graphs:plot_options_form" %}" - hx-trigger="load, change from:#plot-container"></div> - <div id="plot" - class="col d-flex flex-column align-items-center" - hx-get="{% url "graphs:plot" %}" - hx-params="not csrfmiddlewaretoken" - hx-trigger="change from:#plot-container"></div> + hx-trigger="load, change from:#plot-container, click from:#plot-refresh"> + </div> + + <div class="col d-flex flex-column"> + <div id="plot" + class="h-100 w-100" + hx-get="{% url "graphs:plot" %}" + hx-params="not csrfmiddlewaretoken" + hx-trigger="change from:#plot-container"></div> + </div> </div> {% endblock content %} diff --git a/scipost_django/graphs/templates/graphs/plot.html b/scipost_django/graphs/templates/graphs/plot.html index 339a2fc3458b412be682dce220c9783e3bed31d6..45991aceffa695e1b32199e637a9d1821c624a4a 100644 --- a/scipost_django/graphs/templates/graphs/plot.html +++ b/scipost_django/graphs/templates/graphs/plot.html @@ -1,3 +1,21 @@ -{% if plot_svg %} - {{ plot_svg|safe }} -{% endif %} +{% if plot_svg %}{{ plot_svg|safe }}{% endif %} + + +<div id="plot-controls" class="my-2 d-flex gap-2 justify-content-end"> + <button id="plot-refresh" class="btn btn-primary">Refresh</button> + + <button id="plot-download" + type="button" + class="btn btn-primary dropdown-toggle" + data-bs-toggle="dropdown" + aria-expanded="false">Download</button> + <ul class="dropdown-menu"> + <li><a class="dropdown-item" href="{{ request.get_full_path }}&download=svg" >SVG</a></li> + <li><a class="dropdown-item" href="{{ request.get_full_path }}&download=pdf" >PDF</a></li> + <li><a class="dropdown-item" href="{{ request.get_full_path }}&download=png" >PNG</a></li> + <li><a class="dropdown-item" href="{{ request.get_full_path }}&download=jpg" >JPG</a></li> + <li><hr class="dropdown-divider" /></li> + <li><a class="dropdown-item" href="{{ request.get_full_path }}&download=csv">CSV</a></li> + </ul> + +</div> diff --git a/scipost_django/graphs/views.py b/scipost_django/graphs/views.py index aa401b6e1060b83ff82a188f190c23e85e896690..d69a3036e94ac61e877ca30b2c20eb28436d6578 100644 --- a/scipost_django/graphs/views.py +++ b/scipost_django/graphs/views.py @@ -4,7 +4,7 @@ __license__ = "AGPL v3" import io from django.contrib.auth.decorators import login_required, permission_required -from django.shortcuts import render +from django.shortcuts import HttpResponse, render from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View @@ -42,7 +42,7 @@ class PlotView(View): def get(self, request): form = PlotOptionsForm(request.GET) - if not form.is_valid(): + if not form.is_valid() and request.GET.get("widget"): return HTMXResponse( "Invalid plot options: " + str(form.errors), tag="danger" ) @@ -67,8 +67,55 @@ class PlotView(View): else: self.plot_options["generic"][option] = value + if request.GET.get("download"): + return self.download(request.GET.get("download", "svg")) + return self.render_to_response(self.get_context_data()) + def download(self, file_type): + + figure = self.render_figure() + bytes_io = io.BytesIO() + + match file_type: + case "svg": + figure.savefig(bytes_io, format="svg") + response = HttpResponse( + bytes_io.getvalue(), content_type="image/svg+xml" + ) + + case "png": + figure.savefig(bytes_io, format="png", dpi=300) + response = HttpResponse(bytes_io.getvalue(), content_type="image/png") + + case "pdf": + figure.savefig(bytes_io, format="pdf") + response = HttpResponse( + bytes_io.getvalue(), content_type="application/pdf" + ) + + case "jpg": + figure.savefig(bytes_io, format="jpg", dpi=300) + response = HttpResponse(bytes_io.getvalue(), content_type="image/jpg") + + case "csv": + x, y = self.kind.get_data() + + # Write the data to a CSV file + csv = io.StringIO() + csv.write("x,y\n") + for i in range(len(x)): + csv.write(f"{x[i]},{y[i]}\n") + csv.seek(0) + response = HttpResponse(csv, content_type="text/csv") + + case _: + raise ValueError(f"Invalid file type: {file_type}") + + response["Content-Disposition"] = f"attachment; filename=plot.{file_type}" + + return response + def render_figure(self): if not self.plotter or not self.kind: return None