This commit is contained in:
string 2025-10-29 15:34:10 +05:30
parent 9b775a8c63
commit ad57f11f8a
2 changed files with 356 additions and 104 deletions

View File

@ -279,19 +279,31 @@
</div> </div>
</div> </div>
<!-- Chart container -->
<div style="position: relative; height: calc(100% - 50px); width: 100%;">
<!-- Loading indicator -->
<div *ngIf="!dataLoaded" style="text-align: center; padding: 20px; color: #666; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; width: 100%;">
Loading data...
</div>
<!-- No data message --> <!-- No data message -->
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> <div *ngIf="dataLoaded && (noDataAvailable || !isChartDataValid())" style="text-align: center; padding: 20px; color: #666; font-style: italic; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; width: 100%;">
No data available No data available
</div> </div>
<!-- Chart display --> <!-- Chart display - Always render the canvas but conditionally show/hide with CSS -->
<div *ngIf="!noDataAvailable" style="position: relative; height: calc(100% - 50px);">
<canvas baseChart <canvas baseChart
[datasets]="bubbleChartData" [datasets]="bubbleChartData"
[type]="bubbleChartType" [type]="bubbleChartType"
[options]="bubbleChartOptions" [options]="bubbleChartOptions"
(chartHover)="chartHovered($event)" (chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"> (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%'">
</canvas> </canvas>
</div> </div>
</div> </div>

View File

@ -37,55 +37,43 @@ export class BubbleChartComponent implements OnInit, OnChanges {
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
public bubbleChartOptions: ChartConfiguration['options'] = { public bubbleChartOptions: ChartConfiguration['options'] = {
// scales: { responsive: true,
// x: { maintainAspectRatio: false,
// min: 0, scales: {
// max: 30, x: {
// ticks: {} beginAtZero: true,
// }, title: {
// y: { display: true,
// min: 0, text: 'X Axis'
// max: 30, }
// ticks: {} },
// }, y: {
// plugins: { beginAtZero: true,
// title: { title: {
// display: true, display: true,
// text: 'Bubble Chart' 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 bubbleChartType: string = 'bubble';
public bubbleChartData: ChartDataset[] = [ 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',
},
];
// Multi-layer drilldown state tracking // Multi-layer drilldown state tracking
drilldownStack: any[] = []; // Stack to track drilldown navigation history drilldownStack: any[] = []; // Stack to track drilldown navigation history
@ -94,6 +82,7 @@ export class BubbleChartComponent implements OnInit, OnChanges {
// No data state // No data state
noDataAvailable: boolean = false; noDataAvailable: boolean = false;
dataLoaded: boolean = false; // Track if data has been loaded
// Flag to prevent infinite loops // Flag to prevent infinite loops
private isFetchingData: boolean = false; 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} // Bubble charts expect data in the format: {x: number, y: number, r: number}
console.log('Transforming data to bubble format:', { labels, data }); 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 we have the expected bubble data format, return it as is
if (data && data.length > 0 && data[0].data && data[0].data.length > 0 && 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') && typeof data[0].data[0] === 'object' && data[0].data[0].hasOwnProperty('x') &&
data[0].data[0].hasOwnProperty('y') && data[0].data[0].hasOwnProperty('r')) { 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; return data;
} }
// Otherwise, create a default bubble dataset // 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[] = [ const bubbleDatasets: ChartDataset[] = [
{ {
data: [ data: bubblePoints,
{ x: 10, y: 10, r: 10 }, label: label,
{ x: 15, y: 5, r: 15 }, backgroundColor: 'rgba(255, 99, 132, 0.6)',
{ x: 26, y: 12, r: 23 }, borderColor: 'rgba(255, 99, 132, 1)',
{ x: 7, y: 8, r: 8 }, hoverBackgroundColor: 'rgba(255, 99, 132, 0.8)',
], hoverBorderColor: 'rgba(255, 99, 132, 1)',
label: 'Dataset 1', borderWidth: 1,
backgroundColor: 'rgba(255, 0, 0, 0.6)', pointHoverRadius: 10,
borderColor: 'blue',
hoverBackgroundColor: 'purple',
hoverBorderColor: 'red',
} }
]; ];
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 { fetchChartData(): void {
// Set flag to prevent recursive calls // Set flag to prevent recursive calls
this.isFetchingData = true; 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 we're in drilldown mode, fetch the appropriate drilldown data
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
console.log('Fetching drilldown data');
this.fetchDrilldownData(); this.fetchDrilldownData();
// Reset flag after fetching // Reset flag after fetching
this.isFetchingData = false; 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( this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '', filterParams).subscribe(
(data: any) => { (data: any) => {
console.log('Received bubble chart data:', data); 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 // Reset chart data to empty first
if (data && data.chartLabels && data.chartData) { 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 // 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} // Bubble charts expect data in the format: {x: number, y: number, r: number}
this.noDataAvailable = data.chartLabels.length === 0; console.log('Processing chartLabels and chartData format');
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); 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); 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) { } else if (data && data.labels && data.datasets) {
// Handle the original expected format as fallback // Handle the original expected format as fallback
this.noDataAvailable = data.labels.length === 0; console.log('Processing labels and datasets format');
this.bubbleChartData = data.datasets; // 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); console.log('Updated bubble chart with legacy data format:', this.bubbleChartData);
} else {
console.log('No valid data in legacy format');
this.noDataAvailable = true;
}
} else { } else {
console.warn('Bubble chart received data does not have expected structure', data); console.warn('Bubble chart received data does not have expected structure', data);
this.noDataAvailable = true; 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 // Reset flag after fetching
this.isFetchingData = false; this.isFetchingData = false;
}, },
@ -616,15 +742,24 @@ export class BubbleChartComponent implements OnInit, OnChanges {
console.error('Error fetching bubble chart data:', error); console.error('Error fetching bubble chart data:', error);
this.noDataAvailable = true; this.noDataAvailable = true;
this.bubbleChartData = []; this.bubbleChartData = [];
this.dataLoaded = true;
// Trigger change detection
setTimeout(() => {
this.forceChartUpdate();
}, 100);
// Reset flag after fetching // Reset flag after fetching
this.isFetchingData = false; this.isFetchingData = false;
// Keep default data in case of error
} }
); );
} else { } else {
console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
this.noDataAvailable = true; this.noDataAvailable = true;
this.bubbleChartData = []; this.bubbleChartData = [];
this.dataLoaded = true;
// Trigger change detection
setTimeout(() => {
this.forceChartUpdate();
}, 100);
// Reset flag after fetching // Reset flag after fetching
this.isFetchingData = false; this.isFetchingData = false;
} }
@ -654,6 +789,10 @@ export class BubbleChartComponent implements OnInit, OnChanges {
console.warn('Invalid drilldown layer index:', layerIndex); console.warn('Invalid drilldown layer index:', layerIndex);
this.noDataAvailable = true; this.noDataAvailable = true;
this.bubbleChartData = []; this.bubbleChartData = [];
this.dataLoaded = true;
setTimeout(() => {
this.forceChartUpdate();
}, 100);
return; return;
} }
} }
@ -665,6 +804,10 @@ export class BubbleChartComponent implements OnInit, OnChanges {
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
this.noDataAvailable = true; this.noDataAvailable = true;
this.bubbleChartData = []; this.bubbleChartData = [];
this.dataLoaded = true;
setTimeout(() => {
this.forceChartUpdate();
}, 100);
return; 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( this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue, filterParams).subscribe(
(data: any) => { (data: any) => {
console.log('Received drilldown data:', data); console.log('Received drilldown data:', data);
if (data === null) {
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); // Reset chart data to empty first
this.noDataAvailable = true;
this.bubbleChartData = []; this.bubbleChartData = [];
return;
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
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;
}
}
} }
// Handle the actual data structure returned by the API if (hasValidData) {
if (data && data.chartLabels && data.chartData) { this.bubbleChartData = transformedData;
// For bubble charts, we need to transform the data into bubble format this.noDataAvailable = false;
this.noDataAvailable = data.chartLabels.length === 0;
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData);
console.log('Updated bubble chart with drilldown data:', this.bubbleChartData); 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) { } else if (data && data.labels && data.datasets) {
// Handle the original expected format as fallback // Handle the original expected format as fallback
this.noDataAvailable = data.labels.length === 0; // 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.bubbleChartData = data.datasets;
this.noDataAvailable = false;
console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData); 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 { } else {
console.warn('Drilldown received data does not have expected structure', data); console.warn('Drilldown received data does not have expected structure', data);
this.noDataAvailable = true; 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) => { (error) => {
console.error('Error fetching drilldown data:', error); console.error('Error fetching drilldown data:', error);
this.noDataAvailable = true; this.noDataAvailable = true;
this.bubbleChartData = []; 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 { public chartHovered(e: any): void {
console.log('Bubble chart hovered:', e); 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());
}
} }