diff --git a/ts_web/elements/dees-chart-area.demo.ts b/ts_web/elements/dees-chart-area.demo.ts
index 97aa610..694ed38 100644
--- a/ts_web/elements/dees-chart-area.demo.ts
+++ b/ts_web/elements/dees-chart-area.demo.ts
@@ -1,4 +1,4 @@
-import { html, css } from '@design.estate/dees-element';
+import { html, css, cssManager } from '@design.estate/dees-element';
import type { DeesChartArea } from './dees-chart-area.js';
import '@design.estate/dees-wcctools/demotools';
@@ -402,7 +402,7 @@ export const demoFunc = () => {
${css`
.demoBox {
position: relative;
- background: #000000;
+ background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
height: 100%;
width: 100%;
padding: 40px;
@@ -425,9 +425,9 @@ export const demoFunc = () => {
}
.info {
- color: #666;
- font-size: 11px;
- font-family: 'Geist Sans', sans-serif;
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
+ font-size: 12px;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geist Sans', sans-serif;
text-align: center;
margin-top: 8px;
}
diff --git a/ts_web/elements/dees-chart-area.ts b/ts_web/elements/dees-chart-area.ts
index 13e70ce..4c0c453 100644
--- a/ts_web/elements/dees-chart-area.ts
+++ b/ts_web/elements/dees-chart-area.ts
@@ -61,6 +61,23 @@ export class DeesChartArea extends DeesElement {
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();
@@ -73,46 +90,72 @@ export class DeesChartArea extends DeesElement {
}
this.resizeTimeout = window.setTimeout(() => {
- for (let entry of entries) {
- if (entry.target.classList.contains('mainbox') && this.chart) {
- this.resizeChart();
+ // 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
});
- this.registerStartupFunction(async () => {
- this.updateComplete.then(() => {
- const mainbox = this.shadowRoot.querySelector('.mainbox');
- if (mainbox) {
- this.resizeObserver.observe(mainbox);
- }
- });
- });
+ // 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);
}
- this.resizeObserver.disconnect();
+ 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 = [
cssManager.defaultStyles,
css`
:host {
- font-family: 'Geist Sans', sans-serif;
- color: #ccc;
- font-weight: 600;
- font-size: 12px;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
+ color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
+ font-weight: 400;
+ font-size: 14px;
}
.mainbox {
position: relative;
width: 100%;
height: 400px;
- background: #111;
+ background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
+ border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 8px;
overflow: hidden;
}
@@ -122,9 +165,13 @@ export class DeesChartArea extends DeesElement {
top: 0;
left: 0;
width: 100%;
- text-align: center;
- padding-top: 16px;
+ text-align: left;
+ padding: 16px 24px;
z-index: 10;
+ font-size: 14px;
+ font-weight: 500;
+ letter-spacing: -0.01em;
+ color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
}
.chartContainer {
position: absolute;
@@ -132,8 +179,22 @@ export class DeesChartArea extends DeesElement {
left: 0px;
bottom: 0px;
right: 0px;
- padding: 32px 16px 16px 0px;
+ padding: 44px 16px 16px 0px;
overflow: hidden;
+ background: transparent; /* Ensure container doesn't override chart background */
+ }
+
+ /* ApexCharts theme overrides */
+ .apexcharts-canvas {
+ background: transparent !important;
+ }
+
+ .apexcharts-inner {
+ background: transparent !important;
+ }
+
+ .apexcharts-graphical {
+ background: transparent !important;
}
`,
];
@@ -199,12 +260,17 @@ export class DeesChartArea extends DeesElement {
// 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
},
@@ -220,12 +286,18 @@ export class DeesChartArea extends DeesElement {
speed: 350
}
},
+ zoom: {
+ enabled: false, // Disable zoom for cleaner interaction
+ },
+ selection: {
+ enabled: false, // Disable selection
+ },
},
dataLabels: {
enabled: false,
},
stroke: {
- width: 1,
+ width: 2,
curve: 'smooth',
},
xaxis: {
@@ -234,8 +306,10 @@ export class DeesChartArea extends DeesElement {
format: 'HH:mm:ss', // Time formatting with seconds
datetimeUTC: false,
style: {
- colors: '#9e9e9e', // Label color
- fontSize: '11px',
+ 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: {
@@ -251,8 +325,10 @@ export class DeesChartArea extends DeesElement {
labels: {
formatter: this.yAxisFormatter,
style: {
- colors: '#9e9e9e', // Label color
+ 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: {
@@ -269,14 +345,30 @@ export class DeesChartArea extends DeesElement {
x: {
format: 'dd/MM/yy HH:mm',
},
- custom: function ({ series, dataPointIndex, w }: any) {
+ custom: ({ series, dataPointIndex, w }: any) => {
// Iterate through each series and get its value
- let tooltipContent = `
`;
+ // 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 = `
`;
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
`;
+ const color = w.globals.colors[index];
+ const formattedValue = formatter(value);
+ tooltipContent += `
+
+ ${label}:
+ ${formattedValue}
+
`;
});
tooltipContent += `
`;
@@ -286,7 +378,7 @@ export class DeesChartArea extends DeesElement {
grid: {
xaxis: {
lines: {
- show: true, // This enables the grid lines along the x-axis
+ show: false, // Hide vertical grid lines for cleaner look
},
},
yaxis: {
@@ -294,38 +386,67 @@ export class DeesChartArea extends DeesElement {
show: true,
},
},
- borderColor: '#333', // Set the color of the grid lines
+ borderColor: isDark ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 94%)', // Very subtle grid lines
strokeDashArray: 0, // Solid line
- row: {
- colors: [], // This can be used to alternate the shading of the horizontal rows
- opacity: 0.1,
- },
- column: {
- colors: [], // For vertical column bands, not needed here but available for customization
- opacity: 0.1,
+ padding: {
+ top: 10,
+ right: 20,
+ bottom: 10,
+ left: 20,
},
},
fill: {
type: 'gradient', // Gradient fill for the area
gradient: {
- shade: 'dark',
+ shade: isDark ? 'dark' : 'light',
type: 'vertical',
- gradientToColors: ['#9c27b0'], // Gradient color ending
+ 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,
+ },
};
- 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();
+ 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
) {
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);
@@ -393,50 +514,55 @@ export class DeesChartArea extends DeesElement {
return;
}
- // 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;
+ try {
+ // Store the new data first
+ this.internalChartData = newSeries;
- // 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);
- }
- }
+ // Handle rolling window if enabled
+ if (this.rollingWindow > 0 && this.realtimeMode) {
+ const now = Date.now();
+ const cutoffTime = now - this.rollingWindow;
- this.chart.updateSeries(filteredSeries, false);
+ // 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);
}
- } else {
- this.chart.updateSeries(newSeries, animate);
+ } catch (error) {
+ console.error('Failed to update chart series:', error);
}
}
- // New method to update just the x-axis for smooth scrolling
+ // 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;
@@ -453,8 +579,10 @@ export class DeesChartArea extends DeesElement {
format: 'HH:mm:ss',
datetimeUTC: false,
style: {
- colors: '#9e9e9e',
- fontSize: '11px',
+ 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,
@@ -484,32 +612,61 @@ export class DeesChartArea extends DeesElement {
return;
}
- const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
- const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
-
- if (!mainbox || !chartContainer) {
- 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;
+ }
- // Get computed style of the element
- const styleChartContainer = window.getComputedStyle(chartContainer);
+ // Force layout recalculation
+ void mainbox.offsetHeight;
- // 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);
+ // Get computed style of the element
+ const styleChartContainer = window.getComputedStyle(chartContainer);
- // Calculate the actual width and height to use, subtracting padding
- const actualWidth = mainbox.clientWidth - paddingLeft - paddingRight;
- const actualHeight = mainbox.offsetHeight - paddingTop - paddingBottom;
+ // 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);
- await this.chart.updateOptions({
- chart: {
- width: actualWidth,
- height: actualHeight,
- },
- });
+ // 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() {
@@ -528,4 +685,43 @@ export class DeesChartArea extends DeesElement {
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,
+ },
+ },
+ });
+ }
}