barchart
This commit is contained in:
		
							parent
							
								
									ced99e0940
								
							
						
					
					
						commit
						96b90e5dbd
					
				| @ -1,17 +1,283 @@ | ||||
| <div style="display: block; height: 100%; width: 100%;"> | ||||
|   <!-- No filter controls needed with the new simplified approach --> | ||||
|   <!-- Filters are now configured at the drilldown level --> | ||||
|   <!-- Filter Controls Section --> | ||||
|   <div class="filter-section" *ngIf="hasActiveFilters()"> | ||||
|     <!-- Base Filters --> | ||||
|     <div class="filter-group" *ngIf="baseFilters && baseFilters.length > 0"> | ||||
|       <h4>Base Filters</h4> | ||||
|       <div class="filter-controls"> | ||||
|         <div *ngFor="let filter of baseFilters" class="filter-item"> | ||||
|           <div class="filter-label">{{ filter.field }} ({{ filter.type || 'text' }})</div> | ||||
|            | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|           <!-- Text Filter --> | ||||
|           <div *ngIf="!filter.type || filter.type === 'text'" class="filter-input"> | ||||
|             <input type="text"  | ||||
|                    [(ngModel)]="filter.value"  | ||||
|                    (ngModelChange)="onBaseFilterChange(filter)" | ||||
|                    [placeholder]="filter.field" | ||||
|                    class="clr-input filter-text-input"> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Dropdown Filter --> | ||||
|           <div *ngIf="filter.type === 'dropdown'" class="filter-input"> | ||||
|             <select [(ngModel)]="filter.value"  | ||||
|                     (ngModelChange)="onBaseFilterChange(filter)" | ||||
|                     class="clr-select filter-select"> | ||||
|               <option value="">Select {{ filter.field }}</option> | ||||
|               <option *ngFor="let option of getFilterOptions(filter)" [value]="option">{{ option }}</option> | ||||
|             </select> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Multi-Select Filter - Updated to show key first, then dropdown on click --> | ||||
|           <div *ngIf="filter.type === 'multiselect'" class="filter-input multiselect-container"> | ||||
|             <div class="multiselect-display" (click)="toggleMultiselect(filter, 'base')"> | ||||
|               <span class="multiselect-label">{{ filter.field }}</span> | ||||
|               <span class="multiselect-value" *ngIf="getSelectedOptionsCount(filter) > 0"> | ||||
|                 ({{ getSelectedOptionsCount(filter) }} selected) | ||||
|               </span> | ||||
|               <clr-icon shape="caret down" class="dropdown-icon"></clr-icon> | ||||
|             </div> | ||||
|             <div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'base')"> | ||||
|               <div class="checkbox-group"> | ||||
|                 <div *ngFor="let option of getFilterOptions(filter); let i = index" class="checkbox-item"> | ||||
|                   <input type="checkbox"  | ||||
|                          [checked]="isOptionSelected(filter, option)"  | ||||
|                          (change)="onMultiSelectChange(filter, option, $event)" | ||||
|                          [id]="'base-' + filter.field + '-' + i"  | ||||
|                          class="clr-checkbox"> | ||||
|                   <label [for]="'base-' + filter.field + '-' + i" class="checkbox-label">{{ option }}</label> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Date Range Filter --> | ||||
|           <div *ngIf="filter.type === 'date-range'" class="filter-input date-range"> | ||||
|             <div class="date-input-group"> | ||||
|               <input type="date"  | ||||
|                      [(ngModel)]="filter.value.start"  | ||||
|                      (ngModelChange)="onDateRangeChange(filter, { start: $event, end: filter.value.end })" | ||||
|                      placeholder="Start Date" | ||||
|                      class="clr-input filter-date"> | ||||
|               <span class="date-separator">to</span> | ||||
|               <input type="date"  | ||||
|                      [(ngModel)]="filter.value.end"  | ||||
|                      (ngModelChange)="onDateRangeChange(filter, { start: filter.value.start, end: $event })" | ||||
|                      placeholder="End Date" | ||||
|                      class="clr-input filter-date"> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Toggle Filter --> | ||||
|           <div *ngIf="filter.type === 'toggle'" class="filter-input toggle"> | ||||
|             <input type="checkbox"  | ||||
|                    [(ngModel)]="filter.value"  | ||||
|                    (ngModelChange)="onToggleChange(filter, $event)" | ||||
|                    clrToggle  | ||||
|                    class="clr-toggle"> | ||||
|             <label class="toggle-label">{{ filter.field }}</label> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Drilldown Filters --> | ||||
|     <div class="filter-group" *ngIf="drilldownFilters && drilldownFilters.length > 0 && currentDrilldownLevel > 0"> | ||||
|       <h4>Drilldown Filters</h4> | ||||
|       <div class="filter-controls"> | ||||
|         <div *ngFor="let filter of drilldownFilters" class="filter-item"> | ||||
|           <div class="filter-label">{{ filter.field }} ({{ filter.type || 'text' }})</div> | ||||
|            | ||||
|           <!-- Text Filter --> | ||||
|           <div *ngIf="!filter.type || filter.type === 'text'" class="filter-input"> | ||||
|             <input type="text"  | ||||
|                    [(ngModel)]="filter.value"  | ||||
|                    (ngModelChange)="onDrilldownFilterChange(filter)" | ||||
|                    [placeholder]="filter.field" | ||||
|                    class="clr-input filter-text-input"> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Dropdown Filter --> | ||||
|           <div *ngIf="filter.type === 'dropdown'" class="filter-input"> | ||||
|             <select [(ngModel)]="filter.value"  | ||||
|                     (ngModelChange)="onDrilldownFilterChange(filter)" | ||||
|                     class="clr-select filter-select"> | ||||
|               <option value="">Select {{ filter.field }}</option> | ||||
|               <option *ngFor="let option of getFilterOptions(filter)" [value]="option">{{ option }}</option> | ||||
|             </select> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Multi-Select Filter - Updated to show key first, then dropdown on click --> | ||||
|           <div *ngIf="filter.type === 'multiselect'" class="filter-input multiselect-container"> | ||||
|             <div class="multiselect-display" (click)="toggleMultiselect(filter, 'drilldown')"> | ||||
|               <span class="multiselect-label">{{ filter.field }}</span> | ||||
|               <span class="multiselect-value" *ngIf="getSelectedOptionsCount(filter) > 0"> | ||||
|                 ({{ getSelectedOptionsCount(filter) }} selected) | ||||
|               </span> | ||||
|               <clr-icon shape="caret down" class="dropdown-icon"></clr-icon> | ||||
|             </div> | ||||
|             <div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'drilldown')"> | ||||
|               <div class="checkbox-group"> | ||||
|                 <div *ngFor="let option of getFilterOptions(filter); let i = index" class="checkbox-item"> | ||||
|                   <input type="checkbox"  | ||||
|                          [checked]="isOptionSelected(filter, option)"  | ||||
|                          (change)="onMultiSelectChange(filter, option, $event)" | ||||
|                          [id]="'drilldown-' + filter.field + '-' + i"  | ||||
|                          class="clr-checkbox"> | ||||
|                   <label [for]="'drilldown-' + filter.field + '-' + i" class="checkbox-label">{{ option }}</label> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Date Range Filter --> | ||||
|           <div *ngIf="filter.type === 'date-range'" class="filter-input date-range"> | ||||
|             <div class="date-input-group"> | ||||
|               <input type="date"  | ||||
|                      [(ngModel)]="filter.value.start"  | ||||
|                      (ngModelChange)="onDateRangeChange(filter, { start: $event, end: filter.value.end })" | ||||
|                      placeholder="Start Date" | ||||
|                      class="clr-input filter-date"> | ||||
|               <span class="date-separator">to</span> | ||||
|               <input type="date"  | ||||
|                      [(ngModel)]="filter.value.end"  | ||||
|                      (ngModelChange)="onDateRangeChange(filter, { start: filter.value.start, end: $event })" | ||||
|                      placeholder="End Date" | ||||
|                      class="clr-input filter-date"> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Toggle Filter --> | ||||
|           <div *ngIf="filter.type === 'toggle'" class="filter-input toggle"> | ||||
|             <input type="checkbox"  | ||||
|                    [(ngModel)]="filter.value"  | ||||
|                    (ngModelChange)="onToggleChange(filter, $event)" | ||||
|                    clrToggle  | ||||
|                    class="clr-toggle"> | ||||
|             <label class="toggle-label">{{ filter.field }}</label> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Layer Filters --> | ||||
|     <div class="filter-group" *ngIf="hasActiveLayerFilters()"> | ||||
|       <h4>Layer Filters</h4> | ||||
|       <div class="filter-controls"> | ||||
|         <div *ngFor="let filter of getActiveLayerFilters()" class="filter-item"> | ||||
|           <div class="filter-label">{{ filter.field }} ({{ filter.type || 'text' }})</div> | ||||
|            | ||||
|           <!-- Text Filter --> | ||||
|           <div *ngIf="!filter.type || filter.type === 'text'" class="filter-input"> | ||||
|             <input type="text"  | ||||
|                    [(ngModel)]="filter.value"  | ||||
|                    (ngModelChange)="onLayerFilterChange(filter)" | ||||
|                    [placeholder]="filter.field" | ||||
|                    class="clr-input filter-text-input"> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Dropdown Filter --> | ||||
|           <div *ngIf="filter.type === 'dropdown'" class="filter-input"> | ||||
|             <select [(ngModel)]="filter.value"  | ||||
|                     (ngModelChange)="onLayerFilterChange(filter)" | ||||
|                     class="clr-select filter-select"> | ||||
|               <option value="">Select {{ filter.field }}</option> | ||||
|               <option *ngFor="let option of getFilterOptions(filter)" [value]="option">{{ option }}</option> | ||||
|             </select> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Multi-Select Filter - Updated to show key first, then dropdown on click --> | ||||
|           <div *ngIf="filter.type === 'multiselect'" class="filter-input multiselect-container"> | ||||
|             <div class="multiselect-display" (click)="toggleMultiselect(filter, 'layer')"> | ||||
|               <span class="multiselect-label">{{ filter.field }}</span> | ||||
|               <span class="multiselect-value" *ngIf="getSelectedOptionsCount(filter) > 0"> | ||||
|                 ({{ getSelectedOptionsCount(filter) }} selected) | ||||
|               </span> | ||||
|               <clr-icon shape="caret down" class="dropdown-icon"></clr-icon> | ||||
|             </div> | ||||
|             <div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'layer')"> | ||||
|               <div class="checkbox-group"> | ||||
|                 <div *ngFor="let option of getFilterOptions(filter); let i = index" class="checkbox-item"> | ||||
|                   <input type="checkbox"  | ||||
|                          [checked]="isOptionSelected(filter, option)"  | ||||
|                          (change)="onMultiSelectChange(filter, option, $event)" | ||||
|                          [id]="'layer-' + filter.field + '-' + i"  | ||||
|                          class="clr-checkbox"> | ||||
|                   <label [for]="'layer-' + filter.field + '-' + i" class="checkbox-label">{{ option }}</label> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Date Range Filter --> | ||||
|           <div *ngIf="filter.type === 'date-range'" class="filter-input date-range"> | ||||
|             <div class="date-input-group"> | ||||
|               <input type="date"  | ||||
|                      [(ngModel)]="filter.value.start"  | ||||
|                      (ngModelChange)="onDateRangeChange(filter, { start: $event, end: filter.value.end })" | ||||
|                      placeholder="Start Date" | ||||
|                      class="clr-input filter-date"> | ||||
|               <span class="date-separator">to</span> | ||||
|               <input type="date"  | ||||
|                      [(ngModel)]="filter.value.end"  | ||||
|                      (ngModelChange)="onDateRangeChange(filter, { start: filter.value.start, end: $event })" | ||||
|                      placeholder="End Date" | ||||
|                      class="clr-input filter-date"> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <!-- Toggle Filter --> | ||||
|           <div *ngIf="filter.type === 'toggle'" class="filter-input toggle"> | ||||
|             <input type="checkbox"  | ||||
|                    [(ngModel)]="filter.value"  | ||||
|                    (ngModelChange)="onToggleChange(filter, $event)" | ||||
|                    clrToggle  | ||||
|                    class="clr-toggle"> | ||||
|             <label class="toggle-label">{{ filter.field }}</label> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Clear Filters Button --> | ||||
|     <div class="filter-actions"> | ||||
|       <button class="btn btn-sm btn-outline" (click)="clearAllFilters()">Clear All Filters</button> | ||||
|     </div> | ||||
|   </div> | ||||
|    | ||||
|   <!-- Header row with chart title and drilldown navigation --> | ||||
|   <div class="clr-row header-row"> | ||||
|     <div class="clr-col-6"> | ||||
|       <h3 class="chart-title">{{charttitle || 'Bar Chart'}}</h3> | ||||
|     </div> | ||||
|     <div class="clr-col-6" style="text-align: right;"> | ||||
|       <!-- Add drilldown navigation controls --> | ||||
|       <button class="btn btn-sm btn-link" *ngIf="drilldownEnabled && drilldownStack.length > 0" (click)="navigateBack()"> | ||||
|         <cds-icon shape="arrow" direction="left"></cds-icon> | ||||
|         Back to {{drilldownStack.length > 1 ? 'Previous Level' : 'Main Data'}} | ||||
|       </button> | ||||
|     </div> | ||||
|   </div> | ||||
|    | ||||
|   <!-- Show current drilldown level --> | ||||
|   <div class="clr-row" *ngIf="drilldownEnabled && drilldownStack.length > 0"> | ||||
|     <div class="clr-col-12"> | ||||
|       <div class="alert alert-info" style="padding: 8px 12px; margin-bottom: 12px;"> | ||||
|         <div class="alert-items"> | ||||
|           <div class="alert-item static"> | ||||
|             <div class="alert-icon-wrapper"> | ||||
|               <cds-icon class="alert-icon" shape="info-circle"></cds-icon> | ||||
|             </div> | ||||
|             <span class="alert-text"> | ||||
|               Drilldown Level: {{currentDrilldownLevel}}  | ||||
|               <span *ngIf="drilldownStack.length > 0"> | ||||
|                 (Clicked on: {{drilldownStack[drilldownStack.length - 1].clickedLabel}}) | ||||
|               </span> | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|    | ||||
|   <!-- No data message --> | ||||
|   <div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> | ||||
|  | ||||
| @ -1,31 +1,192 @@ | ||||
| // Bar Chart Component Styles | ||||
| :host { | ||||
|   display: block; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
| .filter-section { | ||||
|   margin-bottom: 20px; | ||||
|   padding: 15px; | ||||
|   border: 1px solid #ddd; | ||||
|   border-radius: 4px; | ||||
|   background-color: #f9f9f9; | ||||
| } | ||||
| 
 | ||||
| .bar-chart-container { | ||||
| .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; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| canvas { | ||||
|   display: block; | ||||
|   max-width: 100%; | ||||
|   max-height: 100%; | ||||
| .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; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Responsive design for chart container | ||||
| .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) { | ||||
|   .bar-chart-container { | ||||
|     height: 300px; | ||||
|   .filter-controls { | ||||
|     flex-direction: column; | ||||
|   } | ||||
| } | ||||
|    | ||||
| @media (max-width: 480px) { | ||||
|   .bar-chart-container { | ||||
|     height: 250px; | ||||
|   .filter-item { | ||||
|     min-width: 100%; | ||||
|   } | ||||
|    | ||||
|   .header-row { | ||||
|     .chart-title { | ||||
|       font-size: 16px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -90,6 +90,11 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { | ||||
|   // Subscriptions to unsubscribe on destroy
 | ||||
|   private subscriptions: Subscription[] = []; | ||||
| 
 | ||||
|   // Add properties for filter functionality
 | ||||
|   private openMultiselects: Map<string, string> = new Map(); // Map of filterId -> context
 | ||||
|   private documentClickHandler: ((event: MouseEvent) => void) | null = null; | ||||
|   private filtersInitialized: boolean = false; | ||||
| 
 | ||||
|   constructor( | ||||
|     private dashboardService: Dashboard3Service, | ||||
|     private filterService: FilterService | ||||
| @ -111,6 +116,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; | ||||
| @ -141,6 +152,316 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 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 flag to prevent recursive calls
 | ||||
|     this.isFetchingData = true; | ||||
| @ -661,6 +982,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'); | ||||
|   } | ||||
| } | ||||
| @ -246,9 +246,9 @@ | ||||
|     </div> | ||||
|      | ||||
|     <div class="clr-row"> | ||||
|       <div class="clr-col-8"> | ||||
|       <!-- <div class="clr-col-8"> | ||||
|         <h3>{{charttitle || 'Data Grid'}}</h3> | ||||
|       </div> | ||||
|       </div> --> | ||||
|       <!-- Add drilldown navigation controls --> | ||||
|       <div class="clr-col-4" *ngIf="drilldownEnabled && drilldownStack.length > 0" style="text-align: right;"> | ||||
|         <button class="btn btn-sm btn-link" (click)="navigateBack()"> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user