-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts
index dd020a6..cf490a7 100644
--- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts
+++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts
@@ -22,6 +22,16 @@ import { AlertsService } from 'src/app/services/fnd/alerts.service';
import { isArray } from 'highcharts';
// Add the SureconnectService import
import { SureconnectService } from '../sureconnect/sureconnect.service';
+// Add the CommonFilterComponent import
+import { CommonFilterComponent } from '../common-filter/common-filter.component';
+// Add the CompactFilterComponent import
+import { CompactFilterComponent } from '../common-filter';
+// Add the FilterService import
+import { FilterService } from '../common-filter/filter.service';
+// Add the UnifiedChartComponent import
+import { UnifiedChartComponent } from '../gadgets/unified-chart';
+// Add the DynamicChartLoaderService import
+import { DynamicChartLoaderService } from '../chart-config/dynamic-chart-loader.service';
function isNullArray(arr) {
return !Array.isArray(arr) || arr.length === 0;
@@ -43,8 +53,19 @@ export class EditnewdashComponent implements OnInit {
commonFilterModalOpen: boolean = false; // Add common filter modal state
public entryForm: FormGroup;
public commonFilterForm: FormGroup; // Add common filter form
+
+ // Add filterOptionsString property for compact filter
+ filterOptionsString: string = '';
+
+ // Add availableKeys property for compact filter
+ availableKeys: string[] = [];
+ // Initialize with default widgets and update dynamically
WidgetsMock: WidgetModel[] = [
+ {
+ name: 'Common Filter',
+ identifier: 'common_filter'
+ },
{
name: 'Radar Chart',
identifier: 'radar_chart'
@@ -57,34 +78,34 @@ export class EditnewdashComponent implements OnInit {
name: 'Line Chart',
identifier: 'line_chart'
},
- {
- name: 'Bar Chart',
- identifier: 'bar_chart'
- },
- {
- name: 'Pie Chart',
- identifier: 'pie_chart'
- },
- {
- name: 'Polar Area Chart',
- identifier: 'polar_area_chart'
- },
- {
- name: 'Bubble Chart',
- identifier: 'bubble_chart'
- },
- {
- name: 'Scatter Chart',
- identifier: 'scatter_chart'
- },
// {
- // name: 'Dynamic Chart',
- // identifier: 'dynamic_chart'
+ // name: 'Bar Chart',
+ // identifier: 'bar_chart'
// },
// {
- // name: 'Financial Chart',
- // identifier: 'financial_chart'
+ // name: 'Pie Chart',
+ // identifier: 'pie_chart'
// },
+ // {
+ // name: 'Polar Area Chart',
+ // identifier: 'polar_area_chart'
+ // },
+ // {
+ // name: 'Bubble Chart',
+ // identifier: 'bubble_chart'
+ // },
+ // {
+ // name: 'Scatter Chart',
+ // identifier: 'scatter_chart'
+ // },
+ {
+ name: 'Dynamic Chart',
+ identifier: 'dynamic_chart'
+ },
+ {
+ name: 'Financial Chart',
+ identifier: 'financial_chart'
+ },
{
name: 'To Do',
identifier: 'to_do_chart'
@@ -92,6 +113,14 @@ export class EditnewdashComponent implements OnInit {
{
name: 'Grid View',
identifier: 'grid_view'
+ },
+ {
+ name: 'Compact Filter',
+ identifier: 'compact_filter'
+ },
+ {
+ name: 'Unified Chart',
+ identifier: 'unified_chart'
}
]
@@ -104,18 +133,21 @@ export class EditnewdashComponent implements OnInit {
public dashArr: [];
protected componentCollection = [
- { name: "Line Chart", componentInstance: LineChartComponent },
- { name: "Doughnut Chart", componentInstance: DoughnutChartComponent },
- { name: "Radar Chart", componentInstance: RadarChartComponent },
- { name: "Bar Chart", componentInstance: BarChartComponent },
- { name: "Pie Chart", componentInstance: PieChartComponent },
- { name: "Polar Area Chart", componentInstance: PolarChartComponent },
- { name: "Bubble Chart", componentInstance: BubbleChartComponent },
- { name: "Scatter Chart", componentInstance: ScatterChartComponent },
- { name: "Dynamic Chart", componentInstance: DynamicChartComponent },
- { name: "Financial Chart", componentInstance: FinancialChartComponent },
+ { name: "Common Filter", componentInstance: CommonFilterComponent },
+ { name: "Line Chart", componentInstance: UnifiedChartComponent },
+ { name: "Doughnut Chart", componentInstance: UnifiedChartComponent },
+ { name: "Radar Chart", componentInstance: UnifiedChartComponent },
+ { name: "Bar Chart", componentInstance: UnifiedChartComponent },
+ { name: "Pie Chart", componentInstance: UnifiedChartComponent },
+ { name: "Polar Area Chart", componentInstance: UnifiedChartComponent },
+ { name: "Bubble Chart", componentInstance: UnifiedChartComponent },
+ { name: "Scatter Chart", componentInstance: UnifiedChartComponent },
+ { name: "Dynamic Chart", componentInstance: UnifiedChartComponent },
+ { name: "Financial Chart", componentInstance: UnifiedChartComponent },
{ name: "To Do Chart", componentInstance: ToDoChartComponent },
{ name: "Grid View", componentInstance: GridViewComponent },
+ { name: "Compact Filter", componentInstance: CompactFilterComponent },
+ { name: "Unified Chart", componentInstance: UnifiedChartComponent },
];
model: any;
linesdata: any;
@@ -148,6 +180,7 @@ export class EditnewdashComponent implements OnInit {
yAxis: '',
xAxis: '',
connection: '', // Add connection field
+ chartType: '', // Add chartType field
// Drilldown configuration properties (base level)
drilldownEnabled: false,
drilldownApiUrl: '',
@@ -161,7 +194,12 @@ export class EditnewdashComponent implements OnInit {
drilldownLayers: [] as any[],
// Common filter properties
commonFilterEnabled: false,
- commonFilterEnabledDrilldown: false
+ commonFilterEnabledDrilldown: false,
+ // Compact filter properties
+ filterKey: '',
+ filterType: 'text',
+ filterLabel: '',
+ filterOptions: [] as string[]
};
// Add sureconnect data property
@@ -171,6 +209,9 @@ export class EditnewdashComponent implements OnInit {
// Add drilldown column data property
drilldownColumnData = []; // Add drilldown column data property
+ // Add chart types property for dynamic chart selection
+ chartTypes: any[] = [];
+
constructor(private route: ActivatedRoute,
private router: Router,
private dashboardService: Dashboard3Service,
@@ -178,9 +219,16 @@ export class EditnewdashComponent implements OnInit {
private _fb: FormBuilder,
private datastoreService: DatastoreService,
private alertService: AlertsService,
- private sureconnectService: SureconnectService) { } // Add SureconnectService to constructor
+ private sureconnectService: SureconnectService,
+ private filterService: FilterService,
+ private dynamicChartLoader: DynamicChartLoaderService) { } // Add SureconnectService, FilterService, and DynamicChartLoaderService to constructor
+ // Add property to track if coming from dashboard runner
+ fromRunner: boolean = false;
+
ngOnInit(): void {
+ // Reset the filter service when the component is initialized
+ this.filterService.resetFilters();
// Grid options
this.options = {
@@ -201,8 +249,17 @@ export class EditnewdashComponent implements OnInit {
},
displayGrid: "always",
minCols: 10,
- minRows: 10
+ minRows: 10,
+ // Add resize callback to handle chart resizing
+ itemResizeCallback: this.itemResize.bind(this)
};
+
+ // Check if coming from dashboard runner
+ this.route.queryParams.subscribe(params => {
+ if (params['fromRunner'] === 'true') {
+ this.fromRunner = true;
+ }
+ });
this.editId = this.route.snapshot.params.id;
console.log(this.editId);
@@ -249,12 +306,46 @@ export class EditnewdashComponent implements OnInit {
apiUrl: ['']
});
+ // Load chart types for dynamic chart selection
+ this.loadChartTypesForSelection();
+
// Load sureconnect data first, then load dashboard data
this.loadSureconnectData();
// Load common filter data if it exists
this.loadCommonFilterData();
}
+
+ // Add method to load all chart types for dynamic selection
+ loadChartTypesForSelection() {
+ console.log('Loading chart types for selection');
+ this.dynamicChartLoader.loadActiveChartTypes().subscribe({
+ next: (chartTypes) => {
+ console.log('Loaded chart types for selection:', chartTypes);
+ this.chartTypes = chartTypes;
+
+ // Convert each chart type to a WidgetModel
+ const newWidgets = chartTypes.map(ct => ({
+ name: ct.displayName || ct.name,
+ // identifier: ct.name.toLowerCase().replace(/\s+/g, '_')
+ identifier: `${ct.name.toLowerCase().replace(/\s+/g, '_')}_chart`
+ }));
+
+ // Filter out duplicates by identifier
+ const existingIds = new Set(this.WidgetsMock.map(w => w.identifier));
+ const uniqueNewWidgets = newWidgets.filter(w => !existingIds.has(w.identifier));
+
+ // Append unique new widgets to WidgetsMock
+ this.WidgetsMock = [...this.WidgetsMock, ...uniqueNewWidgets];
+
+ console.log('Updated WidgetsMock:', this.WidgetsMock);
+ },
+ error: (error) => {
+ console.error('Error loading chart types for selection:', error);
+ }
+ });
+}
+
// Add method to load sureconnect data
loadSureconnectData() {
@@ -290,6 +381,9 @@ export class EditnewdashComponent implements OnInit {
dashboardLine: any;
dashboardName: any;
getData() {
+ // Reset the filter service when switching between dashboard records
+ this.filterService.resetFilters();
+
// We get the id in get current router dashboard/:id
this.route.params.subscribe(params => {
// + is used to cast string to int
@@ -339,6 +433,37 @@ export class EditnewdashComponent implements OnInit {
dashboard.component = component.componentInstance;
}
});
+
+ // Map chart names to unified chart types
+ const chartTypeMap = {
+ 'Radar Chart': 'radar',
+ 'Line Chart': 'line',
+ 'Doughnut Chart': 'doughnut',
+ 'Bar Chart': 'bar',
+ 'Pie Chart': 'pie',
+ 'Polar Area Chart': 'polar',
+ 'Bubble Chart': 'bubble',
+ 'Scatter Chart': 'scatter',
+ 'Dynamic Chart': 'line',
+ 'Financial Chart': 'line'
+ };
+
+ // If this is a chart, set the chartType property
+ if (chartTypeMap.hasOwnProperty(dashboard.name)) {
+ dashboard.chartType = chartTypeMap[dashboard.name];
+ // Keep the original name instead of changing it to "Unified Chart"
+ // dashboard.name = "Unified Chart";
+ }
+
+ // Ensure compact filter configuration properties are properly initialized
+ if (dashboard.component === 'Compact Filter' || dashboard.name === 'Compact Filter') {
+ // Make sure all compact filter properties exist
+ if (dashboard.filterKey === undefined) dashboard.filterKey = '';
+ if (dashboard.filterType === undefined) dashboard.filterType = 'text';
+ if (dashboard.filterLabel === undefined) dashboard.filterLabel = '';
+ if (dashboard.filterOptions === undefined) dashboard.filterOptions = [];
+ // table and connection properties should already exist for all components
+ }
});
}
@@ -353,8 +478,43 @@ export class EditnewdashComponent implements OnInit {
dashboard.component = component.name;
}
});
+
+ // Map unified chart types back to chart names for serialization
+ const chartNameMap = {
+ 'radar': 'Radar Chart',
+ 'line': 'Line Chart',
+ 'doughnut': 'Doughnut Chart',
+ 'bar': 'Bar Chart',
+ 'pie': 'Pie Chart',
+ 'polar': 'Polar Area Chart',
+ 'bubble': 'Bubble Chart',
+ 'scatter': 'Scatter Chart'
+ // Removed hardcoded heatmap entry to make it fully dynamic
+ };
+
+ // If this is a unified chart, set the name back to the appropriate chart name
+ if (dashboard.name === 'Unified Chart' && dashboard.chartType && chartNameMap.hasOwnProperty(dashboard.chartType)) {
+ dashboard.name = chartNameMap[dashboard.chartType];
+ }
+ // Also handle the case where the chart already has the correct name
+ else if (dashboard.chartType && chartNameMap.hasOwnProperty(dashboard.chartType) &&
+ dashboard.name === chartNameMap[dashboard.chartType]) {
+ // The name is already correct, no need to change it
+ dashboard.component = "Unified Chart";
+ }
+
+ // Ensure compact filter configuration properties are preserved
+ if (dashboard.name === 'Compact Filter') {
+ // Make sure all compact filter properties exist
+ if (dashboard.filterKey === undefined) dashboard.filterKey = '';
+ if (dashboard.filterType === undefined) dashboard.filterType = 'text';
+ if (dashboard.filterLabel === undefined) dashboard.filterLabel = '';
+ if (dashboard.filterOptions === undefined) dashboard.filterOptions = [];
+ // table and connection properties should already exist for all components
+ }
});
}
+
// Add method to get available fields for a filter dropdown (excluding already selected fields)
getAvailableFields(filters: any[], currentIndex: number, allFields: string[]): string[] {
if (!filters || !allFields) {
@@ -386,110 +546,44 @@ export class EditnewdashComponent implements OnInit {
//this._ds.updateDashboard(this.dashboardId, parsed).subscribe();
}
- onDrop(ev) {
+ onDrop = (ev) => {
+ console.log("on drop event ", ev);
const componentType = ev.dataTransfer.getData("widgetIdentifier");
- let maxChartId = this.dashboardArray?.reduce((maxId, item) => Math.max(maxId, item.chartid), 0);
+ // Safely calculate maxChartId, handling cases where chartid might be NaN or missing
+ console.log("on drop ", componentType);
+ let maxChartId = 0;
+ if (this.dashboardArray && this.dashboardArray.length > 0) {
+ const validChartIds = this.dashboardArray
+ .map(item => item.chartid)
+ .filter(chartid => typeof chartid === 'number' && !isNaN(chartid));
+
+ if (validChartIds.length > 0) {
+ maxChartId = Math.max(...validChartIds);
+ }
+ }
switch (componentType) {
+ // Handle all chart types by converting them to unified charts
case "radar_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: RadarChartComponent,
- name: "Radar Chart"
- });
+ // Use dynamic chart creation for all chart types
+ return this.createDynamicChart('radar', maxChartId);
case "line_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 7,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: LineChartComponent,
- name: "Line Chart"
- });
+ return this.createDynamicChart('line', maxChartId);
case "doughnut_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: DoughnutChartComponent,
- name: "Doughnut Chart"
- });
+ return this.createDynamicChart('doughnut', maxChartId);
case "bar_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: BarChartComponent,
- name: "Bar Chart"
- });
+ return this.createDynamicChart('bar', maxChartId);
case "pie_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: PieChartComponent,
- name: "Pie Chart"
- });
+ return this.createDynamicChart('pie', maxChartId);
case "polar_area_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: PolarChartComponent,
- name: "Polar Area Chart"
- });
+ return this.createDynamicChart('polar', maxChartId);
case "bubble_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: BubbleChartComponent,
- name: "Bubble Chart"
- });
+ return this.createDynamicChart('bubble', maxChartId);
case "scatter_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: ScatterChartComponent,
- name: "Scatter Chart"
- });
+ return this.createDynamicChart('scatter', maxChartId);
case "dynamic_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: DynamicChartComponent,
- name: "Dynamic Chart"
- });
+ return this.createDynamicChart('line', maxChartId); // Default to line for dynamic chart
case "financial_chart":
- return this.dashboardArray.push({
- cols: 5,
- rows: 6,
- x: 0,
- y: 0,
- chartid: maxChartId + 1,
- component: FinancialChartComponent,
- name: "Financial Chart"
- });
+ return this.createDynamicChart('line', maxChartId); // Default to line for financial chart
case "to_do_chart":
return this.dashboardArray.push({
cols: 5,
@@ -500,6 +594,31 @@ export class EditnewdashComponent implements OnInit {
component: ToDoChartComponent,
name: "To Do Chart"
});
+ case "common_filter":
+ return this.dashboardArray.push({
+ cols: 10,
+ rows: 3,
+ x: 0,
+ y: 0,
+ chartid: maxChartId + 1,
+ component: CommonFilterComponent,
+ name: "Common Filter"
+ });
+ case "compact_filter":
+ return this.dashboardArray.push({
+ cols: 3,
+ rows: 2,
+ x: 0,
+ y: 0,
+ chartid: maxChartId + 1,
+ component: CompactFilterComponent,
+ name: "Compact Filter",
+ // Add default configuration for compact filter
+ filterKey: '',
+ filterType: 'text',
+ filterLabel: '',
+ filterOptions: []
+ });
case "grid_view":
return this.dashboardArray.push({
cols: 5,
@@ -510,6 +629,17 @@ export class EditnewdashComponent implements OnInit {
component: GridViewComponent,
name: "Grid View"
});
+ case "unified_chart":
+ return this.createDynamicChart('bar', maxChartId); // Default to bar for unified chart
+ default:
+ // Handle any other chart types dynamically
+ // Extract chart type name from identifier (e.g., "heatmap_chart" -> "heatmap")
+ const chartTypeName = componentType.replace('_chart', '');
+ console.log('Creating dynamic chart of type:', chartTypeName);
+ console.log('Display name for chart:', this.getChartDisplayName(chartTypeName));
+
+ // Use dynamic chart creation for all chart types
+ return this.createDynamicChart(chartTypeName, maxChartId);
}
}
removeItem(item) {
@@ -525,7 +655,14 @@ export class EditnewdashComponent implements OnInit {
}
modelid: number;
+ // Update the editGadget method to initialize filter properties
editGadget(item) {
+ // If coming from dashboard runner, skip showing the config modal
+ if (this.fromRunner) {
+ console.log('Coming from dashboard runner, skipping config modal');
+ return;
+ }
+
this.modeledit = true;
this.modelid = item.chartid;
console.log(this.modelid);
@@ -542,6 +679,94 @@ export class EditnewdashComponent implements OnInit {
if (item['commonFilterEnabledDrilldown'] === undefined) {
this.gadgetsEditdata['commonFilterEnabledDrilldown'] = false;
}
+ // Initialize compact filter properties if not present
+ if (item['filterKey'] === undefined) {
+ this.gadgetsEditdata['filterKey'] = '';
+ }
+ if (item['filterType'] === undefined) {
+ this.gadgetsEditdata['filterType'] = 'text';
+ }
+ if (item['filterLabel'] === undefined) {
+ this.gadgetsEditdata['filterLabel'] = '';
+ }
+ if (item['filterOptions'] === undefined) {
+ this.gadgetsEditdata['filterOptions'] = [];
+ }
+ // Initialize chartType property if not present (for unified chart)
+ if (item['chartType'] === undefined) {
+ this.gadgetsEditdata['chartType'] = 'bar';
+ }
+
+ // Initialize filterOptionsString for compact filter
+ if (item.name === 'Compact Filter') {
+ this.filterOptionsString = this.gadgetsEditdata['filterOptions'].join(', ');
+ // Load available keys when editing a compact filter
+ if (this.gadgetsEditdata['table']) {
+ this.loadAvailableKeys(this.gadgetsEditdata['table'], this.gadgetsEditdata['connection']);
+ }
+ } else {
+ this.filterOptionsString = '';
+ }
+
+ // Initialize base filters with type and options if not present
+ if (item['baseFilters'] === undefined) {
+ this.gadgetsEditdata['baseFilters'] = [];
+ } else {
+ // Ensure each base filter has type and options properties
+ this.gadgetsEditdata['baseFilters'] = this.gadgetsEditdata['baseFilters'].map(filter => ({
+ field: filter.field || '',
+ value: filter.value || '',
+ type: filter.type || 'text',
+ options: filter.options || '',
+ availableValues: filter.availableValues || ''
+ }));
+ }
+
+ // Initialize drilldown filters with type and options if not present
+ if (item['drilldownFilters'] === undefined) {
+ this.gadgetsEditdata['drilldownFilters'] = [];
+ } else {
+ // Ensure each drilldown filter has type and options properties
+ this.gadgetsEditdata['drilldownFilters'] = this.gadgetsEditdata['drilldownFilters'].map(filter => ({
+ field: filter.field || '',
+ value: filter.value || '',
+ type: filter.type || 'text',
+ options: filter.options || '',
+ availableValues: filter.availableValues || ''
+ }));
+ }
+
+ // Initialize drilldown layers with proper filter structure if not present
+ if (item['drilldownLayers'] === undefined) {
+ this.gadgetsEditdata['drilldownLayers'] = [];
+ } else {
+ // Ensure each layer has proper filter structure
+ this.gadgetsEditdata['drilldownLayers'] = this.gadgetsEditdata['drilldownLayers'].map(layer => {
+ // Initialize parameter if not present
+ if (layer['parameter'] === undefined) {
+ layer['parameter'] = '';
+ }
+ // Initialize filters if not present
+ if (layer['filters'] === undefined) {
+ layer['filters'] = [];
+ } else {
+ // Ensure each layer filter has type and options properties
+ layer['filters'] = layer['filters'].map(filter => ({
+ field: filter.field || '',
+ value: filter.value || '',
+ type: filter.type || 'text',
+ options: filter.options || '',
+ availableValues: filter.availableValues || ''
+ }));
+ }
+ // Initialize common filter property for layer if not present
+ if (layer['commonFilterEnabled'] === undefined) {
+ layer['commonFilterEnabled'] = false;
+ }
+ return layer;
+ });
+ }
+
this.getStores();
// Set default connection if none is set and we have connections
@@ -569,37 +794,6 @@ export class EditnewdashComponent implements OnInit {
this.gadgetsEditdata['drilldownParameter'] = '';
}
- // Initialize base filters if not present
- if (item['baseFilters'] === undefined) {
- this.gadgetsEditdata['baseFilters'] = [];
- }
-
- // Initialize drilldown filters if not present
- if (item['drilldownFilters'] === undefined) {
- this.gadgetsEditdata['drilldownFilters'] = [];
- }
-
- // Initialize drilldown layers if not present
- if (item['drilldownLayers'] === undefined) {
- this.gadgetsEditdata['drilldownLayers'] = [];
- } else {
- // Ensure each layer has proper structure (removed parameterKey, added parameter)
- this.gadgetsEditdata['drilldownLayers'].forEach((layer, index) => {
- // Initialize parameter if not present
- if (layer['parameter'] === undefined) {
- layer['parameter'] = '';
- }
- // Initialize filters if not present
- if (layer['filters'] === undefined) {
- layer['filters'] = [];
- }
- // Initialize common filter property for layer if not present
- if (layer['commonFilterEnabled'] === undefined) {
- layer['commonFilterEnabled'] = false;
- }
- });
- }
-
// Reset drilldown column data
this.drilldownColumnData = [];
@@ -608,15 +802,34 @@ export class EditnewdashComponent implements OnInit {
this.refreshBaseDrilldownColumns();
}
- if (item.datastore !== undefined || '' || null) {
+ // Check if we have either datastore or table to fetch columns
+ if ((item.datastore !== undefined && item.datastore !== '' && item.datastore !== null) ||
+ (item.table !== undefined && item.table !== '' && item.table !== null)) {
const datastore = item.datastore;
- this.getTables(datastore);
const table = item.table;
- this.getColumns(datastore, table);
+
+ // Fetch tables if datastore is available
+ if (datastore) {
+ this.getTables(datastore);
+ }
+
+ // Fetch columns if table is available
+ if (table) {
+ this.getColumns(datastore, table);
+ }
+
console.log(item.yAxis);
- if (isArray(item.yAxis)) {
- this.selectedyAxis = item.yAxis;
+ // Set selectedyAxis regardless of whether it's an array or string
+ if (item.yAxis !== undefined && item.yAxis !== '' && item.yAxis !== null) {
+ if (isArray(item.yAxis)) {
+ this.selectedyAxis = item.yAxis;
+ } else {
+ // For single yAxis values, convert to array
+ this.selectedyAxis = [item.yAxis];
+ }
console.log(this.selectedyAxis);
+ } else {
+ this.selectedyAxis = [];
}
} else {
this.selectedyAxis = [];
@@ -640,6 +853,9 @@ export class EditnewdashComponent implements OnInit {
//https://www.w3schools.com/js/tryit.asp?filename=tryjson_stringify_function_tostring
+ // First serialize the dashboard collection to ensure component names are properly set
+ this.serialize(this.dashboardCollection.dashboard);
+
let cmp = this.dashboardCollection.dashboard.forEach(dashboard => {
this.componentCollection.forEach(component => {
if (dashboard.name === component.name) {
@@ -660,8 +876,6 @@ export class EditnewdashComponent implements OnInit {
//console.log(merged);
console.log("temp data", typeof tmp);
console.log(tmp);
- let parsed = JSON.parse(tmp);
- this.serialize(parsed.dashboard);
this.dashbord1_Line.model = tmp;
// let obj = this.dashboardCollection;
@@ -686,14 +900,32 @@ export class EditnewdashComponent implements OnInit {
// }
}
+ // Update the onSubmit method to properly save filter data
onSubmit(id) {
console.log(id);
- if (!isNullArray(this.selectedyAxis)) {
- console.log("get y-axis array", this.selectedyAxis);
+
+ // Check if ID is valid, including handling NaN
+ if (id === null || id === undefined || isNaN(id)) {
+ console.warn('Chart ID is null, undefined, or NaN, using modelid instead:', this.modelid);
+ id = this.modelid;
+ }
+
+ // Ensure we have a valid numeric ID
+ const numId = typeof id === 'number' ? id : parseInt(id, 10);
+ if (isNaN(numId)) {
+ console.error('Unable to determine valid chart ID, aborting onSubmit');
+ return;
+ }
+
+ // Handle both array and string yAxis values
+ if (this.selectedyAxis !== undefined && this.selectedyAxis !== null &&
+ ((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) ||
+ (typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) {
+ console.log("get y-axis", this.selectedyAxis);
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
}
let formdata = this.entryForm.value;
- let num = id;
+ let num = numId;
console.log(this.entryForm.value);
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
if (item.chartid == num) {
@@ -717,6 +949,26 @@ export class EditnewdashComponent implements OnInit {
xyz.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
xyz.commonFilterEnabled = this.gadgetsEditdata.commonFilterEnabled; // Add common filter property
+ // For compact filter, preserve filter configuration properties
+ if (item.name === 'Compact Filter') {
+ xyz.filterKey = this.gadgetsEditdata.filterKey || '';
+ xyz.filterType = this.gadgetsEditdata.filterType || 'text';
+ xyz.filterLabel = this.gadgetsEditdata.filterLabel || '';
+ // Convert filterOptionsString to array
+ if (this.gadgetsEditdata.fieldName === 'Compact Filter') {
+ xyz.filterOptions = this.filterOptionsString.split(',').map(opt => opt.trim()).filter(opt => opt);
+ } else {
+ xyz.filterOptions = this.gadgetsEditdata.filterOptions || [];
+ }
+ xyz.table = this.gadgetsEditdata.table || '';
+ xyz.connection = this.gadgetsEditdata.connection || undefined;
+ }
+
+ // For unified chart, preserve chart configuration properties
+ if (item.name === 'Unified Chart') {
+ xyz.chartType = this.gadgetsEditdata.chartType || 'bar';
+ }
+
console.log(xyz);
return xyz;
}
@@ -751,7 +1003,142 @@ export class EditnewdashComponent implements OnInit {
* This prevents errors when trying to set properties that don't exist on the components
*/
getChartInputs(item: any): any {
- // Only pass properties that are relevant to chart components
+ // For CompactFilterComponent, pass only filter configuration properties
+ if (item.name === 'Compact Filter') {
+ const filterInputs = {
+ filterKey: item['filterKey'] || '',
+ filterType: item['filterType'] || 'text',
+ filterLabel: item['filterLabel'] || '',
+ filterOptions: item['filterOptions'] || [],
+ apiUrl: item['table'] || '', // Use table as API URL
+ connectionId: item['connection'] ? parseInt(item['connection'], 10) : undefined
+ };
+
+ // Preserve configuration in the item itself
+ item['filterKey'] = filterInputs['filterKey'];
+ item['filterType'] = filterInputs['filterType'];
+ item['filterLabel'] = filterInputs['filterLabel'];
+ item['filterOptions'] = filterInputs['filterOptions'];
+ item['table'] = filterInputs['apiUrl'];
+ item['connection'] = item['connection'];
+
+ // Remove undefined properties to avoid passing unnecessary data
+ Object.keys(filterInputs).forEach(key => {
+ if (filterInputs[key] === undefined) {
+ delete filterInputs[key];
+ }
+ });
+
+ return filterInputs;
+ }
+
+ // For CommonFilterComponent, pass only filter-related properties
+ if (item.component && item.component.name === 'CommonFilterComponent') {
+ const commonFilterInputs = {
+ baseFilters: item['baseFilters'] || [],
+ drilldownFilters: item['drilldownFilters'] || [],
+ drilldownLayers: item['drilldownLayers'] || [],
+ fieldName: item['name'] || '',
+ connection: item['connection'] || undefined
+ };
+
+ // Remove undefined properties to avoid passing unnecessary data
+ Object.keys(commonFilterInputs).forEach(key => {
+ if (commonFilterInputs[key] === undefined) {
+ delete commonFilterInputs[key];
+ }
+ });
+
+ return commonFilterInputs;
+ }
+
+ // For UnifiedChartComponent, pass chart properties with chartType
+ // Check if the component is UnifiedChartComponent dynamically
+ if (item.component === UnifiedChartComponent ||
+ (item.component && item.component.name === 'UnifiedChartComponent') ||
+ item.name === 'Unified Chart') {
+ const unifiedChartInputs = {
+ chartType: item.chartType || 'bar',
+ xAxis: item.xAxis,
+ yAxis: item.yAxis,
+ table: item.table,
+ datastore: item.datastore,
+ charttitle: item.charttitle,
+ chartlegend: item.chartlegend,
+ showlabel: item.showlabel,
+ chartcolor: item.chartcolor,
+ slices: item.slices,
+ donut: item.donut,
+ charturl: item.charturl,
+ chartparameter: item.chartparameter,
+ datasource: item.datasource,
+ fieldName: item.name, // Using item.name as fieldName
+ connection: item['connection'], // Add connection field using bracket notation
+ // Base drilldown configuration properties
+ drilldownEnabled: item['drilldownEnabled'],
+ drilldownApiUrl: item['drilldownApiUrl'],
+ // Removed drilldownParameterKey since we're using URL templates
+ drilldownXAxis: item['drilldownXAxis'],
+ drilldownYAxis: item['drilldownYAxis'],
+ drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
+ baseFilters: item['baseFilters'] || [], // Add base filters
+ drilldownFilters: item['drilldownFilters'] || [], // Add drilldown filters
+ // Multi-layer drilldown configurations
+ drilldownLayers: item['drilldownLayers'] || []
+ };
+
+ // Remove undefined properties to avoid passing unnecessary data
+ Object.keys(unifiedChartInputs).forEach(key => {
+ if (unifiedChartInputs[key] === undefined) {
+ delete unifiedChartInputs[key];
+ }
+ });
+
+ return unifiedChartInputs;
+ }
+
+ // For GridViewComponent, pass chart properties with drilldown support
+ if (item.component && item.component.name === 'GridViewComponent') {
+ const gridInputs = {
+ xAxis: item.xAxis,
+ yAxis: item.yAxis,
+ table: item.table,
+ datastore: item.datastore,
+ charttitle: item.charttitle,
+ chartlegend: item.chartlegend,
+ showlabel: item.showlabel,
+ chartcolor: item.chartcolor,
+ slices: item.slices,
+ donut: item.donut,
+ charturl: item.charturl,
+ chartparameter: item.chartparameter,
+ datasource: item.datasource,
+ fieldName: item.name, // Using item.name as fieldName
+ connection: item['connection'], // Add connection field using bracket notation
+ // Base drilldown configuration properties
+ drilldownEnabled: item['drilldownEnabled'],
+ drilldownApiUrl: item['drilldownApiUrl'],
+ // Removed drilldownParameterKey since we're using URL templates
+ drilldownXAxis: item['drilldownXAxis'],
+ drilldownYAxis: item['drilldownYAxis'],
+ drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
+ baseFilters: item['baseFilters'] || [], // Add base filters
+ drilldownFilters: item['drilldownFilters'] || [], // Add drilldown filters
+ // Multi-layer drilldown configurations
+ drilldownLayers: item['drilldownLayers'] || []
+ };
+
+ // Remove undefined properties to avoid passing unnecessary data
+ Object.keys(gridInputs).forEach(key => {
+ if (gridInputs[key] === undefined) {
+ delete gridInputs[key];
+ }
+ });
+
+ return gridInputs;
+ }
+
+ // For all other chart components, pass chart-specific properties
const chartInputs = {
xAxis: item.xAxis,
yAxis: item.yAxis,
@@ -775,8 +1162,8 @@ export class EditnewdashComponent implements OnInit {
drilldownXAxis: item['drilldownXAxis'],
drilldownYAxis: item['drilldownYAxis'],
drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
- baseFilters: item['baseFilters'] || [], // Add base filters
- drilldownFilters: item['drilldownFilters'] || [], // Add drilldown filters
+ baseFilters: item['baseFilters'] || [], // Add base filters with type information
+ drilldownFilters: item['drilldownFilters'] || [], // Add drilldown filters with type information
// Multi-layer drilldown configurations
drilldownLayers: item['drilldownLayers'] || []
};
@@ -791,18 +1178,29 @@ export class EditnewdashComponent implements OnInit {
return chartInputs;
}
+ // Update the applyChanges method to properly save filter data
applyChanges(id) {
console.log('Apply changes for chart ID:', id);
- // Check if ID is valid
- if (id === null || id === undefined) {
- console.warn('Chart ID is null or undefined, using modelid instead:', this.modelid);
+ // Check if ID is valid, including handling NaN
+ if (id === null || id === undefined || isNaN(id)) {
+ console.warn('Chart ID is null, undefined, or NaN, using modelid instead:', this.modelid);
id = this.modelid;
}
- // Update the form with selected Y-axis values if it's an array
- if (!isNullArray(this.selectedyAxis)) {
- console.log("get y-axis array", this.selectedyAxis);
+ // Ensure we have a valid numeric ID
+ const numId = typeof id === 'number' ? id : parseInt(id, 10);
+ if (isNaN(numId)) {
+ console.error('Unable to determine valid chart ID, aborting applyChanges');
+ return;
+ }
+
+ // Update the form with selected Y-axis values
+ // Handle both array and string yAxis values
+ if (this.selectedyAxis !== undefined && this.selectedyAxis !== null &&
+ ((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) ||
+ (typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) {
+ console.log("get y-axis", this.selectedyAxis);
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
}
@@ -835,6 +1233,27 @@ export class EditnewdashComponent implements OnInit {
updatedItem.commonFilterEnabled = this.gadgetsEditdata.commonFilterEnabled; // Add common filter property
updatedItem.commonFilterEnabledDrilldown = this.gadgetsEditdata.commonFilterEnabledDrilldown; // Add drilldown common filter property
+ // For compact filter, preserve filter configuration properties
+ if (item.name === 'Compact Filter') {
+ updatedItem.filterKey = this.gadgetsEditdata.filterKey || '';
+ updatedItem.filterType = this.gadgetsEditdata.filterType || 'text';
+ updatedItem.filterLabel = this.gadgetsEditdata.filterLabel || '';
+ // Convert filterOptionsString to array
+ if (this.gadgetsEditdata.fieldName === 'Compact Filter') {
+ updatedItem.filterOptions = this.filterOptionsString.split(',').map(opt => opt.trim()).filter(opt => opt);
+ } else {
+ updatedItem.filterOptions = this.gadgetsEditdata.filterOptions || [];
+ }
+ updatedItem.table = this.gadgetsEditdata.table || ''; // API URL
+ updatedItem.connection = this.gadgetsEditdata.connection || undefined; // Connection ID
+
+ // Also preserve these properties in gadgetsEditdata for consistency
+ this.gadgetsEditdata.filterKey = updatedItem.filterKey;
+ this.gadgetsEditdata.filterType = updatedItem.filterType;
+ this.gadgetsEditdata.filterLabel = updatedItem.filterLabel;
+ this.gadgetsEditdata.filterOptions = updatedItem.filterOptions;
+ }
+
console.log('Updated item:', updatedItem);
return updatedItem;
}
@@ -872,6 +1291,9 @@ export class EditnewdashComponent implements OnInit {
// Note: We don't close the modal here, allowing the user to make additional changes
// The user can click "Save" when they're done with all changes
+
+ // Reset the filter service to ensure clean state
+ this.filterService.resetFilters();
}
goBack() {
@@ -1069,46 +1491,239 @@ export class EditnewdashComponent implements OnInit {
// We're now using removeBaseFilter and removeLayerFilter methods instead
}
- // Add method to add a base filter
+ // Add method to handle base filter field change
+ onBaseFilterFieldChange(index: number, field: string) {
+ const filter = this.gadgetsEditdata.baseFilters[index];
+ if (filter) {
+ filter.field = field;
+ // If field changes, reset value and options
+ filter.value = '';
+ filter.options = '';
+ filter.availableValues = '';
+
+ // If we have a field and table URL, load available values
+ if (field && this.gadgetsEditdata.table) {
+ this.loadFilterValuesForField(
+ this.gadgetsEditdata.table,
+ this.gadgetsEditdata.connection,
+ field,
+ index,
+ 'base'
+ );
+ }
+ }
+ }
+
+ // Add method to handle base filter type change
+ onBaseFilterTypeChange(index: number, type: string) {
+ const filter = this.gadgetsEditdata.baseFilters[index];
+ if (filter) {
+ filter.type = type;
+ // If type changes to dropdown/multiselect and we have a field, load available values
+ if ((type === 'dropdown' || type === 'multiselect') && filter.field && this.gadgetsEditdata.table) {
+ this.loadFilterValuesForField(
+ this.gadgetsEditdata.table,
+ this.gadgetsEditdata.connection,
+ filter.field,
+ index,
+ 'base'
+ );
+ }
+ }
+ }
+
+ // Add method to handle drilldown filter field change
+ onDrilldownFilterFieldChange(index: number, field: string) {
+ const filter = this.gadgetsEditdata.drilldownFilters[index];
+ if (filter) {
+ filter.field = field;
+ // If field changes, reset value and options
+ filter.value = '';
+ filter.options = '';
+ filter.availableValues = '';
+
+ // If we have a field and drilldown API URL, load available values
+ if (field && this.gadgetsEditdata.drilldownApiUrl) {
+ this.loadFilterValuesForField(
+ this.gadgetsEditdata.drilldownApiUrl,
+ this.gadgetsEditdata.connection,
+ field,
+ index,
+ 'drilldown'
+ );
+ }
+ }
+ }
+
+ // Add method to handle drilldown filter type change
+ onDrilldownFilterTypeChange(index: number, type: string) {
+ const filter = this.gadgetsEditdata.drilldownFilters[index];
+ if (filter) {
+ filter.type = type;
+ // If type changes to dropdown/multiselect and we have a field, load available values
+ if ((type === 'dropdown' || type === 'multiselect') && filter.field && this.gadgetsEditdata.drilldownApiUrl) {
+ this.loadFilterValuesForField(
+ this.gadgetsEditdata.drilldownApiUrl,
+ this.gadgetsEditdata.connection,
+ filter.field,
+ index,
+ 'drilldown'
+ );
+ }
+ }
+ }
+
+ // Add method to handle layer filter field change
+ onLayerFilterFieldChange(layerIndex: number, filterIndex: number, field: string) {
+ const layer = this.gadgetsEditdata.drilldownLayers[layerIndex];
+ if (layer && layer.filters) {
+ const filter = layer.filters[filterIndex];
+ if (filter) {
+ filter.field = field;
+ // If field changes, reset value and options
+ filter.value = '';
+ filter.options = '';
+ filter.availableValues = '';
+
+ // If we have a field and layer API URL, load available values
+ if (field && layer.apiUrl) {
+ this.loadFilterValuesForField(
+ layer.apiUrl,
+ this.gadgetsEditdata.connection,
+ field,
+ filterIndex,
+ 'layer',
+ layerIndex
+ );
+ }
+ }
+ }
+ }
+
+ // Add method to handle layer filter type change
+ onLayerFilterTypeChange(layerIndex: number, filterIndex: number, type: string) {
+ const layer = this.gadgetsEditdata.drilldownLayers[layerIndex];
+ if (layer && layer.filters) {
+ const filter = layer.filters[filterIndex];
+ if (filter) {
+ filter.type = type;
+ // If type changes to dropdown/multiselect and we have a field, load available values
+ if ((type === 'dropdown' || type === 'multiselect') && filter.field && layer.apiUrl) {
+ this.loadFilterValuesForField(
+ layer.apiUrl,
+ this.gadgetsEditdata.connection,
+ filter.field,
+ filterIndex,
+ 'layer',
+ layerIndex
+ );
+ }
+ }
+ }
+ }
+
+ // Add method to load filter values for a specific field
+ loadFilterValuesForField(
+ apiUrl: string,
+ connectionId: string | undefined,
+ field: string,
+ filterIndex: number,
+ filterType: 'base' | 'drilldown' | 'layer',
+ layerIndex?: number
+ ) {
+ if (apiUrl && field) {
+ const connectionIdNum = connectionId ? parseInt(connectionId, 10) : undefined;
+ this.alertService.getValuesFromUrl(apiUrl, connectionIdNum, field).subscribe(
+ (values: string[]) => {
+ // Update the filter with available values
+ if (filterType === 'base') {
+ const filter = this.gadgetsEditdata.baseFilters[filterIndex];
+ if (filter) {
+ filter.availableValues = values.join(', ');
+ // For dropdown/multiselect types, also update the options
+ if (filter.type === 'dropdown' || filter.type === 'multiselect') {
+ filter.options = filter.availableValues;
+ }
+ }
+ } else if (filterType === 'drilldown') {
+ const filter = this.gadgetsEditdata.drilldownFilters[filterIndex];
+ if (filter) {
+ filter.availableValues = values.join(', ');
+ // For dropdown/multiselect types, also update the options
+ if (filter.type === 'dropdown' || filter.type === 'multiselect') {
+ filter.options = filter.availableValues;
+ }
+ }
+ } else if (filterType === 'layer' && layerIndex !== undefined) {
+ const layer = this.gadgetsEditdata.drilldownLayers[layerIndex];
+ if (layer && layer.filters) {
+ const filter = layer.filters[filterIndex];
+ if (filter) {
+ filter.availableValues = values.join(', ');
+ // For dropdown/multiselect types, also update the options
+ if (filter.type === 'dropdown' || filter.type === 'multiselect') {
+ filter.options = filter.availableValues;
+ }
+ }
+ }
+ }
+ },
+ (error) => {
+ console.error('Error loading available values for field:', field, error);
+ }
+ );
+ }
+ }
+
+ // Add method to add a base filter with default properties
addBaseFilter() {
const newFilter = {
field: '',
- value: ''
+ value: '',
+ type: 'text',
+ options: '',
+ availableValues: ''
};
this.gadgetsEditdata.baseFilters.push(newFilter);
}
+
+ // Add method to add a drilldown filter with default properties
+ addDrilldownFilter() {
+ const newFilter = {
+ field: '',
+ value: '',
+ type: 'text',
+ options: '',
+ availableValues: ''
+ };
+ this.gadgetsEditdata.drilldownFilters.push(newFilter);
+ }
+
+ // Add method to add a layer filter with default properties
+ addLayerFilter(layerIndex: number) {
+ const newFilter = {
+ field: '',
+ value: '',
+ type: 'text',
+ options: '',
+ availableValues: ''
+ };
+ if (!this.gadgetsEditdata.drilldownLayers[layerIndex].filters) {
+ this.gadgetsEditdata.drilldownLayers[layerIndex].filters = [];
+ }
+ this.gadgetsEditdata.drilldownLayers[layerIndex].filters.push(newFilter);
+ }
// Add method to remove a base filter
removeBaseFilter(index: number) {
this.gadgetsEditdata.baseFilters.splice(index, 1);
}
- // Add method to add a drilldown filter
- addDrilldownFilter() {
- const newFilter = {
- field: '',
- value: ''
- };
- this.gadgetsEditdata.drilldownFilters.push(newFilter);
- }
-
// Add method to remove a drilldown filter
removeDrilldownFilter(index: number) {
this.gadgetsEditdata.drilldownFilters.splice(index, 1);
}
- // Add method to add a layer filter
- addLayerFilter(layerIndex: number) {
- const newFilter = {
- field: '',
- value: ''
- };
- if (!this.gadgetsEditdata.drilldownLayers[layerIndex].filters) {
- this.gadgetsEditdata.drilldownLayers[layerIndex].filters = [];
- }
- this.gadgetsEditdata.drilldownLayers[layerIndex].filters.push(newFilter);
- }
-
// Add method to remove a layer filter
removeLayerFilter(layerIndex: number, filterIndex: number) {
this.gadgetsEditdata.drilldownLayers[layerIndex].filters.splice(filterIndex, 1);
@@ -1247,4 +1862,355 @@ export class EditnewdashComponent implements OnInit {
// When disabling, the user can edit the filters normally
}
}
+
+ // Add method to handle item resize events
+ itemResize(item: any, itemComponent: any) {
+ console.log('Item resized:', item);
+ // Trigger a window resize event to notify charts to resize
+ window.dispatchEvent(new Event('resize'));
+
+ // Also try to directly notify the chart component if possible
+ if (itemComponent && itemComponent.item && itemComponent.item.component) {
+ // If the resized item contains a chart, we could try to call its resize method directly
+ // This would require the chart component to have a public resize method
+ }
+ }
+
+ // Add method to load available keys for compact filter
+ loadAvailableKeys(apiUrl: string, connectionId: string | undefined) {
+ if (apiUrl) {
+ const connectionIdNum = connectionId ? parseInt(connectionId, 10) : undefined;
+ this.alertService.getColumnfromurl(apiUrl, connectionIdNum).subscribe(
+ (keys: string[]) => {
+ this.availableKeys = keys;
+ },
+ (error) => {
+ console.error('Error loading available keys:', error);
+ this.availableKeys = [];
+ }
+ );
+ }
+ }
+
+ // Add method to load available values for a specific key
+ loadAvailableValues(key: string) {
+ if (key && this.gadgetsEditdata['table']) {
+ const connectionIdNum = this.gadgetsEditdata['connection'] ?
+ parseInt(this.gadgetsEditdata['connection'], 10) : undefined;
+ this.alertService.getValuesFromUrl(this.gadgetsEditdata['table'], connectionIdNum, key).subscribe(
+ (values: string[]) => {
+ // Update filter options string for dropdown/multiselect
+ if (this.gadgetsEditdata['filterType'] === 'dropdown' ||
+ this.gadgetsEditdata['filterType'] === 'multiselect') {
+ this.filterOptionsString = values.join(', ');
+ // Also update the gadgetsEditdata filterOptions array
+ this.gadgetsEditdata['filterOptions'] = values;
+ }
+ },
+ (error) => {
+ console.error('Error loading available values:', error);
+ }
+ );
+ }
+ }
+
+ // Add method to handle filter key change
+ onFilterKeyChange(key: string) {
+ this.gadgetsEditdata['filterKey'] = key;
+ // Load available values when filter key changes
+ if (key && (this.gadgetsEditdata['filterType'] === 'dropdown' ||
+ this.gadgetsEditdata['filterType'] === 'multiselect')) {
+ this.loadAvailableValues(key);
+ }
+ }
+
+ // Add method to handle filter type change
+ onFilterTypeChange(type: string) {
+ this.gadgetsEditdata['filterType'] = type;
+ // Load available values when filter type changes to dropdown or multiselect
+ if ((type === 'dropdown' || type === 'multiselect') && this.gadgetsEditdata['filterKey']) {
+ this.loadAvailableValues(this.gadgetsEditdata['filterKey']);
+ }
+ }
+
+ // Add method to handle API URL change for compact filter
+ onCompactFilterApiUrlChange(url: string) {
+ this.gadgetsEditdata['table'] = url;
+ // Load available keys when API URL changes
+ if (url) {
+ this.loadAvailableKeys(url, this.gadgetsEditdata['connection']);
+ }
+ }
+
+ // Add method to handle connection change for compact filter
+ onCompactFilterConnectionChange(connectionId: string) {
+ this.gadgetsEditdata['connection'] = connectionId;
+ // Reload available keys when connection changes
+ if (this.gadgetsEditdata['table']) {
+ this.loadAvailableKeys(this.gadgetsEditdata['table'], connectionId);
+ }
+ }
+
+ // Add method to apply dynamic template to a chart
+ applyDynamicTemplate(chartItem: any, template: any) {
+ console.log('Applying dynamic template to chart:', chartItem, template);
+
+ // Apply HTML template
+ if (template.templateHtml) {
+ // In a real implementation, you would dynamically render the HTML template
+ // For now, we'll just log it
+ console.log('HTML Template:', template.templateHtml);
+ }
+
+ // Apply CSS styles
+ if (template.templateCss) {
+ // In a real implementation, you would dynamically apply the CSS styles
+ // For now, we'll just log it
+ console.log('CSS Template:', template.templateCss);
+ }
+
+ // Return the chart item with template applied
+ return {
+ ...chartItem,
+ template: template
+ };
+ }
+
+ // Add method to test dynamic chart creation
+ testDynamicChartCreation() {
+ console.log('Testing dynamic chart creation');
+
+ // Show a success message to the user
+ alert('Dynamic chart test started. Check the browser console for detailed output.');
+
+ // Load all chart types
+ this.dynamicChartLoader.loadAllChartConfigurations().subscribe({
+ next: (chartTypes) => {
+ console.log('Loaded chart types:', chartTypes);
+
+ // Find bar chart type
+ const barChartType = chartTypes.find((ct: any) => ct.name === 'bar');
+ if (barChartType) {
+ console.log('Found bar chart type:', barChartType);
+
+ // Load configuration for bar chart
+ this.dynamicChartLoader.loadChartConfiguration(barChartType.id).subscribe({
+ next: (config) => {
+ console.log('Loaded bar chart configuration:', config);
+
+ // Create a test chart item
+ const chartItem = {
+ cols: 5,
+ rows: 6,
+ x: 0,
+ y: 0,
+ chartid: 100,
+ component: UnifiedChartComponent,
+ name: 'Test Dynamic Bar Chart',
+ chartType: 'bar',
+ xAxis: '',
+ yAxis: '',
+ table: '',
+ connection: undefined,
+ // Add dynamic fields from configuration
+ dynamicFields: config.dynamicFields || []
+ };
+
+ console.log('Created test chart item:', chartItem);
+
+ // If we have templates, apply the default one
+ if (config.templates && config.templates.length > 0) {
+ const defaultTemplate = config.templates.find((t: any) => t.isDefault) || config.templates[0];
+ if (defaultTemplate) {
+ console.log('Applying default template:', defaultTemplate);
+ const chartWithTemplate = this.applyDynamicTemplate(chartItem, defaultTemplate);
+ console.log('Chart with template:', chartWithTemplate);
+
+ // Show success message
+ alert('Dynamic chart test completed successfully! Check console for details.');
+ }
+ } else {
+ // Show success message even without templates
+ alert('Dynamic chart test completed successfully! No templates found. Check console for details.');
+ }
+ },
+ error: (error) => {
+ console.error('Error loading bar chart configuration:', error);
+ alert('Error loading bar chart configuration. Check console for details.');
+ }
+ });
+ } else {
+ console.warn('Bar chart type not found');
+ alert('Bar chart type not found in the database.');
+ }
+ },
+ error: (error) => {
+ console.error('Error loading chart types:', error);
+ alert('Error loading chart types. Check console for details.');
+ }
+ });
+ }
+
+ // Add method to load dynamic chart configuration
+ loadDynamicChartConfiguration(chartTypeId: number) {
+ console.log(`Loading dynamic chart configuration for chart type ${chartTypeId}`);
+ this.dynamicChartLoader.loadChartConfiguration(chartTypeId).subscribe({
+ next: (config) => {
+ console.log('Loaded dynamic chart configuration:', config);
+ // Here you would apply the configuration to the UI
+ // For example, populate form fields, set up templates, etc.
+ },
+ error: (error) => {
+ console.error('Error loading dynamic chart configuration:', error);
+ }
+ });
+ }
+
+
+
+ // Add method to create a dynamic chart with configuration from database
+ createDynamicChart = (chartTypeName: string, maxChartId: number) => {
+ console.log(`Creating dynamic chart of type: ${chartTypeName}`);
+
+ // First, get the chart type by name
+ this.dynamicChartLoader.getChartTypeByName(chartTypeName).subscribe({
+ next: (chartType) => {
+ if (chartType) {
+ console.log(`Found chart type:`, chartType);
+
+ // Load the complete configuration for this chart type
+ this.dynamicChartLoader.loadChartConfiguration(chartType.id).subscribe({
+ next: (config) => {
+ console.log(`Loaded configuration for ${chartTypeName}:`, config);
+
+ // Create the chart item with dynamic configuration
+ const chartItem = {
+ cols: 5,
+ rows: 6,
+ x: 0,
+ y: 0,
+ chartid: maxChartId + 1,
+ component: UnifiedChartComponent,
+ name: chartType.displayName || chartTypeName,
+ chartType: chartType.name,
+ xAxis: '',
+ yAxis: '',
+ table: '',
+ connection: undefined,
+ // Add any dynamic fields from the configuration
+ dynamicFields: config.dynamicFields || []
+ };
+
+ // Add UI components as configuration properties
+ if (config.uiComponents && config.uiComponents.length > 0) {
+ config.uiComponents.forEach(component => {
+ chartItem[component.componentName] = '';
+ });
+ }
+
+ this.dashboardArray.push(chartItem);
+ console.log(`Created dynamic chart:`, chartItem);
+ },
+ error: (error) => {
+ console.error(`Error loading configuration for ${chartTypeName}:`, error);
+ // Fallback to default configuration
+ this.createDefaultChart(chartTypeName, this.getChartDisplayName(chartTypeName));
+ }
+ });
+ } else {
+ console.warn(`Chart type ${chartTypeName} not found, using default configuration`);
+ this.createDefaultChart(chartTypeName, this.getChartDisplayName(chartTypeName));
+ }
+ },
+ error: (error) => {
+ console.error('Error loading configuration for chart type:', error);
+ // Fallback to default configuration
+ this.createDefaultChart(chartTypeName, this.getChartDisplayName(chartTypeName));
+ }
+ });
+ }
+
+ // Fallback method to create default chart configuration
+ createDefaultChart = (chartTypeName: string, chartDisplayName: string) => {
+ console.log(`Creating default chart for ${chartTypeName}`);
+
+ // Map chart type names to chart types - making it fully dynamic
+ const chartTypeMap = {
+ 'bar': 'bar',
+ 'line': 'line',
+ 'pie': 'pie',
+ 'doughnut': 'doughnut',
+ 'radar': 'radar',
+ 'polar': 'polar',
+ 'bubble': 'bubble',
+ 'scatter': 'scatter'
+ // Removed hardcoded heatmap entry to make it fully dynamic
+ };
+
+ // Get the chart type from the name - default to bubble for unknown chart types
+ const chartType = chartTypeMap[chartTypeName.toLowerCase()] || 'bubble';
+
+ // Safely calculate maxChartId, handling cases where chartid might be NaN or missing
+ let maxChartId = 0;
+ if (this.dashboardArray && this.dashboardArray.length > 0) {
+ const validChartIds = this.dashboardArray
+ .map(item => item.chartid)
+ .filter(chartid => typeof chartid === 'number' && !isNaN(chartid));
+
+ if (validChartIds.length > 0) {
+ maxChartId = Math.max(...validChartIds);
+ }
+ }
+
+ const chartItem = {
+ cols: 5,
+ rows: 6,
+ x: 0,
+ y: 0,
+ chartid: maxChartId + 1,
+ component: UnifiedChartComponent,
+ name: chartDisplayName,
+ chartType: chartType,
+ xAxis: '',
+ yAxis: '',
+ table: '',
+ connection: undefined
+ };
+
+ this.dashboardArray.push(chartItem);
+ console.log('Created default chart:', chartItem);
+
+ // Update the dashboard collection
+ this.dashboardCollection.dashboard = this.dashboardArray.slice();
+
+ // Force gridster to refresh
+ if (this.options && this.options.api) {
+ this.options.api.optionsChanged();
+ }
+ }
+
+ // Helper method to get display name for chart type - making it fully dynamic
+ getChartDisplayName = (chartTypeName: string): string => {
+ const displayNameMap = {
+ 'bar': 'Bar Chart',
+ 'line': 'Line Chart',
+ 'pie': 'Pie Chart',
+ 'doughnut': 'Doughnut Chart',
+ 'radar': 'Radar Chart',
+ 'polar': 'Polar Area Chart',
+ 'bubble': 'Bubble Chart',
+ 'scatter': 'Scatter Chart'
+ // Removed hardcoded heatmap entry to make it fully dynamic
+ };
+
+ // For unknown chart types, create a display name by capitalizing the first letter and adding ' Chart'
+ const displayName = displayNameMap[chartTypeName.toLowerCase()];
+ if (displayName) {
+ return displayName;
+ } else {
+ // Capitalize first letter and add ' Chart'
+ return chartTypeName.charAt(0).toUpperCase() + chartTypeName.slice(1) + ' Chart';
+ }
+ }
+
}
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart.zip b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart.zip
new file mode 100644
index 0000000..bf020b9
Binary files /dev/null and b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart.zip differ
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html
index e1b27cf..4c1ba64 100644
--- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html
+++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html
@@ -1,31 +1,334 @@
-
-
-
-
-
-
0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
-
Drilldown Level: {{currentDrilldownLevel}}
-
- Back to Level {{currentDrilldownLevel - 1}}
-
-
- Back to Main View
-
+
+
+
+
+
0">
+
Base Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0 && currentDrilldownLevel > 0">
+
Drilldown Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Layer Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clear All Filters
+
-
-
- No data available
+
+
-
-
-
+
+
+
+
+ No data available
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss
index e69de29..c70fd33 100644
--- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss
+++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss
@@ -0,0 +1,278 @@
+// Chart container structure
+.chart-container {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ // Filter section styling
+ .filter-section {
+ margin-bottom: 20px;
+ padding: 15px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ background-color: #f9f9f9;
+ }
+
+ .filter-group {
+ margin-bottom: 15px;
+
+ h4 {
+ margin-top: 0;
+ margin-bottom: 10px;
+ color: #333;
+ font-weight: 600;
+ }
+ }
+
+ .filter-controls {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ }
+
+ .filter-item {
+ flex: 1 1 300px;
+ min-width: 250px;
+ padding: 10px;
+ background: white;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ }
+
+ .filter-label {
+ font-weight: 500;
+ margin-bottom: 8px;
+ color: #555;
+ font-size: 14px;
+ }
+
+ .filter-input {
+ width: 100%;
+
+ .filter-text-input,
+ .filter-select,
+ .filter-date {
+ width: 100%;
+ padding: 6px 12px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 14px;
+ }
+
+ .filter-select {
+ height: 34px;
+ }
+ }
+
+ .multiselect-container {
+ position: relative;
+ }
+
+ .multiselect-display {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 6px 12px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background: white;
+ cursor: pointer;
+ min-height: 34px;
+
+ .multiselect-label {
+ flex: 1;
+ font-size: 14px;
+ }
+
+ .multiselect-value {
+ color: #666;
+ font-size: 12px;
+ margin-right: 8px;
+ }
+
+ .dropdown-icon {
+ flex-shrink: 0;
+ transition: transform 0.2s ease;
+ }
+
+ &:hover {
+ border-color: #999;
+ }
+ }
+
+ .multiselect-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ background: white;
+ border: 1px solid #ccc;
+ border-top: none;
+ border-radius: 0 0 4px 4px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ max-height: 200px;
+ overflow-y: auto;
+
+ .checkbox-group {
+ padding: 8px;
+
+ .checkbox-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 0;
+
+ .checkbox-label {
+ margin: 0;
+ font-size: 14px;
+ cursor: pointer;
+ }
+ }
+ }
+ }
+
+ .date-range {
+ .date-input-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .date-separator {
+ margin: 0 5px;
+ color: #777;
+ }
+ }
+
+ .toggle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .toggle-label {
+ margin: 0;
+ font-size: 14px;
+ cursor: pointer;
+ }
+ }
+
+ .filter-actions {
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid #eee;
+
+ .btn {
+ font-size: 13px;
+ }
+ }
+
+ // Chart header styling
+ .chart-header {
+ margin-bottom: 20px;
+
+ .header-row {
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #eee;
+
+ .chart-title {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ }
+ }
+ }
+
+ // Chart wrapper and content
+ .chart-wrapper {
+ flex: 1;
+ position: relative;
+
+ .chart-content {
+ position: relative;
+ height: 100%;
+ min-height: 300px; // Ensure minimum height for chart
+
+ &.loading {
+ opacity: 0.7;
+
+ .chart-display {
+ filter: blur(2px);
+ }
+ }
+
+ .no-data-message {
+ text-align: center;
+ padding: 20px;
+ color: #666;
+ font-style: italic;
+ }
+
+ .chart-display {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ transition: filter 0.3s ease;
+ }
+
+ .loading-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255, 255, 255, 0.8);
+
+ .shimmer-bar {
+ width: 80%;
+ height: 20px;
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+ border-radius: 4px;
+ }
+ }
+ }
+ }
+}
+
+@keyframes shimmer {
+ 0% {
+ background-position: -200% 0;
+ }
+ 100% {
+ background-position: 200% 0;
+ }
+}
+
+// Responsive design
+@media (max-width: 768px) {
+ .chart-container {
+ .filter-controls {
+ flex-direction: column;
+ }
+
+ .filter-item {
+ min-width: 100%;
+ }
+
+ .chart-header {
+ .header-row {
+ .chart-title {
+ font-size: 16px;
+ }
+ }
+ }
+
+ .chart-content {
+ min-height: 250px; // Adjust for mobile
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts
index ee9938d..292f9ea 100644
--- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts
+++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts
@@ -1,6 +1,9 @@
-import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
+import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges, ViewChild } from '@angular/core';
import { Dashboard3Service } from '../../../../../../services/builder/dashboard3.service';
import { Subscription } from 'rxjs';
+import { FilterService } from '../../common-filter/filter.service';
+// Add BaseChartDirective import for chart resizing
+import { BaseChartDirective } from 'ng2-charts';
@Component({
selector: 'app-bar-chart',
@@ -34,6 +37,9 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
// Multi-layer drilldown configuration inputs
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
+ // Add ViewChild to access the chart directive
+ @ViewChild(BaseChartDirective) chart?: BaseChartDirective;
+
barChartLabels: string[] = ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes'];
barChartType: string = 'bar';
barChartPlugins = [];
@@ -42,6 +48,58 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
];
barChartLegend: boolean = true;
+ // Add responsive chart options
+ barChartOptions: any = {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ ticks: {
+ autoSkip: false,
+ maxRotation: 45,
+ minRotation: 45,
+ padding: 15,
+ font: {
+ size: 12
+ }
+ },
+ grid: {
+ display: false
+ }
+ },
+ y: {
+ beginAtZero: true,
+ ticks: {
+ font: {
+ size: 12
+ }
+ }
+ }
+ },
+ plugins: {
+ legend: {
+ display: true,
+ position: 'top',
+ labels: {
+ font: {
+ size: 12
+ }
+ }
+ },
+ tooltip: {
+ enabled: true
+ }
+ },
+ layout: {
+ padding: {
+ bottom: 60,
+ left: 15,
+ right: 15,
+ top: 15
+ }
+ }
+ };
+
// Multi-layer drilldown state tracking
drilldownStack: any[] = []; // Stack to track drilldown navigation history
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
@@ -51,15 +109,34 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
// No data state
noDataAvailable: boolean = false;
+ // Loading state
+ isLoading: boolean = false;
+
// Flag to prevent infinite loops
private isFetchingData: boolean = false;
// Subscriptions to unsubscribe on destroy
private subscriptions: Subscription[] = [];
- constructor(private dashboardService: Dashboard3Service) { }
+ // Add properties for filter functionality
+ private openMultiselects: Map
= new Map(); // Map of filterId -> context
+ private documentClickHandler: ((event: MouseEvent) => void) | null = null;
+ private filtersInitialized: boolean = false;
+
+ constructor(
+ private dashboardService: Dashboard3Service,
+ private filterService: FilterService
+ ) { }
ngOnInit(): void {
+ // Subscribe to filter changes
+ this.subscriptions.push(
+ this.filterService.filterState$.subscribe(filters => {
+ // When filters change, refresh the chart data
+ this.fetchChartData();
+ })
+ );
+
// Initialize with default data
this.fetchChartData();
}
@@ -67,6 +144,12 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
ngOnChanges(changes: SimpleChanges): void {
console.log('BarChartComponent input changes:', changes);
+ // 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 xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
@@ -92,28 +175,348 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
// Update legend visibility if it changed
if (changes.chartlegend !== undefined) {
this.barChartLegend = changes.chartlegend.currentValue;
+ this.barChartOptions.plugins.legend.display = this.barChartLegend;
console.log('Chart legend changed to:', this.barChartLegend);
}
}
+ // Initialize filter values with proper default values based on type
+ private initializeFilterValues(): void {
+ console.log('Initializing filter values');
+
+ // Initialize base filters
+ if (this.baseFilters) {
+ this.baseFilters.forEach(filter => {
+ if (filter.value === undefined || filter.value === null) {
+ switch (filter.type) {
+ case 'multiselect':
+ filter.value = [];
+ break;
+ case 'date-range':
+ filter.value = { start: null, end: null };
+ break;
+ case 'toggle':
+ filter.value = false;
+ break;
+ default:
+ filter.value = '';
+ }
+ }
+ });
+ }
+
+ // Initialize drilldown filters
+ if (this.drilldownFilters) {
+ this.drilldownFilters.forEach(filter => {
+ if (filter.value === undefined || filter.value === null) {
+ switch (filter.type) {
+ case 'multiselect':
+ filter.value = [];
+ break;
+ case 'date-range':
+ filter.value = { start: null, end: null };
+ break;
+ case 'toggle':
+ filter.value = false;
+ break;
+ default:
+ filter.value = '';
+ }
+ }
+ });
+ }
+
+ // Initialize layer filters
+ if (this.drilldownLayers) {
+ this.drilldownLayers.forEach(layer => {
+ if (layer.filters) {
+ layer.filters.forEach((filter: any) => {
+ if (filter.value === undefined || filter.value === null) {
+ switch (filter.type) {
+ case 'multiselect':
+ filter.value = [];
+ break;
+ case 'date-range':
+ filter.value = { start: null, end: null };
+ break;
+ case 'toggle':
+ filter.value = false;
+ break;
+ default:
+ filter.value = '';
+ }
+ }
+ });
+ }
+ });
+ }
+
+ console.log('Filter values initialized:', {
+ baseFilters: this.baseFilters,
+ drilldownFilters: this.drilldownFilters,
+ drilldownLayers: this.drilldownLayers
+ });
+ }
+
+ // Check if there are active filters
+ hasActiveFilters(): boolean {
+ 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 false;
+ }
+
+ // Get active layer filters for current drilldown level
+ 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) {
+ return this.drilldownLayers[layerIndex].filters;
+ }
+ }
+ return [];
+ }
+
+ // Get filter options for dropdown/multiselect filters
+ getFilterOptions(filter: any): string[] {
+ if (filter.options) {
+ if (Array.isArray(filter.options)) {
+ return filter.options;
+ } else if (typeof filter.options === 'string') {
+ return filter.options.split(',').map(opt => opt.trim()).filter(opt => opt);
+ }
+ }
+ return [];
+ }
+
+ // Check if an option is selected for multiselect filters
+ isOptionSelected(filter: any, option: string): boolean {
+ if (!filter.value) {
+ return false;
+ }
+
+ if (Array.isArray(filter.value)) {
+ return filter.value.includes(option);
+ }
+
+ return filter.value === option;
+ }
+
+ // Handle base filter changes
+ onBaseFilterChange(filter: any): void {
+ console.log('Base filter changed:', filter);
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle drilldown filter changes
+ onDrilldownFilterChange(filter: any): void {
+ console.log('Drilldown filter changed:', filter);
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle layer filter changes
+ onLayerFilterChange(filter: any): void {
+ console.log('Layer filter changed:', filter);
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // 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)) {
+ filter.value.push(option);
+ }
+ } else {
+ // Remove option from array
+ filter.value = filter.value.filter((item: string) => item !== option);
+ }
+
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle date range changes
+ onDateRangeChange(filter: any, dateRange: { start: string | null, end: string | null }): void {
+ filter.value = dateRange;
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle toggle changes
+ onToggleChange(filter: any, checked: boolean): void {
+ filter.value = checked;
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Toggle multiselect dropdown visibility
+ toggleMultiselect(filter: any, context: string): void {
+ const filterId = `${context}-${filter.field}`;
+ if (this.isMultiselectOpen(filter, context)) {
+ this.openMultiselects.delete(filterId);
+ } else {
+ // 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();
+ }
+ }
+
+ // Add document click handler to close dropdowns when clicking outside
+ private addDocumentClickHandler(): void {
+ if (!this.documentClickHandler) {
+ this.documentClickHandler = (event: MouseEvent) => {
+ const target = event.target as HTMLElement;
+ // Check if click is outside any multiselect dropdown
+ if (!target.closest('.multiselect-container')) {
+ this.openMultiselects.clear();
+ this.removeDocumentClickHandler();
+ }
+ };
+
+ // Use setTimeout to ensure the click event that opened the dropdown doesn't immediately close it
+ setTimeout(() => {
+ document.addEventListener('click', this.documentClickHandler!);
+ }, 0);
+ }
+ }
+
+ // Remove document click handler
+ private removeDocumentClickHandler(): void {
+ if (this.documentClickHandler) {
+ document.removeEventListener('click', this.documentClickHandler);
+ this.documentClickHandler = null;
+ }
+ }
+
+ // Check if multiselect dropdown is open
+ isMultiselectOpen(filter: any, context: string): boolean {
+ const filterId = `${context}-${filter.field}`;
+ return this.openMultiselects.has(filterId);
+ }
+
+ // Get count of selected options for a multiselect filter
+ getSelectedOptionsCount(filter: any): number {
+ if (!filter.value) {
+ return 0;
+ }
+
+ if (Array.isArray(filter.value)) {
+ return filter.value.length;
+ }
+
+ return 0;
+ }
+
+ // Clear all filters
+ clearAllFilters(): void {
+ // Clear base filters
+ if (this.baseFilters) {
+ this.baseFilters.forEach(filter => {
+ if (filter.type === 'multiselect') {
+ filter.value = [];
+ } else if (filter.type === 'date-range') {
+ filter.value = { start: null, end: null };
+ } else if (filter.type === 'toggle') {
+ filter.value = false;
+ } else {
+ filter.value = '';
+ }
+ });
+ }
+
+ // Clear drilldown filters
+ if (this.drilldownFilters) {
+ this.drilldownFilters.forEach(filter => {
+ if (filter.type === 'multiselect') {
+ filter.value = [];
+ } else if (filter.type === 'date-range') {
+ filter.value = { start: null, end: null };
+ } else if (filter.type === 'toggle') {
+ filter.value = false;
+ } else {
+ filter.value = '';
+ }
+ });
+ }
+
+ // Clear layer filters
+ if (this.drilldownLayers) {
+ this.drilldownLayers.forEach(layer => {
+ if (layer.filters) {
+ layer.filters.forEach((filter: any) => {
+ if (filter.type === 'multiselect') {
+ filter.value = [];
+ } else if (filter.type === 'date-range') {
+ filter.value = { start: null, end: null };
+ } else if (filter.type === 'toggle') {
+ filter.value = false;
+ } else {
+ filter.value = '';
+ }
+ });
+ }
+ });
+ }
+
+ // Close all multiselect dropdowns
+ this.openMultiselects.clear();
+
+ // Refresh data
+ this.fetchChartData();
+ }
+
fetchChartData(): void {
+ // Set loading state
+ this.isLoading = true;
+
// Set flag to prevent recursive calls
this.isFetchingData = true;
// If we're in drilldown mode, fetch the appropriate drilldown data
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
this.fetchDrilldownData();
- // Reset flag after fetching
+ // Reset flags after fetching
this.isFetchingData = false;
+ this.isLoading = false;
return;
}
// If we have the necessary data, fetch chart data from the service
if (this.table && this.xAxis && this.yAxis) {
- console.log('Fetching bar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
+ console.log('=== BAR CHART DEBUG INFO ===');
+ console.log('Table:', this.table);
+ console.log('X-Axis:', this.xAxis);
+ console.log('Y-Axis:', this.yAxis);
+ console.log('Connection:', this.connection);
// Convert yAxis to string if it's an array
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
+ console.log('Y-Axis String:', yAxisString);
// Get the parameter value from the drilldown stack for base level (should be empty)
let parameterValue = '';
@@ -123,31 +526,55 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
console.log('Bar chart data URL:', url);
// Convert baseFilters to filter parameters
- let filterParams = '';
+ const filterObj = {};
+
+ // Add base filters
if (this.baseFilters && this.baseFilters.length > 0) {
- const filterObj = {};
this.baseFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
- if (Object.keys(filterObj).length > 0) {
- filterParams = JSON.stringify(filterObj);
- }
}
- console.log('Base filter parameters:', filterParams);
+
+ // Add common filters
+ const commonFilters = this.filterService.getFilterValues();
+ const filterDefinitions = this.filterService.getFilters();
+ 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 !== '') {
+ filterObj[fieldName] = filterValue;
+ }
+ }
+ });
+
+ // Convert to JSON string for API call
+ let filterParams = '';
+ if (Object.keys(filterObj).length > 0) {
+ filterParams = JSON.stringify(filterObj);
+ }
+
+ console.log('Final filter object:', filterObj);
// Fetch data from the dashboard service with parameter field and value
// For base level, we pass empty parameter and value, but now also pass filters
const subscription = this.dashboardService.getChartData(this.table, 'bar', this.xAxis, yAxisString, this.connection, '', '', filterParams).subscribe(
(data: any) => {
+ console.log('=== BAR CHART DATA RESPONSE ===');
console.log('Received bar chart data:', data);
if (data === null) {
console.warn('Bar chart API returned null data. Check if the API endpoint is working correctly.');
this.noDataAvailable = true;
this.barChartLabels = [];
this.barChartData = [];
- // Reset flag after fetching
+ // Reset flags after fetching
this.isFetchingData = false;
+ this.isLoading = false;
return;
}
@@ -160,6 +587,7 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
// Trigger change detection
// this.barChartData = [...this.barChartData];
console.log('Updated bar chart with data:', { labels: this.barChartLabels, data: this.barChartData });
+ console.log('=== CHART UPDATED SUCCESSFULLY ===');
} else if (data && data.labels && data.datasets) {
// Backend has already filtered the data, just display it
this.noDataAvailable = data.labels.length === 0;
@@ -168,22 +596,26 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
// Trigger change detection
// this.barChartData = [...this.barChartData];
console.log('Updated bar chart with legacy data format:', { labels: this.barChartLabels, data: this.barChartData });
+ console.log('=== CHART UPDATED SUCCESSFULLY (LEGACY) ===');
} else {
console.warn('Bar chart received data does not have expected structure', data);
this.noDataAvailable = true;
this.barChartLabels = [];
this.barChartData = [];
}
- // Reset flag after fetching
+ // Reset flags after fetching
this.isFetchingData = false;
+ this.isLoading = false;
},
(error) => {
+ console.error('=== BAR CHART ERROR ===');
console.error('Error fetching bar chart data:', error);
this.noDataAvailable = true;
this.barChartLabels = [];
this.barChartData = [];
- // Reset flag after fetching
+ // Reset flags after fetching
this.isFetchingData = false;
+ this.isLoading = false;
// Keep default data in case of error
}
);
@@ -195,8 +627,9 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
this.noDataAvailable = true;
this.barChartLabels = [];
this.barChartData = [];
- // Reset flag after fetching
+ // Reset flags after fetching
this.isFetchingData = false;
+ this.isLoading = false;
}
}
@@ -286,33 +719,49 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
console.log('Drilldown data URL:', url);
// Convert drilldown layer filters to filter parameters (if applicable)
- let filterParams = '';
+ const filterObj = {};
+
+ // Add drilldown layer filters
if (drilldownConfig.filters && drilldownConfig.filters.length > 0) {
- const filterObj = {};
drilldownConfig.filters.forEach((filter: any) => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
- if (Object.keys(filterObj).length > 0) {
- filterParams = JSON.stringify(filterObj);
- }
}
- console.log('Drilldown layer filter parameters:', filterParams);
- // Convert drilldownFilters to filter parameters for drilldown level
- let drilldownFilterParams = '';
+ // Add drilldownFilters
if (this.drilldownFilters && this.drilldownFilters.length > 0) {
- const filterObj = {};
this.drilldownFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
- if (Object.keys(filterObj).length > 0) {
- drilldownFilterParams = JSON.stringify(filterObj);
- }
}
+
+ // Add common filters
+ const commonFilters = this.filterService.getFilterValues();
+ const filterDefinitions = this.filterService.getFilters();
+ 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 !== '') {
+ filterObj[fieldName] = filterValue;
+ }
+ }
+ });
+
+ // 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);
// For drilldown level, we pass the parameter value from the drilldown stack and drilldown filters
@@ -342,17 +791,23 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
// Trigger change detection
// this.barChartData = [...this.barChartData];
console.log('Updated bar chart with drilldown data:', { labels: this.barChartLabels, data: this.barChartData });
+ // Set loading state to false
+ this.isLoading = false;
} else if (data && data.labels && data.datasets) {
// Backend has already filtered the data, just display it
this.noDataAvailable = data.labels.length === 0;
this.barChartLabels = data.labels;
this.barChartData = data.datasets;
console.log('Updated bar chart with drilldown legacy data format:', { labels: this.barChartLabels, data: this.barChartData });
+ // Set loading state to false
+ this.isLoading = false;
} else {
console.warn('Drilldown received data does not have expected structure', data);
this.noDataAvailable = true;
this.barChartLabels = [];
this.barChartData = [];
+ // Set loading state to false
+ this.isLoading = false;
}
},
(error) => {
@@ -360,12 +815,17 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
this.noDataAvailable = true;
this.barChartLabels = [];
this.barChartData = [];
+ // Set loading state to false
+ this.isLoading = false;
// Keep current data in case of error
}
);
// Add subscription to array for cleanup
this.subscriptions.push(subscription);
+
+ // Set loading state
+ this.isLoading = true;
}
// Reset to original data (go back to base level)
@@ -426,6 +886,18 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
}
}
+ // Public method to refresh data when filters change
+ refreshData(): void {
+ this.fetchChartData();
+ }
+
+ // Method to handle window resize events
+ onResize(): void {
+ if (this.chart) {
+ this.chart.chart?.resize();
+ }
+ }
+
// Ensure labels and data arrays have the same length
private syncLabelAndDataArrays(): void {
// For bar charts, we need to ensure all datasets have the same number of data points
@@ -557,6 +1029,12 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
this.originalBarChartLabels = [];
this.originalBarChartData = [];
+ // Clear multiselect tracking
+ this.openMultiselects.clear();
+
+ // Remove document click handler
+ this.removeDocumentClickHandler();
+
console.log('BarChartComponent destroyed and cleaned up');
}
}
\ 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.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html
index 874bbed..e2c52aa 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
@@ -1,28 +1,310 @@
-
-
-
0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
-
Drilldown Level: {{currentDrilldownLevel}}
-
- Back to Level {{currentDrilldownLevel - 1}}
-
-
- Back to Main View
-
+
+
+
+
+
0">
+
Base Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0 && currentDrilldownLevel > 0">
+
Drilldown Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Layer Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clear All Filters
+
-
-
- No data available
+
+
-
-
+
+
0">
+
+
+
+
+
+
+
+
+ Drilldown Level: {{currentDrilldownLevel}}
+ 0">
+ (Clicked on: {{drilldownStack[drilldownStack.length - 1].clickedLabel}})
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading data...
+
+
+
+
+ No data available
+
+
+
\ 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.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.scss
index e69de29..a9282e4 100644
--- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.scss
+++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.scss
@@ -0,0 +1,192 @@
+.filter-section {
+ margin-bottom: 20px;
+ padding: 15px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ background-color: #f9f9f9;
+}
+
+.filter-group {
+ margin-bottom: 15px;
+
+ h4 {
+ margin-top: 0;
+ margin-bottom: 10px;
+ color: #333;
+ font-weight: 600;
+ }
+}
+
+.filter-controls {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+}
+
+.filter-item {
+ flex: 1 1 300px;
+ min-width: 250px;
+ padding: 10px;
+ background: white;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+}
+
+.filter-label {
+ font-weight: 500;
+ margin-bottom: 8px;
+ color: #555;
+ font-size: 14px;
+}
+
+.filter-input {
+ width: 100%;
+
+ .filter-text-input,
+ .filter-select,
+ .filter-date {
+ width: 100%;
+ padding: 6px 12px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 14px;
+ }
+
+ .filter-select {
+ height: 34px;
+ }
+}
+
+.multiselect-container {
+ position: relative;
+}
+
+.multiselect-display {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 6px 12px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background: white;
+ cursor: pointer;
+ min-height: 34px;
+
+ .multiselect-label {
+ flex: 1;
+ font-size: 14px;
+ }
+
+ .multiselect-value {
+ color: #666;
+ font-size: 12px;
+ margin-right: 8px;
+ }
+
+ .dropdown-icon {
+ flex-shrink: 0;
+ transition: transform 0.2s ease;
+ }
+
+ &:hover {
+ border-color: #999;
+ }
+}
+
+.multiselect-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ background: white;
+ border: 1px solid #ccc;
+ border-top: none;
+ border-radius: 0 0 4px 4px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ max-height: 200px;
+ overflow-y: auto;
+
+ .checkbox-group {
+ padding: 8px;
+
+ .checkbox-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 0;
+
+ .checkbox-label {
+ margin: 0;
+ font-size: 14px;
+ cursor: pointer;
+ }
+ }
+ }
+}
+
+.date-range {
+ .date-input-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .date-separator {
+ margin: 0 5px;
+ color: #777;
+ }
+}
+
+.toggle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .toggle-label {
+ margin: 0;
+ font-size: 14px;
+ cursor: pointer;
+ }
+}
+
+.filter-actions {
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid #eee;
+
+ .btn {
+ font-size: 13px;
+ }
+}
+
+// New header row styling
+.header-row {
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #eee;
+
+ .chart-title {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ }
+}
+
+// Responsive design
+@media (max-width: 768px) {
+ .filter-controls {
+ flex-direction: column;
+ }
+
+ .filter-item {
+ min-width: 100%;
+ }
+
+ .header-row {
+ .chart-title {
+ font-size: 16px;
+ }
+ }
+}
\ 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 d23e208..bd9d0fc 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
@@ -1,6 +1,8 @@
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChartConfiguration, ChartDataset, ChartOptions } from 'chart.js';
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
+import { FilterService } from '../../common-filter/filter.service';
+import { Subscription } from 'rxjs';
@Component({
selector: 'app-bubble-chart',
@@ -35,55 +37,77 @@ 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'
+ },
+ ticks: {
+ autoSkip: false,
+ maxRotation: 45,
+ minRotation: 45
+ }
+ },
+ y: {
+ beginAtZero: true,
+ title: {
+ display: true,
+ text: 'Y Axis'
+ }
+ }
+ },
+ plugins: {
+ legend: {
+ display: true,
+ position: 'top',
+ },
+ tooltip: {
+ enabled: true,
+ mode: 'point',
+ intersect: false,
+ callbacks: {
+ label: function(context: any) {
+ const point: any = context.raw;
+ if (point && point.hasOwnProperty('y') && point.hasOwnProperty('r')) {
+ const yValue = parseFloat(point.y);
+ const rValue = parseFloat(point.r);
+ if (!isNaN(yValue) && !isNaN(rValue)) {
+ return `Value: ${yValue.toFixed(2)}, Size: ${rValue.toFixed(1)}`;
+ }
+ }
+ return context.dataset.label || '';
+ }
+ }
+ }
+ },
+ animation: {
+ duration: 800,
+ easing: 'easeInOutQuart'
+ },
+ // Enable individual point styling
+ elements: {
+ point: {
+ hoverRadius: 12,
+ hoverBorderWidth: 3
+ }
+ },
+ // Add padding to ensure x-axis labels are visible
+ layout: {
+ padding: {
+ left: 10,
+ right: 10,
+ top: 10,
+ bottom: 30
+ }
+ }
};
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
@@ -92,19 +116,45 @@ 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;
+
+ // Subscriptions to unsubscribe on destroy
+ private subscriptions: Subscription[] = [];
- constructor(private dashboardService: Dashboard3Service) { }
+ // Add properties for filter functionality
+ private openMultiselects: Map
= new Map(); // Map of filterId -> context
+ private documentClickHandler: ((event: MouseEvent) => void) | null = null;
+ private filtersInitialized: boolean = false;
+
+ constructor(
+ private dashboardService: Dashboard3Service,
+ private filterService: FilterService
+ ) { }
ngOnInit(): void {
+ // Subscribe to filter changes
+ this.subscriptions.push(
+ this.filterService.filterState$.subscribe(filters => {
+ // When filters change, refresh the chart data
+ this.fetchChartData();
+ })
+ );
+
this.fetchChartData();
}
ngOnChanges(changes: SimpleChanges): void {
console.log('BubbleChartComponent input changes:', changes);
+ // 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 xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
@@ -127,13 +177,561 @@ export class BubbleChartComponent implements OnInit, OnChanges {
this.fetchChartData();
}
}
+
+ // Initialize filter values with proper default values based on type
+ private initializeFilterValues(): void {
+ console.log('Initializing filter values');
+
+ // Initialize base filters
+ if (this.baseFilters) {
+ this.baseFilters.forEach(filter => {
+ if (filter.value === undefined || filter.value === null) {
+ switch (filter.type) {
+ case 'multiselect':
+ filter.value = [];
+ break;
+ case 'date-range':
+ filter.value = { start: null, end: null };
+ break;
+ case 'toggle':
+ filter.value = false;
+ break;
+ default:
+ filter.value = '';
+ }
+ }
+ });
+ }
+
+ // Initialize drilldown filters
+ if (this.drilldownFilters) {
+ this.drilldownFilters.forEach(filter => {
+ if (filter.value === undefined || filter.value === null) {
+ switch (filter.type) {
+ case 'multiselect':
+ filter.value = [];
+ break;
+ case 'date-range':
+ filter.value = { start: null, end: null };
+ break;
+ case 'toggle':
+ filter.value = false;
+ break;
+ default:
+ filter.value = '';
+ }
+ }
+ });
+ }
+
+ // Initialize layer filters
+ if (this.drilldownLayers) {
+ this.drilldownLayers.forEach(layer => {
+ if (layer.filters) {
+ layer.filters.forEach((filter: any) => {
+ if (filter.value === undefined || filter.value === null) {
+ switch (filter.type) {
+ case 'multiselect':
+ filter.value = [];
+ break;
+ case 'date-range':
+ filter.value = { start: null, end: null };
+ break;
+ case 'toggle':
+ filter.value = false;
+ break;
+ default:
+ filter.value = '';
+ }
+ }
+ });
+ }
+ });
+ }
+
+ console.log('Filter values initialized:', {
+ baseFilters: this.baseFilters,
+ drilldownFilters: this.drilldownFilters,
+ drilldownLayers: this.drilldownLayers
+ });
+ }
+
+ // Check if there are active filters
+ hasActiveFilters(): boolean {
+ 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 false;
+ }
+
+ // Get active layer filters for current drilldown level
+ 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) {
+ return this.drilldownLayers[layerIndex].filters;
+ }
+ }
+ return [];
+ }
+
+ // Get filter options for dropdown/multiselect filters
+ getFilterOptions(filter: any): string[] {
+ if (filter.options) {
+ if (Array.isArray(filter.options)) {
+ return filter.options;
+ } else if (typeof filter.options === 'string') {
+ return filter.options.split(',').map(opt => opt.trim()).filter(opt => opt);
+ }
+ }
+ return [];
+ }
+
+ // Check if an option is selected for multiselect filters
+ isOptionSelected(filter: any, option: string): boolean {
+ if (!filter.value) {
+ return false;
+ }
+
+ if (Array.isArray(filter.value)) {
+ return filter.value.includes(option);
+ }
+
+ return filter.value === option;
+ }
+
+ // Handle base filter changes
+ onBaseFilterChange(filter: any): void {
+ console.log('Base filter changed:', filter);
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle drilldown filter changes
+ onDrilldownFilterChange(filter: any): void {
+ console.log('Drilldown filter changed:', filter);
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle layer filter changes
+ onLayerFilterChange(filter: any): void {
+ console.log('Layer filter changed:', filter);
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // 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)) {
+ filter.value.push(option);
+ }
+ } else {
+ // Remove option from array
+ filter.value = filter.value.filter((item: string) => item !== option);
+ }
+
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle date range changes
+ onDateRangeChange(filter: any, dateRange: { start: string | null, end: string | null }): void {
+ filter.value = dateRange;
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Handle toggle changes
+ onToggleChange(filter: any, checked: boolean): void {
+ filter.value = checked;
+ // Refresh data when filter changes
+ this.fetchChartData();
+ }
+
+ // Toggle multiselect dropdown visibility
+ toggleMultiselect(filter: any, context: string): void {
+ const filterId = `${context}-${filter.field}`;
+ if (this.isMultiselectOpen(filter, context)) {
+ this.openMultiselects.delete(filterId);
+ } else {
+ // 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();
+ }
+ }
+
+ // Add document click handler to close dropdowns when clicking outside
+ private addDocumentClickHandler(): void {
+ if (!this.documentClickHandler) {
+ this.documentClickHandler = (event: MouseEvent) => {
+ const target = event.target as HTMLElement;
+ // Check if click is outside any multiselect dropdown
+ if (!target.closest('.multiselect-container')) {
+ this.openMultiselects.clear();
+ this.removeDocumentClickHandler();
+ }
+ };
+
+ // Use setTimeout to ensure the click event that opened the dropdown doesn't immediately close it
+ setTimeout(() => {
+ document.addEventListener('click', this.documentClickHandler!);
+ }, 0);
+ }
+ }
+
+ // Remove document click handler
+ private removeDocumentClickHandler(): void {
+ if (this.documentClickHandler) {
+ document.removeEventListener('click', this.documentClickHandler);
+ this.documentClickHandler = null;
+ }
+ }
+
+ // Check if multiselect dropdown is open
+ isMultiselectOpen(filter: any, context: string): boolean {
+ const filterId = `${context}-${filter.field}`;
+ return this.openMultiselects.has(filterId);
+ }
+
+ // Get count of selected options for a multiselect filter
+ getSelectedOptionsCount(filter: any): number {
+ if (!filter.value) {
+ return 0;
+ }
+
+ if (Array.isArray(filter.value)) {
+ return filter.value.length;
+ }
+
+ return 0;
+ }
+
+ // Clear all filters
+ clearAllFilters(): void {
+ // Clear base filters
+ if (this.baseFilters) {
+ this.baseFilters.forEach(filter => {
+ if (filter.type === 'multiselect') {
+ filter.value = [];
+ } else if (filter.type === 'date-range') {
+ filter.value = { start: null, end: null };
+ } else if (filter.type === 'toggle') {
+ filter.value = false;
+ } else {
+ filter.value = '';
+ }
+ });
+ }
+
+ // Clear drilldown filters
+ if (this.drilldownFilters) {
+ this.drilldownFilters.forEach(filter => {
+ if (filter.type === 'multiselect') {
+ filter.value = [];
+ } else if (filter.type === 'date-range') {
+ filter.value = { start: null, end: null };
+ } else if (filter.type === 'toggle') {
+ filter.value = false;
+ } else {
+ filter.value = '';
+ }
+ });
+ }
+
+ // Clear layer filters
+ if (this.drilldownLayers) {
+ this.drilldownLayers.forEach(layer => {
+ if (layer.filters) {
+ layer.filters.forEach((filter: any) => {
+ if (filter.type === 'multiselect') {
+ filter.value = [];
+ } else if (filter.type === 'date-range') {
+ filter.value = { start: null, end: null };
+ } else if (filter.type === 'toggle') {
+ filter.value = false;
+ } else {
+ filter.value = '';
+ }
+ });
+ }
+ });
+ }
+
+ // Close all multiselect dropdowns
+ this.openMultiselects.clear();
+
+ // Refresh data
+ this.fetchChartData();
+ }
+
+ // Helper function to replace alpha value in RGBA color string
+ private replaceAlpha(color: string, newAlpha: number): string {
+ // Match rgba(r, g, b, a) format and replace alpha value
+ return color.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/, `rgba($1, $2, $3, ${newAlpha})`);
+ }
+
+ // Function to generate different colors for bubbles
+ private generateBubbleColor(index: number, value: number, total: number): string {
+ // Generate colors based on index or value
+ // Using HSL color model for better color distribution
+ 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)`;
+ }
+
+ // 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;
+ 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);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+
+ return {
+ r: Math.round(r * 255),
+ g: Math.round(g * 255),
+ b: Math.round(b * 255)
+ };
+ }
+
+ // 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);
+ }
+
+ // Transform data to bubble chart format
+ private transformToBubbleData(labels: any[], data: any[]): ChartDataset[] {
+ // 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}
+ 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;
+ }
+
+ // 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;
+ if (typeof yValues[i] === 'string') {
+ y = parseFloat(yValues[i]);
+ } 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
+ // For fewer points, we can use larger bubbles; for more points, smaller bubbles to prevent overlap
+ 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;
+ maxRadius = 20;
+ } else if (dataPointCount > 20) {
+ 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++) {
+ // 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 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,
+ 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[] = [
+ {
+ data: bubblePoints,
+ label: label,
+ backgroundColor: bubbleColors,
+ borderColor: bubbleColors.map(color => this.replaceAlpha(color, 1)), // Slightly more opaque border
+ hoverBackgroundColor: bubbleColors.map(color => this.replaceAlpha(color, 0.9)),
+ hoverBorderColor: 'rgba(255, 255, 255, 1)',
+ borderWidth: 2,
+ pointHoverRadius: 10,
+ }
+ ];
+
+ console.log('Transformed bubble data:', 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;
@@ -160,7 +758,49 @@ export class BubbleChartComponent implements OnInit, OnChanges {
filterParams = JSON.stringify(filterObj);
}
}
- console.log('Base filter parameters:', filterParams);
+
+ // 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 {
+ const baseFilterObj = JSON.parse(filterParams);
+ Object.assign(mergedFilterObj, baseFilterObj);
+ } catch (e) {
+ 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];
+ // 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 !== '') {
+ mergedFilterObj[fieldName] = filterValue;
+ }
+ } else {
+ // Fallback to using filterId as field name if no field is defined
+ if (filterValue !== undefined && filterValue !== null && filterValue !== '') {
+ mergedFilterObj[filterId] = filterValue;
+ }
+ }
+ });
+
+ if (Object.keys(mergedFilterObj).length > 0) {
+ filterParams = JSON.stringify(mergedFilterObj);
+ }
+ }
+
+ console.log('Final filter parameters:', filterParams);
// Log the URL that will be called
const url = `chart/getdashjson/bubble?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
@@ -171,32 +811,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;
},
@@ -204,6 +894,11 @@ 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;
}
@@ -212,6 +907,11 @@ export class BubbleChartComponent implements OnInit, OnChanges {
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;
}
@@ -241,6 +941,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;
}
}
@@ -252,6 +956,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;
}
@@ -307,6 +1015,35 @@ export class BubbleChartComponent implements OnInit, OnChanges {
}
}
+ // Add common filters to drilldown filter parameters
+ const commonFilters = this.filterService.getFilterValues();
+ if (Object.keys(commonFilters).length > 0) {
+ // Merge common filters with drilldown filters
+ const mergedFilterObj = {};
+
+ // Add drilldown filters first
+ if (filterParams) {
+ try {
+ const drilldownFilterObj = JSON.parse(filterParams);
+ Object.assign(mergedFilterObj, drilldownFilterObj);
+ } catch (e) {
+ console.warn('Failed to parse drilldown filter parameters:', e);
+ }
+ }
+
+ // Add common filters
+ Object.keys(commonFilters).forEach(key => {
+ const value = commonFilters[key];
+ if (value !== undefined && value !== null && value !== '') {
+ mergedFilterObj[key] = value;
+ }
+ });
+
+ if (Object.keys(mergedFilterObj).length > 0) {
+ filterParams = JSON.stringify(mergedFilterObj);
+ }
+ }
+
// Log the URL that will be called
const url = `chart/getdashjson/bubble?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
console.log('Drilldown data URL:', url);
@@ -316,68 +1053,88 @@ 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
- // 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 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 = [];
+ this.dataLoaded = true;
+ setTimeout(() => {
+ this.forceChartUpdate();
+ }, 100);
}
);
}
- // Transform chart data to bubble chart format
- private transformToBubbleData(labels: string[], datasets: any[]): ChartDataset[] {
- // 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 is a simple transformation - in a real implementation, you might want to
- // create a more sophisticated mapping based on your data structure
- return datasets.map((dataset, index) => {
- // Create bubble data points
- const bubbleData = labels.map((label, i) => {
- // Use x-axis data as x coordinate, y-axis data as y coordinate, and a fixed radius
- const xValue = dataset.data[i] || 0;
- const yValue = i < datasets.length ? (datasets[i].data[index] || 0) : 0;
- const radius = 10; // Fixed radius for now
-
- return { x: xValue, y: yValue, r: radius };
- });
-
- return {
- data: bubbleData,
- label: dataset.label || `Dataset ${index + 1}`,
- backgroundColor: dataset.backgroundColor || `rgba(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, 0.6)`,
- borderColor: dataset.borderColor || 'rgba(0, 0, 0, 1)',
- hoverBackgroundColor: dataset.hoverBackgroundColor || 'rgba(255, 255, 255, 0.8)',
- hoverBorderColor: dataset.hoverBorderColor || 'rgba(0, 0, 0, 1)'
- };
- });
- }
-
// Reset to original data (go back to base level)
resetToOriginalData(): void {
console.log('Resetting to original data');
@@ -438,16 +1195,18 @@ export class BubbleChartComponent implements OnInit, OnChanges {
// Get the index of the clicked element
const clickedIndex = e.active[0].index;
- // Get the label of the clicked element
- // For bubble charts, we might not have labels in the same way as other charts
- const clickedLabel = `Bubble ${clickedIndex}`;
+ // Get the dataset index
+ const datasetIndex = e.active[0].datasetIndex;
- console.log('Clicked on bubble:', { index: clickedIndex, label: clickedLabel });
+ // Get the data point
+ const dataPoint = this.bubbleChartData[datasetIndex].data[clickedIndex];
+
+ console.log('Clicked on bubble:', { 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
- this.originalBubbleChartData = [...this.bubbleChartData];
+ this.originalBubbleChartData = JSON.parse(JSON.stringify(this.bubbleChartData));
console.log('Stored original data for drilldown');
}
@@ -489,9 +1248,10 @@ export class BubbleChartComponent implements OnInit, OnChanges {
// Add this click to the drilldown stack
const stackEntry = {
level: nextDrilldownLevel,
+ datasetIndex: datasetIndex,
clickedIndex: clickedIndex,
- clickedLabel: clickedLabel,
- clickedValue: clickedLabel // Using label as value for now
+ dataPoint: dataPoint,
+ clickedValue: dataPoint // Using data point as value for now
};
this.drilldownStack.push(stackEntry);
@@ -515,6 +1275,54 @@ export class BubbleChartComponent implements OnInit, OnChanges {
}
public chartHovered(e: any): void {
- console.log(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());
}
}
\ No newline at end of file
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html
index 0828c55..1acc617 100644
--- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html
+++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html
@@ -1,44 +1,315 @@
-
-
-
0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
-
Drilldown Level: {{currentDrilldownLevel}}
-
- Back to Level {{currentDrilldownLevel - 1}}
-
-
- Back to Main View
-
+
+
+
+
+
0">
+
Base Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0 && currentDrilldownLevel > 0">
+
Drilldown Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Layer Filters
+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clear All Filters
+
-
{{ charttitle }}
-
-
-
-
-
Loading chart data...
+
+