From ad57f11f8a754de984f7276d651b0ba56d08e7be Mon Sep 17 00:00:00 2001 From: Gaurav Kumar Date: Wed, 29 Oct 2025 15:34:10 +0530 Subject: [PATCH] bubble --- .../bubble-chart/bubble-chart.component.html | 28 +- .../bubble-chart/bubble-chart.component.ts | 432 ++++++++++++++---- 2 files changed, 356 insertions(+), 104 deletions(-) diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html index dfddf26..1c2b01e 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html @@ -279,19 +279,31 @@ - -
- No data available -
- - -
+ +
+ +
+ Loading data... +
+ + +
+ No data available +
+ + + (chartClick)="chartClicked($event)" + [style.visibility]="dataLoaded && !noDataAvailable && isChartDataValid() ? 'visible' : 'hidden'" + [style.position]="'absolute'" + [style.top]="'0'" + [style.left]="'0'" + [style.height]="'100%'" + [style.width]="'100%'">
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts index 094e1d0..3b812dc 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts @@ -37,55 +37,43 @@ export class BubbleChartComponent implements OnInit, OnChanges { @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations public bubbleChartOptions: ChartConfiguration['options'] = { - // scales: { - // x: { - // min: 0, - // max: 30, - // ticks: {} - // }, - // y: { - // min: 0, - // max: 30, - // ticks: {} - // }, - // plugins: { - // title: { - // display: true, - // text: 'Bubble Chart' - // } - // } - // } + responsive: true, + maintainAspectRatio: false, + scales: { + x: { + beginAtZero: true, + title: { + display: true, + text: 'X Axis' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Y Axis' + } + } + }, + plugins: { + legend: { + display: true, + position: 'top', + }, + tooltip: { + enabled: true, + mode: 'point', + intersect: false + } + }, + animation: { + duration: 800, + easing: 'easeInOutQuart' + } }; public bubbleChartType: string = 'bubble'; - public bubbleChartData: ChartDataset[] = [ - { - data: [ - { x: 10, y: 10, r: 10 }, - { x: 15, y: 5, r: 15 }, - { x: 26, y: 12, r: 23 }, - { x: 7, y: 8, r: 8 }, - ], - label: 'Investment Equities', - backgroundColor: 'rgba(255, 0, 0, 0.6)', // Red - borderColor: 'blue', - hoverBackgroundColor: 'purple', - hoverBorderColor: 'red', - }, - { - data: [ - { x: 5, y: 15, r: 12 }, - { x: 20, y: 7, r: 8 }, - { x: 12, y: 18, r: 15 }, - { x: 8, y: 6, r: 10 }, - ], - label: 'Investment Bonds', - backgroundColor: 'rgba(0, 255, 0, 0.6)', // Green - borderColor: 'green', - hoverBackgroundColor: 'yellow', - hoverBorderColor: 'blue', - }, - ]; + public bubbleChartData: ChartDataset[] = []; // Multi-layer drilldown state tracking drilldownStack: any[] = []; // Stack to track drilldown navigation history @@ -94,6 +82,7 @@ export class BubbleChartComponent implements OnInit, OnChanges { // No data state noDataAvailable: boolean = false; + dataLoaded: boolean = false; // Track if data has been loaded // Flag to prevent infinite loops private isFetchingData: boolean = false; @@ -471,39 +460,126 @@ export class BubbleChartComponent implements OnInit, OnChanges { // Bubble charts expect data in the format: {x: number, y: number, r: number} console.log('Transforming data to bubble format:', { labels, data }); + // Handle null/undefined data + if (!labels || !data) { + console.log('Labels or data is null/undefined, returning empty dataset'); + return []; + } + // If we have the expected bubble data format, return it as is if (data && data.length > 0 && data[0].data && data[0].data.length > 0 && typeof data[0].data[0] === 'object' && data[0].data[0].hasOwnProperty('x') && data[0].data[0].hasOwnProperty('y') && data[0].data[0].hasOwnProperty('r')) { + console.log('Data is already in bubble format, returning as is'); return data; } - // Otherwise, create a default bubble dataset - const bubbleDatasets: ChartDataset[] = [ - { - data: [ - { x: 10, y: 10, r: 10 }, - { x: 15, y: 5, r: 15 }, - { x: 26, y: 12, r: 23 }, - { x: 7, y: 8, r: 8 }, - ], - label: 'Dataset 1', - backgroundColor: 'rgba(255, 0, 0, 0.6)', - borderColor: 'blue', - hoverBackgroundColor: 'purple', - hoverBorderColor: 'red', + // Transform the data properly for bubble chart + // Assuming labels are x-values and data[0].data are y-values + if (labels && data && data.length > 0 && data[0].data) { + console.log('Transforming regular data to bubble format'); + const yValues = data[0].data; + const label = data[0].label || 'Dataset 1'; + + // Handle case where yValues might not be an array + if (!Array.isArray(yValues)) { + console.log('yValues is not an array, returning empty dataset'); + return []; } - ]; + + console.log('yValues type:', typeof yValues); + console.log('yValues length:', yValues.length); + console.log('First few yValues:', yValues.slice(0, 5)); + + // Create bubble points from labels (x) and data (y) + const bubblePoints = []; + const minLength = Math.min(labels.length, yValues.length); + + console.log('Processing data points:', { labels, yValues, minLength }); + + for (let i = 0; i < minLength; i++) { + // Log each point for debugging + console.log(`Processing point ${i}: label=${labels[i]}, yValue=${yValues[i]}, type=${typeof yValues[i]}`); + + // Convert y to number if it's a string + let y; + if (typeof yValues[i] === 'string') { + y = parseFloat(yValues[i]); + console.log(`Converted string yValue to number: ${yValues[i]} -> ${y}`); + } else { + y = yValues[i]; + } + + // Handle NaN values + if (isNaN(y)) { + console.log(`Skipping point ${i} due to NaN y value: ${yValues[i]}`); + continue; + } + + // Calculate radius based on the y-value + const r = Math.max(5, Math.min(30, Math.abs(y) / 10)); + + // For x-value, we'll use the index position since labels are strings + const x = i; + + // Add the point + const point = { + x, + y, + r + }; + console.log(`Adding point ${i}:`, point); + bubblePoints.push(point); + } + + console.log('Generated bubble points:', bubblePoints); + console.log('Generated bubble points count:', bubblePoints.length); + + // If we have no valid points, return empty array + if (bubblePoints.length === 0) { + console.log('No valid bubble points generated, returning empty dataset'); + return []; + } + + // Create a single dataset with all bubble points + const bubbleDatasets: ChartDataset[] = [ + { + data: bubblePoints, + label: label, + backgroundColor: 'rgba(255, 99, 132, 0.6)', + borderColor: 'rgba(255, 99, 132, 1)', + hoverBackgroundColor: 'rgba(255, 99, 132, 0.8)', + hoverBorderColor: 'rgba(255, 99, 132, 1)', + borderWidth: 1, + pointHoverRadius: 10, + } + ]; + + console.log('Transformed bubble data:', bubbleDatasets); + return bubbleDatasets; + } - return bubbleDatasets; + console.log('Could not transform data, returning empty dataset'); + // Return empty dataset instead of default data + return []; } fetchChartData(): void { // Set flag to prevent recursive calls this.isFetchingData = true; + this.dataLoaded = false; // Mark data as not loaded yet + this.noDataAvailable = false; // Reset no data flag + + console.log('Starting fetchChartData, current state:', { + table: this.table, + xAxis: this.xAxis, + yAxis: this.yAxis, + connection: this.connection + }); // If we're in drilldown mode, fetch the appropriate drilldown data if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + console.log('Fetching drilldown data'); this.fetchDrilldownData(); // Reset flag after fetching this.isFetchingData = false; @@ -583,32 +659,82 @@ export class BubbleChartComponent implements OnInit, OnChanges { this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '', filterParams).subscribe( (data: any) => { console.log('Received bubble chart data:', data); - if (data === null) { - console.warn('Bubble chart API returned null data. Check if the API endpoint is working correctly.'); - this.noDataAvailable = true; - this.bubbleChartData = []; - // Reset flag after fetching - this.isFetchingData = false; - return; - } - // Handle the actual data structure returned by the API - if (data && data.chartLabels && data.chartData) { + // Reset chart data to empty first + this.bubbleChartData = []; + + if (data === null || data === undefined) { + console.warn('Bubble chart API returned null/undefined data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + } else if (data && data.chartLabels && data.chartData) { // For bubble charts, we need to transform the data into bubble format // Bubble charts expect data in the format: {x: number, y: number, r: number} - this.noDataAvailable = data.chartLabels.length === 0; - this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); - console.log('Updated bubble chart with data:', this.bubbleChartData); + console.log('Processing chartLabels and chartData format'); + const transformedData = this.transformToBubbleData(data.chartLabels, data.chartData); + console.log('Transformed data:', transformedData); + + // Check if we have valid data + let hasValidData = false; + if (transformedData && transformedData.length > 0) { + for (const dataset of transformedData) { + if (dataset.data && dataset.data.length > 0) { + hasValidData = true; + break; + } + } + } + + if (hasValidData) { + // Create a new array reference to trigger change detection + this.bubbleChartData = [...transformedData]; + this.noDataAvailable = false; + console.log('Updated bubble chart with data:', this.bubbleChartData); + } else { + console.log('No valid data after transformation'); + this.noDataAvailable = true; + } } else if (data && data.labels && data.datasets) { // Handle the original expected format as fallback - this.noDataAvailable = data.labels.length === 0; - this.bubbleChartData = data.datasets; - console.log('Updated bubble chart with legacy data format:', this.bubbleChartData); + console.log('Processing labels and datasets format'); + // Check if we have valid data + let hasValidData = false; + if (data.datasets && data.datasets.length > 0) { + for (const dataset of data.datasets) { + if (dataset.data && dataset.data.length > 0) { + hasValidData = true; + break; + } + } + } + + if (hasValidData) { + // Create a new array reference to trigger change detection + this.bubbleChartData = [...data.datasets]; + this.noDataAvailable = false; + console.log('Updated bubble chart with legacy data format:', this.bubbleChartData); + } else { + console.log('No valid data in legacy format'); + this.noDataAvailable = true; + } } else { console.warn('Bubble chart received data does not have expected structure', data); this.noDataAvailable = true; - this.bubbleChartData = []; } + + this.dataLoaded = true; // Mark data as loaded + + console.log('Final state after data fetch:', { + noDataAvailable: this.noDataAvailable, + dataLoaded: this.dataLoaded, + bubbleChartDataLength: this.bubbleChartData.length, + isChartDataValid: this.isChartDataValid() + }); + + // Trigger change detection with a small delay to ensure proper rendering + setTimeout(() => { + this.forceChartUpdate(); + }, 100); + // Reset flag after fetching this.isFetchingData = false; }, @@ -616,15 +742,24 @@ export class BubbleChartComponent implements OnInit, OnChanges { console.error('Error fetching bubble chart data:', error); this.noDataAvailable = true; this.bubbleChartData = []; + this.dataLoaded = true; + // Trigger change detection + setTimeout(() => { + this.forceChartUpdate(); + }, 100); // Reset flag after fetching this.isFetchingData = false; - // Keep default data in case of error } ); } else { console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); this.noDataAvailable = true; this.bubbleChartData = []; + this.dataLoaded = true; + // Trigger change detection + setTimeout(() => { + this.forceChartUpdate(); + }, 100); // Reset flag after fetching this.isFetchingData = false; } @@ -654,6 +789,10 @@ export class BubbleChartComponent implements OnInit, OnChanges { console.warn('Invalid drilldown layer index:', layerIndex); this.noDataAvailable = true; this.bubbleChartData = []; + this.dataLoaded = true; + setTimeout(() => { + this.forceChartUpdate(); + }, 100); return; } } @@ -665,6 +804,10 @@ export class BubbleChartComponent implements OnInit, OnChanges { console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); this.noDataAvailable = true; this.bubbleChartData = []; + this.dataLoaded = true; + setTimeout(() => { + this.forceChartUpdate(); + }, 100); return; } @@ -758,35 +901,84 @@ export class BubbleChartComponent implements OnInit, OnChanges { this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue, filterParams).subscribe( (data: any) => { console.log('Received drilldown data:', data); - if (data === null) { - console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); - this.noDataAvailable = true; - this.bubbleChartData = []; - return; - } - // Handle the actual data structure returned by the API - if (data && data.chartLabels && data.chartData) { + // Reset chart data to empty first + this.bubbleChartData = []; + + if (data === null || data === undefined) { + console.warn('Drilldown API returned null/undefined data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + } else if (data && data.chartLabels && data.chartData) { // For bubble charts, we need to transform the data into bubble format - this.noDataAvailable = data.chartLabels.length === 0; - this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); - console.log('Updated bubble chart with drilldown data:', this.bubbleChartData); + const transformedData = this.transformToBubbleData(data.chartLabels, data.chartData); + + // Check if we have valid data + let hasValidData = false; + if (transformedData && transformedData.length > 0) { + for (const dataset of transformedData) { + if (dataset.data && dataset.data.length > 0) { + hasValidData = true; + break; + } + } + } + + if (hasValidData) { + this.bubbleChartData = transformedData; + this.noDataAvailable = false; + console.log('Updated bubble chart with drilldown data:', this.bubbleChartData); + } else { + console.log('No valid data after transformation in drilldown'); + this.noDataAvailable = true; + } } else if (data && data.labels && data.datasets) { // Handle the original expected format as fallback - this.noDataAvailable = data.labels.length === 0; - this.bubbleChartData = data.datasets; - console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData); + // Check if we have valid data + let hasValidData = false; + if (data.datasets && data.datasets.length > 0) { + for (const dataset of data.datasets) { + if (dataset.data && dataset.data.length > 0) { + hasValidData = true; + break; + } + } + } + + if (hasValidData) { + this.bubbleChartData = data.datasets; + this.noDataAvailable = false; + console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData); + } else { + console.log('No valid data in legacy format in drilldown'); + this.noDataAvailable = true; + } } else { console.warn('Drilldown received data does not have expected structure', data); this.noDataAvailable = true; - this.bubbleChartData = []; } + + this.dataLoaded = true; // Mark data as loaded + + console.log('Final state after drilldown data fetch:', { + noDataAvailable: this.noDataAvailable, + dataLoaded: this.dataLoaded, + bubbleChartDataLength: this.bubbleChartData.length, + isChartDataValid: this.isChartDataValid() + }); + + // Trigger change detection + setTimeout(() => { + this.forceChartUpdate(); + }, 100); }, (error) => { console.error('Error fetching drilldown data:', error); this.noDataAvailable = true; this.bubbleChartData = []; - // Keep current data in case of error + this.dataLoaded = true; + setTimeout(() => { + this.forceChartUpdate(); + }, 100); } ); } @@ -933,4 +1125,52 @@ export class BubbleChartComponent implements OnInit, OnChanges { public chartHovered(e: any): void { console.log('Bubble chart hovered:', e); } + + // Method to check if chart data is valid + public isChartDataValid(): boolean { + console.log('Checking if chart data is valid:', this.bubbleChartData); + if (!this.bubbleChartData || this.bubbleChartData.length === 0) { + console.log('Chart data is null or empty'); + return false; + } + + // Check if any dataset has data + for (const dataset of this.bubbleChartData) { + console.log('Checking dataset:', dataset); + if (dataset.data && dataset.data.length > 0) { + console.log('Dataset has data, length:', dataset.data.length); + // For bubble charts, check if data points have x, y, r properties + for (const point of dataset.data) { + console.log('Checking point:', point); + if (typeof point === 'object' && point.hasOwnProperty('x') && point.hasOwnProperty('y') && point.hasOwnProperty('r')) { + // Valid bubble point + console.log('Found valid bubble point'); + return true; + } + } + } + } + + console.log('No valid chart data found'); + return false; + } + + // Method to force chart update + private forceChartUpdate(): void { + console.log('Forcing chart update'); + console.log('Current bubbleChartData:', this.bubbleChartData); + console.log('Current bubbleChartData length:', this.bubbleChartData ? this.bubbleChartData.length : 0); + if (this.bubbleChartData && this.bubbleChartData.length > 0) { + console.log('First dataset data length:', this.bubbleChartData[0].data ? this.bubbleChartData[0].data.length : 0); + } + // Create a new reference to trigger change detection + if (this.bubbleChartData) { + this.bubbleChartData = [...this.bubbleChartData]; + } + // Also update noDataAvailable to trigger UI changes + this.noDataAvailable = this.noDataAvailable; + console.log('Chart update forced, noDataAvailable:', this.noDataAvailable); + console.log('Chart update forced, bubbleChartData length:', this.bubbleChartData ? this.bubbleChartData.length : 0); + console.log('Chart update forced, isChartDataValid:', this.isChartDataValid()); + } } \ No newline at end of file