Ändern und Aktualisieren von Werten mittels HTML Slider

Django, Flask, Bottle, WSGI, CGI…
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Hallo,
ich habe etwas mit Flask gearbeitet und stehe an einer Stelle, an der ich nicht weiter komme.
Dazu habe ich ein Minimalbeispiel erstellt.
Ich würde gern etwas in einem HTML-Canvas zeichen und die Zeichnung in Abhängigkeit von Slider-Werten verändern.
Das Auslesen der Slider-Werte und die direkte Weiterleitung an Python habe ich schon implementiert. Auf dieser Basis werden mittels Python Koordinaten neu berechnet und an die HTML Zeichnung zurückgegeben. Die Rückgabe funktioniert allerdings nicht und ich weiß auch nicht, wie ich es lösen soll.
Zudem habe ich noch einige Fragen bzgl. Javascript, aber die passen dann besser in ein anderes Forum.
Im Minimalbeispiel sollen die aktuellen Slider-Werte von Python an das HTML-Canvas gesendet und angezeigt werden.
Könnt ihr mir da weiter helfen?

app.py

Code: Alles auswählen

from flask import Flask, render_template, request
import json



app = Flask(__name__)

dict_default = {}
dict_default['A'] = 0.1
dict_default['B'] = 0.2
dict_default['C'] = 0.3

@app.route('/', methods=['GET', 'POST'])
def index():
    print('Startseite')
    print(dict_default)
    return render_template('index.html', dict_default=dict_default, dict_default_json=json.dumps(dict_default))


@app.route('/update', methods=['POST', 'GET'])
def update():
    print('Update')
    data = json.loads(request.data)
    for key, value in data.items():
        dict_default[key] = value
    print(dict_default)
    return render_template('index.html', dict_default=dict_default, dict_default_json=json.dumps(dict_default))


if __name__ == '__main__':
    app.run(debug=True, port=5000)

index.html

Code: Alles auswählen

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<form>
    <p>
        <canvas id="canvas1" width="200" height="200"></canvas>
    </p>

    {% for key, value in dict_default.items() %}
        <p>
            <label>{{ key }}: <span class="bold" id="{{ 'val' ~ loop.index }}"> {{ value }} </span></label>
            <input type="range" min="0" max="1" value="{{ value }}" step="0.1" class="myslider" id="{{ 'rs' ~ loop.index }}">
    {% endfor %}

</form>

<meta id="my-data" data-default="{{ dict_default_json }}">


<script>
    function createVariablesSlider(json_count){
        var rangeslider = [];
        for (var i = 1; i <= json_count; ++i) {
          rangeslider[i] = document.getElementById('rs'.concat(i.toString()));
        }
        return rangeslider;
    }
    function createVariablesOutput(json_count){
        var output = [];
        for (var i = 1; i <= json_count; ++i) {
          output[i] = document.getElementById('val'.concat(i.toString()));
        }
        return output;
    }

    const Http = new XMLHttpRequest();
    var json_default = $('#my-data').data("default");
    var json_count = Object.keys(json_default).length;
    var current;
    const sliders = document.getElementsByClassName("myslider");

    const canvas = document.getElementById("canvas1");
    const ctx = canvas.getContext("2d");
    var boxwidth = canvas.width;
    var boxheight = canvas.height;

    ctx.clearRect(-canvas.width/2, -canvas.height/2, canvas.width, canvas.height);
    ctx.fillText('A: ' + json_default['A'], 100, 60)
    ctx.fillText('B: ' + json_default['B'], 100, 80)
    ctx.fillText('C: ' + json_default['C'], 100, 100)


    rangeslider = createVariablesSlider(json_count);
    output = createVariablesOutput(json_count);


    // Loop?
    rangeslider[1].oninput = function() {
        current = this.value;
        output[1].innerHTML = current;
        Http.open('POST', '/update');
        var obj = new Object();
        // Loop?
        obj.A = sliders[0].value;
        obj.B = sliders[1].value;
        obj.C = sliders[2].value;
        Http.send(JSON.stringify(obj));
    }
    rangeslider[2].oninput = function() {
        current = this.value;
        output[2].innerHTML = current;
        Http.open('POST', '/update');
        var obj = new Object();
        // Loop?
        obj.A = sliders[0].value;
        obj.B = sliders[1].value;
        obj.C = sliders[2].value;
        Http.send(JSON.stringify(obj));
    }
    rangeslider[3].oninput = function() {
        current = this.value;
        output[3].innerHTML = current;
        Http.open('POST', '/update');
        var obj = new Object();
        // Loop?
        obj.A = sliders[0].value;
        obj.B = sliders[1].value;
        obj.C = sliders[2].value;
        Http.send(JSON.stringify(obj));
    }



