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