- Implemented DeesInputFileupload component with file upload functionality, including drag-and-drop support, file previews, and clear all option. - Developed DeesInputRichtext component featuring a rich text editor with a formatting toolbar, link management, and word count display. - Created demo for DeesInputRichtext showcasing various use cases including basic editing, placeholder text, different heights, and disabled state. - Added styles for both components to ensure a consistent and user-friendly interface. - Introduced types for toolbar buttons in the rich text editor for better type safety and maintainability.
		
			
				
	
	
		
			668 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			668 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {
 | |
|   DeesElement,
 | |
|   customElement,
 | |
|   property,
 | |
|   state,
 | |
|   type TemplateResult,
 | |
| } from '@design.estate/dees-element';
 | |
| 
 | |
| import * as domtools from '@design.estate/dees-domtools';
 | |
| import { demoFunc } from './demo.js';
 | |
| import { chartAreaStyles } from './styles.js';
 | |
| import { renderChartArea } from './template.js';
 | |
| 
 | |
| import ApexCharts from 'apexcharts';
 | |
| 
 | |
| declare global {
 | |
|   interface HTMLElementTagNameMap {
 | |
|     'dees-chart-area': DeesChartArea;
 | |
|   }
 | |
| }
 | |
| 
 | |
| @customElement('dees-chart-area')
 | |