</script>

</body>
</html>
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

`jquery` würde ich ja in neuem Javascript-Code nicht mehr verwenden (also neu im Sinne von seit 10 Jahren nicht mehr).
Soweit ich sehe, benutzt Du $ auch nur für einen sehr trivialen Fall um auf dataset zuzugreifen.
Ungefähr genauso lange benutzt man XMLHTTPRequest nicht mehr, weil das durch fetch ersetzt worden ist.
Das setzen von on<xxx> wurde schon vor noch längerer Zeit durch addEventListener ersetzt.

Deine Slider hast Du einmal in der Variable `sliders` und einmal in `rangeslider`, eine davon kann weg.
Bei Deinen Paragraphen fehlt das </p>
Auch bei HTML sollte man nichtssagende Präfixe wie my oder Nummern an Namen wie canvas1 vermeiden und statt dessen aussagekäftige Namen verwenden.

Das ganze könnte daher ungefähr so aussehen:

Code: Alles auswählen

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <p>
        <canvas id="tolle_grafik" width="200" height="200"></canvas>
    </p>

    {% for key, value in dict_default.items() %}
        <p>
            <label>{{ key }}: <span class="bold"> {{ value }} </span></label>
            <input type="range" min="0" max="1" value="{{ value }}" step="0.1" class="range_slider" data-key="{{key}}">
        </p>
    {% endfor %}

</div>

<script>
    function get_slider_values() {
        const data = {};
        for(const slider of rangesliders) {
            data[slider.dataset.key] = slider.value;
        }
        return data;
    }

    async function slider_changed(event) {
        const paragraph = this.closest('p')
        paragraph.querySelector('span.bold').innerHTML = this.value;

        const data = get_slider_values();
        const response = await fetch("/update", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify(data),
        });
        // TODO: update canvas
        // const result = response.json();
    }

    const rangesliders = document.getElementsByClassName('range_slider')
    for(const slider of rangeslider) {
        slider.addEventListener("input", slider_changed)
    }

    const json_default = get_slider_values();

    const canvas = document.getElementById("tolle_grafik");
    const ctx = canvas.getContext("2d");
    var boxwidth = canvas.width;
    var boxheight = canvas.height;

    ctx.clearRect(-canvas.width/2, -canvas.height/2, canvas.width, canvas.height);
    ctx.fillText('A: ' + json_default['A'], 100, 60)
    ctx.fillText('B: ' + json_default['B'], 100, 80)
    ctx.fillText('C: ' + json_default['C'], 100, 100)
</script>

