Apache ECharts — Dashboard Analytics Research¶
Project: Forma3D.Connect
Version: 1.0
Date: February 13, 2026
Status: Implemented → ADR-050
Executive Summary¶
This document evaluates Apache ECharts as the charting library for building an analytics dashboard in Forma3D.Connect. The current dashboard shows only basic stat cards and lists — no visual charts or trend analysis. Adding pie/donut charts for orders, print jobs, and shipments (grouped by status) along with revenue breakdowns and trend lines would give operators immediate visual insight into their 3D print farm operations.
Recommendation¶
Adopt Apache ECharts via echarts-for-react as the charting library. It offers the richest pie chart variants (donut, rose, nested, sunburst), excellent TypeScript support, and professional label formatting — all of which align with the analytics requirements below. The bundle size trade-off is acceptable when using on-demand imports (~150 KB gzipped vs ~320 KB full).
Table of Contents¶
- Current State
- Library Comparison
- ECharts Technical Assessment
- Proposed Dashboard Metrics
- Chart Specifications
- Mockups
- Implementation Plan
- Backend API Requirements
- Risks & Mitigations
- Decision
1. Current State¶
The current dashboard (apps/web/src/pages/dashboard.tsx) provides:
| Element | Content |
|---|---|
| System Health Card | Status indicator, database connection, uptime, version |
| Stat Cards (×4) | Pending Orders, Processing, Active Print Jobs, Needs Attention |
| Recent Orders List | Last 5 orders with status badges |
| Active Print Jobs List | Last 5 active jobs with printer name |
| Welcome Card | Static branding message |
What's missing:
- No visual charts or graphs
- No time-based filtering (today / week / month / all-time)
- No revenue or monetary aggregations
- No trend analysis (is the business growing?)
- No print success/failure rate visualization
- No shipping pipeline visibility
2. Library Comparison¶
| Criterion | ECharts | Recharts | Chart.js | Nivo |
|---|---|---|---|---|
| Pie/Donut Variants | Basic, donut, rose, nested, sunburst, semi-circle | Basic, donut | Basic, doughnut | Basic, donut |
| TypeScript | Native (written in TS) | Good | Via @types |
Native |
| Bundle Size (gz) | ~320 KB full / ~150 KB tree-shaken | ~160 KB | ~11 KB | Varies by package |
| Label Customization | Rich text, leader lines, inside/outside | Basic | Plugin-based | Good |
| Animation Quality | Excellent built-in | Basic | Good | Good |
| Interactive Tooltips | Advanced (formatter functions, rich HTML) | Component-based | Plugin-based | Built-in |
| React 19 Compat | Works (peer dep override) | Had breaking issues | Works via wrapper | Works |
| GitHub Stars | ~65.5K | ~26.5K | ~67K | ~14K |
| npm Downloads/wk | ~1.7M | ~11.7M | ~6.4M | ~14K |
| Dark Theme | Built-in dark theme | Manual | Manual | Theme prop |
| API Style | JSON configuration | JSX declarative | Imperative | JSX declarative |
Verdict: ECharts wins on chart variety, label richness, and built-in dark theme support — critical for our admin dashboard. The bundle size cost is manageable with on-demand imports. Recharts has broader React adoption but lacks the pie chart variants we need.
3. ECharts Technical Assessment¶
3.1 Packages Required¶
echarts ^5.6.0 # Core library
echarts-for-react ^3.0.6 # React wrapper (3 KB gzipped)
3.2 React 19 Compatibility¶
echarts-for-react@3.0.6 declares peer dependencies for React ^15 || ^16 || ^17. It works with React 19 without issues — only a peer dependency warning. Fix with pnpm config:
{
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"echarts-for-react>react": "19",
"echarts-for-react>react-dom": "19"
}
}
}
}
3.3 Tree-Shaking (On-Demand Imports)¶
Full import of ECharts adds ~320 KB gzipped. Using on-demand imports reduces this to ~150 KB:
// libs/ui/src/charts/echarts-setup.ts
import * as echarts from 'echarts/core';
import { PieChart, BarChart, LineChart, GaugeChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
PieChart,
BarChart,
LineChart,
GaugeChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
CanvasRenderer,
]);
export { echarts };
// Usage in components
import ReactEChartsCore from 'echarts-for-react/lib/core';
import { echarts } from '@forma3d/ui/charts/echarts-setup';
<ReactEChartsCore echarts={echarts} option={chartOption} />
3.4 Dark Theme Integration¶
ECharts ships with a built-in dark theme. We can also create a custom Forma3D theme that matches our CSS variables:
echarts.registerTheme('forma3d-dark', {
backgroundColor: 'transparent',
textStyle: { color: 'var(--color-text-primary)' },
// ... custom colors matching our design system
});
4. Proposed Dashboard Metrics¶
4.1 Order Metrics¶
| Metric | Description | Chart Type | Time Filter |
|---|---|---|---|
| Orders by Status | Count + EUR per status (PENDING, PROCESSING, PARTIALLY_COMPLETED, COMPLETED, FAILED, CANCELLED) | Donut chart | Today / Week / Month / All-time |
| Revenue by Status | EUR breakdown by order status | Inner ring of nested donut | Same |
| Order Volume Trend | Daily order count over time | Line chart | Last 7d / 30d / 90d |
| Average Order Value | EUR average per order | KPI card | Same |
| Revenue Trend | Daily revenue in EUR | Bar chart | Last 7d / 30d |
| Top Products by Revenue | Revenue per product SKU | Horizontal bar or sunburst | Month / All-time |
4.2 Print Job Metrics¶
| Metric | Description | Chart Type | Time Filter |
|---|---|---|---|
| Print Jobs by Status | Count per status (QUEUED, ASSIGNED, PRINTING, COMPLETED, FAILED, CANCELLED) | Donut chart | Today / Week / Month / All-time |
| Print Success Rate | % of completed vs total finished jobs | Gauge chart | Same |
| Average Print Duration | Hours per job | KPI card | Same |
| Failure Reasons | Breakdown of why jobs fail | Rose/Nightingale chart | Month / All-time |
| Printer Utilization | Jobs per printer | Horizontal bar | Today / Week |
| Retry Rate | % of jobs requiring retries | KPI card | Same |
4.3 Shipment Metrics¶
| Metric | Description | Chart Type | Time Filter |
|---|---|---|---|
| Shipments by Status | Count per status (PENDING, LABEL_CREATED, ANNOUNCED, IN_TRANSIT, DELIVERED, FAILED, CANCELLED) | Donut chart | Today / Week / Month / All-time |
| Delivery Rate | % of DELIVERED vs total | KPI card | Same |
| Avg. Time to Delivery | Days from label creation to delivery | KPI card | Month |
| Shipping Pipeline | Funnel visualization from pending to delivered | Funnel chart | This week |
| Failed Shipments | Count and reasons | Badge + list | Same |
4.4 Business Overview Metrics¶
| Metric | Description | Chart Type |
|---|---|---|
| Revenue Breakdown | Nested: inner = order status, outer = product SKU | Nested donut / Sunburst |
| Fulfillment Pipeline | Order → Print → Ship conversion funnel | Funnel chart |
| Order-to-Delivery Time | Average time from order creation to shipment delivery | KPI card + trend line |
| Daily Summary | Orders received, completed, shipped today | Multi-metric KPI row |
5. Chart Specifications¶
5.1 Orders by Status — Donut Chart¶
The primary chart. Shows the distribution of orders across all 6 statuses for the selected time period.
Data structure:
interface OrderStatusMetric {
status: OrderStatus;
count: number;
percentage: number;
totalValue: number; // EUR
}
interface OrderStatusChartData {
period: 'today' | 'week' | 'month' | 'all';
totalOrders: number;
totalRevenue: number;
avgOrderValue: number;
statuses: OrderStatusMetric[];
}
ECharts config (simplified):
const option: EChartsOption = {
tooltip: {
trigger: 'item',
formatter: ({ data }) =>
`${data.name}<br/>` +
`${data.value} orders (${data.percentage}%)<br/>` +
`€${data.totalValue.toLocaleString()}`,
},
legend: { orient: 'vertical', left: 'left' },
series: [
{
type: 'pie',
radius: ['40%', '70%'], // Donut
avoidLabelOverlap: true,
label: {
formatter: '{b}\n{c} orders ({d}%)\n€{totalValue}',
position: 'outside',
},
labelLine: { show: true },
data: [
{ value: 12, name: 'Pending', itemStyle: { color: '#9CA3AF' } },
{ value: 24, name: 'Processing', itemStyle: { color: '#3B82F6' } },
{ value: 8, name: 'Partial', itemStyle: { color: '#F59E0B' } },
{ value: 28, name: 'Completed', itemStyle: { color: '#10B981' } },
{ value: 4, name: 'Failed', itemStyle: { color: '#EF4444' } },
{ value: 4, name: 'Cancelled', itemStyle: { color: '#6B7280' } },
],
},
],
};
Color mapping (matching existing constants):
| Status | Color | Hex |
|---|---|---|
| PENDING | Gray | #9CA3AF |
| PROCESSING | Blue | #3B82F6 |
| PARTIALLY_COMPLETED | Amber | #F59E0B |
| COMPLETED | Green | #10B981 |
| FAILED | Red | #EF4444 |
| CANCELLED | Gray (dim) | #6B7280 |
5.2 Print Jobs by Status — Donut Chart¶
Same donut pattern, with print-job-specific statuses and colors.
Color mapping:
| Status | Color | Hex |
|---|---|---|
| QUEUED | Light Blue | #60A5FA |
| ASSIGNED | Indigo | #818CF8 |
| PRINTING | Purple | #A855F7 |
| COMPLETED | Green | #10B981 |
| FAILED | Red | #EF4444 |
| CANCELLED | Gray (dim) | #6B7280 |
Center label: Total jobs count + average duration per job.
5.3 Shipments by Status — Donut Chart¶
Seven statuses representing the full shipping pipeline.
Color mapping:
| Status | Color | Hex |
|---|---|---|
| PENDING | Gray | #9CA3AF |
| LABEL_CREATED | Amber | #F59E0B |
| ANNOUNCED | Light Blue | #60A5FA |
| IN_TRANSIT | Indigo | #818CF8 |
| DELIVERED | Green | #10B981 |
| FAILED | Red | #EF4444 |
| CANCELLED | Gray (dim) | #6B7280 |
Center label: Total shipments + delivered count.
5.4 Revenue Breakdown — Nested Donut / Sunburst¶
A nested chart with two rings:
- Inner ring: Revenue by order status (Completed, Processing, Pending)
- Outer ring: Revenue by product SKU within each status
This leverages ECharts' unique nested pie capability — not available in Recharts or Chart.js.
5.5 Print Success Rate — Gauge Chart¶
A semi-circle gauge showing the success rate percentage (COMPLETED / (COMPLETED + FAILED)) with:
- Green zone: 90–100%
- Yellow zone: 75–90%
- Red zone: < 75%
5.6 Order Volume Trend — Line Chart¶
A time-series line chart showing daily order count. Supports hover tooltip with date and count.
5.7 Revenue Trend — Bar Chart¶
A bar chart showing daily/weekly revenue in EUR. Each bar is colored by the dominant order status for that day.
6. Mockups¶
6.1 Full Dashboard Layout¶
The proposed dashboard layout with stat cards, three donut charts, revenue bar chart, and order trend line:

