Skip to content

Commit

Permalink
This is the base version of the charts, it is incomplete and still re…
Browse files Browse the repository at this point in the history
…quires fine tuning
  • Loading branch information
alymasani committed Oct 12, 2024
1 parent 1cd24a3 commit ef7f835
Show file tree
Hide file tree
Showing 12 changed files with 2,126 additions and 50 deletions.
1,401 changes: 1,361 additions & 40 deletions RocketControlUnitGUI/package-lock.json

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion RocketControlUnitGUI/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@sveltejs/adapter-node": "^4.0.1",
"@tailwindcss/forms": "0.5.7",
"@tailwindcss/typography": "0.5.10",
"@types/leaflet": "^1.9.12",
"@types/node": "20.10.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
Expand All @@ -40,6 +41,18 @@
"type": "module",
"dependencies": {
"@floating-ui/dom": "1.5.3",
"pocketbase": "^0.20.3"
"@theatre/core": "^0.7.2",
"@theatre/studio": "^0.7.2",
"@threlte/core": "^7.3.1",
"@threlte/extras": "^8.11.5",
"@threlte/flex": "^1.0.3",
"@threlte/theatre": "^2.1.8",
"@types/three": "^0.169.0",
"chart": "^0.1.2",
"chart.js": "^4.4.4",
"chartjs": "^0.3.24",
"leaflet": "^1.9.4",
"pocketbase": "^0.20.3",
"three": "^0.169.0"
}
}
103 changes: 103 additions & 0 deletions RocketControlUnitGUI/src/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// data.ts
import PocketBase from 'pocketbase';

const pb = new PocketBase('http://127.0.0.1:8090');
pb.autoCancellation(false);

let isAuthenticated = false;

const ADMIN_EMAIL = '[email protected]';
const ADMIN_PASSWORD = 'avionicsSOAR';

// Function to authenticate the admin user
export async function authenticate() {
if (!isAuthenticated) {
await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
isAuthenticated = true;
}
}

type RecordData = { [key: string]: any };
export type AllData = { [collectionName: string]: RecordData[] };

// Fetch paginated data with existing logic
export async function fetchPaginatedData(
collectionName: string,
sendToChart: (data: RecordData[]) => void,
batchSize: number = 10
) {
console.log(`Fetching data from collection: ${collectionName}`);
let page = 1;
let hasMoreData = true;

try {
await authenticate(); // Authenticate once before fetching data

while (hasMoreData) {
console.log('Fetching data from collection:', collectionName, 'Page:', page);
const records = await pb.collection(collectionName).getList(page, batchSize);
console.log('Fetched records:', records.items);

if (records.items.length === 0) {
console.warn(`No records found for ${collectionName} on page ${page}.`);
break;
}

const dynamicKeys = Object.keys(records.items[0]).filter(key => key !== 'id' && key !== 'created');

const transformedBatch: RecordData[] = records.items.map((record: RecordData) => {
const transformedRecord: RecordData = {};
dynamicKeys.forEach(key => {
transformedRecord[key] = record[key];
});
return transformedRecord;
});

console.log('Transformed batch:', transformedBatch);
sendToChart(transformedBatch);

if (records.items.length < batchSize) {
hasMoreData = false;
} else {
page += 1;
}
}
console.log('All data fetched for collection:', collectionName);
} catch (error) {
console.error('Error fetching paginated data for collection:', collectionName, error);
}
}

// Real-time subscription to a collection
export async function subscribeToCollection(
collectionName: string,
handleDataUpdate: (data: RecordData) => void
) {
try {
await authenticate(); // Ensure we're authenticated before subscribing

// Subscribe to the collection for any changes
pb.collection(collectionName).subscribe('*', function (e) {
console.log(`Received real-time update for collection ${collectionName}:`, e.record);
handleDataUpdate(e.record);
});

console.log(`Subscribed to real-time updates for collection: ${collectionName}`);
} catch (error) {
console.error(`Error subscribing to collection ${collectionName}:`, error);
}
}

// Function to unsubscribe from a collection (optional)
export async function unsubscribeFromCollection(collectionName: string) {
try {
await authenticate(); // Ensure we're authenticated before unsubscribing

// Unsubscribe from the collection
pb.collection(collectionName).unsubscribe('*');

console.log(`Unsubscribed from real-time updates for collection: ${collectionName}`);
} catch (error) {
console.error(`Error unsubscribing from collection ${collectionName}:`, error);
}
}
46 changes: 37 additions & 9 deletions RocketControlUnitGUI/src/routes/data/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@

<script lang="ts">
import LineChart from './LineChart.svelte';
import MapChart from './MapChart.svelte';
import RocketChart from './RocketChart.svelte';
import {Canvas} from '@threlte/core';
</script>

<main>
<!-- <div style="display: flex; flex-direction: column; align-items: center;">
<LineChart collection="Rcupressure" />
<LineChart collection="RcuTemperature" />
<LineChart collection="Imu" />
</div> -->
<!--
<MapChart /> -->
<div class="canvas-container">
<Canvas>
<RocketChart />
</Canvas>
</div>