</body>
</html>
Im Code fehlt noch der eigentliche Code, nämlich, Dein Canvas zu aktualisieren.
Die /update-Methode sollte dann auch kein komplettes HTML zurückliefern, sondern eine Beschreibung, wie das Canvas aktualisiert werden soll, idealerweise in Form von JSON-kodierten Daten.
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Vielen Dank für die nützlichen Tipps und Hinweise. Ich hatte mir das alles aus zahlreichen Foren zusammengebastelt.
Nun wäre noch die Frage wie man das mit dem "update canvas" angeht. Wie macht man das sinnvoll und zeitgemäß? Ich habe bisher eine Lösung, jedoch auch nur mit diesem $-Operator.
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Den Context hast Du doch bereits als globale Variable. Und dann zeichnest Du ganz normal. Was auch immer Du darstellen möchtest.
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Ich habe ja folgendes vor:
- Sliderwerte zu Python senden (funktioniert)
- Mittels der Werte neue Koordinaten berechnen (funktioniert)
- Neue Koordinaten von Python zum Context senden & zeichnen (das müsste doch irgendwie mittels GET funktionieren, habe es bisher aber nicht so richtig geschafft)
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie Zeichnest Du denn Deine Koordinaten auf das Canvas?
Die Information schickt Dein Post-Request JSON-Kodiert an den Browser, und dort hast Du dann den Code, der zeichnet.
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Ich zeichne mittels Javascript in dem Canvas.
Ich kann morgen mal ein Beispiel dazu senden.
Ich würde gern die Koordinaten von Python an Java senden und dort dann zeichnen. Vielleicht drücke ich mich aber auch falsch aus.
Beispiel folgt :)
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Beispielhaft sieht das so aus:

Code: Alles auswählen

const canvas = document.getElementById("canvas1");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(100,100);
ctx.stroke();
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Was ist Dein eigentliches Ziel? Was soll dargestellt werden? Warum ein Canvas? Warum nicht svg? Es gibt auch fertige Javascript-Bibliotheken, die komplexere Graphiken erzeugen können.

Wenn Du auf dem Level bleiben möchtest, dann bist Du schnell dabei, eine eigene Zeichnungssprache zu erfinden.

Code: Alles auswählen

[
    {"mode": "stroke", "points": [[0, 0], [100, 100]], "color": "red"},
]
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Ok ich versuche es mal zu beschreiben.
Ziel ist es eine Oberfläche zu erstellen, die Querschnitte eines Bauteils in Abhängigkeit von Parametern darstellt. Jeder Slider verstellt somit einen Parameter.
Ich habe in Python eine Klassse erstellt, welche die Koordinatenpunkte und die Art der Linie (Gerade, Kreisbogen) in Abhängigkeit der Parameter ausgibt.
Ich möchte also mit Hilfe der Sliderwerte eine Instanz der Klasse erstellen und die Koordinatenpunkte zurückgeben und entsprechend auf der Oberfläche die Punkte verbinden und darstellen.
Die Möglichkeit mit dem Canvas habe ich dabei öfter gesehen und fand sie nicht schlecht.
Später sollte jedoch auch eine Zoom-Funktion (evtl. auch Verschiebung) im Grafikfenster möglich sein, damit man Details gut erkennen kann.

Liege ich da komplett daneben oder ist die Idee mit dem Canvas geeignet dafür?
Ich freue mich auf jeden Fall sehr über deine Hilfe.
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn es nur um Linien geht, dass solltest Du Dir zu erst svg anschauen, ob das für Deine Zwecke geeignet ist. SVG ist ein Vektorformat, so dass Zoom, Verschieben, etc einfach über entsprechende Transformationen geht. Canvas sind Pixelgrafiken.

Bei svg würde dann der POST-Request einfach das komplette svg-Bild zurückliefern, dass man dann z.B. per innerHTML dem SVG-Container zuweisen kann.

https://itnext.io/javascript-zoom-like- ... c0df016d8d
https://jillix.github.io/svg.pan-zoom.js/
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Vielen Dank, dann mache ich das mal :)
Also müsste ich das svg-Bild in Python zeichnen oder benötigt man noch ein Javascript dazwischen?
Ich werde mal etwas recherchieren.
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Ok ich denke ich habs, danke!
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Hätte noch eine Frage zu HTML.
Ich habe die App jetzt auf einem Rechner laufen. Man kann von anderen Rechnern aus über die IP im Browser auf die App zugreifen. Ein Bestandteil dieser App ist das Öffnen eines bestimmten Ordners bei Klick auf einen Button.
Das habe ich so in Python implementiert, aber nicht daran gedacht, dass der Ordner immer auf dem Rechner geöffnet wird, der die App ausführt.
Nun wäre ja ein Workaround, den Ordner direkt per HTML aufzurufen, bspw. so:

