Files
dees-catalog/ts_web/elements/dees-chart-area.ts

376 lines
10 KiB
TypeScript
Raw Normal View History

2024-02-03 11:26:15 +01:00
import {
DeesElement,
css,
cssManager,
customElement,
html,
property,
2024-02-03 14:42:20 +01:00
state,
2024-02-03 11:26:15 +01:00
type TemplateResult,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-chart-area.demo.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;
2024-02-03 14:42:20 +01:00
// instance
@state()
public chart: ApexCharts;
@property()
2024-02-03 14:49:25 +01:00
public label: string = 'Untitled Chart';
2024-02-03 14:42:20 +01:00
@property({ type: Array })
public series: ApexAxisChartSeries = [];
@property({ attribute: false })
public yAxisFormatter: (value: number) => string = (val) => `${val} Mbps`;
2024-02-05 13:11:05 +01:00
private resizeObserver: ResizeObserver;
private resizeTimeout: number;
2024-02-05 13:11:05 +01:00
2024-02-03 11:26:15 +01:00
constructor() {
super();
domtools.elementBasic.setup();
2024-02-05 13:11:05 +01:00
this.resizeObserver = new ResizeObserver((entries) => {
// Debounce resize calls to prevent excessive updates
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
2024-02-05 13:11:05 +01:00
}
this.resizeTimeout = window.setTimeout(() => {
for (let entry of entries) {
if (entry.target.classList.contains('mainbox') && this.chart) {
this.resizeChart();
}
}
}, 100); // 100ms debounce
2024-02-05 13:11:05 +01:00
});
2024-02-05 13:11:05 +01:00
this.registerStartupFunction(async () => {
this.updateComplete.then(() => {
const mainbox = this.shadowRoot.querySelector('.mainbox');
if (mainbox) {
this.resizeObserver.observe(mainbox);
2024-02-05 13:11:05 +01:00
}
});
});
2024-02-05 13:11:05 +01:00
this.registerGarbageFunction(async () => {
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
}
2024-02-05 13:11:05 +01:00
this.resizeObserver.disconnect();
});
2024-02-03 11:26:15 +01:00
}
2024-02-03 14:42:20 +01:00
public static styles = [
cssManager.defaultStyles,
css`
:host {
font-family: 'Geist Sans', sans-serif;
2024-02-03 14:42:20 +01:00
color: #ccc;
font-weight: 600;
font-size: 12px;
}
.mainbox {
position: relative;
width: 100%;
height: 400px;
background: #111;
2024-02-03 14:42:20 +01:00
border-radius: 8px;
overflow: hidden;
2024-02-03 14:42:20 +01:00
}
.chartTitle {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
padding-top: 16px;
z-index: 10;
2024-02-03 14:42:20 +01:00
}
.chartContainer {
2024-02-05 13:11:05 +01:00
position: absolute;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
padding: 32px 16px 16px 0px;
overflow: hidden;
2024-02-03 14:42:20 +01:00
}
`,
];
2024-02-03 11:26:15 +01:00
public render(): TemplateResult {
return html`
<div class="mainbox">
<div class="chartTitle">${this.label}</div>
<div class="chartContainer"></div>
</div>
`;
2024-02-03 11:26:15 +01:00
}
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 },
],
},
];
2024-02-03 14:42:20 +01:00
var options: ApexCharts.ApexOptions = {
series: chartSeries,
2024-02-03 11:26:15 +01:00
chart: {
width: initialWidth || 100, // Use actual width or fallback
height: initialHeight || 100, // Use actual height or fallback
2024-02-03 11:26:15 +01:00
type: 'area',
2024-02-03 14:42:20 +01:00
toolbar: {
show: false, // This line disables the toolbar
},
animations: {
enabled: true,
speed: 400,
animateGradually: {
enabled: false, // Disable gradual animation for cleaner updates
delay: 0
},
dynamicAnimation: {
enabled: true,
speed: 350
}
},
2024-02-03 11:26:15 +01:00
},
dataLabels: {
enabled: false,
},
stroke: {
2024-02-03 14:42:20 +01:00
width: 1,
2024-02-03 11:26:15 +01:00
curve: 'smooth',
},
xaxis: {
type: 'datetime', // Time-series data
labels: {
format: 'hh:mm A', // Time formatting
style: {
colors: '#9e9e9e', // Label color
fontSize: '12px',
2024-02-03 14:42:20 +01:00
},
},
axisBorder: {
show: false, // Hide x-axis border
},
axisTicks: {
show: false, // Hide x-axis ticks
},
2024-02-03 11:26:15 +01:00
},
2024-02-03 14:42:20 +01:00
yaxis: {
min: 0,
labels: {
formatter: this.yAxisFormatter,
style: {
colors: '#9e9e9e', // Label color
fontSize: '12px',
},
},
axisBorder: {
show: false, // Hide y-axis border
},
axisTicks: {
show: false, // Hide y-axis ticks
2024-02-03 14:42:20 +01:00
},
},
2024-02-03 11:26:15 +01:00
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
2024-02-03 11:26:15 +01:00
x: {
format: 'dd/MM/yy HH:mm',
},
custom: function ({ series, dataPointIndex, w }: any) {
// Iterate through each series and get its value
let tooltipContent = `<div style="padding: 10px; background: #1e1e2f; color: white; border-radius: 5px;">`;
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 += `<strong>${label}:</strong> ${value} Mbps<br/>`;
});
tooltipContent += `</div>`;
return tooltipContent;
},
2024-02-03 11:26:15 +01:00
},
2024-02-03 14:42:20 +01:00
grid: {
xaxis: {
lines: {
show: true, // This enables the grid lines along the x-axis
},
},
yaxis: {
lines: {
show: true,
},
},
borderColor: '#333', // Set the color of the grid lines
strokeDashArray: 0, // Solid line
2024-02-03 14:42:20 +01:00
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,
},
},
fill: {
type: 'gradient', // Gradient fill for the area
gradient: {
shade: 'dark',
type: 'vertical',
gradientToColors: ['#9c27b0'], // Gradient color ending
stops: [0, 100],
},
},
2024-02-03 11:26:15 +01:00
};
2024-02-03 14:42:20 +01:00
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));
2024-02-03 14:42:20 +01:00
await this.resizeChart();
}
public async updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
// 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,
},
},
});
}
}
public async updateSeries(newSeries: ApexAxisChartSeries, animate: boolean = true) {
if (!this.chart) {
return;
}
this.chart.updateSeries(newSeries, animate);
}
public async appendData(newData: { data: any[] }[]) {
if (!this.chart) {
return;
}
// Use ApexCharts' appendData method for smoother real-time updates
await 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);
}
2024-02-03 14:42:20 +01:00
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;
}
2024-02-03 14:42:20 +01:00
// Get computed style of the element
const styleChartContainer = window.getComputedStyle(chartContainer);
2024-02-03 11:26:15 +01:00
2024-02-03 14:42:20 +01:00
// 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);
2024-02-03 14:42:20 +01:00
// Calculate the actual width and height to use, subtracting padding
const actualWidth = mainbox.clientWidth - paddingLeft - paddingRight;
const actualHeight = mainbox.offsetHeight - paddingTop - paddingBottom;
2024-02-03 14:42:20 +01:00
await this.chart.updateOptions({
chart: {
width: actualWidth,
height: actualHeight,
},
});
2024-02-03 11:26:15 +01:00
}
}