</main>

<style>
main {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 20px;
}
</script>

<svelte:head></svelte:head>

<main>
<p>DATA</p>
</main>
.canvas-container{
width:800px;
height:600px;
}
</style>

191 changes: 191 additions & 0 deletions RocketControlUnitGUI/src/routes/data/LineChart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { fetchPaginatedData, subscribeToCollection, unsubscribeFromCollection } from '../../data';
import { Chart, LineController, LineElement, PointElement, LinearScale, Title, CategoryScale } from 'chart.js';
Chart.register(LineController, LineElement, PointElement, LinearScale, Title, CategoryScale);
interface RecordData {
[key: string]: any;
}
export let collection: string;
export let fields: string[] = [];
let chartData: RecordData[] = [];
let chart: Chart | null = null;
let canvasContainer: HTMLCanvasElement | null = null;
const MAX_DATA_POINTS = 10;
let isUpdating = false;
let dynamicFields: string[] = [];
async function getInitialData() {
try {
await fetchPaginatedData(collection, handleFirstBatch, 1);
} catch (error) {
console.error(`Error fetching initial data for ${collection}:`, error);
}
}
function sendToChart(batchData: RecordData[]) {
chartData = [...chartData, ...batchData];
if (chartData.length > MAX_DATA_POINTS) {
chartData = chartData.slice(-MAX_DATA_POINTS);
}
if (!isUpdating) {
isUpdating = true;
requestAnimationFrame(() => {
createOrUpdateChart();
isUpdating = false;
});
}
}
async function handleFirstBatch(batchData: RecordData[]) {
console.log('Fetched batch data for', collection, ':', batchData);
if (batchData.length > 0) {
determineFields(batchData[0]);
const transformedBatch = batchData.map(transformData);
console.log('Transformed batch data for', collection, ':', transformedBatch);
sendToChart(transformedBatch);
} else {
console.warn('No records fetched for', collection);
}
}
function determineFields(record: RecordData) {
if (fields.length === 0) {
dynamicFields = Object.keys(record).filter((key) => key !== 'id' && key !== 'created');
}
}
function transformData(record: RecordData): RecordData {
const dataFields = fields.length ? fields : dynamicFields;
const transformed: RecordData = {};
dataFields.forEach(field => {
transformed[field] = record[field];
});
return transformed;
}
function createOrUpdateChart() {
if (!canvasContainer) return;
const dataFields = fields.length ? fields : dynamicFields;
if (dataFields.length === 0) {
console.warn(`No fields available for chart creation for ${collection}.`);
return;
}
const chartConfigData = {
labels: chartData.map((_, index) => index.toString()),
datasets: dataFields.map((field) => ({
label: field,
data: chartData.map((d) => d[field] !== undefined ? d[field] : 0),
borderColor: generateColorForField(field),
fill: false,
})),
};
console.log('Chart configuration data for', collection, ':', chartConfigData);
if (chart) {
chart.data = chartConfigData;
chart.update();
} else {
chart = new Chart(canvasContainer, {
type: 'line',
data: chartConfigData,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: { display: true, text: 'Data Points', color: '#777' },
grid: { color: 'rgba(200, 200, 200, 0.1)' },
ticks: { color: '#777' }
},
y: {
title: { display: true, text: 'Value', color: '#777' },
beginAtZero: true,
grid: { color: 'rgba(200, 200, 200, 0.1)' },
ticks: { color: '#777' }
},
},
plugins: {
legend: {
display: true,
position: 'top',
labels: { color: '#333', font: { size: 12, weight: 500 } }
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.7)',
titleColor: '#FFF',
bodyColor: '#FFF',
cornerRadius: 4,
}
},
elements: {
line: { borderWidth: 2 },
point: { radius: 3, hoverRadius: 5, backgroundColor: '#FFF', borderWidth: 1.5 },
}
},
});
}
}
function generateColorForField(field: string): string {
const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'];
const index = fields.length ? fields.indexOf(field) : dynamicFields.indexOf(field);
return colors[index % colors.length];
}
// Function to handle real-time updates
function handleRealTimeUpdate(record: RecordData) {
const transformedRecord = transformData(record);
sendToChart([transformedRecord]);
}
// On mount, fetch initial data and start real-time updates
onMount(() => {
getInitialData(); // Fetch initial data
subscribeToCollection(collection, handleRealTimeUpdate); // Subscribe to real-time updates
});
// On destroy, clean up subscriptions
onDestroy(() => {
unsubscribeFromCollection(collection); // Unsubscribe from real-time updates when component is destroyed
});
</script>

<div class="chart-container">
<h1>{collection} Graph</h1>
<canvas bind:this={canvasContainer}></canvas>
</div>

<style>
.chart-container {
display: flex;
flex-direction: column;
width: 400px;
height: 300px;
position: relative;
background: linear-gradient(135deg, #495a8f 0%, #495f9f 100%);
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
margin: 20px;
justify-content: space-evenly;
align-items: center;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
</style>

Loading

0 comments on commit ef7f835

Please sign in to comment.