diff --git a/readme.hints.md b/readme.hints.md index ec2dbb9..b77b61c 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -8,8 +8,10 @@ ### dees-chart-area - Fully functional area chart component using ApexCharts - Displays time-series data with gradient fills -- Responsive with ResizeObserver +- Responsive with ResizeObserver (debounced to prevent flicker) - Demo shows CPU and memory usage metrics +- Fixed: Chart now properly respects container boundaries on initial render +- Overflow prevention with proper CSS containment ### dees-chart-log - Server log viewer component (not a chart despite the name) diff --git a/ts_web/elements/dees-chart-area.ts b/ts_web/elements/dees-chart-area.ts index 40b4f57..6ff6bcd 100644 --- a/ts_web/elements/dees-chart-area.ts +++ b/ts_web/elements/dees-chart-area.ts @@ -33,27 +33,40 @@ export class DeesChartArea extends DeesElement { public label: string = 'Untitled Chart'; private resizeObserver: ResizeObserver; + private resizeTimeout: number; constructor() { super(); domtools.elementBasic.setup(); this.resizeObserver = new ResizeObserver((entries) => { - for (let entry of entries) { - if (entry.target.classList.contains('mainbox')) { - this.resizeChart(); // Call resizeChart when the .mainbox size changes - } + // Debounce resize calls to prevent excessive updates + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); } + + this.resizeTimeout = window.setTimeout(() => { + for (let entry of entries) { + if (entry.target.classList.contains('mainbox') && this.chart) { + this.resizeChart(); + } + } + }, 100); // 100ms debounce }); + this.registerStartupFunction(async () => { this.updateComplete.then(() => { const mainbox = this.shadowRoot.querySelector('.mainbox'); if (mainbox) { - this.resizeObserver.observe(mainbox); // Start observing the .mainbox element + this.resizeObserver.observe(mainbox); } }); }); + this.registerGarbageFunction(async () => { + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + } this.resizeObserver.disconnect(); }); } @@ -73,6 +86,7 @@ export class DeesChartArea extends DeesElement { height: 400px; background: #111; border-radius: 8px; + overflow: hidden; } .chartTitle { @@ -82,6 +96,7 @@ export class DeesChartArea extends DeesElement { width: 100%; text-align: center; padding-top: 16px; + z-index: 10; } .chartContainer { position: absolute; @@ -90,6 +105,7 @@ export class DeesChartArea extends DeesElement { bottom: 0px; right: 0px; padding: 32px 16px 16px 0px; + overflow: hidden; } `, ]; @@ -104,7 +120,30 @@ export class DeesChartArea extends DeesElement { } public async firstUpdated() { - const domtoolsInstance = await this.domtoolsPromise; + 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; + var options: ApexCharts.ApexOptions = { series: [ { @@ -129,12 +168,20 @@ export class DeesChartArea extends DeesElement { }, ], chart: { - width: 0, // Adjusted for responsive width - height: 0, // Adjusted for responsive height + width: initialWidth || 100, // Use actual width or fallback + height: initialHeight || 100, // Use actual height or fallback type: 'area', toolbar: { show: false, // This line disables the toolbar }, + animations: { + enabled: true, + speed: 400, + animateGradually: { + enabled: true, + delay: 150 + }, + }, }, dataLabels: { enabled: false, @@ -184,14 +231,11 @@ export class DeesChartArea extends DeesElement { x: { format: 'dd/MM/yy HH:mm', }, - custom: function ({ series, seriesIndex, dataPointIndex, w }) { - // Get the x value - const xValue = w.globals.labels[dataPointIndex]; + custom: function ({ series, dataPointIndex, w }: any) { // Iterate through each series and get its value let tooltipContent = `
`; - tooltipContent += ``; // `Time: ${xValue}
`; - series.forEach((s, index) => { + series.forEach((s: number[], index: number) => { const label = w.globals.seriesNames[index]; // Get series label const value = s[dataPointIndex]; // Get value at data point tooltipContent += `${label}: ${value} Mbps
`; @@ -235,15 +279,25 @@ export class DeesChartArea extends DeesElement { }; 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(); } public async resizeChart() { + if (!this.chart) { + return; + } + const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox'); const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer'); + + if (!mainbox || !chartContainer) { + return; + } // Get computed style of the element - const styleMainbox = window.getComputedStyle(mainbox); const styleChartContainer = window.getComputedStyle(chartContainer); // Extract padding values diff --git a/ts_web/elements/dees-chart-log.ts b/ts_web/elements/dees-chart-log.ts index 8678056..ebab4b9 100644 --- a/ts_web/elements/dees-chart-log.ts +++ b/ts_web/elements/dees-chart-log.ts @@ -5,8 +5,6 @@ import { customElement, html, property, - state, - type CSSResult, type TemplateResult, } from '@design.estate/dees-element'; @@ -259,7 +257,7 @@ export class DeesChartLog extends DeesElement { } public async firstUpdated() { - const domtoolsInstance = await this.domtoolsPromise; + await this.domtoolsPromise; this.logContainer = this.shadowRoot.querySelector('.logContainer'); // Initialize with demo server logs