scatter
This commit is contained in:
		
							parent
							
								
									f740076d60
								
							
						
					
					
						commit
						1b17bb706d
					
				| @ -1,4 +1,4 @@ | ||||
| <div class="doughnut-chart-container"> | ||||
| <div class="chart-container"> | ||||
|   <!-- Filter Controls Section --> | ||||
|   <div class="filter-section" *ngIf="hasActiveFilters()"> | ||||
|     <!-- Base Filters --> | ||||
| @ -245,6 +245,7 @@ | ||||
|   </div> | ||||
|    | ||||
|   <!-- Header row with chart title and drilldown navigation --> | ||||
|   <div class="chart-header"> | ||||
|     <div class="clr-row header-row"> | ||||
|       <div class="clr-col-6"> | ||||
|         <h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3> | ||||
| @ -278,9 +279,10 @@ | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|    | ||||
|   <div class="chart-wrapper"> | ||||
|     <div class="chart-content" [class.loading]="doughnutChartLabels.length === 0 && doughnutChartData.length === 0 && !noDataAvailable"> | ||||
|     <div class="chart-content" [class.loading]="isLoading"> | ||||
|       <!-- Show no data message --> | ||||
|       <div class="no-data-message" *ngIf="noDataAvailable"> | ||||
|         <p>No chart data available</p> | ||||
| @ -289,7 +291,7 @@ | ||||
|       <!-- Show chart when data is available --> | ||||
|       <canvas baseChart  | ||||
|         *ngIf="!noDataAvailable && doughnutChartLabels.length > 0 && doughnutChartData.length > 0" | ||||
|         [data]="doughnutChartData"  | ||||
|         [datasets]="doughnutChartData"  | ||||
|         [labels]="doughnutChartLabels"  | ||||
|         [type]="doughnutChartType" | ||||
|         [options]="doughnutChartOptions" | ||||
| @ -298,7 +300,7 @@ | ||||
|       </canvas> | ||||
|        | ||||
|       <!-- Loading overlay --> | ||||
|       <div class="loading-overlay" *ngIf="doughnutChartLabels.length === 0 && doughnutChartData.length === 0 && !noDataAvailable"> | ||||
|       <div class="loading-overlay" *ngIf="isLoading"> | ||||
|         <div class="shimmer-donut"></div> | ||||
|       </div> | ||||
|     </div> | ||||
| @ -308,7 +310,7 @@ | ||||
|     <div class="legend-item" *ngFor="let label of doughnutChartLabels; let i = index"> | ||||
|       <span class="legend-color" [style.background-color]="getLegendColor(i)"></span> | ||||
|       <span class="legend-label">{{ label }}</span> | ||||
|       <span class="legend-value">{{ doughnutChartData && doughnutChartData[i] !== undefined ? doughnutChartData[i] : 0 }}</span> | ||||
|       <span class="legend-value">{{ doughnutChartData && doughnutChartData[0] && doughnutChartData[0].data && doughnutChartData[0].data[i] !== undefined ? doughnutChartData[0].data[i] : 0 }}</span> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| @ -1,260 +1,20 @@ | ||||
| .doughnut-chart-container { | ||||
| // Chart container structure - simplified to match shield dashboard | ||||
| .chart-container { | ||||
|   height: 100%; | ||||
|   min-height: 400px; // Ensure minimum height | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   height: 400px; | ||||
|   min-height: 400px; | ||||
|   padding: 20px; | ||||
|   background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); | ||||
|   border-radius: 12px; | ||||
|   box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); | ||||
|   font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||
|   transition: all 0.3s ease; | ||||
|   border: 1px solid #eaeaea; | ||||
| } | ||||
|    | ||||
| .doughnut-chart-container:hover { | ||||
|   box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2); | ||||
|   transform: translateY(-2px); | ||||
| } | ||||
| 
 | ||||
| .compact-filters-container { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   gap: 5px; | ||||
|   margin-bottom: 10px; | ||||
|   padding: 5px; | ||||
|   background: #ffffff; | ||||
|   border: 1px solid #e9ecef; | ||||
|   border-radius: 6px; | ||||
|   min-height: 40px; | ||||
| } | ||||
| 
 | ||||