Code: Alles auswählen

<a href="file:///G:/"><button>Ordner öffnen</button>Test</a>
Leider öffnet der Browser dann dennoch keinerlei Explorer. Wenn ich jedoch einen Rechtsklick auf die Schaltfläche tätige und dann "Link in einem neuen Tab öffnen" klicke, öffnet sich der File-Explorer.

Hat jemand eine Idee, woran es liegen könnte?
Benutzeravatar
__blackjack__
User
Beiträge: 13998
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patrick1990: Schau auf die Konsole vom Browser oder wo immer bei Deinem die Fehlermeldungen landen.

Webseiten können keine lokalen Dateien verlinken. Aus Sicherheitsgründen.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Das geht aus Sicherheitsgründen nicht. Warum soll ein Ordner geöffnet werden? Was ist der Zweck des Ganzen?
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

Die Console sagt folgendes: "Sicherheitsfehler: Inhalt auf http://127.0.0.1:8080/ darf file:///G:/ nicht laden oder verlinken."
Schade, das lässt sich sicher auch nicht beheben. Finde es nur seltsam, dass es geht, wenn ich die gewünschte Adresse in einem neuen Tab öffne.

Die Idee (der Zweck) ist folgende(r):
Einige Kollegen können über die Flask-Anwendung über Ihren Browser auf eine Datenbank zugreifen. In dieser Datenbank sind Protokolle hinterlegt und die zugehörigen Daten (oftmals mehrere GB pro Protokoll) sind in einem Ordner auf einem Netzwerklaufwerk abgelegt. Ich habe nun einen HTML-Button, der bei Betätigung diesen Ordner öffnen soll. Jedoch bei jedem der Kollegen auf Ihrem PC und nicht auf dem Host-PC.
Gibt es da evtl. noch eine andere Möglichkeit?
Benutzeravatar
Kebap
User
Beiträge: 772
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Ich glaube, Browser können einen "Datei öffnen"-Dialog anzeigen, zum Beispiel wenn man Fotos zu einem Sharehoster hochladen will.
Vielleicht hilft das schon in deinem Fall, wenn diese Popups direkt im Ordner mit den Protokollen starten, und die User das Protokoll anklicken können, je nachdem welches Format es ist, würde es im Browser geöffnet, oder in einer eigenen Anwendung.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kebap: beim "Datei öffnen"-Dialog kann man aber nicht vorgeben, welches Verzeichnis da geöffnet werden soll.

@Patrick1990: Du kannst einen Copy-To-Clipboard-Button anbieten, so dass die Nutzerin nur im Explorer per Copy-Paste das Verzeichnis einfügen kann. Eventuell kannst Du auch auch alle Protokolle im Browser darstellen, so dass man gleich den Pfad zu einem bestimmten Protokoll kopieren kann.

Falls das Programm, das mit diesem Dateityp assoziiert ist, ein URL-Schema registriert hat, kann man damit auch direkt die Applikation öffnen, z.B. bei Excel: href="ms-excel:ofe|u|file://G:/Pfad/zur/Datei.xlsx"
Dann wird der Nutzer gefragt, ob er wirklich diese Datei öffnen will, aber mit Ok landet man direkt in Excel.

Und nun kommt der Trick. Es gibt ein Protokoll, das die Explorer-Suche öffnet: href="search-ms:displayname=Protokolle&amp;crumb=&amp;crumb=location:G:\pfad\zu\den\protokollen"
Patrick1990
User
Beiträge: 134
Registriert: Freitag 3. Juni 2016, 05:45

@Sirius3: Ja, die Protokolle werden direkt im Browser angezeigt. Die Anhänge dazu (Fotos, Messdaten, ..., jedoch unterschiedlichste Dateiformate) werden jedoch in einem Ordner auf einem Netzwerklaufwerk hinterlegt. Die Datenbank enthält einen Verweis auf den Pfad des Ordners.

Den Trick werde ich direkt mal testen :)
Antworten