Skip to content

Commit ef7f835

Browse files
committed
This is the base version of the charts, it is incomplete and still requires fine tuning
1 parent 1cd24a3 commit ef7f835

12 files changed

Lines changed: 2126 additions & 50 deletions

File tree

RocketControlUnitGUI/package-lock.json

Lines changed: 1361 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

RocketControlUnitGUI/package.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@sveltejs/adapter-node": "^4.0.1",
2020
"@tailwindcss/forms": "0.5.7",
2121
"@tailwindcss/typography": "0.5.10",
22+
"@types/leaflet": "^1.9.12",
2223
"@types/node": "20.10.0",
2324
"@typescript-eslint/eslint-plugin": "^6.0.0",
2425
"@typescript-eslint/parser": "^6.0.0",
@@ -40,6 +41,18 @@
4041
"type": "module",
4142
"dependencies": {
4243
"@floating-ui/dom": "1.5.3",
43-
"pocketbase": "^0.20.3"
44+
"@theatre/core": "^0.7.2",
45+
"@theatre/studio": "^0.7.2",
46+
"@threlte/core": "^7.3.1",
47+
"@threlte/extras": "^8.11.5",
48+
"@threlte/flex": "^1.0.3",
49+
"@threlte/theatre": "^2.1.8",
50+
"@types/three": "^0.169.0",
51+
"chart": "^0.1.2",
52+
"chart.js": "^4.4.4",
53+
"chartjs": "^0.3.24",
54+
"leaflet": "^1.9.4",
55+
"pocketbase": "^0.20.3",
56+
"three": "^0.169.0"
4457
}
4558
}