| .drilldown-indicator { | ||||
|   background-color: #e0e0e0; | ||||
|   padding: 10px; | ||||
|   margin-bottom: 15px; | ||||
|   border-radius: 8px; | ||||
|   text-align: center; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   gap: 10px; | ||||
|   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
| 
 | ||||
| .drilldown-text { | ||||
|   font-weight: bold; | ||||
|   color: #333; | ||||
|   font-size: 16px; | ||||
| } | ||||
| 
 | ||||
| .btn { | ||||
|   padding: 6px 12px; | ||||
|   border: none; | ||||
|   border-radius: 4px; | ||||
|   cursor: pointer; | ||||
|   font-size: 14px; | ||||
|   transition: all 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .btn:hover { | ||||
|   transform: translateY(-2px); | ||||
|   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | ||||
| } | ||||
| 
 | ||||
| .btn-sm { | ||||
|   padding: 4px 8px; | ||||
|   font-size: 12px; | ||||
| } | ||||
| 
 | ||||
| .btn-secondary { | ||||
|   background-color: #007cba; | ||||
|   color: white; | ||||
| } | ||||
| 
 | ||||
| .btn-danger { | ||||
|   background-color: #dc3545; | ||||
|   color: white; | ||||
| } | ||||
| 
 | ||||
| .chart-header { | ||||
|   margin-bottom: 20px; | ||||
|    | ||||
|   .chart-title { | ||||
|     font-size: 22px; | ||||
|     font-weight: 600; | ||||
|     color: #0a192f; | ||||
|     margin: 0; | ||||
|     text-align: center; | ||||
|     padding-bottom: 10px; | ||||
|     border-bottom: 2px solid #3498db; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .chart-wrapper { | ||||
|   position: relative; | ||||
|   flex: 1; | ||||
|   min-height: 250px; | ||||
|   margin: 15px 0; | ||||
|   background: #f8f9fa; | ||||
|   border: 1px solid #e9ecef; | ||||
|   border-radius: 8px; | ||||
|   padding: 10px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| } | ||||
| 
 | ||||
| .chart-wrapper canvas { | ||||
|   max-width: 100%; | ||||
|   max-height: 100%; | ||||
|   filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)); | ||||
| } | ||||
| 
 | ||||
| .chart-wrapper canvas:hover { | ||||
|   filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15)); | ||||
|   transform: scale(1.02); | ||||
|   transition: all 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .chart-content { | ||||
|   position: relative; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|    | ||||
|   &.loading { | ||||
|     opacity: 0.7; | ||||
|      | ||||
|     canvas { | ||||
|       filter: blur(2px); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   .no-data-message { | ||||
|     text-align: center; | ||||
|     padding: 30px; | ||||
|     color: #666; | ||||
|     font-size: 18px; | ||||
|     font-style: italic; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|   } | ||||
|    | ||||
|   .no-data-message p { | ||||
|     margin: 0; | ||||
|   } | ||||
|    | ||||
|   .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-donut { | ||||
|       width: 120px; | ||||
|       height: 120px; | ||||
|       border-radius: 50%; | ||||
|       background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); | ||||
|       background-size: 200% 100%; | ||||
|       animation: shimmer 1.5s infinite; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .chart-legend { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   justify-content: center; | ||||
|   gap: 15px; | ||||
|   margin-top: 20px; | ||||
|   padding: 20px; | ||||
|   background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); | ||||
|   border-radius: 8px; | ||||
|   border: 1px solid #dee2e6; | ||||
|   box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05); | ||||
| } | ||||
| 
 | ||||
| .legend-item { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding: 12px 20px; | ||||
|   background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); | ||||
|   border-radius: 25px; | ||||
|   box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); | ||||
|   transition: all 0.3s ease; | ||||
|   border: 1px solid #eaeaea; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| .legend-item:hover { | ||||
|   transform: translateY(-3px); | ||||
|   box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); | ||||
|   border-color: #3498db; | ||||
|   background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); | ||||
| } | ||||
| 
 | ||||
| .legend-color { | ||||
|   width: 20px; | ||||
|   height: 20px; | ||||
|   border-radius: 50%; | ||||
|   margin-right: 12px; | ||||
|   display: inline-block; | ||||
|   border: 2px solid white; | ||||
|   box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); | ||||
| } | ||||
| 
 | ||||
