From 1263805d61da8fd61436f3901b4cf45560d160ae Mon Sep 17 00:00:00 2001 From: Gaurav Kumar Date: Tue, 4 Nov 2025 18:37:45 +0530 Subject: [PATCH] chart --- .../dynamic-chart-loader.service.ts | 28 +- .../unified-chart.component.html | 28 +- .../unified-chart/unified-chart.component.ts | 468 ++++++++++-------- 3 files changed, 285 insertions(+), 239 deletions(-) diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/chart-config/dynamic-chart-loader.service.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/chart-config/dynamic-chart-loader.service.ts index da26031..62c63c6 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/chart-config/dynamic-chart-loader.service.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/chart-config/dynamic-chart-loader.service.ts @@ -22,7 +22,7 @@ export class DynamicChartLoaderService { */ loadAllChartConfigurations(): Observable { console.log('Loading all chart configurations dynamically'); - + // Load all chart types first return this.apiRequest.get(this.chartTypesUrl).pipe( map(chartTypes => { @@ -43,7 +43,7 @@ export class DynamicChartLoaderService { dynamicFields: DynamicField[] }> { console.log(`Loading complete configuration for chart type ${chartTypeId}`); - + // Load all related data in parallel return forkJoin({ chartType: this.apiRequest.get(`${this.chartTypesUrl}/${chartTypeId}`), @@ -89,17 +89,25 @@ export class DynamicChartLoaderService { * Get chart type by name * This is useful for finding a chart type by its name rather than ID */ - getChartTypeByName(name: string): Observable { + // getChartTypeByName(name: string): Observable { + // console.log(`Finding chart type by name: ${name}`); + // return this.apiRequest.get(`${this.chartTypesUrl}/byname?chartName=${name}`).pipe( + // map((chartTypes: ChartType[]) => { + // console.log('Available chart types:', chartTypes); + // const chartType = chartTypes.find(ct => ct.name === name); + // console.log(`Found chart type for name ${name}:`, chartType); + // return chartType || null; + // }) + // ); + // } + + getChartTypeByName(name: string): Observable { console.log(`Finding chart type by name: ${name}`); - return this.apiRequest.get(`${this.chartTypesUrl}`).pipe( - map((chartTypes: ChartType[]) => { - const chartType = chartTypes.find(ct => ct.name === name); - console.log(`Found chart type for name ${name}:`, chartType); - return chartType || null; - }) - ); + return this.apiRequest.get(`${this.chartTypesUrl}/byname?chartName=${name}`); } + + /** * Load all active chart types * This is used to populate the chart selection in the dashboard editor diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.html index 20296fe..d1551ee 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.html @@ -43,7 +43,7 @@ --> -
+ -
+ -
+ -
+ -
+ -
+ -
+
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.ts index e1f3b89..6e9d03f 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/unified-chart/unified-chart.component.ts @@ -28,7 +28,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { @Input() datasource: string; @Input() fieldName: string; @Input() connection: number; - + // Drilldown configuration inputs @Input() drilldownEnabled: boolean = false; @Input() drilldownApiUrl: string; @@ -47,27 +47,27 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { chartOptions: any = {}; chartPlugins = []; chartLegend: boolean = true; - + // Bubble chart specific properties bubbleChartData: ChartDataset[] = []; - + // No data state noDataAvailable: boolean = false; - + // Loading state isLoading: boolean = false; - + // Multi-layer drilldown state tracking drilldownStack: any[] = []; currentDrilldownLevel: number = 0; originalChartData: any = {}; - + // Filter visibility toggle showFilters: boolean = false; - + // Flag to prevent infinite loops private isFetchingData: boolean = false; - + // Subscriptions to unsubscribe on destroy private subscriptions: Subscription[] = []; @@ -81,6 +81,12 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { dynamicStyles: string = ''; dynamicOptions: any = null; + // Add setter to log when dynamicTemplate changes + setDynamicTemplate(value: string) { + console.log('Setting dynamic template:', value); + this.dynamicTemplate = value; + } + constructor( private dashboardService: Dashboard3Service, private filterService: FilterService, @@ -96,7 +102,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.fetchChartData(); }) ); - + // Log initial input values for debugging console.log('UnifiedChartComponent ngOnInit - initial input values:', { chartType: this.chartType, @@ -107,28 +113,33 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { drilldownFilters: this.drilldownFilters, drilldownLayers: this.drilldownLayers }); - + // Check if filters are available console.log('Has filters in ngOnInit:', this.hasFilters()); - + // Initialize filter values if they haven't been initialized yet if (!this.filtersInitialized) { this.initializeFilterValues(); this.filtersInitialized = true; } - + // Initialize chart options with default structure this.initializeChartOptions(); - + // Load dynamic template and options for this chart type this.loadDynamicChartConfig(); - + this.fetchChartData(); } ngOnChanges(changes: SimpleChanges): void { console.log('UnifiedChartComponent input changes:', changes); - + + // Log chartType changes specifically + if (changes.chartType) { + console.log('Chart type changed from', changes.chartType.previousValue, 'to', changes.chartType.currentValue); + } + // Log all input values for debugging console.log('Current input values:', { chartType: this.chartType, @@ -139,13 +150,13 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { drilldownFilters: this.drilldownFilters, drilldownLayers: this.drilldownLayers }); - + // Initialize filter values if they haven't been initialized yet if (!this.filtersInitialized && (changes.baseFilters || changes.drilldownFilters || changes.drilldownLayers)) { this.initializeFilterValues(); this.filtersInitialized = true; } - + // Check if any of the key properties have changed const chartTypeChanged = changes.chartType && !changes.chartType.firstChange; const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; @@ -154,14 +165,14 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { const connectionChanged = changes.connection && !changes.connection.firstChange; const baseFiltersChanged = changes.baseFilters && !changes.baseFilters.firstChange; const drilldownFiltersChanged = changes.drilldownFilters && !changes.drilldownFilters.firstChange; - + // Drilldown configuration changes const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; - + // Log base filters changes for debugging if (baseFiltersChanged) { console.log('Base filters changed:', changes.baseFilters); @@ -178,7 +189,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { }); } } - + // Also log when baseFilters is not changed but we still have filters if (!baseFiltersChanged && this.baseFilters && this.baseFilters.length > 0) { console.log('Base filters present but not changed, logging current state:'); @@ -191,21 +202,21 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { }); }); } - + // Load dynamic template and options if chart type changed if (chartTypeChanged) { this.loadDynamicChartConfig(); } - + // Only fetch data if the actual chart configuration changed and we're not already fetching if (!this.isFetchingData && (chartTypeChanged || xAxisChanged || yAxisChanged || tableChanged || connectionChanged || baseFiltersChanged || drilldownFiltersChanged || - drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || - drilldownLayersChanged)) { + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged)) { console.log('Chart configuration changed, fetching new data'); this.initializeChartOptions(); this.fetchChartData(); } - + // Update legend visibility if it changed if (changes.chartlegend !== undefined) { this.chartLegend = changes.chartlegend.currentValue; @@ -228,7 +239,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { ngOnDestroy(): void { // Unsubscribe from all subscriptions this.subscriptions.forEach(sub => sub.unsubscribe()); - + // Remove document click handler if (this.documentClickHandler) { document.removeEventListener('click', this.documentClickHandler); @@ -243,49 +254,66 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } console.log(`Loading dynamic chart configuration for chart type: ${this.chartType}`); + console.log('Current dynamic template:', this.dynamicTemplate); // Get chart type by name and load its configuration + console.log('Calling getChartTypeByName with:', this.chartType); this.dynamicChartLoader.getChartTypeByName(this.chartType).subscribe({ next: (chartType) => { + console.log('Received chart type by name :', chartType); if (chartType) { console.log('Found chart type:', chartType); - + // Load the complete configuration for this chart type + console.log('Loading chart configuration for chart type ID:', chartType.id); this.dynamicChartLoader.loadChartConfiguration(chartType.id).subscribe({ next: (config) => { + console.log('Received chart configuration:', config); console.log('Loaded chart configuration:', config); - + // Apply the first template if available if (config.templates && config.templates.length > 0) { const defaultTemplate = config.templates.find(t => t.isDefault) || config.templates[0]; if (defaultTemplate) { - this.dynamicTemplate = defaultTemplate.templateHtml || ''; + this.setDynamicTemplate(defaultTemplate.templateHtml || ''); this.dynamicStyles = defaultTemplate.templateCss || ''; - + // Apply styles to the component this.applyDynamicStyles(); - - console.log('Applied dynamic template and styles'); + + console.log('Applied dynamic template and styles', { + template: this.dynamicTemplate, + styles: this.dynamicStyles + }); } + } else { + console.log('No templates found for chart type:', this.chartType); } - + // Apply dynamic options if available + console.log('Checking for dynamic fields:', config.dynamicFields); if (config.dynamicFields && config.dynamicFields.length > 0) { // Find the field that contains chart options - const optionsField = config.dynamicFields.find(field => + const optionsField = config.dynamicFields.find(field => field.fieldName === 'chartOptions' || field.fieldName.toLowerCase().includes('options')); - + + if (!optionsField) { + console.log('No chartOptions field found in dynamic fields'); + } + if (optionsField && optionsField.fieldOptions) { try { this.dynamicOptions = JSON.parse(optionsField.fieldOptions); console.log('Applied dynamic chart options:', this.dynamicOptions); - + // Merge dynamic options with current chart options this.mergeDynamicOptions(); } catch (e) { console.error('Error parsing dynamic chart options:', e); } } + } else { + console.log('No dynamic fields found for chart type:', this.chartType); } }, error: (error) => { @@ -294,6 +322,16 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { }); } else { console.log(`Chart type ${this.chartType} not found in database`); + // Log available chart types for debugging + console.log('Available chart types in database:'); + this.dynamicChartLoader.loadAllChartConfigurations().subscribe({ + next: (chartTypes) => { + console.log('All chart types:', chartTypes); + }, + error: (error) => { + console.error('Error loading chart types:', error); + } + }); } }, error: (error) => { @@ -307,7 +345,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { if (this.dynamicOptions) { // Merge dynamic options with existing chart options this.chartOptions = { ...this.chartOptions, ...this.dynamicOptions }; - + // If we have a chart instance, update it if (this.chart) { this.chart.options = this.chartOptions; @@ -352,7 +390,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { private initializeFilterValues(): void { console.log('Initializing filter values'); console.log('Base filters before initialization:', this.baseFilters); - + // Initialize base filters if (this.baseFilters && Array.isArray(this.baseFilters)) { this.baseFilters.forEach((filter, index) => { @@ -380,9 +418,9 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Initialize as empty array if not provided this.baseFilters = []; } - + console.log('Base filters after initialization:', this.baseFilters); - + // Initialize drilldown filters if (this.drilldownFilters && Array.isArray(this.drilldownFilters)) { this.drilldownFilters.forEach((filter, index) => { @@ -410,7 +448,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Initialize as empty array if not provided this.drilldownFilters = []; } - + // Initialize layer filters if (this.drilldownLayers && Array.isArray(this.drilldownLayers)) { this.drilldownLayers.forEach((layer, layerIndex) => { @@ -443,7 +481,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Initialize as empty array if not provided this.drilldownLayers = []; } - + console.log('Filter values initialized:', { baseFilters: this.baseFilters, drilldownFilters: this.drilldownFilters, @@ -464,13 +502,13 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } } }; - + // If we have dynamic options, use them instead of the default ones if (this.dynamicOptions) { this.mergeDynamicOptions(); return; } - + switch (this.chartType) { case 'bar': this.initializeBarChartOptions(); @@ -713,7 +751,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { mode: 'point', intersect: false, callbacks: { - label: function(context: any) { + label: function (context: any) { const point: any = context.raw; if (point && point.hasOwnProperty('y') && point.hasOwnProperty('r')) { const yValue = parseFloat(point.y); @@ -822,34 +860,34 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.isFetchingData = true; this.isLoading = true; this.noDataAvailable = false; - + // Ensure chart options are initialized if (!this.chartOptions) { this.initializeChartOptions(); } - + console.log('Starting fetchChartData for chart type:', this.chartType); - + // 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(); return; } - + // If we have the necessary data, fetch chart data from the service if (this.table && this.xAxis && this.yAxis) { - console.log('Fetching chart data for:', { - chartType: this.chartType, - table: this.table, - xAxis: this.xAxis, - yAxis: this.yAxis, - connection: this.connection + console.log('Fetching chart data for:', { + chartType: this.chartType, + table: this.table, + xAxis: this.xAxis, + yAxis: this.yAxis, + connection: this.connection }); - + // Convert yAxis to string if it's an array const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; - + // Convert baseFilters to filter parameters let filterParams = ''; if (this.baseFilters && this.baseFilters.length > 0) { @@ -863,15 +901,15 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { filterParams = JSON.stringify(filterObj); } } - + // Add common filters to filter parameters const commonFilters = this.filterService.getFilterValues(); console.log('Common filters from service:', commonFilters); - + if (Object.keys(commonFilters).length > 0) { // Merge common filters with base filters const mergedFilterObj = {}; - + // Add base filters first if (filterParams) { try { @@ -881,7 +919,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { console.warn('Failed to parse base filter parameters:', e); } } - + // Add common filters using the field name as the key, not the filter id Object.keys(commonFilters).forEach(filterId => { const filterValue = commonFilters[filterId]; @@ -899,35 +937,35 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } } }); - + if (Object.keys(mergedFilterObj).length > 0) { filterParams = JSON.stringify(mergedFilterObj); } } - + console.log('Final filter parameters:', filterParams); - + // Fetch data from the dashboard service this.dashboardService.getChartData( - this.table, - this.chartType, - this.xAxis, - yAxisString, - this.connection, - '', - '', + this.table, + this.chartType, + this.xAxis, + yAxisString, + this.connection, + '', + '', filterParams ).subscribe( (data: any) => { console.log('Received chart data:', data); - + if (data === null || data === undefined) { console.warn('Chart API returned null/undefined data.'); this.noDataAvailable = true; } else if (data && data.chartLabels && data.chartData) { // Handle the standard format this.noDataAvailable = data.chartLabels.length === 0; - + if (this.chartType === 'bubble') { // For bubble charts, transform the data this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); @@ -938,7 +976,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } else if (data && data.labels && data.datasets) { // Handle the legacy format this.noDataAvailable = data.labels.length === 0; - + if (this.chartType === 'bubble') { // For bubble charts, use the datasets directly this.bubbleChartData = data.datasets; @@ -950,11 +988,11 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { console.warn('Chart received data does not have expected structure', data); this.noDataAvailable = true; } - + // Reset flags after fetching this.isFetchingData = false; this.isLoading = false; - + // Trigger chart update setTimeout(() => { if (this.chart) { @@ -968,25 +1006,25 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.chartLabels = []; this.chartData = []; this.bubbleChartData = []; - + // Reset flags after fetching this.isFetchingData = false; this.isLoading = false; } ); } else { - console.log('Missing required data for chart:', { - chartType: this.chartType, - table: this.table, - xAxis: this.xAxis, - yAxis: this.yAxis, - connection: this.connection + console.log('Missing required data for chart:', { + chartType: this.chartType, + table: this.table, + xAxis: this.xAxis, + yAxis: this.yAxis, + connection: this.connection }); this.noDataAvailable = true; this.chartLabels = []; this.chartData = []; this.bubbleChartData = []; - + // Reset flags after fetching this.isFetchingData = false; this.isLoading = false; @@ -997,7 +1035,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { fetchDrilldownData(): void { console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); console.log('Drilldown stack:', this.drilldownStack); - + // Get the current drilldown configuration based on the current level let drilldownConfig; if (this.currentDrilldownLevel === 1) { @@ -1022,9 +1060,9 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { return; } } - + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); - + // Check if we have valid drilldown configuration if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); @@ -1034,7 +1072,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.bubbleChartData = []; return; } - + // Get the parameter value from the drilldown stack let parameterValue = ''; if (this.drilldownStack.length > 0) { @@ -1042,11 +1080,11 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { parameterValue = lastEntry.clickedValue || ''; console.log('Parameter value from last click:', parameterValue); } - + // Get the parameter field from drilldown config const parameterField = drilldownConfig.parameter || ''; console.log('Parameter field:', parameterField); - + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { apiUrl: drilldownConfig.apiUrl, xAxis: drilldownConfig.xAxis, @@ -1055,16 +1093,16 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { parameterValue: parameterValue, connection: this.connection }); - + // Build the actual API URL with parameter replacement let actualApiUrl = drilldownConfig.apiUrl; console.log('Original API URL:', actualApiUrl); console.log('Parameter value to use:', parameterValue); console.log('Parameter field:', parameterField); - + // Check if the URL contains angle brackets for parameter replacement const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); - + if (hasAngleBrackets && parameterValue) { // Replace angle brackets placeholder with actual value console.log('Replacing angle brackets with parameter value'); @@ -1072,10 +1110,10 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); console.log('URL after angle bracket replacement:', actualApiUrl); } - + // Convert drilldown layer filters to filter parameters (if applicable) const filterObj = {}; - + // Add drilldown layer filters if (drilldownConfig.filters && drilldownConfig.filters.length > 0) { drilldownConfig.filters.forEach((filter: any) => { @@ -1084,7 +1122,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } }); } - + // Add drilldownFilters if (this.drilldownFilters && this.drilldownFilters.length > 0) { this.drilldownFilters.forEach(filter => { @@ -1093,15 +1131,15 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } }); } - + // Add common filters const commonFilters = this.filterService.getFilterValues(); Object.keys(commonFilters).forEach(filterId => { const filterValue = commonFilters[filterId]; - + // Find the filter definition to get the field name const filterDef = this.filterService.getFilters().find(f => f.id === filterId); - + if (filterDef && filterDef.field) { const fieldName = filterDef.field; if (filterValue !== undefined && filterValue !== null && filterValue !== '') { @@ -1109,15 +1147,15 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } } }); - + // Convert to JSON string for API call let drilldownFilterParams = ''; if (Object.keys(filterObj).length > 0) { drilldownFilterParams = JSON.stringify(filterObj); } - + console.log('Drilldown filter parameters:', drilldownFilterParams); - + // Fetch data from the dashboard service this.dashboardService.getChartData( actualApiUrl, @@ -1139,12 +1177,12 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.bubbleChartData = []; return; } - + // Handle the actual data structure returned by the API if (data && data.chartLabels && data.chartData) { // Backend has already filtered the data, just display it this.noDataAvailable = data.chartLabels.length === 0; - + if (this.chartType === 'bubble') { // For bubble charts, transform the data this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); @@ -1152,16 +1190,16 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.chartLabels = data.chartLabels; this.chartData = data.chartData; } - - console.log('Updated chart with drilldown data:', { - labels: this.chartLabels, + + console.log('Updated chart with drilldown data:', { + labels: this.chartLabels, data: this.chartData, bubbleData: this.bubbleChartData }); } else if (data && data.labels && data.datasets) { // Handle the legacy format this.noDataAvailable = data.labels.length === 0; - + if (this.chartType === 'bubble') { // For bubble charts, use the datasets directly this.bubbleChartData = data.datasets; @@ -1169,9 +1207,9 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.chartLabels = data.labels; this.chartData = data.datasets; } - - console.log('Updated chart with drilldown legacy data format:', { - labels: this.chartLabels, + + console.log('Updated chart with drilldown legacy data format:', { + labels: this.chartLabels, data: this.chartData, bubbleData: this.bubbleChartData }); @@ -1182,10 +1220,10 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.chartData = []; this.bubbleChartData = []; } - + // Set loading state to false this.isLoading = false; - + // Trigger chart update setTimeout(() => { if (this.chart) { @@ -1207,43 +1245,43 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Transform data to bubble chart format private transformToBubbleData(labels: any[], data: any[]): ChartDataset[] { 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')) { + 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; } - + // 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)); - + // Find min and max values for scaling let minValue = Infinity; let maxValue = -Infinity; const validYValues = []; - + // First pass: collect valid values and find min/max for (let i = 0; i < yValues.length; i++) { let y; @@ -1252,21 +1290,21 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } else { y = yValues[i]; } - + if (!isNaN(y)) { validYValues.push(y); minValue = Math.min(minValue, y); maxValue = Math.max(maxValue, y); } } - + console.log('Value range:', { minValue, maxValue }); - + // Adjust radius range based on number of data points const dataPointCount = Math.min(labels.length, yValues.length); let minRadius = 3; let maxRadius = 30; - + // Adjust radius range based on data point count if (dataPointCount > 50) { minRadius = 2; @@ -1275,16 +1313,16 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { minRadius = 3; maxRadius = 25; } - + console.log('Radius range:', { minRadius, maxRadius, dataPointCount }); - + // Create bubble points from labels (x) and data (y) const bubblePoints = []; const bubbleColors = []; const minLength = Math.min(labels.length, yValues.length); - + console.log('Processing data points:', { labels, yValues, minLength }); - + for (let i = 0; i < minLength; i++) { // Convert y to number if it's a string let y; @@ -1294,47 +1332,47 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } 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 with logarithmic scaling const r = this.logScale(y, minValue, maxValue, minRadius, maxRadius); - + console.log(`Value: ${y}, Radius: ${r}`); - + // For x-value, we'll use the index position since labels are strings const x = i; - + // Generate a unique color for this bubble const backgroundColor = this.generateBubbleColor(i, y, minLength); - + // Store the color for the dataset bubbleColors.push(backgroundColor); - + // Add the point - const point = { - x, - y, + 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); console.log('Generated bubble colors count:', bubbleColors.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[] = [ { @@ -1348,11 +1386,11 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { pointHoverRadius: 10, } ]; - + console.log('Transformed bubble data:', bubbleDatasets); return bubbleDatasets; } - + console.log('Could not transform data, returning empty dataset'); return []; } @@ -1360,14 +1398,14 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Helper function to calculate logarithmic scaling private logScale(value: number, min: number, max: number, minRadius: number, maxRadius: number): number { if (min === max) return (minRadius + maxRadius) / 2; - + // Normalize value to 0-1 range const normalized = (value - min) / (max - min); - + // Apply logarithmic scaling (base 10) // Add 1 to avoid log(0) and scale to 1-10 range const logValue = Math.log10(normalized * 9 + 1); - + // Scale to desired radius range return minRadius + (logValue / Math.log10(10)) * (maxRadius - minRadius); } @@ -1379,12 +1417,12 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { const hue = (index * 137.508) % 360; // Golden angle approximation for good distribution const saturation = 80 + (index % 20); // High saturation for vibrant colors const lightness = 40 + (index % 30); // Vary lightness for contrast - + // Convert HSL to RGB const h = hue / 360; const s = saturation / 100; const l = lightness / 100; - + const rgb = this.hslToRgb(h, s, l); return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.7)`; } @@ -1392,26 +1430,26 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Helper function to convert HSL to RGB private hslToRgb(h: number, s: number, l: number): { r: number, g: number, b: number } { let r, g, b; - + if (s === 0) { r = g = b = l; // achromatic } else { const hue2rgb = (p: number, q: number, t: number) => { if (t < 0) t += 1; if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; - + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); + r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); + b = hue2rgb(p, q, h - 1 / 3); } - + return { r: Math.round(r * 255), g: Math.round(g * 255), @@ -1430,18 +1468,18 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { console.log('Resetting to original data'); console.log('Current stack before reset:', this.drilldownStack); console.log('Current level before reset:', this.currentDrilldownLevel); - + this.currentDrilldownLevel = 0; this.drilldownStack = []; - + if (this.originalChartData.labels && this.originalChartData.data) { this.chartLabels = [...this.originalChartData.labels]; this.chartData = [...this.originalChartData.data]; console.log('Restored original data'); } - + console.log('After reset - data:', { labels: this.chartLabels, data: this.chartData }); - + // Re-fetch original data this.fetchChartData(); } @@ -1450,17 +1488,17 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { navigateBack(): void { console.log('Navigating back, current stack:', this.drilldownStack); console.log('Current level:', this.currentDrilldownLevel); - + if (this.drilldownStack.length > 0) { // Remove the last entry from the stack const removedEntry = this.drilldownStack.pop(); console.log('Removed entry from stack:', removedEntry); - + // Update the current drilldown level this.currentDrilldownLevel = this.drilldownStack.length; console.log('New level after pop:', this.currentDrilldownLevel); console.log('Stack after pop:', this.drilldownStack); - + if (this.drilldownStack.length > 0) { // Fetch data for the previous level console.log('Fetching data for previous level'); @@ -1480,15 +1518,15 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Chart click handler public chartClicked(e: any): void { console.log('Chart clicked:', e); - + // If drilldown is enabled and we have a valid click event if (this.drilldownEnabled && e.active && e.active.length > 0) { // Get the index of the clicked element const clickedIndex = e.active[0].index; - + // Get the dataset index const datasetIndex = e.active[0].datasetIndex; - + // Get the data point let dataPoint; if (this.chartType === 'bubble') { @@ -1496,9 +1534,9 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } else { dataPoint = this.chartData[datasetIndex].data[clickedIndex]; } - + console.log('Clicked on chart element:', { datasetIndex, clickedIndex, dataPoint }); - + // If we're not at the base level, store original data if (this.currentDrilldownLevel === 0) { // Store original data before entering drilldown mode @@ -1509,16 +1547,16 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { }; console.log('Stored original data for drilldown'); } - + // Determine the next drilldown level const nextDrilldownLevel = this.currentDrilldownLevel + 1; - + console.log('Next drilldown level will be:', nextDrilldownLevel); - + // Check if there's a drilldown configuration for this level let hasDrilldownConfig = false; let drilldownConfig; - + if (nextDrilldownLevel === 1) { // Base drilldown level drilldownConfig = { @@ -1534,15 +1572,15 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { if (layerIndex < this.drilldownLayers.length) { drilldownConfig = this.drilldownLayers[layerIndex]; hasDrilldownConfig = drilldownConfig.enabled && - !!drilldownConfig.apiUrl && - !!drilldownConfig.xAxis && - !!drilldownConfig.yAxis; + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; } } - + console.log('Drilldown config for next level:', drilldownConfig); console.log('Has drilldown config:', hasDrilldownConfig); - + // If there's a drilldown configuration for the next level, proceed if (hasDrilldownConfig) { // Add this click to the drilldown stack @@ -1553,17 +1591,17 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { dataPoint: dataPoint, clickedValue: dataPoint // Using data point as value for now }; - + this.drilldownStack.push(stackEntry); - + console.log('Added to drilldown stack:', stackEntry); console.log('Current drilldown stack:', this.drilldownStack); - + // Update the current drilldown level this.currentDrilldownLevel = nextDrilldownLevel; - + console.log('Entering drilldown level:', this.currentDrilldownLevel); - + // Fetch drilldown data for the new level this.fetchDrilldownData(); } else { @@ -1586,7 +1624,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { console.log('Bubble 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); @@ -1603,7 +1641,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } } } - + console.log('No valid bubble chart data found'); return false; } else { @@ -1612,12 +1650,12 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { console.log('Chart labels or data is null'); return false; } - + if (this.chartLabels.length === 0 || this.chartData.length === 0) { console.log('Chart labels or data is empty'); return false; } - + // Check if any dataset has data for (const dataset of this.chartData) { if (dataset.data && dataset.data.length > 0) { @@ -1625,7 +1663,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { return true; } } - + console.log('No valid chart data found'); return false; } @@ -1633,18 +1671,18 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Check if there are active filters hasActiveFilters(): boolean { - return (this.baseFilters && this.baseFilters.length > 0) || - (this.drilldownFilters && this.drilldownFilters.length > 0) || - this.hasActiveLayerFilters(); + return (this.baseFilters && this.baseFilters.length > 0) || + (this.drilldownFilters && this.drilldownFilters.length > 0) || + this.hasActiveLayerFilters(); } // Check if there are active layer filters for current drilldown level hasActiveLayerFilters(): boolean { if (this.currentDrilldownLevel > 1 && this.drilldownLayers && this.drilldownLayers.length > 0) { const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown - return layerIndex < this.drilldownLayers.length && - this.drilldownLayers[layerIndex].filters && - this.drilldownLayers[layerIndex].filters.length > 0; + return layerIndex < this.drilldownLayers.length && + this.drilldownLayers[layerIndex].filters && + this.drilldownLayers[layerIndex].filters.length > 0; } return false; } @@ -1653,8 +1691,8 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { getActiveLayerFilters(): any[] { if (this.currentDrilldownLevel > 1 && this.drilldownLayers && this.drilldownLayers.length > 0) { const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown - if (layerIndex < this.drilldownLayers.length && - this.drilldownLayers[layerIndex].filters) { + if (layerIndex < this.drilldownLayers.length && + this.drilldownLayers[layerIndex].filters) { return this.drilldownLayers[layerIndex].filters; } } @@ -1678,11 +1716,11 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { if (!filter.value) { return false; } - + if (Array.isArray(filter.value)) { return filter.value.includes(option); } - + return filter.value === option; } @@ -1710,12 +1748,12 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Handle multiselect changes onMultiSelectChange(filter: any, option: string, event: any): void { const checked = event.target.checked; - + // Initialize filter.value as array if it's not already if (!Array.isArray(filter.value)) { filter.value = []; } - + if (checked) { // Add option to array if not already present if (!filter.value.includes(option)) { @@ -1725,7 +1763,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Remove option from array filter.value = filter.value.filter((item: string) => item !== option); } - + // Refresh data when filter changes this.fetchChartData(); } @@ -1737,7 +1775,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { if (!filter.value) { filter.value = { start: null, end: null }; } - + // Refresh data when filter changes this.fetchChartData(); } @@ -1748,10 +1786,10 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { if (!filter.value) { filter.value = { start: null, end: null }; } - + // Update the specific date type (start or end) filter.value[dateType] = event.target.value; - + // Refresh data when filter changes this.fetchChartData(); } @@ -1772,7 +1810,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { // Close all other multiselects first this.openMultiselects.clear(); this.openMultiselects.set(filterId, context); - + // Add document click handler to close dropdown when clicking outside this.addDocumentClickHandler(); } @@ -1789,7 +1827,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { this.removeDocumentClickHandler(); } }; - + // Use setTimeout to ensure the click event that opened the dropdown doesn't immediately close it setTimeout(() => { document.addEventListener('click', this.documentClickHandler!); @@ -1816,15 +1854,15 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { if (!filter.value) { return 0; } - + if (Array.isArray(filter.value)) { return filter.value.length; } - + return 0; } - + // Clear all filters clearAllFilters(): void { @@ -1842,7 +1880,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } }); } - + // Clear drilldown filters if (this.drilldownFilters) { this.drilldownFilters.forEach(filter => { @@ -1857,7 +1895,7 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } }); } - + // Clear layer filters if (this.drilldownLayers) { this.drilldownLayers.forEach(layer => { @@ -1876,10 +1914,10 @@ export class UnifiedChartComponent implements OnInit, OnChanges, OnDestroy { } }); } - + // Close all multiselect dropdowns this.openMultiselects.clear(); - + // Refresh data this.fetchChartData(); }