RocketControlUnitGUI/src/data.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// data.ts
2+
import PocketBase from 'pocketbase';
3+
4+
const pb = new PocketBase('http://127.0.0.1:8090');
5+
pb.autoCancellation(false);
6+
7+
let isAuthenticated = false;
8+
9+
const ADMIN_EMAIL = 'jgerbrandt@live.com';
10+
const ADMIN_PASSWORD = 'avionicsSOAR';
11+
12+
// Function to authenticate the admin user
13+
export async function authenticate() {
14+
if (!isAuthenticated) {
15+
await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
16+
isAuthenticated = true;
17+
}
18+
}
19+
20+
type RecordData = { [key: string]: any };
21+
export type AllData = { [collectionName: string]: RecordData[] };
22+
23+
// Fetch paginated data with existing logic
24+
export async function fetchPaginatedData(
25+
collectionName: string,
26+
sendToChart: (data: RecordData[]) => void,
27+
batchSize: number = 10
28+
) {
29+
console.log(`Fetching data from collection: ${collectionName}`);
30+
let page = 1;
31+
let hasMoreData = true;
32+
33+
try {
34+
await authenticate(); // Authenticate once before fetching data
35+
36+
while (hasMoreData) {
37+
console.log('Fetching data from collection:', collectionName, 'Page:', page);
38+
const records = await pb.collection(collectionName).getList(page, batchSize);
39+
console.log('Fetched records:', records.items);
40+
41+
if (records.items.length === 0) {
42+
console.warn(`No records found for ${collectionName} on page ${page}.`);
43+
break;
44+
}
45+
46+
const dynamicKeys = Object.keys(records.items[0]).filter(key => key !== 'id' && key !== 'created');
47+
48+
const transformedBatch: RecordData[] = records.items.map((record: RecordData) => {
49+
const transformedRecord: RecordData = {};
50+
dynamicKeys.forEach(key => {
51+
transformedRecord[key] = record[key];
52+
});
53+
return transformedRecord;
54+
});
55+
56+
console.log('Transformed batch:', transformedBatch);
57+
sendToChart(transformedBatch);
58+
59+
if (records.items.length < batchSize) {
60+
hasMoreData = false;
61+
} else {
62+
page += 1;
63+
}
64+
}
65+
console.log('All data fetched for collection:', collectionName);
66+
} catch (error) {
67+
console.error('Error fetching paginated data for collection:', collectionName, error);
68+
}
69+
}
70+
71+
// Real-time subscription to a collection
72+
export async function subscribeToCollection(
73+
collectionName: string,
74+
handleDataUpdate: (data: RecordData) => void
75+
) {
76+
try {
77+
await authenticate(); // Ensure we're authenticated before subscribing
78+
79+
// Subscribe to the collection for any changes
80+
pb.collection(collectionName).subscribe('*', function (e) {
81+
console.log(`Received real-time update for collection ${collectionName}:`, e.record);
82+
handleDataUpdate(e.record);
83+
});
84+
85+
console.log(`Subscribed to real-time updates for collection: ${collectionName}`);
86+
} catch (error) {
87+
console.error(`Error subscribing to collection ${collectionName}:`, error);
88+
}
89+
}
90+
91+
// Function to unsubscribe from a collection (optional)
92+
export async function unsubscribeFromCollection(collectionName: string) {
93+
try {
94+
await authenticate(); // Ensure we're authenticated before unsubscribing
95+
96+
// Unsubscribe from the collection
97+
pb.collection(collectionName).unsubscribe('*');
98+
99+
console.log(`Unsubscribed from real-time updates for collection: ${collectionName}`);
100+
} catch (error) {
101+
console.error(`Error unsubscribing from collection ${collectionName}:`, error);
102+
}
103+
}
Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,39 @@
1-
21
<script lang="ts">
2+
import LineChart from './LineChart.svelte';
3+
import MapChart from './MapChart.svelte';
4+
import RocketChart from './RocketChart.svelte';
5+
import {Canvas} from '@threlte/core';
6+
</script>
7+
8+
<main>
9+
<!-- <div style="display: flex; flex-direction: column; align-items: center;">
10+
<LineChart collection="Rcupressure" />
11+
<LineChart collection="RcuTemperature" />
12+
<LineChart collection="Imu" />
13+
</div> -->
14+
<!--
15+
<MapChart /> -->
16+
<div class="canvas-container">
17+
<Canvas>
18+
<RocketChart />
19+
</Canvas>
20+
</div>
21+
22+
23+
</main>
24+
25+
<style>
26+
main {
27+
display: flex;
28+
flex-direction: row;
29+
align-items: center;
30+
justify-content: center;
31+
padding: 20px;
32+
}
333
4-
5-
</script>
6-
7-
<svelte:head></svelte:head>
8-
9-
<main>
10-
<p>DATA</p>
11-
</main>
34+
.canvas-container{
35+
width:800px;
36+
height:600px;
37+
}
38+
</style>
39+
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<script lang="ts">
2+
import { onMount, onDestroy } from 'svelte';
3+
import { fetchPaginatedData, subscribeToCollection, unsubscribeFromCollection } from '../../data';
4+
import { Chart, LineController, LineElement, PointElement, LinearScale, Title, CategoryScale } from 'chart.js';
5+
6+
Chart.register(LineController, LineElement, PointElement, LinearScale, Title, CategoryScale);
7+
8+
interface RecordData {
9+
[key: string]: any;
10+
}
11+
12+
export let collection: string;
13+
export let fields: string[] = [];
14+
15+
let chartData: RecordData[] = [];
16+
let chart: Chart | null = null;
17+
let canvasContainer: HTMLCanvasElement | null = null;
18+
const MAX_DATA_POINTS = 10;
19+
let isUpdating = false;
20+
let dynamicFields: string[] = [];
21+
22+
23+
async function getInitialData() {
24+
try {
25+
await fetchPaginatedData(collection, handleFirstBatch, 1);
26+
} catch (error) {
27+
console.error(`Error fetching initial data for ${collection}:`, error);
28+
}
29+
}
30+
31+
function sendToChart(batchData: RecordData[]) {
32+
chartData = [...chartData, ...batchData];
33+
34+
if (chartData.length > MAX_DATA_POINTS) {
35+
chartData = chartData.slice(-MAX_DATA_POINTS);
36+
}
37+
38+
if (!isUpdating) {
39+
isUpdating = true;
40+
requestAnimationFrame(() => {
41+
createOrUpdateChart();
42+
isUpdating = false;
43+
});
44+
}
45+
}
46+
47+
async function handleFirstBatch(batchData: RecordData[]) {
48+
console.log('Fetched batch data for', collection, ':', batchData);
49+
if (batchData.length > 0) {
50+
determineFields(batchData[0]);
51+
const transformedBatch = batchData.map(transformData);
52+
console.log('Transformed batch data for', collection, ':', transformedBatch);
53+
sendToChart(transformedBatch);
54+
} else {
55+
console.warn('No records fetched for', collection);
56+
}
57+
}
58+
59+
function determineFields(record: RecordData) {
60+
if (fields.length === 0) {
61+
dynamicFields = Object.keys(record).filter((key) => key !== 'id' && key !== 'created');
62+
}
63+
}
64+
65+
function transformData(record: RecordData): RecordData {
66+
const dataFields = fields.length ? fields : dynamicFields;
67+
const transformed: RecordData = {};
68+
dataFields.forEach(field => {
69+
transformed[field] = record[field];
70+
});
71+
return transformed;
72+
}
73+
74+
function createOrUpdateChart() {
75+
if (!canvasContainer) return;
76+
77+
const dataFields = fields.length ? fields : dynamicFields;
78+
if (dataFields.length === 0) {
79+
console.warn(`No fields available for chart creation for ${collection}.`);
80+
return;
81+
}
82+
83+
const chartConfigData = {
84+
labels: chartData.map((_, index) => index.toString()),
85+
datasets: dataFields.map((field) => ({
86+
label: field,
87+
data: chartData.map((d) => d[field] !== undefined ? d[field] : 0),
88+
borderColor: generateColorForField(field),
89+
fill: false,
90+
})),
91+
};
92+
93+
console.log('Chart configuration data for', collection, ':', chartConfigData);
94+
95+
if (chart) {
96+
chart.data = chartConfigData;
97+
chart.update();
98+
} else {
99+
chart = new Chart(canvasContainer, {
100+
type: 'line',
101+
data: chartConfigData,
102+
options: {
103+
responsive: true,
104+
maintainAspectRatio: false,
105+
scales: {
106+
x: {
107+
title: { display: true, text: 'Data Points', color: '#777' },
108+
grid: { color: 'rgba(200, 200, 200, 0.1)' },
109+
ticks: { color: '#777' }
110+
},
111+
y: {
112+
title: { display: true, text: 'Value', color: '#777' },
113+
beginAtZero: true,
114+
grid: { color: 'rgba(200, 200, 200, 0.1)' },
115+
ticks: { color: '#777' }
116+
},
117+
},
118+
plugins: {
119+
legend: {
120+
display: true,
121+
position: 'top',
122+
labels: { color: '#333', font: { size: 12, weight: 500 } }
123+
},
124+
tooltip: {
125+
backgroundColor: 'rgba(0,0,0,0.7)',
126+
titleColor: '#FFF',
127+
bodyColor: '#FFF',
128+
cornerRadius: 4,
129+
}
130+
},
131+
elements: {
132+
line: { borderWidth: 2 },
133+
point: { radius: 3, hoverRadius: 5, backgroundColor: '#FFF', borderWidth: 1.5 },
134+
}
135+
},
136+
});
137+
}
138+
}
139+
140+
function generateColorForField(field: string): string {
141+
const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'];
142+
const index = fields.length ? fields.indexOf(field) : dynamicFields.indexOf(field);
143+
return colors[index % colors.length];
144+
}
145+
146+
// Function to handle real-time updates
147+
function handleRealTimeUpdate(record: RecordData) {
148+
const transformedRecord = transformData(record);
149+
sendToChart([transformedRecord]);
150+
}
151+
152+
// On mount, fetch initial data and start real-time updates
153+
onMount(() => {
154+
getInitialData(); // Fetch initial data
155+
subscribeToCollection(collection, handleRealTimeUpdate); // Subscribe to real-time updates
156+
});
157+
158+
// On destroy, clean up subscriptions
159+
onDestroy(() => {
160+
unsubscribeFromCollection(collection); // Unsubscribe from real-time updates when component is destroyed
161+
});
162+
</script>
163+
164+
<div class="chart-container">
165+
<h1>{collection} Graph</h1>
166+
<canvas bind:this={canvasContainer}></canvas>
167+
</div>
168+
169+
<style>
170+
.chart-container {
171+
display: flex;
172+
flex-direction: column;
173+
width: 400px;
174+
height: 300px;
175+
position: relative;
176+
background: linear-gradient(135deg, #495a8f 0%, #495f9f 100%);
177+
border-radius: 12px;
178+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
179+
padding: 20px;
180+
margin: 20px;
181+
justify-content: space-evenly;
182+
align-items: center;
183+
}
184+
185+
canvas {
186+
width: 100%;
187+
height: 100%;
188+
display: block;
189+
}
190+
</style>
191+

0 commit comments

Comments
 (0)