การสร้างแผนภูมิแบบเรียลไทม์ด้วย GraphQL และ Postgres
เผยแพร่แล้ว: 2022-03-10แผนภูมิเป็นส่วนสำคัญของอุตสาหกรรมที่เกี่ยวข้องกับข้อมูล แผนภูมิมีประโยชน์ในอุตสาหกรรมการลงคะแนนและการเลือกตั้ง และยังช่วยให้เราเข้าใจพฤติกรรมและคุณลักษณะต่างๆ ของผู้ใช้และลูกค้าที่เราทำงานด้วยได้ดียิ่งขึ้น
เหตุใดแผนภูมิตามเวลาจริงจึงมีความสำคัญมาก มีประโยชน์ในกรณีที่มีการสร้างข้อมูลใหม่อย่างต่อเนื่อง ตัวอย่างเช่น เมื่อใช้อนุกรมตามเวลาจริงในการแสดงราคาหุ้นจะเป็นประโยชน์อย่างมากสำหรับแผนภูมิแบบเรียลไทม์ ในบทช่วยสอนนี้ ฉันจะอธิบายวิธีสร้างแผนภูมิแบบเรียลไทม์ด้วยเทคโนโลยีโอเพนซอร์สที่เหมาะสำหรับงานนี้โดยเฉพาะ
หมายเหตุ : บทช่วยสอนนี้ต้องการความรู้พื้นฐานเกี่ยวกับ React และ GraphQL
ซ้อนกัน
- PostgreSQL
จุดสำคัญเบื้องหลังการใช้แผนภูมิคือการแสดงภาพข้อมูลปริมาณมาก ดังนั้นเราจึงต้องการฐานข้อมูลที่จัดการข้อมูลขนาดใหญ่ได้อย่างมีประสิทธิภาพและจัดเตรียม API ที่ใช้งานง่ายเพื่อปรับโครงสร้างใหม่ ฐานข้อมูล SQL ช่วยให้เราสามารถให้ความเห็นที่เป็นนามธรรมและข้อมูลรวมสำหรับเรา เราจะใช้ Postgres ซึ่งเป็นฐานข้อมูลที่ผ่านการทดสอบตามเวลาและมีประสิทธิภาพสูง นอกจากนี้ยังมีส่วนขยายโอเพนซอร์ซแฟนซีเช่น Timescale และ PostGIS ซึ่งช่วยให้เราสร้างแผนภูมิตามตำแหน่งทางภูมิศาสตร์และตามอนุกรมเวลาตามลำดับ เราจะใช้ Timescale เพื่อสร้างแผนภูมิอนุกรมเวลาของเรา - เครื่องยนต์ GraphQL
โพสต์นี้เกี่ยวกับการสร้างแผนภูมิแบบเรียลไทม์ และ GraphQL มาพร้อมกับข้อมูลจำเพาะที่ชัดเจนสำหรับการสมัครสมาชิกแบบเรียลไทม์ Hasura GraphQL Engine เป็นเซิร์ฟเวอร์ GraphQL โอเพ่นซอร์สที่ใช้การเชื่อมต่อ Postgres และช่วยให้คุณสามารถสืบค้นข้อมูล Postgres ผ่าน GraphQL แบบเรียลไทม์ นอกจากนี้ยังมาพร้อมกับเลเยอร์การควบคุมการเข้าถึงที่ช่วยให้คุณจำกัดข้อมูลของคุณตามกฎการควบคุมการเข้าถึงที่กำหนดเอง - แผนภูมิJS
ChartJS เป็นไลบรารีโอเพ่นซอร์สที่ได้รับความนิยมและได้รับการดูแลอย่างดีสำหรับการสร้างแผนภูมิด้วย JavaScript เราจะใช้chart.js
พร้อมกับ ReactJS abstractionreact-chartjs-2
เกี่ยวกับสาเหตุที่ React เป็นเพราะ React ช่วยให้นักพัฒนามี API ที่ขับเคลื่อนด้วยเหตุการณ์ที่ใช้งานง่าย นอกจากนี้ การไหลของข้อมูลแบบทิศทางเดียวของ React ยังเหมาะอย่างยิ่งสำหรับการสร้างแผนภูมิที่ขับเคลื่อนด้วยข้อมูล
ความต้องการ
สำหรับบทช่วยสอนนี้ คุณจะต้องมีสิ่งต่อไปนี้ในระบบของคุณ:
- นักเทียบท่า CE
Docker เป็นซอฟต์แวร์ที่ให้คุณบรรจุแอปพลิเคชันของคุณ ภาพนักเทียบท่าเป็นแพ็กเก็ตอิสระที่มีซอฟต์แวร์พร้อมกับการพึ่งพาและระบบปฏิบัติการที่เรียบง่าย อิมเมจนักเทียบท่าดังกล่าวสามารถเรียกใช้ในทางเทคนิคในเครื่องใดก็ได้ที่ติดตั้งนักเทียบท่า คุณจะต้องมีนักเทียบท่าสำหรับบทช่วยสอนนี้- อ่านเพิ่มเติมเกี่ยวกับ Docker
- ติดตั้ง Docker
- npm: npm เป็นแพ็คเกจจัดการสำหรับ JavaScript
การสาธิต
เราจะสร้างแผนภูมิอนุกรมเวลาแบบสดต่อไปนี้ซึ่งแสดงอุณหภูมิสูงสุดของสถานที่หนึ่งๆ ในช่วงเวลา 5 วินาทีในช่วง 20 นาทีที่ผ่านมาจากช่วงเวลาปัจจุบัน
การตั้งค่าแบ็กเอนด์
เรียกใช้บริการ
แบ็กเอนด์ประกอบด้วยฐานข้อมูล Postgres ส่วนขยายไทม์สเกล และ Hasura GraphQL Engine ให้เราใช้ฐานข้อมูลและเซิร์ฟเวอร์ GraphQL ของเราทำงานโดยเรียกใช้อิมเมจนักเทียบท่าที่เกี่ยวข้อง สร้างไฟล์ชื่อ docker-compose.yaml
แล้ววางเนื้อหานี้ลงไป
หมายเหตุ : docker-compose
เป็นโปรแกรมอรรถประโยชน์ในการเรียกใช้ภาพนักเทียบท่าหลายภาพอย่างเปิดเผย
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:
docker-compose.yaml
นี้มีข้อกำหนดสำหรับสองบริการ:
-
timescale
นี่คือฐานข้อมูล Postgres ของเราที่ติดตั้งส่วนขยาย Timescale มีการกำหนดค่าให้ทำงานที่พอร์ต 5432 -
graphql-engine
นี่คืออินสแตนซ์ Hasura GraphQL Engine ของเรา เช่น เซิร์ฟเวอร์ GraphQL ที่ชี้ไปที่ฐานข้อมูลและให้ GraphQL APIs เหนือมัน มีการกำหนดค่าให้ทำงานที่พอร์ต 8080 และพอร์ต 8080 ถูกแมปกับพอร์ต 8080 ของเครื่องที่คอนเทนเนอร์เทียบท่านี้ทำงานอยู่ ซึ่งหมายความว่าคุณสามารถเข้าถึงเซิร์ฟเวอร์ GraphQL นี้ผ่านที่localhost:8080
ของเครื่อง
มาเรียกใช้คอนเทนเนอร์นักเทียบท่าเหล่านี้โดยเรียกใช้คำสั่งต่อไปนี้ทุกที่ที่คุณวาง docker-compose.yaml
docker-compose up -d
คำสั่งนี้จะดึงภาพนักเทียบท่าจากคลาวด์และรันตามลำดับที่กำหนด อาจใช้เวลาสองสามวินาทีตามความเร็วอินเทอร์เน็ตของคุณ เมื่อเสร็จแล้ว คุณสามารถเข้าถึงคอนโซล GraphQL Engine ได้ที่ https://localhost:8080/console
การตั้งค่าฐานข้อมูล
ต่อไป ให้เราสร้างตารางที่เรียกว่าอุณหภูมิที่เก็บค่าอุณหภูมิในช่วงเวลาต่างๆ ไปที่แท็บข้อมูลในคอนโซลและไปที่ส่วน SQL
สร้างตาราง temperature
ของเราโดยเรียกใช้บล็อก SQL นี้:
CREATE TABLE temperature ( temperature numeric not null, location text not null, recorded_at timestamptz not null default now() );
สิ่งนี้จะสร้างตาราง Postgres อย่างง่ายในฐานข้อมูล แต่เราต้องการใช้ประโยชน์จากการแบ่งช่วงเวลาของส่วนขยาย Timescale ในการดำเนินการนี้ เราต้องแปลงตารางนี้เป็นตารางไฮเปอร์เทเบิลของไทม์สเกลโดยเรียกใช้คำสั่ง SQL:
SELECT create_hypertable('temperature', 'recorded_at');
คำสั่งนี้สร้างไฮเปอร์เทเบิลที่แบ่งพาร์ติชันตามเวลาในฟิลด์ที่ recorded_at
ตอนนี้ เนื่องจากตารางนี้ถูกสร้างขึ้น เราจึงสามารถเริ่มต้นการสืบค้น GraphQL ได้โดยตรง คุณสามารถลองใช้ได้โดยคลิกที่แท็บ GraphiQL
ที่ด้านบน ลองทำการกลายพันธุ์ก่อน:
mutation { insert_temperature ( objects: [{ temperature: 13.4 location: "London" }] ) { returning { recorded_at temperature } } }
การกลายพันธุ์ของ GraphQL ด้านบนจะแทรกแถวในตาราง temperature
ตอนนี้ลองทำแบบสอบถาม GraphQL เพื่อตรวจสอบว่าข้อมูลถูกแทรกหรือไม่
จากนั้นลองทำแบบสอบถาม:
query { temperature { recorded_at temperature location } }
หวังว่ามันจะได้ผล :)
ตอนนี้ งานในมือของเราคือการสร้างแผนภูมิอนุกรมเวลาแบบสดที่แสดงอุณหภูมิสูงสุดของสถานที่ในช่วงเวลา 5 วินาทีในช่วง 20 นาทีที่ผ่านมาจากช่วงเวลาปัจจุบัน มาสร้างมุมมองที่ให้ข้อมูลนี้แก่เรา
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 );
มุมมองนี้จัดกลุ่มข้อมูลจากตาราง temperature
ในหน้าต่าง 5 วินาทีด้วยอุณหภูมิสูงสุด ( max_temp)
การจัดกลุ่มรองทำได้โดยใช้ฟิลด์ location
ข้อมูลทั้งหมดนี้เป็นเพียงช่วงยี่สิบนาทีที่ผ่านมาจากช่วงเวลาปัจจุบัน
แค่นั้นแหละ. แบ็กเอนด์ของเราถูกตั้งค่า ให้เราสร้างแผนภูมิแบบเรียลไทม์ที่ดี
ส่วนหน้า
สวัสดีการสมัครสมาชิก GraphQL
การสมัครสมาชิก GraphQL เป็นการสืบค้น GraphQL แบบ "สด" พวกเขาทำงานผ่าน WebSockets และมีโครงสร้างการตอบสนองเหมือนกันทุกประการเช่นการสืบค้น GraphQL กลับไปที่ https://localhost:8080/console
และลองสมัครสมาชิก GraphQL สำหรับมุมมองที่เราสร้างขึ้น
subscription { last_20_min_temp( order_by: { five_sec_interval: asc } where: { location: { _eq: "London" } } ) { five_sec_interval location max_temp } }
การสมัครรับข้อมูลนี้จะสมัครรับข้อมูลในมุมมองที่สถานที่คือ London
และมีการเรียงลำดับจากน้อยไปหามากของ five_second_intervals
โดยปกติการตอบสนองจากมุมมองจะเป็นอาร์เรย์ที่ว่างเปล่าเพราะเราไม่ได้แทรกอะไรในฐานข้อมูลในช่วงยี่สิบนาทีที่ผ่านมา (คุณอาจเห็นข้อความที่เราแทรกในบางครั้ง หากคุณมาถึงส่วนนี้ภายในยี่สิบนาที)
{ "data": { "last_20_min_temp": [] } }
เปิดการสมัครสมาชิกนี้ไว้ เปิดแท็บอื่นแล้วลองใส่ค่าอื่นในตาราง temperatures
โดยใช้การกลายพันธุ์แบบเดียวกับที่เราดำเนินการก่อนหน้านี้ หลังจากแทรกแล้ว หากคุณกลับไปที่แท็บที่เปิดการสมัครรับข้อมูล คุณจะเห็นการตอบกลับอัปเดตโดยอัตโนมัติ นั่นคือความมหัศจรรย์แบบเรียลไทม์ที่ GraphQL Engine มอบให้ ลองใช้การสมัครรับข้อมูลนี้เพื่อเพิ่มพลังให้กับแผนภูมิแบบเรียลไทม์ของเรา
เริ่มต้นใช้งาน Create-React-App
ให้เราเริ่มต้นอย่างรวดเร็วด้วยตัวเริ่มต้นแอป React โดยใช้สร้างแอปตอบโต้ เรียกใช้คำสั่ง:
npx create-react-app time-series-chart
นี้จะสร้างโครงการเริ่มต้นที่ว่างเปล่า cd
ลงในนั้นและติดตั้ง GraphQL และไลบรารีแผนภูมิ ติดตั้งช่วงเวลาเพื่อแปลงการประทับเวลาให้อยู่ในรูปแบบที่มนุษย์อ่านได้
cd time-series-chart npm install --save apollo-boost apollo-link-ws subscriptions-transport-ws graphql react-apollo chart.js react-chartjs-2 moment
สุดท้าย ให้รันแอปด้วย npm start
และแอป React พื้นฐานจะเปิดขึ้นที่ https://localhost:3000
การตั้งค่าไคลเอ็นต์ Apollo สำหรับ GraphQL . ฝั่งไคลเอ็นต์
ปัจจุบันไคลเอนต์ Apollo เป็นไคลเอนต์ GraphQL ที่ดีที่สุดที่ใช้งานได้กับเซิร์ฟเวอร์ที่รองรับ GraphQL Relay modern ก็ดีเหมือนกัน แต่เซิฟเวอร์ต้องรองรับ relay spec เพื่อใช้ประโยชน์จาก Relay modern ทั้งหมด เราจะใช้ไคลเอ็นต์ Apollo สำหรับ GraphQL ฝั่งไคลเอ็นต์สำหรับบทช่วยสอนนี้ ให้เราทำการตั้งค่าเพื่อมอบไคลเอนต์ Apollo ให้กับแอพ
ฉันไม่ได้เข้าสู่รายละเอียดปลีกย่อยของการตั้งค่านี้เนื่องจากข้อมูลโค้ดต่อไปนี้นำมาจากเอกสารโดยตรง ไปที่ src/index.js
ในไดเร็กทอรีแอป React และสร้างอินสแตนซ์ของไคลเอ็นต์ Apollo และเพิ่มข้อมูลโค้ดนี้ด้านบน 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 });
สุดท้าย ห่อ App
ไว้ใน ApolloProvider
เพื่อให้เราสามารถใช้ไคลเอ็นต์ Apollo ในคอมโพเนนต์ลูกได้ ในที่สุด App.js
ของคุณควรมีลักษณะดังนี้:
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') );
ไคลเอนต์ Apollo ถูกตั้งค่าแล้ว ตอนนี้เราสามารถใช้ GraphQL แบบเรียลไทม์จากแอพของเราได้อย่างง่ายดาย ไปที่ src/App.js
การสร้างแผนภูมิ
ChartJS จัดเตรียม API ที่ค่อนข้างเรียบร้อยสำหรับการสร้างแผนภูมิ เราจะสร้างแผนภูมิเส้น ดังนั้นแผนภูมิเส้นจึงคาดหวังข้อมูลของแบบฟอร์ม:
{ "labels": ["label1", "label2", "label3", "label4"], "datasets": [{ "label": "Sample dataset", "data": [45, 23, 56, 55], "pointBackgroundColor": ["red", "brown", "green", "yellow"], "borderColor": "brown", "fill": false }], }
หากใช้ชุดข้อมูลด้านบนเพื่อแสดงแผนภูมิเส้น จะมีลักษณะดังนี้:
ให้เราลองสร้างแผนภูมิตัวอย่างนี้ก่อน นำเข้า Line
จาก react-chartjs-2
และแสดงผลผ่านวัตถุด้านบนเป็น data prop วิธีการเรนเดอร์จะมีลักษณะดังนี้:
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> ); }
ต่อไป เราจะสมัครรับข้อมูลในมุมมองของเราและป้อนลงในแผนภูมิเส้น แต่เราจะทำการสมัครสมาชิกกับลูกค้าได้อย่างไร?
คอมโพเนนต์ <Subscription>
ของ Apollo ทำงานโดยใช้รูปแบบ prop การแสดงผล โดยที่องค์ประกอบลูกขององค์ประกอบจะถูกแสดงผลด้วยบริบทของข้อมูลการสมัครรับข้อมูล
<Subscription subscription={gql`subscription { parent { child } }`} /> { ({data, error, loading}) => { if (error) return <Error error={error} />; if (loading) return <Loading />; return <RenderData data={data} />; } } </Subscription>
ให้เราใช้องค์ประกอบการ Subscription
ดังกล่าวเพื่อสมัครรับข้อมูลพร็อพเพอร์ตี้ของเรา แล้วแปลงข้อมูลการสมัครรับข้อมูลเป็นโครงสร้างที่ ChartJS คาดหวัง ตรรกะการแปลงมีลักษณะดังนี้:
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'); })
หมายเหตุ : คุณยังสามารถใช้ไลบรารีโอเพ่นซอร์ส graphq2chartjs เพื่อแปลงข้อมูลจากการตอบสนองของ GraphQL เป็นรูปแบบที่ ChartJS คาดหวัง
หลังจากใช้สิ่งนี้ภายในองค์ประกอบการสมัครสมาชิก App.js
ของเรามีลักษณะดังนี้:
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
อย่างไรก็ตาม ข้อมูลจะว่างเปล่า ดังนั้น ให้ใส่ข้อมูลตัวอย่างเพื่อที่เราจะได้เห็นว่ามีเวทย์มนตร์เกิดขึ้นจริง
หมายเหตุ : ฉันได้เพิ่มตัวเลือกเพิ่มเติมในแผนภูมิเส้นเพราะฉันไม่ชอบแอนิเมชั่นแฟนซีใน ChartJS อนุกรมเวลาดูน่ารักเมื่อมันเรียบง่าย อย่างไรก็ตาม คุณสามารถลบตัวเลือกตัวเลือกได้หากต้องการ
การแทรกข้อมูลตัวอย่าง
ให้เขียนสคริปต์ที่เติมฐานข้อมูลของเราด้วยข้อมูลจำลอง สร้างไดเร็กทอรีแยกต่างหาก (นอกแอพนี้) และสร้างไฟล์ชื่อ script.js
โดยมีเนื้อหาดังต่อไปนี้
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 );
ตอนนี้รันสองคำสั่งนี้:
npm install --save node-fetch node script.js
คุณสามารถกลับไปที่ https://localhost:3000
และดูการอัพเดตแผนภูมิ
จบ
คุณสามารถสร้างแผนภูมิแบบเรียลไทม์ส่วนใหญ่ได้โดยใช้แนวคิดที่เรากล่าวถึงข้างต้น อัลกอริทึมคือ:
- ปรับใช้ GraphQL Engine ด้วย Postgres;
- สร้างตารางที่คุณต้องการจัดเก็บข้อมูล
- สมัครสมาชิกตารางเหล่านั้นจากแอป React ของคุณ
- แสดงแผนภูมิ
คุณสามารถค้นหาซอร์สโค้ดได้ที่นี่