| .legend-label { | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   color: #2c3e50; | ||||
|   margin-right: 15px; | ||||
|   white-space: nowrap; | ||||
|   text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
| 
 | ||||
| .legend-value { | ||||
|   font-size: 16px; | ||||
|   font-weight: 700; | ||||
|   color: #3498db; | ||||
|   background: linear-gradient(135deg, #e9ecef 0%, #dde1e5 100%); | ||||
|   padding: 6px 12px; | ||||
|   border-radius: 12px; | ||||
|   box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); | ||||
|   min-width: 40px; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| @keyframes shimmer { | ||||
|   0% { | ||||
|     background-position: -200% 0; | ||||
|   } | ||||
|   100% { | ||||
|     background-position: 200% 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Filter section styles | ||||
| .filter-section { | ||||
|   // Filter section styling | ||||
|   .filter-section { | ||||
|     margin-bottom: 20px; | ||||
|     padding: 15px; | ||||
|     border: 1px solid #ddd; | ||||
|     border-radius: 4px; | ||||
|     background-color: #f9f9f9; | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .filter-group { | ||||
|   .filter-group { | ||||
|     margin-bottom: 15px; | ||||
|      | ||||
|     h4 { | ||||
| @ -263,31 +23,31 @@ | ||||
|       color: #333; | ||||
|       font-weight: 600; | ||||
|     } | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .filter-controls { | ||||
|   .filter-controls { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 15px; | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .filter-item { | ||||
|   .filter-item { | ||||
|     flex: 1 1 300px; | ||||
|     min-width: 250px; | ||||
|     padding: 10px; | ||||
|     background: white; | ||||
|     border: 1px solid #e0e0e0; | ||||
|     border-radius: 4px; | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .filter-label { | ||||
|   .filter-label { | ||||
|     font-weight: 500; | ||||
|     margin-bottom: 8px; | ||||
|     color: #555; | ||||
|     font-size: 14px; | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .filter-input { | ||||
|   .filter-input { | ||||
|     width: 100%; | ||||
|      | ||||
|     .filter-text-input, | ||||
| @ -303,13 +63,13 @@ | ||||
|     .filter-select { | ||||
|       height: 34px; | ||||
|     } | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .multiselect-container { | ||||
|   .multiselect-container { | ||||
|     position: relative; | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .multiselect-display { | ||||
|   .multiselect-display { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
| @ -339,9 +99,9 @@ | ||||
|     &:hover { | ||||
|       border-color: #999; | ||||
|     } | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .multiselect-dropdown { | ||||
|   .multiselect-dropdown { | ||||
|     position: absolute; | ||||
|     top: 100%; | ||||
|     left: 0; | ||||
| @ -371,9 +131,9 @@ | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .date-range { | ||||
|   .date-range { | ||||
|     .date-input-group { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
| @ -384,9 +144,9 @@ | ||||
|       margin: 0 5px; | ||||
|       color: #777; | ||||
|     } | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .toggle { | ||||
|   .toggle { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     gap: 8px; | ||||
| @ -396,9 +156,9 @@ | ||||
|       font-size: 14px; | ||||
|       cursor: pointer; | ||||
|     } | ||||
| } | ||||
|   } | ||||
| 
 | ||||
| .filter-actions { | ||||
|   .filter-actions { | ||||
|     margin-top: 15px; | ||||
|     padding-top: 15px; | ||||
|     border-top: 1px solid #eee; | ||||
| @ -406,10 +166,13 @@ | ||||
|     .btn { | ||||
|       font-size: 13px; | ||||
|     } | ||||
| } | ||||
|   } | ||||
|    | ||||
| // New header row styling | ||||
| .header-row { | ||||
|   // Chart header styling | ||||
|   .chart-header { | ||||
|     margin-bottom: 20px; | ||||
|      | ||||
|     .header-row { | ||||
|       margin-bottom: 15px; | ||||
|       padding-bottom: 10px; | ||||
|       border-bottom: 1px solid #eee; | ||||
| @ -418,56 +181,126 @@ | ||||
|         margin: 0; | ||||
|         font-size: 18px; | ||||
|         font-weight: 600; | ||||
|     color: #333; | ||||
|     text-align: left; | ||||
|     padding-bottom: 0; | ||||
|     border-bottom: none; | ||||
|         color: #0a192f; | ||||
|       } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* Responsive design */ | ||||
| @media (max-width: 768px) { | ||||
|   .doughnut-chart-container { | ||||
|     padding: 15px; | ||||
|   } | ||||
|    | ||||
|   .chart-header .chart-title { | ||||
|     font-size: 18px; | ||||
|   } | ||||
|    | ||||
|   .drilldown-indicator { | ||||
|     flex-direction: column; | ||||
|     gap: 5px; | ||||
|   } | ||||
|    | ||||
|   .drilldown-text { | ||||
|     font-size: 14px; | ||||
|   } | ||||
|    | ||||
|   // Chart wrapper and content - simplified to match shield dashboard | ||||
|   .chart-wrapper { | ||||
|     min-height: 200px; | ||||
|   } | ||||
|     flex: 1; | ||||
|     position: relative; | ||||
|      | ||||
|   .chart-legend { | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|   } | ||||
|     .chart-content { | ||||
|       position: relative; | ||||
|       height: 100%; | ||||
|       min-height: 300px; | ||||
|        | ||||
|   .legend-item { | ||||
|     width: 100%; | ||||
|     max-width: 300px; | ||||
|     justify-content: space-between; | ||||
|       &.loading { | ||||
|         opacity: 0.7; | ||||
|          | ||||
|         canvas { | ||||
|           filter: blur(2px); | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       .no-data-message { | ||||
|     font-size: 16px; | ||||
|         text-align: center; | ||||
|         padding: 20px; | ||||
|         color: #666; | ||||
|         font-style: italic; | ||||
|       } | ||||
|        | ||||
|   .compact-filters-container { | ||||
|       canvas { | ||||
|         max-width: 100%; | ||||
|         max-height: calc(100% - 40px); // Leave space for legend | ||||
|         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-donut { | ||||
|           width: 120px; | ||||
|           height: 120px; | ||||
|           border-radius: 50%; | ||||
|           background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); | ||||
|           background-size: 200% 100%; | ||||
|           animation: shimmer 1.5s infinite; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Chart legend - simplified | ||||
|   .chart-legend { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     justify-content: center; | ||||
|     gap: 15px; | ||||
|     margin: 20px 0; | ||||
|     padding: 15px; | ||||
|      | ||||
|     .legend-item { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       padding: 8px 15px; | ||||
|       background: #f8f9fa; | ||||
|       border-radius: 20px; | ||||
|       border: 1px solid #e9ecef; | ||||
|       min-width: 120px; | ||||
|       justify-content: space-between; | ||||
|     } | ||||
|      | ||||
|     .legend-color { | ||||
|       width: 15px; | ||||
|       height: 15px; | ||||
|       border-radius: 50%; | ||||
|       margin-right: 8px; | ||||
|       display: inline-block; | ||||
|       flex-shrink: 0; | ||||
|     } | ||||
|      | ||||
|     .legend-label { | ||||
|       font-size: 14px; | ||||
|       font-weight: 500; | ||||
|       color: #2c3e50; | ||||
|       margin-right: 10px; | ||||
|       white-space: nowrap; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|     } | ||||
|      | ||||
|     .legend-value { | ||||
|       font-size: 14px; | ||||
|       font-weight: 600; | ||||
|       color: #3498db; | ||||
|       min-width: 30px; | ||||
|       text-align: right; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @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; | ||||
|     } | ||||
| @ -476,9 +309,35 @@ | ||||
|       min-width: 100%; | ||||
|     } | ||||
|      | ||||
|     .chart-header { | ||||
|       .header-row { | ||||
|         .chart-title { | ||||
|           font-size: 16px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     .chart-content { | ||||
|       min-height: 250px; | ||||
|     } | ||||
|      | ||||
|     .chart-legend { | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|        | ||||
|       .legend-item { | ||||
|         width: 100%; | ||||
|         max-width: 300px; | ||||
|         justify-content: space-between; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     .chart-content { | ||||
|       min-height: 250px; | ||||
|        | ||||
|       canvas { | ||||
|         max-height: calc(100% - 60px); // More space for legend on mobile | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -36,7 +36,21 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|   @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
 | ||||
| 
 | ||||
|   public doughnutChartLabels: string[] = ["Category A", "Category B", "Category C"]; | ||||
|   public doughnutChartData: number[] = [30, 50, 20]; | ||||
|   public doughnutChartData: any[] = [ | ||||
|     { | ||||
|       data: [30, 50, 20], | ||||
|       backgroundColor: [ | ||||
|         '#FF6384', | ||||
|         '#36A2EB', | ||||
|         '#FFCE56' | ||||
|       ], | ||||
|       hoverBackgroundColor: [ | ||||
|         '#FF6384', | ||||
|         '#36A2EB', | ||||
|         '#FFCE56' | ||||
|       ] | ||||
|     } | ||||
|   ]; | ||||
|   public doughnutChartType: string = "doughnut"; | ||||
|   public doughnutChartOptions: any = { | ||||
|     responsive: true, | ||||
| @ -72,6 +86,14 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|         borderWidth: 2, | ||||
|         borderColor: '#fff' | ||||
|       } | ||||
|     }, | ||||
|     layout: { | ||||
|       padding: { | ||||
|         top: 20, | ||||
|         bottom: 20, | ||||
|         left: 20, | ||||
|         right: 20 | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|    | ||||
| @ -96,6 +118,9 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|   // No data state
 | ||||
|   noDataAvailable: boolean = false; | ||||
|    | ||||
|   // Loading state
 | ||||
|   isLoading: boolean = false; | ||||
|    | ||||
|   // Flag to prevent infinite loops
 | ||||
|   private isFetchingData: boolean = false; | ||||
|    | ||||
| @ -143,17 +168,33 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|     if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) { | ||||
|       // Add default data to ensure chart visibility
 | ||||
|       this.doughnutChartLabels = ["Category A", "Category B", "Category C"]; | ||||
|       this.doughnutChartData = [30, 50, 20]; | ||||
|       this.doughnutChartData = [ | ||||
|         { | ||||
|           data: [30, 50, 20], | ||||
|           backgroundColor: [ | ||||
|             '#FF6384', | ||||
|             '#36A2EB', | ||||
|             '#FFCE56' | ||||
|           ], | ||||
|           hoverBackgroundColor: [ | ||||
|             '#FF6384', | ||||
|             '#36A2EB', | ||||
|             '#FFCE56' | ||||
|           ] | ||||
|         } | ||||
|       ]; | ||||
|     } | ||||
|      | ||||
|     // Ensure we have matching arrays
 | ||||
|     if (this.doughnutChartLabels.length !== this.doughnutChartData.length) { | ||||
|       const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length); | ||||
|     if (this.doughnutChartLabels.length !== (this.doughnutChartData[0]?.data?.length || 0)) { | ||||
|       const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData[0]?.data?.length || 0); | ||||
|       while (this.doughnutChartLabels.length < maxLength) { | ||||
|         this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`); | ||||
|       } | ||||
|       while (this.doughnutChartData.length < maxLength) { | ||||
|         this.doughnutChartData.push(0); | ||||
|       if (this.doughnutChartData[0]) { | ||||
|         while (this.doughnutChartData[0].data.length < maxLength) { | ||||
|           this.doughnutChartData[0].data.push(0); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @ -529,14 +570,18 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|   } | ||||
|    | ||||
|   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; | ||||
|     } | ||||
|      | ||||
| @ -618,8 +663,9 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|             this.noDataAvailable = true; | ||||
|             this.doughnutChartLabels = []; | ||||
|             this.doughnutChartData = []; | ||||
|             // Reset flag after fetching
 | ||||
|             // Reset flags after fetching
 | ||||
|             this.isFetchingData = false; | ||||
|             this.isLoading = false; | ||||
|             return; | ||||
|           } | ||||
|            | ||||
| @ -628,34 +674,44 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|             // Backend has already filtered the data, just display it
 | ||||
|             this.noDataAvailable = data.chartLabels.length === 0; | ||||
|             this.doughnutChartLabels = data.chartLabels; | ||||
|             this.doughnutChartData = data.chartData; | ||||
|             // Trigger change detection
 | ||||
|             this.doughnutChartData = [...this.doughnutChartData]; | ||||
|             this.doughnutChartData = [ | ||||
|               { | ||||
|                 data: data.chartData, | ||||
|                 backgroundColor: this.chartColors.slice(0, data.chartData.length), | ||||
|                 hoverBackgroundColor: this.chartColors.slice(0, data.chartData.length) | ||||
|               } | ||||
|             ]; | ||||
|             console.log('Updated doughnut chart with data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||
|           } else if (data && data.labels && data.datasets) { | ||||
|             // Backend has already filtered the data, just display it
 | ||||
|             this.noDataAvailable = data.labels.length === 0; | ||||
|             this.doughnutChartLabels = data.labels; | ||||
|             this.doughnutChartData = data.datasets[0]?.data || []; | ||||
|             // Trigger change detection
 | ||||
|             this.doughnutChartData = [...this.doughnutChartData]; | ||||
|             this.doughnutChartData = data.datasets; | ||||
|             console.log('Updated doughnut chart with legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||
|           } else { | ||||
|             console.warn('Received data does not have expected structure', data); | ||||
|             this.noDataAvailable = true; | ||||
|             this.doughnutChartLabels = []; | ||||
|             this.doughnutChartData = []; | ||||
|             this.doughnutChartData = [ | ||||
|               { | ||||
|                 data: [], | ||||
|                 backgroundColor: [], | ||||
|                 hoverBackgroundColor: [] | ||||
|               } | ||||
|           // Reset flag after fetching
 | ||||
|             ]; | ||||
|           } | ||||
|           // Reset flags after fetching
 | ||||
|           this.isFetchingData = false; | ||||
|           this.isLoading = false; | ||||
|         }, | ||||
|         (error) => { | ||||
|           console.error('Error fetching doughnut chart data:', error); | ||||
|           this.noDataAvailable = true; | ||||
|           this.doughnutChartLabels = []; | ||||
|           this.doughnutChartData = []; | ||||
|           // Reset flag after fetching
 | ||||
|           // Reset flags after fetching
 | ||||
|           this.isFetchingData = false; | ||||
|           this.isLoading = false; | ||||
|           // Keep default data in case of error
 | ||||
|         } | ||||
|       ); | ||||
| @ -664,8 +720,9 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|       this.noDataAvailable = true; | ||||
|       this.doughnutChartLabels = []; | ||||
|       this.doughnutChartData = []; | ||||
|       // Reset flag after fetching
 | ||||
|       // Reset flags after fetching
 | ||||
|       this.isFetchingData = false; | ||||
|       this.isLoading = false; | ||||
|     } | ||||
|   } | ||||
|    | ||||
| @ -812,23 +869,37 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|           // Backend has already filtered the data, just display it
 | ||||
|           this.noDataAvailable = data.chartLabels.length === 0; | ||||
|           this.doughnutChartLabels = data.chartLabels; | ||||
|           this.doughnutChartData = data.chartData; | ||||
|           // Trigger change detection
 | ||||
|           this.doughnutChartData = [...this.doughnutChartData]; | ||||
|           this.doughnutChartData = [ | ||||
|             { | ||||
|               data: data.chartData, | ||||
|               backgroundColor: this.chartColors.slice(0, data.chartData.length), | ||||
|               hoverBackgroundColor: this.chartColors.slice(0, data.chartData.length) | ||||
|             } | ||||
|           ]; | ||||
|           console.log('Updated doughnut chart with drilldown data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||
|           // 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.doughnutChartLabels = data.labels; | ||||
|           this.doughnutChartData = data.datasets[0]?.data || []; | ||||
|           // Trigger change detection
 | ||||
|           this.doughnutChartData = [...this.doughnutChartData]; | ||||
|           this.doughnutChartData = data.datasets; | ||||
|           console.log('Updated doughnut chart with drilldown legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||
|           // Set loading state to false
 | ||||
|           this.isLoading = false; | ||||
|         } else { | ||||
|           console.warn('Drilldown received data does not have expected structure', data); | ||||
|           this.noDataAvailable = true; | ||||
|           this.doughnutChartLabels = []; | ||||
|           this.doughnutChartData = []; | ||||
|           this.doughnutChartData = [ | ||||
|             { | ||||
|               data: [], | ||||
|               backgroundColor: [], | ||||
|               hoverBackgroundColor: [] | ||||
|             } | ||||
|           ]; | ||||
|           // Set loading state to false
 | ||||
|           this.isLoading = false; | ||||
|         } | ||||
|       }, | ||||
|       (error) => { | ||||
| @ -836,9 +907,14 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|         this.noDataAvailable = true; | ||||
|         this.doughnutChartLabels = []; | ||||
|         this.doughnutChartData = []; | ||||
|         // Set loading state to false
 | ||||
|         this.isLoading = false; | ||||
|         // Keep current data in case of error
 | ||||
|       } | ||||
|     ); | ||||
|      | ||||
|     // Set loading state
 | ||||
|     this.isLoading = true; | ||||
|   } | ||||
|    | ||||
|   // Reset to original data (go back to base level)
 | ||||
| @ -855,7 +931,7 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck | ||||
|       console.log('Restored original labels'); | ||||
|     } | ||||
|     if (this.originalDoughnutChartData.length > 0) { | ||||
|       this.doughnutChartData = [...this.originalDoughnutChartData]; | ||||
|       this.doughnutChartData = JSON.parse(JSON.stringify(this.originalDoughnutChartData)); | ||||
|       console.log('Restored original data'); | ||||
|     } | ||||
|      | ||||
|  | ||||
| @ -288,6 +288,7 @@ | ||||
|   <div *ngIf="!noDataAvailable" style="position: relative; height: calc(100% - 50px);"> | ||||
|     <canvas baseChart | ||||
|       [datasets]="scatterChartData" | ||||
|       [options]="scatterChartOptions" | ||||
|       [type]="scatterChartType" | ||||
|       (chartHover)="chartHovered($event)" | ||||
|       (chartClick)="chartClicked($event)"> | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; | ||||
| import { ChartData,ChartDataset } from 'chart.js'; | ||||
| import { ChartData,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'; | ||||
| @ -113,6 +113,81 @@ export class ScatterChartComponent implements OnInit, OnChanges { | ||||
|       ], | ||||
|     }, | ||||
|   ]; | ||||
|    | ||||
|   public scatterChartOptions: any = { | ||||
|     responsive: true, | ||||
|     maintainAspectRatio: false, | ||||
|     scales: { | ||||
|       x: { | ||||
|         type: 'linear', | ||||
|         position: 'bottom', | ||||
|         title: { | ||||
|           display: true, | ||||
|           text: 'X Axis' | ||||
|         }, | ||||
|         ticks: { | ||||
|           autoSkip: true, | ||||
|           maxTicksLimit: 10, | ||||
|           callback: function(value: any) { | ||||
|             if (typeof value === 'number') { | ||||
|               // Format large numbers for better readability
 | ||||
|               if (Math.abs(value) >= 1000000) { | ||||
|                 return (value / 1000000).toFixed(1) + 'M'; | ||||
|               } else if (Math.abs(value) >= 1000) { | ||||
|                 return (value / 1000).toFixed(1) + 'K'; | ||||
|               } | ||||
|               return value.toString(); | ||||
|             } | ||||
|             return value; | ||||
|           } | ||||
|         }, | ||||
|         grid: { | ||||
|           display: true, | ||||
|           color: 'rgba(0, 0, 0, 0.1)' | ||||
|         } | ||||
|       }, | ||||
|       y: { | ||||
|         title: { | ||||
|           display: true, | ||||
|           text: 'Y Axis' | ||||
|         }, | ||||
|         ticks: { | ||||
|           autoSkip: true, | ||||
|           maxTicksLimit: 10, | ||||
|           callback: function(value: any) { | ||||
|             if (typeof value === 'number') { | ||||
|               // Format large numbers for better readability
 | ||||
|               if (Math.abs(value) >= 1000000) { | ||||
|                 return (value / 1000000).toFixed(1) + 'M'; | ||||
|               } else if (Math.abs(value) >= 1000) { | ||||
|                 return (value / 1000).toFixed(1) + 'K'; | ||||
|               } | ||||
|               return value.toString(); | ||||
|             } | ||||
|             return value; | ||||
|           } | ||||
|         }, | ||||
|         grid: { | ||||
|           display: true, | ||||
|           color: 'rgba(0, 0, 0, 0.1)' | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     plugins: { | ||||
|       legend: { | ||||
|         display: true, | ||||
|         position: 'top', | ||||
|       }, | ||||
|       tooltip: { | ||||
|         callbacks: { | ||||
|           label: function(context: any) { | ||||
|             return `(${context.parsed.x}, ${context.parsed.y})`; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   public scatterChartType: string = 'scatter'; | ||||
|    | ||||
|   // Multi-layer drilldown state tracking
 | ||||
| @ -457,6 +532,56 @@ export class ScatterChartComponent implements OnInit, OnChanges { | ||||
|       return data; | ||||
|     } | ||||
|      | ||||
|     // Transform the data properly for scatter chart
 | ||||
|     // Assuming labels are x-values and data[0].data are y-values
 | ||||
|     if (labels && data && data.length > 0 && data[0].data) { | ||||
|       const yValues = data[0].data; | ||||
|       const label = data[0].label || 'Dataset 1'; | ||||
|        | ||||
|       // Create scatter points from labels (x) and data (y)
 | ||||
|       const scatterPoints = []; | ||||
|       const minLength = Math.min(labels.length, yValues.length); | ||||
|        | ||||
|       for (let i = 0; i < minLength; i++) { | ||||
|         // Convert to numbers if they're strings
 | ||||
|         const x = typeof labels[i] === 'string' ? parseFloat(labels[i]) : labels[i]; | ||||
|         const y = typeof yValues[i] === 'string' ? parseFloat(yValues[i]) : yValues[i]; | ||||
|          | ||||
|         // Only add valid points
 | ||||
|         if (!isNaN(x) && !isNaN(y)) { | ||||
|           scatterPoints.push({ x, y }); | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       // Generate different colors for each point to avoid all points showing the same color
 | ||||
|       const backgroundColors = []; | ||||
|       const borderColors = []; | ||||
|        | ||||
|       for (let i = 0; i < scatterPoints.length; i++) { | ||||
|         // Generate a color based on the point index for variety
 | ||||
|         const hue = (i * 137.508) % 360; // Use golden angle to spread colors
 | ||||
|         backgroundColors.push(`hsla(${hue}, 70%, 50%, 0.6)`); | ||||
|         borderColors.push(`hsla(${hue}, 70%, 40%, 1)`); | ||||
|       } | ||||
|        | ||||
|       // Create a single dataset with all scatter points
 | ||||
|       const scatterDatasets: ChartDataset[] = [ | ||||
|         { | ||||
|           data: scatterPoints, | ||||
|           label: label, | ||||
|           pointRadius: 8, | ||||
|           pointHoverRadius: 10, | ||||
|           backgroundColor: backgroundColors, | ||||
|           borderColor: borderColors, | ||||
|           borderWidth: 1, | ||||
|           pointHoverBackgroundColor: 'rgba(255, 99, 132, 1)', | ||||
|         } | ||||
|       ]; | ||||
|        | ||||
|       console.log('Transformed scatter data:', scatterDatasets); | ||||
|       return scatterDatasets; | ||||
|     } | ||||
|      | ||||
|     // Otherwise, create a default scatter dataset
 | ||||
|     const scatterDatasets: ChartDataset[] = [ | ||||
|       { | ||||
| @ -587,11 +712,19 @@ export class ScatterChartComponent implements OnInit, OnChanges { | ||||
|             // Scatter charts expect data in the format: {x: number, y: number}
 | ||||
|             this.noDataAvailable = data.chartLabels.length === 0; | ||||
|             this.scatterChartData = this.transformToScatterData(data.chartLabels, data.chartData); | ||||
|              | ||||
|             // Update chart options with axis titles
 | ||||
|             this.updateChartOptionsWithAxisTitles(); | ||||
|              | ||||
|             console.log('Updated scatter chart with data:', this.scatterChartData); | ||||
|           } else if (data && data.labels && data.datasets) { | ||||
|             // Handle the original expected format as fallback
 | ||||
|             this.noDataAvailable = data.labels.length === 0; | ||||
|             this.scatterChartData = data.datasets; | ||||
|              | ||||
|             // Update chart options with axis titles
 | ||||
|             this.updateChartOptionsWithAxisTitles(); | ||||
|              | ||||
|             console.log('Updated scatter chart with legacy data format:', this.scatterChartData); | ||||
|           } else { | ||||
|             console.warn('Scatter chart received data does not have expected structure', data); | ||||
| @ -619,6 +752,20 @@ export class ScatterChartComponent implements OnInit, OnChanges { | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Update chart options with axis titles
 | ||||
|   private updateChartOptionsWithAxisTitles(): void { | ||||
|     // Update X axis title
 | ||||
|     if (this.scatterChartOptions.scales && this.scatterChartOptions.scales.x) { | ||||
|       this.scatterChartOptions.scales.x.title.text = this.xAxis || 'X Axis'; | ||||
|     } | ||||
|      | ||||
|     // Update Y axis title
 | ||||
|     if (this.scatterChartOptions.scales && this.scatterChartOptions.scales.y) { | ||||
|       const yAxisLabel = Array.isArray(this.yAxis) ? this.yAxis[0] : this.yAxis; | ||||
|       this.scatterChartOptions.scales.y.title.text = yAxisLabel || 'Y Axis'; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Fetch drilldown data based on current drilldown level
 | ||||
|   fetchDrilldownData(): void { | ||||
|     console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user