This commit is contained in:
2026-04-04 11:05:01 +00:00
parent 4dba14060e
commit ff32470d8a
29 changed files with 1431 additions and 2 deletions

View File

@@ -0,0 +1,142 @@
import {
customElement,
property,
type TemplateResult,
} from '@design.estate/dees-element';
import { DeesChartEchartsBase } from '../dees-chart-echarts-base.js';
import { demoFunc } from './demo.js';
import { barStyles } from './styles.js';
import { renderChartBar } from './template.js';
import { getEchartsSeriesColors } from '../dees-chart-echarts-theme.js';
export interface IBarSeriesItem {
name: string;
data: number[];
color?: string;
}
declare global {
interface HTMLElementTagNameMap {
'dees-chart-bar': DeesChartBar;
}
}
@customElement('dees-chart-bar')
export class DeesChartBar extends DeesChartEchartsBase {
public static demo = demoFunc;
public static demoGroups = ['Chart'];
@property({ type: Array })
accessor categories: string[] = [];
@property({ type: Array })
accessor series: IBarSeriesItem[] = [];
@property({ type: Boolean })
accessor horizontal: boolean = false;
@property({ type: Boolean })
accessor stacked: boolean = false;
@property({ type: Boolean })
accessor showLegend: boolean = true;
@property({ attribute: false })
accessor valueFormatter: (value: number) => string = (val) => `${val}`;
public static styles = barStyles;
public render(): TemplateResult {
return renderChartBar(this);
}
public async updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
if (
this.chartInstance &&
(changedProperties.has('categories') ||
changedProperties.has('series') ||
changedProperties.has('horizontal') ||
changedProperties.has('stacked') ||
changedProperties.has('showLegend'))
) {
this.updateChart();
}
}
protected buildOption(): Record<string, any> {
const seriesColors = getEchartsSeriesColors(this.goBright);
const isDark = !this.goBright;
const formatter = this.valueFormatter;
const categoryAxis: Record<string, any> = {
type: 'category',
data: this.categories,
axisLine: { lineStyle: { color: isDark ? 'hsl(0 0% 20%)' : 'hsl(0 0% 85%)' } },
axisLabel: { color: isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 40%)' },
};
const valueAxis: Record<string, any> = {
type: 'value',
axisLine: { show: false },
axisLabel: {
color: isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 40%)',
formatter: (val: number) => formatter(val),
},
splitLine: { lineStyle: { color: isDark ? 'hsl(0 0% 14%)' : 'hsl(0 0% 92%)' } },
};
const seriesData = this.series.map((s, index) => ({
name: s.name,
type: 'bar' as const,
data: s.data,
stack: this.stacked ? 'total' : undefined,
itemStyle: {
color: s.color || seriesColors[index % seriesColors.length],
borderRadius: this.stacked ? [0, 0, 0, 0] : this.horizontal ? [0, 4, 4, 0] : [4, 4, 0, 0],
},
barMaxWidth: 40,
emphasis: {
itemStyle: {
shadowBlur: 6,
shadowColor: 'rgba(0, 0, 0, 0.15)',
},
},
}));
// For stacked bars, round the top corners of the last visible series
if (this.stacked && seriesData.length > 0) {
const last = seriesData[seriesData.length - 1];
last.itemStyle.borderRadius = this.horizontal ? [0, 4, 4, 0] : [4, 4, 0, 0];
}
return {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => {
const items = Array.isArray(params) ? params : [params];
let result = `<strong>${items[0].axisValueLabel}</strong><br/>`;
for (const p of items) {
result += `${p.marker} ${p.seriesName}: <strong>${formatter(p.value)}</strong><br/>`;
}
return result;
},
},
legend: this.showLegend && this.series.length > 1
? { bottom: 8, itemWidth: 10, itemHeight: 10 }
: { show: false },
grid: {
left: 16,
right: 16,
top: 16,
bottom: this.showLegend && this.series.length > 1 ? 40 : 16,
containLabel: true,
},
xAxis: this.horizontal ? valueAxis : categoryAxis,
yAxis: this.horizontal ? categoryAxis : valueAxis,
series: seriesData,
};
}
}