Layout structure:
- Row 1: 4 enhanced KPI stat cards (replacing existing simple stat cards with trend indicators like "+3 from yesterday")
- Row 2: 3 donut charts side-by-side (Orders | Print Jobs | Shipments) with a single shared period dropdown (Today / Last Week / Last Month / All Time)
- Row 3: Revenue bar chart — "This Week" (left) + Order trend line — "Last 30 Days" (right)
- Row 4: Existing Recent Orders + Active Print Jobs lists (moved down, untouched)
6.2 Orders by Status — Donut Chart¶
Donut chart with leader-line labels showing count, percentage, and EUR amount per status. Center displays total orders and revenue.

Features:
- Time filter tabs: Today | This Week | This Month | All Time
- Center summary: total order count + total EUR
- Leader-line labels: status name, count, percentage, EUR
- Tooltip on hover with detailed breakdown
- Clickable slices to navigate to filtered order list
6.3 Print Jobs by Status — Donut Chart¶
Similar donut with print job-specific colors. Center shows total jobs and average duration.

Features:
- Time filter tabs
- Center summary: total jobs + average duration
- Side KPI cards: Total Printers, Active Printers, Error Rate
- Clickable slices for filtered job views
6.4 Shipments by Status — Donut Chart¶
Seven-segment donut reflecting the full shipping lifecycle from pending to delivered.