| export class DeesChartArea extends DeesElement {
 | |
|   public static demo = demoFunc;
 | |
| 
 | |
|   // instance
 | |
|   @state()
 | |
|   public chart: ApexCharts;
 | |
| 
 | |
|   @property()
 | |
|   public label: string = 'Untitled Chart';
 | |
| 
 | |
|   @property({ type: Array })
 | |
|   public series: ApexAxisChartSeries = [];
 | |
|   
 | |
|   // Override getter to return internal chart data
 | |
|   get chartSeries(): ApexAxisChartSeries {
 | |
|     return this.internalChartData.length > 0 ? this.internalChartData : this.series;
 | |
|   }
 | |
| 
 | |
|   @property({ attribute: false })
 | |
|   public yAxisFormatter: (value: number) => string = (val) => `${val} Mbps`;
 | |
|   
 | |
|   @property({ type: Number })
 | |
|   public rollingWindow: number = 0; // 0 means no rolling window
 | |
|   
 | |
|   @property({ type: Boolean })
 | |
|   public realtimeMode: boolean = false;
 | |
|   
 | |
|   @property({ type: String })
 | |
|   public yAxisScaling: 'fixed' | 'dynamic' | 'percentage' = 'dynamic';
 | |
|   
 | |
|   @property({ type: Number })
 | |
|   public yAxisMax: number = 100; // Used when yAxisScaling is 'fixed' or 'percentage'
 | |
|   
 | |
|   @property({ type: Number })
 | |
|   public autoScrollInterval: number = 1000; // Auto-scroll interval in milliseconds (0 to disable)
 | |
| 
 | |
|   private resizeObserver: ResizeObserver;
 | |
|   private resizeTimeout: number;
 | |
|   private internalChartData: ApexAxisChartSeries = [];
 | |
|   private autoScrollTimer: number | null = null;
 | |
|   private readonly DEBUG_RESIZE = false; // Set to true to enable resize debugging
 | |
|   
 | |
|   // Chart color schemes
 | |
|   private readonly CHART_COLORS = {
 | |
|     dark: [
 | |
|       'hsl(217.2 91.2% 59.8%)', // Blue
 | |
|       'hsl(173.4 80.4% 40%)',   // Teal
 | |
|       'hsl(280.3 87.4% 66.7%)', // Purple
 | |
|       'hsl(24.6 95% 53.1%)',    // Orange
 | |
|     ],
 | |
|     light: [
 | |
|       'hsl(222.2 47.4% 51.2%)', // Blue (shadcn primary)
 | |
|       'hsl(142.1 76.2% 36.3%)', // Green (shadcn success)
 | |
|       'hsl(280.3 47.7% 50.2%)', // Purple (muted)
 | |
|       'hsl(20.5 90.2% 48.2%)',  // Orange (shadcn destructive variant)
 | |
|     ]
 | |
|   };
 | |
| 
 | |
|   constructor() {
 | |
|     super();
 | |
|     domtools.elementBasic.setup();
 | |
| 
 | |
|     this.resizeObserver = new ResizeObserver((entries) => {
 | |
|       // Debounce resize calls to prevent excessive updates
 | |
|       if (this.resizeTimeout) {
 | |
|         clearTimeout(this.resizeTimeout);
 | |
|       }
 | |
|       
 | |
|       this.resizeTimeout = window.setTimeout(() => {
 | |
|         // Simply resize if we have a chart, since we're only observing the mainbox
 | |
|         if (this.chart) {
 | |
|           // Log resize event for debugging
 | |
|           if (this.DEBUG_RESIZE && entries.length > 0) {
 | |
|             const entry = entries[0];
 | |
|             console.log('DeesChartArea - Resize detected:', {
 | |
|               width: entry.contentRect.width,
 | |
|               height: entry.contentRect.height
 | |
|             });
 | |
|           }
 | |
|           this.resizeChart();
 | |
|         }
 | |
|       }, 100); // 100ms debounce
 | |
|     });
 | |
|     
 | |
|     // Note: ResizeObserver is now set up after chart initialization in firstUpdated()
 | |
|     // to ensure proper timing and avoid race conditions
 | |
|     
 | |
|     this.registerGarbageFunction(async () => {
 | |
|       if (this.resizeTimeout) {
 | |
|         clearTimeout(this.resizeTimeout);
 | |
|       }
 | |
|       if (this.resizeObserver) {
 | |
|         this.resizeObserver.disconnect();
 | |
|       }
 | |
|       this.stopAutoScroll();
 | |
|       
 | |
|       // Critical: Destroy chart instance to prevent memory leak
 | |
|       if (this.chart) {
 | |
|         try {
 | |
|           this.chart.destroy();
 | |
|           this.chart = null;
 | |
|         } catch (error) {
 | |
|           console.error('Error destroying chart:', error);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   }
 | |
|   
 | |
|   public async connectedCallback() {
 | |
|     super.connectedCallback();
 | |
|     
 | |
|     // Trigger resize when element is connected to DOM
 | |
|     // This helps with dynamically added charts
 | |
|     if (this.chart) {
 | |
|       // Wait a frame for layout to settle
 | |
|       await new Promise(resolve => requestAnimationFrame(resolve));
 | |
|       await this.resizeChart();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public static styles = chartAreaStyles;
 | |
| 
 | |
|   public render(): TemplateResult {
 | |
|     return renderChartArea(this);
 | |
|   }
 | |
| 
 | |
| 
 | |
| 
 | |
|   public async firstUpdated() {
 | |
|     await this.domtoolsPromise;
 | |
|     
 | |
|     // Wait for next animation frame to ensure layout is complete
 | |
|     await new Promise(resolve => requestAnimationFrame(resolve));
 | |
|     
 | |
|     // Get actual dimensions of the container
 | |
|     const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
 | |
|     const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
 | |
|     
 | |
|     if (!mainbox || !chartContainer) {
 | |
|       console.error('Chart containers not found');
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     // Calculate initial dimensions
 | |
|     const styleChartContainer = window.getComputedStyle(chartContainer);
 | |
|     const paddingTop = parseInt(styleChartContainer.paddingTop, 10);
 | |
|     const paddingBottom = parseInt(styleChartContainer.paddingBottom, 10);
 | |
|     const paddingLeft = parseInt(styleChartContainer.paddingLeft, 10);
 | |
|     const paddingRight = parseInt(styleChartContainer.paddingRight, 10);
 | |
|     
 | |
|     const initialWidth = mainbox.clientWidth - paddingLeft - paddingRight;
 | |
|     const initialHeight = mainbox.offsetHeight - paddingTop - paddingBottom;
 | |
|     
 | |
|     // Use provided series data or default demo data
 | |
|     const chartSeries = this.series.length > 0 ? this.series : [
 | |
|       {
 | |
|         name: 'cpu',
 | |
|         data: [
 | |
|           { x: '2025-01-15T03:00:00', y: 25 },
 | |
|           { x: '2025-01-15T07:00:00', y: 30 },
 | |
|           { x: '2025-01-15T11:00:00', y: 20 },
 | |
|           { x: '2025-01-15T15:00:00', y: 35 },
 | |
|           { x: '2025-01-15T19:00:00', y: 25 },
 | |
|         ],
 | |
|       },
 | |
|       {
 | |
|         name: 'memory',
 | |
|         data: [
 | |
|           { x: '2025-01-15T03:00:00', y: 10 },
 | |
|           { x: '2025-01-15T07:00:00', y: 12 },
 | |
|           { x: '2025-01-15T11:00:00', y: 10 },
 | |
|           { x: '2025-01-15T15:00:00', y: 30 },
 | |
|           { x: '2025-01-15T19:00:00', y: 40 },
 | |
|         ],
 | |
|       },
 | |
|     ];
 | |
|     
 | |
|     // Store internal data
 | |
|     this.internalChartData = chartSeries;
 | |
|     
 | |
|     // Get current theme
 | |
|     const isDark = !this.goBright;
 | |
|     const theme = isDark ? 'dark' : 'light';
 | |
|     
 | |
|     var options: ApexCharts.ApexOptions = {
 | |
|       series: chartSeries,
 | |
|       chart: {
 | |
|         width: initialWidth || 100, // Use actual width or fallback
 | |
|         height: initialHeight || 100, // Use actual height or fallback
 | |
|         type: 'area',
 | |
|         background: 'transparent', // Transparent background to inherit from container
 | |
|         toolbar: {
 | |
|           show: false, // This line disables the toolbar
 | |
|         },
 | |
|         animations: {
 | |
|           enabled: !this.realtimeMode, // Disable animations in realtime mode
 | |
|           speed: 400,
 | |
|           animateGradually: {
 | |
|             enabled: false, // Disable gradual animation for cleaner updates
 | |
|             delay: 0
 | |
|           },
 | |
|           dynamicAnimation: {
 | |
|             enabled: !this.realtimeMode,
 | |
|             speed: 350
 | |
|           }
 | |
|         },
 | |
|         zoom: {
 | |
|           enabled: false, // Disable zoom for cleaner interaction
 | |
|         },
 | |
|         selection: {
 | |
|           enabled: false, // Disable selection
 | |
|         },
 | |
|       },
 | |
|       dataLabels: {
 | |
|         enabled: false,
 | |
|       },
 | |
|       stroke: {
 | |
|         width: 2,
 | |
|         curve: 'smooth',
 | |
|       },
 | |
|       xaxis: {
 | |
|         type: 'datetime', // Time-series data
 | |
|         labels: {
 | |
|           format: 'HH:mm:ss', // Time formatting with seconds
 | |
|           datetimeUTC: false,
 | |
|           style: {
 | |
|             colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'], // Label color
 | |
|             fontSize: '12px',
 | |
|             fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
 | |
|             fontWeight: '400',
 | |
|           },
 | |
|         },
 | |
|         axisBorder: {
 | |
|           show: false, // Hide x-axis border
 | |
|         },
 | |
|         axisTicks: {
 | |
|           show: false, // Hide x-axis ticks
 | |
|         },
 | |
|       },
 | |
|       yaxis: {
 | |
|         min: 0,
 | |
|         max: this.yAxisScaling === 'dynamic' ? undefined : this.yAxisMax,
 | |
|         labels: {
 | |
|           formatter: this.yAxisFormatter,
 | |
|           style: {
 | |
|             colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'], // Label color
 | |
|             fontSize: '12px',
 | |
|             fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
 | |
|             fontWeight: '400',
 | |
|           },
 | |
|         },
 | |
|         axisBorder: {
 | |
|           show: false, // Hide y-axis border
 | |
|         },
 | |
|         axisTicks: {
 | |
|           show: false, // Hide y-axis ticks
 | |
|         },
 | |
|       },
 | |
|       tooltip: {
 | |
|         shared: true, // Enables the tooltip to display across series
 | |
|         intersect: false, // Allows hovering anywhere on the chart
 | |
|         followCursor: true, // Makes tooltip follow mouse even between points
 | |
|         x: {
 | |
|           format: 'dd/MM/yy HH:mm',
 | |
|         },
 | |
|         custom: ({ series, dataPointIndex, w }: any) => {
 | |
|           // Iterate through each series and get its value
 | |
|           // Note: We can't access component instance here, so we'll use w.config.theme.mode
 | |
|           const currentTheme = w.config.theme.mode;
 | |
|           const isDarkMode = currentTheme === 'dark';
 | |
|           const bgColor = isDarkMode ? 'hsl(0 0% 9%)' : 'hsl(0 0% 100%)';
 | |
|           const textColor = isDarkMode ? 'hsl(0 0% 95%)' : 'hsl(0 0% 9%)';
 | |
|           const borderColor = isDarkMode ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 89.8%)';
 | |
|           
 | |
|           // Get formatter from chart config
 | |
|           const formatter = w.config.yaxis[0]?.labels?.formatter || ((val: number) => val.toString());
 | |
|           
 | |
|           let tooltipContent = `<div style="padding: 12px; background: ${bgColor}; color: ${textColor}; border-radius: 6px; box-shadow: 0 2px 8px 0 hsl(0 0% 0% / ${isDarkMode ? '0.2' : '0.1'}); border: 1px solid ${borderColor};font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 12px;">`;
 | |
| 
 | |
|           series.forEach((s: number[], index: number) => {
 | |
|             const label = w.globals.seriesNames[index]; // Get series label
 | |
|             const value = s[dataPointIndex]; // Get value at data point
 | |
|             const color = w.globals.colors[index];
 | |
|             const formattedValue = formatter(value);
 | |
|             tooltipContent += `<div style="display: flex; align-items: center; gap: 8px; margin: ${index > 0 ? '6px' : '0'} 0;">
 | |
|               <span style="display: inline-block; width: 10px; height: 10px; background: ${color}; border-radius: 2px;"></span>
 | |
|               <span style="font-weight: 500;">${label}:</span>
 | |
|               <span style="margin-left: auto; font-weight: 600;">${formattedValue}</span>
 | |
|             </div>`;
 | |
|           });
 | |
| 
 | |
|           tooltipContent += `</div>`;
 | |
|           return tooltipContent;
 | |
|         },
 | |
|       },
 | |
|       grid: {
 | |
|         xaxis: {
 | |
|           lines: {
 | |
|             show: false, // Hide vertical grid lines for cleaner look
 | |
|           },
 | |
|         },
 | |
|         yaxis: {
 | |
|           lines: {
 | |
|             show: true,
 | |
|           },
 | |
|         },
 | |
|         borderColor: isDark ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 94%)', // Very subtle grid lines
 | |
|         strokeDashArray: 0, // Solid line
 | |
|         padding: {
 | |
|           top: 10,
 | |
|           right: 20,
 | |
|           bottom: 10,
 | |
|           left: 20,
 | |
|         },
 | |
|       },
 | |
|       fill: {
 | |
|         type: 'gradient', // Gradient fill for the area
 | |
|         gradient: {
 | |
|           shade: isDark ? 'dark' : 'light',
 | |
|           type: 'vertical',
 | |
|           shadeIntensity: 0.1,
 | |
|           opacityFrom: isDark ? 0.2 : 0.3,
 | |
|           opacityTo: 0,
 | |
|           stops: [0, 100],
 | |
|         },
 | |
|       },
 | |
|       colors: isDark ? this.CHART_COLORS.dark : this.CHART_COLORS.light,
 | |
|       theme: {
 | |
|         mode: theme,
 | |
|       },
 | |
|     };
 | |
|     
 | |
|     try {
 | |
|       this.chart = new ApexCharts(this.shadowRoot.querySelector('.chartContainer'), options);
 | |
|       await this.chart.render();
 | |
|       
 | |
|       // Give the chart a moment to fully initialize before resizing
 | |
|       await new Promise(resolve => setTimeout(resolve, 100));
 | |
|       await this.resizeChart();
 | |
|       
 | |
|       // Ensure resize observer is watching the mainbox
 | |
|       const mainbox = this.shadowRoot.querySelector('.mainbox');
 | |
|       if (mainbox && this.resizeObserver) {
 | |
|         // Disconnect any previous observations
 | |
|         this.resizeObserver.disconnect();
 | |
|         // Start observing the mainbox
 | |
|         this.resizeObserver.observe(mainbox);
 | |
|         if (this.DEBUG_RESIZE) {
 | |
|           console.log('DeesChartArea - ResizeObserver attached to mainbox');
 | |
|         }
 | |
|       }
 | |
|     } catch (error) {
 | |
|       console.error('Failed to initialize chart:', error);
 | |
|       // Optionally, you could set an error state here
 | |
|       // this.chartState = 'error';
 | |
|       // this.errorMessage = 'Failed to initialize chart';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public async updated(changedProperties: Map<string, any>) {
 | |
|     super.updated(changedProperties);
 | |
|     
 | |
|     // Update chart theme when goBright changes
 | |
|     if (changedProperties.has('goBright') && this.chart) {
 | |
|       await this.updateChartTheme();
 | |
|     }
 | |
|     
 | |
|     // Update chart if series data changes
 | |
|     if (changedProperties.has('series') && this.chart && this.series.length > 0) {
 | |
|       await this.updateSeries(this.series);
 | |
|     }
 | |
|     
 | |
|     // Update y-axis formatter if it changes
 | |
|     if (changedProperties.has('yAxisFormatter') && this.chart) {
 | |
|       await this.chart.updateOptions({
 | |
|         yaxis: {
 | |
|           labels: {
 | |
|             formatter: this.yAxisFormatter,
 | |
|           },
 | |
|         },
 | |
|       });
 | |
|     }
 | |
|     
 | |
|     // Handle realtime mode changes
 | |
|     if (changedProperties.has('realtimeMode') && this.chart) {
 | |
|       await this.chart.updateOptions({
 | |
|         chart: {
 | |
|           animations: {
 | |
|             enabled: !this.realtimeMode,
 | |
|             speed: 400,
 | |
|             animateGradually: {
 | |
|               enabled: false,
 | |
|               delay: 0
 | |
|             },
 | |
|             dynamicAnimation: {
 | |
|               enabled: !this.realtimeMode,
 | |
|               speed: 350
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|       
 | |
|       // Start/stop auto-scroll based on realtime mode
 | |
|       if (this.realtimeMode && this.rollingWindow > 0 && this.autoScrollInterval > 0) {
 | |
|         this.startAutoScroll();
 | |
|       } else {
 | |
|         this.stopAutoScroll();
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     // Handle auto-scroll interval changes
 | |
|     if (changedProperties.has('autoScrollInterval') && this.chart) {
 | |
|       this.stopAutoScroll();
 | |
|       if (this.realtimeMode && this.rollingWindow > 0 && this.autoScrollInterval > 0) {
 | |
|         this.startAutoScroll();
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     // Handle y-axis scaling changes
 | |
|     if ((changedProperties.has('yAxisScaling') || changedProperties.has('yAxisMax')) && this.chart) {
 | |
|       await this.chart.updateOptions({
 | |
|         yaxis: {
 | |
|           min: 0,
 | |
|           max: this.yAxisScaling === 'dynamic' ? undefined : this.yAxisMax
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public async updateSeries(newSeries: ApexAxisChartSeries, animate: boolean = true) {
 | |
|     if (!this.chart) {
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     try {
 | |
|       // Store the new data first
 | |
|       this.internalChartData = newSeries;
 | |
|       
 | |
|       // Handle rolling window if enabled
 | |
|       if (this.rollingWindow > 0 && this.realtimeMode) {
 | |
|         const now = Date.now();
 | |
|         const cutoffTime = now - this.rollingWindow;
 | |
|         
 | |
|         // Filter data to only include points within the rolling window
 | |
|         const filteredSeries = newSeries.map(series => ({
 | |
|           name: series.name,
 | |
|           data: (series.data as any[]).filter(point => {
 | |
|             if (typeof point === 'object' && point !== null && 'x' in point) {
 | |
|               return new Date(point.x).getTime() > cutoffTime;
 | |
|             }
 | |
|             return false;
 | |
|           })
 | |
|         }));
 | |
|         
 | |
|         // Only update if we have data
 | |
|         if (filteredSeries.some(s => s.data.length > 0)) {
 | |
|           // Handle y-axis scaling first
 | |
|           if (this.yAxisScaling === 'dynamic') {
 | |
|             const allValues = filteredSeries.flatMap(s => (s.data as any[]).map(d => d.y));
 | |
|             if (allValues.length > 0) {
 | |
|               const maxValue = Math.max(...allValues);
 | |
|               const dynamicMax = Math.ceil(maxValue * 1.1);
 | |
|               await this.chart.updateOptions({
 | |
|                 yaxis: {
 | |
|                   min: 0,
 | |
|                   max: dynamicMax
 | |
|                 }
 | |
|               }, false, false);
 | |
|             }
 | |
|           }
 | |
|           
 | |
|           await this.chart.updateSeries(filteredSeries, false);
 | |
|         }
 | |
|       } else {
 | |
|         await this.chart.updateSeries(newSeries, animate);
 | |
|       }
 | |
|     } catch (error) {
 | |
|       console.error('Failed to update chart series:', error);
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // Update just the x-axis for smooth scrolling in realtime mode
 | |
|   // Public for advanced usage in demos, but typically handled automatically
 | |
|   public async updateTimeWindow() {
 | |
|     if (!this.chart || this.rollingWindow <= 0) {
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     const now = Date.now();
 | |
|     const cutoffTime = now - this.rollingWindow;
 | |
|     
 | |
|     await this.chart.updateOptions({
 | |
|       xaxis: {
 | |
|         min: cutoffTime,
 | |
|         max: now,
 | |
|         labels: {
 | |
|           format: 'HH:mm:ss',
 | |
|           datetimeUTC: false,
 | |
|           style: {
 | |
|             colors: [!this.goBright ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'],
 | |
|             fontSize: '12px',
 | |
|             fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
 | |
|             fontWeight: '400',
 | |
|           },
 | |
|         },
 | |
|         tickAmount: 6,
 | |
|       }
 | |
|     }, false, false);
 | |
|   }
 | |
| 
 | |
|   public async appendData(newData: { data: any[] }[]) {
 | |
|     if (!this.chart) {
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     // Use ApexCharts' appendData method for smoother real-time updates
 | |
|     this.chart.appendData(newData);
 | |
|   }
 | |
|   
 | |
|   public async updateOptions(options: ApexCharts.ApexOptions, redrawPaths?: boolean, animate?: boolean) {
 | |
|     if (!this.chart) {
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     return this.chart.updateOptions(options, redrawPaths, animate);
 | |
|   }
 | |
| 
 | |
|   public async resizeChart() {
 | |
|     if (!this.chart) {
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     if (this.DEBUG_RESIZE) {
 | |
|       console.log('DeesChartArea - resizeChart called');
 | |
|     }
 | |
|     
 | |
|     try {
 | |
|       const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
 | |
|       const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
 | |
|       
 | |
|       if (!mainbox || !chartContainer) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // Force layout recalculation
 | |
|       void mainbox.offsetHeight;
 | |
| 
 | |
|       // Get computed style of the element
 | |
|       const styleChartContainer = window.getComputedStyle(chartContainer);
 | |
| 
 | |
|       // Extract padding values
 | |
|       const paddingTop = parseInt(styleChartContainer.paddingTop, 10);
 | |
|       const paddingBottom = parseInt(styleChartContainer.paddingBottom, 10);
 | |
|       const paddingLeft = parseInt(styleChartContainer.paddingLeft, 10);
 | |
|       const paddingRight = parseInt(styleChartContainer.paddingRight, 10);
 | |
| 
 | |
|       // Calculate the actual width and height to use, subtracting padding
 | |
|       const actualWidth = mainbox.clientWidth - paddingLeft - paddingRight;
 | |
|       const actualHeight = mainbox.offsetHeight - paddingTop - paddingBottom;
 | |
|       
 | |
|       // Validate dimensions
 | |
|       if (actualWidth > 0 && actualHeight > 0) {
 | |
|         if (this.DEBUG_RESIZE) {
 | |
|           console.log('DeesChartArea - Updating chart dimensions:', {
 | |
|             width: actualWidth,
 | |
|             height: actualHeight
 | |
|           });
 | |
|         }
 | |
|         
 | |
|         await this.chart.updateOptions({
 | |
|           chart: {
 | |
|             width: actualWidth,
 | |
|             height: actualHeight,
 | |
|           },
 | |
|         }, true, false); // Redraw paths but don't animate
 | |
|       }
 | |
|     } catch (error) {
 | |
|       console.error('Failed to resize chart:', error);
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Manually trigger a chart resize. Useful when automatic detection doesn't work.
 | |
|    * This is a convenience method that can be called from outside the component.
 | |
|    */
 | |
|   public async forceResize() {
 | |
|     await this.resizeChart();
 | |
|   }
 | |
|   
 | |
|   private startAutoScroll() {
 | |
|     if (this.autoScrollTimer) {
 | |
|       return; // Already running
 | |
|     }
 | |
|     
 | |
|     this.autoScrollTimer = window.setInterval(() => {
 | |
|       this.updateTimeWindow();
 | |
|     }, this.autoScrollInterval);
 | |
|   }
 | |
|   
 | |
|   private stopAutoScroll() {
 | |
|     if (this.autoScrollTimer) {
 | |
|       window.clearInterval(this.autoScrollTimer);
 | |
|       this.autoScrollTimer = null;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   private async updateChartTheme() {
 | |
|     if (!this.chart) {
 | |
|       return;
 | |
|     }
 | |
|     
 | |
|     const isDark = !this.goBright;
 | |
|     const theme = isDark ? 'dark' : 'light';
 | |
|     
 | |
|     await this.chart.updateOptions({
 | |
|       theme: {
 | |
|         mode: theme,
 | |
|       },
 | |
|       colors: isDark ? this.CHART_COLORS.dark : this.CHART_COLORS.light,
 | |
|       xaxis: {
 | |
|         labels: {
 | |
|           style: {
 | |
|             colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'],
 | |
|           },
 | |
|         },
 | |
|       },
 | |
|       yaxis: {
 | |
|         labels: {
 | |
|           style: {
 | |
|             colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'],
 | |
|           },
 | |
|         },
 | |
|       },
 | |
|       grid: {
 | |
|         borderColor: isDark ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 94%)',
 | |
|       },
 | |
|       fill: {
 | |
|         gradient: {
 | |
|           shade: isDark ? 'dark' : 'light',
 | |
|           opacityFrom: isDark ? 0.2 : 0.3,
 | |
|         },
 | |
|       },
 | |
|     });
 | |
|   }
 | |
| }
 |