View File

@@ -0,0 +1,120 @@
import { html, css, cssManager } from '@design.estate/dees-element';
import type { DeesChartBar } from './component.js';
import '@design.estate/dees-wcctools/demotools';
import './component.js';
export const demoFunc = () => {
const endpointCategories = ['/api/users', '/api/orders', '/api/products', '/api/auth', '/api/search'];
const endpointSeries = [
{ name: 'GET', data: [1240, 890, 720, 2100, 560] },
{ name: 'POST', data: [320, 450, 180, 890, 40] },
{ name: 'PUT', data: [90, 210, 150, 30, 10] },
];
const regionCategories = ['US-East', 'US-West', 'EU', 'Asia', 'Other'];
const regionSeries = [
{ name: 'Requests', data: [4500, 3200, 2800, 1900, 600] },
];
return html`
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
const vertChart = elementArg.querySelector('#vert-chart') as DeesChartBar;
const horizChart = elementArg.querySelector('#horiz-chart') as DeesChartBar;
const stackChart = elementArg.querySelector('#stack-chart') as DeesChartBar;
const buttons = elementArg.querySelectorAll('dees-button');
buttons.forEach((button: any) => {
const text = button.text?.trim();
if (text === 'Randomize') {
button.addEventListener('click', () => {
vertChart.series = endpointSeries.map((s) => ({
...s,
data: s.data.map((v) => Math.round(v * (0.5 + Math.random()))),
}));
horizChart.series = regionSeries.map((s) => ({
...s,
data: s.data.map((v) => Math.round(v * (0.5 + Math.random()))),
}));
stackChart.series = endpointSeries.map((s) => ({
...s,
data: s.data.map((v) => Math.round(v * (0.5 + Math.random()))),
}));
});
}
});
}}>
<style>
${css`
.demoBox {
position: relative;
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
height: 100%;
width: 100%;
padding: 40px;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 24px;
}
.chartRow {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.controls {
display: flex;
gap: 12px;
margin-bottom: 8px;
}
.info {
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;
}
`}
</style>
<div class="demoBox">
<div class="controls">
<dees-button-group label="Actions:">
<dees-button>Randomize</dees-button>
</dees-button-group>
</div>
<div class="chartRow">
<dees-chart-bar
id="vert-chart"
.label=${'Requests by Endpoint'}
.categories=${endpointCategories}
.series=${endpointSeries}
.valueFormatter=${(val: number) => `${val} req`}
></dees-chart-bar>
<dees-chart-bar
id="horiz-chart"
.label=${'Traffic by Region'}
.categories=${regionCategories}
.series=${regionSeries}
.horizontal=${true}
.valueFormatter=${(val: number) => `${(val / 1000).toFixed(1)}k`}
></dees-chart-bar>
</div>
<dees-chart-bar
id="stack-chart"
.label=${'Stacked: Requests by Endpoint'}
.categories=${endpointCategories}
.series=${endpointSeries}
.stacked=${true}
.valueFormatter=${(val: number) => `${val} req`}
></dees-chart-bar>
<div class="info">
Bar chart with vertical, horizontal, and stacked modes •
Click 'Randomize' to update data with animation
</div>
</div>
</dees-demowrapper>
`;
};

View File

@@ -0,0 +1 @@
export * from './component.js';

View File

@@ -0,0 +1,7 @@
import { css } from '@design.estate/dees-element';
import { echartsBaseStyles } from '../dees-chart-echarts-styles.js';
export const barStyles = [
...echartsBaseStyles,
css``,
];

View File

@@ -0,0 +1,13 @@
import { html, type TemplateResult } from '@design.estate/dees-element';
import type { DeesChartBar } from './component.js';
export const renderChartBar = (component: DeesChartBar): TemplateResult => {
return html`
<dees-tile>
<div slot="header" class="chartHeader">
<span class="chartLabel">${component.label}</span>
</div>
<div class="chartContainer"></div>
</dees-tile>
`;
};