Features:
- Time filter tabs
- Center summary: total shipments + delivered count
- Color gradient from gray (pending) through blue (in-transit) to green (delivered)
- Visual pipeline feeling — cold-to-warm progression
6.5 Advanced Charts — Revenue Breakdown & Print Success Rate¶
Nested sunburst chart for revenue analysis and gauge chart for print success rate:

Revenue Breakdown (left):
- Inner ring: revenue by order status
- Outer ring: revenue by product SKU
- ECharts-exclusive feature (nested pie/sunburst)
Print Success Rate (right):
- Semi-circle gauge: overall success percentage
- Companion rose chart: failure reason breakdown
- Color zones: green (90%+), yellow (75–90%), red (<75%)
7. Implementation Plan¶
Phase 1: Foundation (1–2 days)¶
- Install dependencies:
echarts,echarts-for-react - Configure pnpm peer dependency overrides for React 19
- Create shared chart setup module in
libs/uiwith on-demand imports - Register custom Forma3D dark theme
- Create reusable
<ChartCard>wrapper component with time filter tabs
Phase 2: Core Donut Charts (2–3 days)¶
- Build
<OrderStatusChart>component - Build
<PrintJobStatusChart>component - Build
<ShipmentStatusChart>component - Create TanStack Query hooks for each:
useOrderStatusMetrics(),usePrintJobMetrics(),useShipmentMetrics() - Add time-period selector (today/week/month/all) with query parameter support
- Integrate into dashboard page (Row 2)
Phase 3: Trend & Revenue Charts (2–3 days)¶
- Build
<OrderTrendChart>(line chart) - Build
<RevenueTrendChart>(bar chart) - Build
<RevenueBreakdownChart>(nested donut) - Create corresponding backend endpoints and query hooks
- Integrate into dashboard page (Row 3)
Phase 4: Advanced Charts (1–2 days)¶
- Build
<PrintSuccessGauge>component - Build
<FailureReasonsChart>(rose/nightingale) - Add to a dedicated analytics sub-section or expandable panel
Phase 5: Polish (1 day)¶
- Responsive behavior (stack charts on mobile)
- Loading states and empty states for each chart
- Click-to-filter navigation (click a pie slice to go to filtered list)
- Accessibility: chart descriptions for screen readers
- Performance testing: verify bundle size impact
Total estimate: 7–11 days
8. Backend API Requirements¶
New endpoints needed in apps/api:
GET /api/v1/analytics/orders¶
// Query params
interface OrderAnalyticsQuery {
period: 'today' | 'week' | 'month' | 'all';
}
// Response
interface OrderAnalyticsResponse {
period: string;
totalOrders: number;
totalRevenue: number; // EUR
avgOrderValue: number; // EUR
statuses: Array<{
status: OrderStatus;
count: number;
percentage: number;
totalValue: number; // EUR
}>;
}
GET /api/v1/analytics/print-jobs¶
interface PrintJobAnalyticsResponse {
period: string;
totalJobs: number;
avgDurationSeconds: number;
successRate: number; // 0–100
retryRate: number; // 0–100
statuses: Array<{
status: PrintJobStatus;
count: number;
percentage: number;
}>;
}
GET /api/v1/analytics/shipments¶
interface ShipmentAnalyticsResponse {
period: string;
totalShipments: number;
deliveryRate: number; // 0–100
avgDeliveryDays: number;
statuses: Array<{
status: ShipmentStatus;
count: number;
percentage: number;
}>;
}
GET /api/v1/analytics/trends¶
interface TrendsQuery {
metric: 'orders' | 'revenue';
days: 7 | 30 | 90;
}
interface TrendsResponse {
metric: string;
dataPoints: Array<{
date: string; // ISO date
value: number;
}>;
}
GET /api/v1/analytics/revenue-breakdown¶
interface RevenueBreakdownResponse {
totalRevenue: number;
byStatus: Array<{
status: OrderStatus;
revenue: number;
products: Array<{
sku: string;
name: string;
revenue: number;
}>;
}>;
}
All endpoints are scoped to the authenticated tenant (tenantId from JWT).
9. Risks & Mitigations¶
| Risk | Impact | Probability | Mitigation |
|---|---|---|---|
| Bundle size increase | Slower initial load | Medium | On-demand imports (~150 KB gz vs ~320 KB full); lazy-load analytics page |
| React 19 peer dep warning | Build noise | High | pnpm peerDependencyRules override; monitor for echarts-for-react v4 |
| Performance with large datasets | Slow rendering | Low | ECharts handles 10M+ data points; our dataset is small (thousands) |
echarts-for-react maintenance |
Stale wrapper | Medium | Wrapper is thin (3 KB); easy to fork or replace with direct useRef integration |
| Accessibility | Screen reader gaps | Medium | Use ECharts aria option for auto-generated descriptions; add role="img" and aria-label |
| Mobile responsiveness | Charts too small | Medium | Stack vertically on mobile; use responsive option based on container width |
| Backend query performance | Slow analytics endpoints | Medium | Add database indexes on status + createdAt; consider materialized views for all-time aggregations |
10. Decision¶
Recommended: Apache ECharts via echarts-for-react¶
Why ECharts over alternatives:
- Richest pie chart variety — donut, rose, nested, sunburst — all needed for our use cases
- Professional label formatting — leader lines with multi-line rich text labels showing count + percentage + EUR
- Built-in dark theme — matches our existing dark-mode admin dashboard
- Excellent TypeScript support — native type definitions for all chart options
- Interactive features — click handlers on slices for drill-down navigation to filtered views
- Single library — covers all chart types (pie, bar, line, gauge, sunburst, funnel) without mixing libraries
- Active ecosystem — 65K+ GitHub stars, ~1.7M weekly npm downloads, Apache Foundation backing
Trade-offs accepted:
- Larger bundle than Chart.js (~150 KB gz with tree-shaking vs ~11 KB)
- JSON config API style instead of JSX components (acceptable — config objects are easily typed and composable)
- Peer dependency override needed for React 19 (non-blocking)
Packages to Install¶
pnpm add echarts echarts-for-react
Priority Order¶
- P0 — Orders by Status donut (most valuable — immediate visibility into order pipeline)
- P0 — Print Jobs by Status donut (core operational metric for print farm management)
- P1 — Shipments by Status donut (shipping pipeline visibility)
- P1 — Revenue trend bar chart (business health indicator)
- P2 — Order volume trend line (growth tracking)
- P2 — Revenue breakdown sunburst (product-level analysis)
- P3 — Print success gauge (operational quality metric)
- P3 — Failure reasons rose chart (root cause analysis)