Πρόσφατα αντιμετωπίσαμε το πρόβλημα να εμφανίσουμε ένα σχήμα γραφήματος ποιότητας σε μια εφαρμογή ή ιστότοπο. Αλλά επιπλέον θέλετε να μπορείτε να στέλνετε συμβάντα ενημέρωσης από το back-end της python και να έχετε όλες τις ωραίες αλληλεπιδράσεις όπως το πάτημα κουμπιού και τα συμβάντα εισαγωγής κειμένου που έρχονται με το Angular, σωστά;

Ένα στοιχείο γραφήματος Bokeh μπορεί να μην είναι πάντα η βέλτιστη λύση, αλλά παρόλο που - θα θέλαμε να μοιραστούμε μαζί σας σε αυτό το αποθετήριο GitHub - αυτό που πιστεύουμε είναι ένα ωραίο, ελάχιστο παράδειγμα και ένας επίδειξης του τρόπου ενσωμάτωσης ενός backend python σε μια γωνιακή εφαρμογή.

Το σημείο εκκίνησης

είναι το BokehJS lib το οποίο ενσωματώνουμε σε ένα Angular έργο. Τα δεδομένα σχεδίασης παρέχονται από μια υπηρεσία websocket, στο παράδειγμά μας χρησιμοποιούμε το aiohttp, αλλά μπορείτε να ρυθμίσετε οποιαδήποτε άλλη σύνδεση websocket. Ένα γωνιακό στοιχείο μπορεί να ενσωματωθεί οπουδήποτε στο html με το όνομα ετικέτας του, το ακόλουθο απόσπασμα δείχνει το στοιχείο γραφήματος Bokeh

<bokeh-chart></bokeh-chart>

Το στοιχείο του διαγράμματος bokeh είναι ένα κανονικό γωνιακό στοιχείο, με ένα τμήμα html

<div [id]="id"></div>

και ένα δακτυλόγραφο μέρος. Το στοιχείο γραφήματος χρειάζεται μόνο να παρέχει το αναγνωριστικό στο δικό του τμήμα html. Τα δεδομένα για το γράφημα παρέχονται από μια υπηρεσία που καλείται ακριβώς κατά την προετοιμασία του στοιχείου στο ngOnInit. Το σχετικό τμήμα γραφομηχανής του στοιχείου γραφήματος Bokeh μοιάζει

...
export class BokehChartComponent implements OnInit {
  public id: string;

  constructor(
    private bokehService: BokehService) { }


 ngOnInit() {
     this.id = "chart";
     this.bokehService.getChart(this.id);
 }
}

Δεδομένου ότι η lib BokehJS δεν έχει διαθέσιμους τύπους, η ενσωμάτωση στο γωνιακό δεν είναι τόσο απλή όσο θα έπρεπε. Κάποιος έχει πρόσβαση σε αυτό το lib μόνο μέσω του καθολικού εκτεθειμένου αντικειμένου του lib, το οποίο σε αυτήν την περίπτωση ονομάζεται επίσης Bokehκαι το οποίο είναι το μόνο άγκιστρο που είναι απαραίτητο για την ενσωμάτωση ενός γραφήματος.

// this is the global hook to the bokehjs lib (without types)
declare var Bokeh: any;

Αυτή η μαγεία λειτουργεί μόνο όπως αναμένεται εάν συνδέσετε το συνηθισμένο σενάριο java στο κορυφαίο αρχείο html της γωνιακής εφαρμογής index.html

<head>
 ...
  <link
    href="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.css"
    rel="stylesheet" type="text/css">
  <script src="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.js"></script>
 </head>

Η υπηρεσία Bokeh

παρέχει τα δεδομένα για το γράφημα μέσω μιας MessageService η οποία ενσωματώνει τη σύνδεση στο backend έτσι ώστε να χρειάζεται μόνο να στείλει ένα σωστά μορφοποιημένο μήνυμα με την εκτεθειμένη μέθοδο sendMsg(msg).

export class BokehService extends Connector {
constructor(private msgService: MessageService) {
 super(‘BokehService’);
 this.msgService.register(this);
 }
…
public getChart(id: string) {
 const msg = {
 name: ‘addChart’,
 args: [id],
 action: ‘default’
 };
 this.msgService.sendMsg(msg);
 }

Αυτή η υπηρεσία εκθέτει επίσης μια μέθοδο στο backend, η οποία στην πραγματικότητα σχεδιάζει το γράφημα στο εγγενές στοιχείο DOM, όπου πρέπει πρώτα να διαγράψουμε τα προηγούμενα διαγράμματα.

public plot(msg: Message) {
      const id = msg.args.id;
      const el = document.getElementById(id);
      // first remove the previous charts as child
      // like this, bokeh does not let us update a chart
      while (el.hasChildNodes()) {
            el.removeChild(el.lastChild);
      }
      // be sure to include the correct dom-id as second argument
      Bokeh.embed.embed_item(msg.args.item, id);
    }

Η υπηρεσία back-end

στο παράδειγμά μας είναι γραμμένο σε python. Χρησιμοποιούμε το aiohttp ως ασύγχρονη λύση για έναν web server. Αμέσως μετά την εκκίνηση της γωνιακής εφαρμογής στο πρόγραμμα περιήγησης, το γωνιακό WebsocketService συνδέεται αμέσως με το backend της python στην πλευρά του διακομιστή. Θυμηθείτε ότι κατά την παραγωγή θα εφαρμόσατε περισσότερη ασφάλεια σε αυτό το σημείο, όπως ένας έλεγχος ταυτότητας. Το backend είναι έτοιμο να λάβει συμβάντα από angular, όπως π.χ. δώστε μου τα δεδομένα για το διάγραμμα Bokeh.

Το addChart που καλείται από το μήνυμα από το angular, στέλνει το chartItem ως στοιχείο json που συνδέεται με την υπηρεσία websocket

    async def addChart(self, id_, user):
        """
        Example for adding a bokeh chart from backend

        """
        chartItem = self.chartProvider.chartExample()
        print("try to add chart for dom-id %s" % id_)
        context = {"name": "BokehService",
                   "args": {"item": chartItem, "id": id_},
                   "action": "plot"}
        await self.send_event(json.dumps(context), user=user)

Το ενδιαφέρον μέρος εδώ είναι η μέθοδος send_event που στην πραγματικότητα βασίζεται στην υλοποίηση του διακομιστή websocket. Όπως αναφέρθηκε ήδη, αυτό το μέρος μπορεί να διαφέρει στην ατομική σας εφαρμογή.

Το ελάχιστο παράδειγμα για το γράφημα, γραμμένο επίσης ως συνάρτηση μέλους της κλάσης ChartProvider, φαίνεται πολύ απλό και απλώς παράγει τα δεδομένα για μια απλή αμαρτία στο Bokeh

import time
import numpy as np
from bokeh.plotting import figure
from bokeh.embed import json_item
class ChartProvider():
    def chartExample(self):
        t0 = time.time()
        # prepare some data
        self.phi += 0.02
        x = np.arange(0., 10., 0.1)
        y = np.sin(x + self.phi)
        # create a new plot
        p = figure()
        p.line(x, y, legend="SIN")
        chart_item = json_item(p)
        print(time.time()-t0)
        return chart_item