Erstellen von Echtzeit-Diagrammen mit GraphQL und Postgres
Veröffentlicht: 2022-03-10Diagramme sind ein wesentlicher Bestandteil jeder Branche, die mit Daten zu tun hat. Diagramme sind in der Abstimmungs- und Umfragebranche nützlich und helfen uns auch dabei, die unterschiedlichen Verhaltensweisen und Eigenschaften der Benutzer und Kunden, mit denen wir zusammenarbeiten, besser zu verstehen.
Warum sind Echtzeit-Charts so wichtig? Nun, sie sind nützlich, wenn ständig neue Daten produziert werden; Wenn beispielsweise Live-Zeitreihen zur Visualisierung von Aktienkursen verwendet werden, ist dies eine großartige Verwendung für Echtzeit-Charts. In diesem Tutorial erkläre ich, wie man Echtzeit-Charts mit Open-Source-Technologien erstellt, die genau für diese spezielle Aufgabe geeignet sind.
Hinweis : Dieses Tutorial erfordert Grundkenntnisse in React und GraphQL.
Stapel
- PostgreSQL
Der eigentliche Sinn hinter der Verwendung von Diagrammen ist die Visualisierung „riesiger“ Volumendaten. Wir brauchen daher eine Datenbank, die große Datenmengen effizient handhabt und eine intuitive API zur Umstrukturierung bereitstellt. SQL-Datenbanken ermöglichen es uns, Ansichten zu erstellen, die Daten für uns abstrahieren und aggregieren. Wir werden Postgres verwenden, eine bewährte und hocheffiziente Datenbank. Es hat auch ausgefallene Open-Source-Erweiterungen wie Timescale und PostGIS, die es uns ermöglichen, geolokalisierungsbasierte bzw. zeitreihenbasierte Diagramme zu erstellen. Wir werden Timescale verwenden, um unser Zeitreihendiagramm zu erstellen. - GraphQL-Engine
In diesem Beitrag geht es um das Erstellen von Echtzeit-Diagrammen, und GraphQL enthält eine klar definierte Spezifikation für Echtzeit-Abonnements. Hasura GraphQL Engine ist ein Open-Source-GraphQL-Server, der eine Postgres-Verbindung herstellt und es Ihnen ermöglicht, die Postgres-Daten über Echtzeit-GraphQL abzufragen. Es verfügt außerdem über eine Zugriffskontrollebene, mit der Sie Ihre Daten basierend auf benutzerdefinierten Zugriffskontrollregeln einschränken können. - DiagrammJS
ChartJS ist eine beliebte und gut gepflegte Open-Source-Bibliothek zum Erstellen von Diagrammen mit JavaScript. Wir werdenchart.js
zusammen mit seiner ReactJS-Abstraktion „react-chartjs-2
. Der Grund für React liegt darin, dass React Entwickler mit einer intuitiven ereignisgesteuerten API ausstattet. Außerdem ist der unidirektionale Datenfluss von React ideal zum Erstellen von Diagrammen, die datengesteuert sind.
Anforderungen
Für dieses Tutorial benötigen Sie Folgendes auf Ihrem System:
- Docker-CE
Docker ist eine Software, mit der Sie Ihre Anwendungen containerisieren können. Ein Docker-Image ist ein unabhängiges Paket, das Software zusammen mit ihren Abhängigkeiten und einem minimalistischen Betriebssystem enthält. Solche Docker-Images können technisch auf jedem Computer ausgeführt werden, auf dem Docker installiert ist. Für dieses Tutorial benötigen Sie Docker.- Lesen Sie mehr über Docker
- Installieren Sie Docker
- npm: npm ist die Paketverwaltung für JavaScript.
Demo
Wir werden das folgende Live-Zeitreihendiagramm erstellen, das die maximale Temperatur eines Ortes in Intervallen von 5 Sekunden in den letzten 20 Minuten ab dem gegenwärtigen Moment anzeigt.
Einrichten des Backends
Ausführen der Dienste
Das Backend besteht aus einer Postgres-Datenbank, ihrer Zeitskalenerweiterung und der Hasura GraphQL Engine. Lassen Sie uns die Datenbank und unseren GraphQL-Server zum Laufen bringen, indem Sie die entsprechenden Docker-Images ausführen. Erstellen Sie eine Datei namens docker-compose.yaml
und fügen Sie diesen Inhalt darin ein.
Hinweis : docker-compose
ist ein Dienstprogramm zum deklarativen Ausführen mehrerer Docker-Images.
version: '2' services: timescale: image: timescale/timescaledb:latest-pg10 restart: always environment: POSTGRES_PASSWORD: postgrespassword volumes: - db_data:/var/lib/postgresql/data graphql-engine: image: hasura/graphql-engine:v1.0.0-alpha38 ports: - "8080:8080" depends_on: - "timescale" restart: always environment: HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@timescale:5432/postgres HASURA_GRAPHQL_ACCESS_KEY: mylongsecretkey command: - graphql-engine - serve - --enable-console volumes: db_data:
Diese docker-compose.yaml
enthält die Spezifikation für zwei Dienste:
-
timescale
Dies ist unsere Postgres-Datenbank mit installierter Timescale-Erweiterung. Es ist so konfiguriert, dass es auf Port 5432 ausgeführt wird. -
graphql-engine
Dies ist unsere Hasura GraphQL Engine-Instanz, dh der GraphQL-Server, der auf die Datenbank verweist und GraphQL-APIs darüber bereitstellt. Er ist für die Ausführung an Port 8080 konfiguriert, und Port 8080 ist Port 8080 der Maschine zugeordnet, auf der dieser Docker-Container ausgeführt wird. Das bedeutet, dass Sie überlocalhost:8080
des Computers auf diesen GraphQL-Server zugreifen können.
Lassen Sie uns diese Docker-Container ausführen, indem Sie den folgenden Befehl dort ausführen, wo Sie Ihre docker-compose.yaml
platziert haben.
docker-compose up -d
Dieser Befehl zieht die Docker-Images aus der Cloud und führt sie in der angegebenen Reihenfolge aus. Je nach Internetgeschwindigkeit kann es einige Sekunden dauern. Sobald dies abgeschlossen ist, können Sie unter https://localhost:8080/console
auf Ihre GraphQL Engine-Konsole zugreifen.
Einrichten der Datenbank
Lassen Sie uns als Nächstes eine Tabelle namens Temperatur erstellen, die die Temperaturwerte zu verschiedenen Zeiten speichert. Wechseln Sie in der Konsole zur Registerkarte Daten und zum Abschnitt SQL
. Erstellen Sie unsere temperature
, indem Sie diesen SQL-Block ausführen:
CREATE TABLE temperature ( temperature numeric not null, location text not null, recorded_at timestamptz not null default now() );
Dadurch wird eine einfache Postgres-Tabelle in der Datenbank erstellt. Wir möchten jedoch die Zeitintervallpartitionierung der Timescale-Erweiterung nutzen. Dazu müssen wir diese Tabelle in die Hypertabelle von timescale konvertieren, indem wir den SQL-Befehl ausführen:
SELECT create_hypertable('temperature', 'recorded_at');
Dieser Befehl erstellt eine nach Zeit partitionierte recorded_at
im Feldrecorded_at .
Da diese Tabelle nun erstellt ist, können wir direkt damit beginnen, GraphQL-Abfragen darüber zu machen. Sie können sie ausprobieren, indem Sie oben auf die Registerkarte GraphiQL
klicken. Versuchen Sie zuerst, eine Mutation vorzunehmen:
mutation { insert_temperature ( objects: [{ temperature: 13.4 location: "London" }] ) { returning { recorded_at temperature } } }
Die obige GraphQL-Mutation fügt eine Zeile in die temperature
ein. Versuchen Sie nun, eine GraphQL-Abfrage durchzuführen, um zu überprüfen, ob die Daten eingefügt wurden.
Versuchen Sie dann, eine Abfrage zu erstellen:
query { temperature { recorded_at temperature location } }
Hoffe es hat geklappt :)
Unsere Aufgabe besteht nun darin, ein Live-Zeitreihendiagramm zu erstellen, das die maximale Temperatur eines Ortes in Intervallen von 5 Sekunden in den letzten 20 Minuten ab dem gegenwärtigen Moment anzeigt. Lassen Sie uns eine Ansicht erstellen, die uns genau diese Daten liefert.
CREATE VIEW last_20_min_temp AS ( SELECT time_bucket('5 seconds', recorded_at) AS five_sec_interval, location, MAX(temperature) AS max_temp FROM temperature WHERE recorded_at > NOW() - interval '20 minutes' GROUP BY five_sec_interval, location ORDER BY five_sec_interval ASC );
Diese Ansicht gruppiert die Daten aus der temperature
in 5-Sekunden-Fenstern mit ihrer maximalen Temperatur ( max_temp)
. Die sekundäre Gruppierung erfolgt über das location
. All diese Daten stammen nur aus den letzten zwanzig Minuten ab dem gegenwärtigen Moment.
Das ist es. Unser Backend ist eingerichtet. Lassen Sie uns nun ein nettes Echtzeit-Diagramm erstellen.
Vorderes Ende
Hallo GraphQL-Abonnements
GraphQL-Abonnements sind im Wesentlichen „Live“-GraphQL-Abfragen. Sie arbeiten über WebSockets und haben genau dieselbe Antwortstruktur wie GraphQL-Abfragen. Gehen Sie zurück zu https://localhost:8080/console
und versuchen Sie, ein GraphQL-Abonnement für die von uns erstellte Ansicht abzuschließen.
subscription { last_20_min_temp( order_by: { five_sec_interval: asc } where: { location: { _eq: "London" } } ) { five_sec_interval location max_temp } }
Dieses Abonnement abonniert die Daten in der Ansicht, in der der Standort London
ist, und sie sind in aufsteigender Reihenfolge der five_second_intervals
.
Die Antwort der Ansicht wäre natürlich ein leeres Array, da wir in den letzten zwanzig Minuten nichts in die Datenbank eingefügt haben. (Möglicherweise sehen Sie den Eintrag, den wir vor einiger Zeit eingefügt haben, wenn Sie diesen Abschnitt innerhalb von zwanzig Minuten erreicht haben.)
{ "data": { "last_20_min_temp": [] } }
Behalten Sie dieses Abonnement bei, öffnen Sie eine andere Registerkarte und versuchen Sie, einen anderen Wert in die temperatures
einzufügen, indem Sie dieselbe Mutation verwenden, die wir zuvor durchgeführt haben. Wenn Sie nach dem Einfügen zu der Registerkarte zurückkehren, auf der das Abonnement aktiv war, sehen Sie, dass die Antwort automatisch aktualisiert wurde. Das ist die Echtzeit-Magie, die GraphQL Engine bietet. Lassen Sie uns dieses Abonnement verwenden, um unser Echtzeit-Diagramm zu betreiben.
Erste Schritte mit der Create-React-App
Lassen Sie uns schnell mit einem React-App-Starter beginnen, indem Sie eine React-App erstellen. Führen Sie den Befehl aus:
npx create-react-app time-series-chart
Dadurch wird ein leeres Starterprojekt erstellt. cd
hinein und installieren Sie die GraphQL- und Diagrammbibliotheken. Installieren Sie außerdem Moment zum Konvertieren von Zeitstempeln in ein für Menschen lesbares Format.
cd time-series-chart npm install --save apollo-boost apollo-link-ws subscriptions-transport-ws graphql react-apollo chart.js react-chartjs-2 moment
Führen Sie die App schließlich mit npm start
aus, und eine einfache React-App wird unter https://localhost:3000
geöffnet.
Einrichten des Apollo-Clients für clientseitiges GraphQL
Der Apollo-Client ist derzeit der beste GraphQL-Client, der mit jedem GraphQL-kompatiblen Server funktioniert. Relay Modern ist auch gut, aber der Server muss die Relay-Spezifikation unterstützen, um alle Vorteile von Relay Modern nutzen zu können. Für dieses Tutorial verwenden wir den Apollo-Client für clientseitiges GraphQL. Lassen Sie uns die Einrichtung durchführen, um den Apollo-Client für die App bereitzustellen.
Ich gehe nicht auf die Feinheiten dieses Setups ein, da die folgenden Codeausschnitte direkt aus der Dokumentation stammen. Gehen Sie zu src/index.js
im React-App-Verzeichnis und instanziieren Sie den Apollo-Client und fügen Sie dieses Code-Snippet über ReactDOM.render
.
import { WebSocketLink } from 'apollo-link-ws'; import { ApolloClient } from 'apollo-client'; import { ApolloProvider } from 'react-apollo'; import { InMemoryCache } from 'apollo-cache-inmemory'; // Create a WebSocket link: const link = new WebSocketLink({ uri: `ws://localhost:8080/v1alpha1/graphql`, options: { reconnect: true, connectionParams: { headers: { "x-hasura-admin-secret: "mylongsecretkey" } } } }) const cache = new InMemoryCache(); const client = new ApolloClient({ link, cache });
Wickeln Sie schließlich die App
in ApolloProvider
, damit wir den Apollo-Client in den untergeordneten Komponenten verwenden können. Ihre App.js
sollte schließlich so aussehen:
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { WebSocketLink } from 'apollo-link-ws'; import { ApolloClient } from 'apollo-client'; import { ApolloProvider } from 'react-apollo'; import { InMemoryCache } from 'apollo-cache-inmemory'; // Create a WebSocket link: const link = new WebSocketLink({ uri: `ws://localhost:8080/v1alpha1/graphql`, options: { reconnect: true, connectionParams: { headers: { "x-hasura-admin-secret: "mylongsecretkey" } } } }) const cache = new InMemoryCache(); const client = new ApolloClient({ link, cache }); ReactDOM.render( ( <ApolloProvider client={client}> <App /> </ApolloProvider> ), document.getElementById('root') );
Der Apollo-Client wurde eingerichtet. Wir können jetzt ganz einfach Echtzeit-GraphQL von unserer App aus verwenden. Gehen Sie zu src/App.js
.
Erstellen des Diagramms
ChartJS bietet eine ziemlich übersichtliche API zum Erstellen von Diagrammen. Wir werden ein Liniendiagramm erstellen; ein Liniendiagramm erwartet also Daten der Form:
{ "labels": ["label1", "label2", "label3", "label4"], "datasets": [{ "label": "Sample dataset", "data": [45, 23, 56, 55], "pointBackgroundColor": ["red", "brown", "green", "yellow"], "borderColor": "brown", "fill": false }], }
Wenn der obige Datensatz zum Rendern eines Liniendiagramms verwendet wird, würde er ungefähr so aussehen:
Lassen Sie uns zuerst versuchen, dieses Beispieldiagramm zu erstellen. Importieren Sie Line
aus react-chartjs-2
und rendern Sie sie, indem Sie das obige Objekt als Datenstütze übergeben. Die Render-Methode würde in etwa so aussehen:
render() { const data = { "labels": ["label1", "label2", "label3", "label4"], "datasets": [{ "label": "Sample dataset", "data": [45, 23, 56, 55], "pointBackgroundColor": ["red", "brown", "green", "yellow"], "borderColor": "brown", "fill": false }], } return ( <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '20px'}} > <Line data={data} /> </div> ); }
Als Nächstes abonnieren wir die Daten in unserer Ansicht und speisen sie in das Liniendiagramm ein. Aber wie führen wir Abonnements auf dem Client durch?
Die <Subscription>
-Komponenten von Apollo arbeiten mit dem Render-Prop-Muster, bei dem die Kinder einer Komponente mit dem Kontext der Abonnementdaten gerendert werden.
<Subscription subscription={gql`subscription { parent { child } }`} /> { ({data, error, loading}) => { if (error) return <Error error={error} />; if (loading) return <Loading />; return <RenderData data={data} />; } } </Subscription>
Lassen Sie uns eine solche Subscription
verwenden, um unsere Ansicht zu abonnieren und dann die Abonnementdaten in die von ChartJS erwartete Struktur umzuwandeln. Die Transformationslogik sieht folgendermaßen aus:
let chartJSData = { labels: [], datasets: [{ label: "Max temperature every five seconds", data: [], pointBackgroundColor: [], borderColor: 'brown', fill: false }] }; data.last_20_min_temp.forEach((item) => { const humanReadableTime = moment(item.five_sec_interval).format('LTS'); chartJSData.labels.push(humanReadableTime); chartJSData.datasets[0].data.push(item.max_temp); chartJSData.datasets[0].pointBackgroundColor.push('brown'); })
Hinweis : Sie können auch die Open-Source-Bibliothek graphq2chartjs verwenden, um die Daten aus der GraphQL-Antwort in ein von ChartJS erwartetes Formular umzuwandeln.
Nachdem Sie dies in der Subscription-Komponente verwendet haben, sieht unsere App.js
wie folgt aus:
import React, { Component } from 'react'; import { Line } from 'react-chartjs-2'; import { Subscription } from 'react-apollo'; import gql from 'graphql-tag'; import moment from 'moment'; const TWENTY_MIN_TEMP_SUBSCRIPTION= gql' subscription { last_20_min_temp( order_by: { five_sec_interval: asc } where: { location: { _eq: "London" } } ) { five_sec_interval location max_temp } } ' class App extends Component { render() { return ( <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '20px'}} > <Subscription subscription={TWENTY_MIN_TEMP_SUBSCRIPTION}> { ({data, error, loading}) => { if (error) { console.error(error); return "Error"; } if (loading) { return "Loading"; } let chartJSData = { labels: [], datasets: [{ label: "Max temperature every five seconds", data: [], pointBackgroundColor: [], borderColor: 'brown', fill: false }] }; data.last_20_min_temp.forEach((item) => { const humanReadableTime = moment(item.five_sec_interval).format('LTS'); chartJSData.labels.push(humanReadableTime); chartJSData.datasets[0].data.push(item.max_temp); chartJSData.datasets[0].pointBackgroundColor.push('brown'); }) return ( <Line data={chartJSData} options={{ animation: {duration: 0}, scales: { yAxes: [{ticks: { min: 5, max: 20 }}]} }} /> ); } } </Subscription> </div> ); } } export default App;
https://localhost:3000
steht Ihnen ein voll funktionsfähiges Echtzeit-Diagramm zur Verfügung. Es wäre jedoch leer, also füllen wir einige Beispieldaten aus, damit wir tatsächlich sehen können, wie etwas Magisches passiert.
Hinweis : Ich habe dem Liniendiagramm einige weitere Optionen hinzugefügt, weil ich diese ausgefallenen Animationen in ChartJS nicht mag. Eine Zeitreihe sieht gut aus, wenn sie einfach ist, aber Sie können die Optionsstütze entfernen, wenn Sie möchten.
Beispieldaten einfügen
Lassen Sie uns ein Skript schreiben, das unsere Datenbank mit Dummy-Daten füllt. Erstellen Sie ein separates Verzeichnis (außerhalb dieser App) und erstellen Sie eine Datei namens script.js
mit folgendem Inhalt:
const fetch = require('node-fetch'); setInterval( () => { const randomTemp = (Math.random() * 5) + 10; fetch( `https://localhost:8080/v1alpha1/graphql`, { method: 'POST', body: JSON.stringify({ query: ` mutation ($temp: numeric) { insert_temperature ( objects: [{ temperature: $temp location: "London" }] ) { returning { recorded_at temperature } } } `, variables: { temp: randomTemp } }) } ).then((resp) => resp.json().then((respObj) => console.log(JSON.stringify(respObj, null, 2)))); }, 2000 );
Führen Sie nun diese beiden Befehle aus:
npm install --save node-fetch node script.js
Sie können zu https://localhost:3000
zurückkehren und sehen, wie sich das Diagramm aktualisiert.
Beenden
Sie können die meisten Echtzeit-Charts mit den oben besprochenen Ideen erstellen. Der Algorithmus ist:
- Stellen Sie die GraphQL-Engine mit Postgres bereit;
- Erstellen Sie Tabellen, in denen Sie Daten speichern möchten;
- Abonnieren Sie diese Tabellen über Ihre React-App;
- Rendern Sie das Diagramm.
Den Quellcode finden Sie hier.