+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 319 of 354

πŸ“˜ Analytics Dashboard: Data Visualization

Master analytics dashboard: data visualization in TypeScript with practical examples, best practices, and real-world applications πŸš€

πŸš€Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript πŸ“
  • TypeScript installation ⚑
  • VS Code or preferred IDE πŸ’»

What you'll learn

  • Understand the concept fundamentals 🎯
  • Apply the concept in real projects πŸ—οΈ
  • Debug common issues πŸ›
  • Write type-safe code ✨

πŸ“˜ Analytics Dashboard: Data Visualization

Welcome to this exciting journey into building analytics dashboards with TypeScript! 🎯 Ever wondered how companies create those beautiful, interactive dashboards that turn boring data into compelling visual stories? Today, you’ll learn exactly how to do that with type-safe TypeScript code!

We’ll explore how to create stunning data visualizations that not only look great but are also maintainable, scalable, and bug-free thanks to TypeScript’s powerful type system. Ready to transform raw data into visual insights? Let’s dive in! πŸš€

πŸ“š Understanding Analytics Dashboards

Think of an analytics dashboard like a car’s dashboard πŸš— - it takes complex data from various sensors and presents it in an easy-to-understand visual format. Just as a speedometer shows your speed at a glance, analytics dashboards transform raw business data into meaningful charts, graphs, and metrics.

In TypeScript, we create these dashboards by:

  • Defining data structures with interfaces and types πŸ“Š
  • Processing data with type-safe transformations πŸ”„
  • Rendering visualizations with typed chart libraries πŸ“ˆ
  • Handling user interactions with proper event typing πŸ–±οΈ

The Power of Type-Safe Visualizations

When building dashboards, TypeScript helps us:

// 🎯 Catch data format errors at compile time
interface SalesData {
  date: Date;
  revenue: number;
  region: string;
}

// πŸ›‘οΈ Ensure chart configs are valid
interface ChartConfig {
  type: 'line' | 'bar' | 'pie';
  data: SalesData[];
  options?: ChartOptions;
}

πŸ”§ Basic Syntax and Usage

Let’s start by setting up the foundation for our analytics dashboard. We’ll use popular charting libraries with TypeScript support.

Setting Up Data Types

// πŸ“Š Define your data structures
interface MetricData {
  timestamp: Date;
  value: number;
  category: string;
}

interface DashboardData {
  sales: MetricData[];
  users: MetricData[];
  revenue: MetricData[];
}

// 🎨 Define chart configuration types
type ChartType = 'line' | 'bar' | 'pie' | 'area' | 'scatter';

interface ChartConfiguration {
  type: ChartType;
  title: string;
  dataKey: keyof DashboardData;
  color?: string;
}

Creating a Basic Chart Component

// πŸ“ˆ Simple chart component with TypeScript
class Chart {
  private config: ChartConfiguration;
  private data: MetricData[];

  constructor(config: ChartConfiguration, data: DashboardData) {
    this.config = config;
    this.data = data[config.dataKey]; // Type-safe data access!
  }

  render(container: HTMLElement): void {
    // 🎨 Render logic here
    console.log(`Rendering ${this.config.type} chart: ${this.config.title}`);
  }
}

πŸ’‘ Practical Examples

Example 1: Sales Performance Dashboard πŸ›’

Let’s build a real sales dashboard that tracks daily revenue:

// πŸ’° Sales dashboard with multiple visualizations
interface SalesMetric {
  date: Date;
  revenue: number;
  units: number;
  region: 'North' | 'South' | 'East' | 'West';
}

class SalesDashboard {
  private salesData: SalesMetric[] = [];
  
  // πŸ“Š Add type-safe data
  addSalesData(metric: SalesMetric): void {
    this.salesData.push(metric);
  }
  
  // πŸ“ˆ Calculate total revenue with type safety
  getTotalRevenue(): number {
    return this.salesData.reduce((sum, sale) => sum + sale.revenue, 0);
  }
  
  // πŸ—ΊοΈ Get revenue by region
  getRevenueByRegion(): Map<string, number> {
    const regionMap = new Map<string, number>();
    
    this.salesData.forEach(sale => {
      const current = regionMap.get(sale.region) || 0;
      regionMap.set(sale.region, current + sale.revenue);
    });
    
    return regionMap;
  }
  
  // πŸ“Š Generate chart data with proper types
  generateChartData(): ChartData[] {
    return Array.from(this.getRevenueByRegion().entries()).map(([region, revenue]) => ({
      label: region,
      value: revenue,
      percentage: (revenue / this.getTotalRevenue()) * 100
    }));
  }
}

// ✨ Type-safe chart data
interface ChartData {
  label: string;
  value: number;
  percentage: number;
}

Example 2: Real-Time User Activity Monitor πŸ‘₯

Create a dashboard that tracks user activity in real-time:

// πŸš€ Real-time activity tracking
interface UserActivity {
  userId: string;
  action: 'login' | 'purchase' | 'view' | 'logout';
  timestamp: Date;
  metadata?: Record<string, any>;
}

class ActivityMonitor {
  private activities: UserActivity[] = [];
  private updateCallbacks: ((data: ActivitySummary) => void)[] = [];
  
  // πŸ“‘ Add activity with type checking
  trackActivity(activity: UserActivity): void {
    this.activities.push(activity);
    this.notifySubscribers();
  }
  
  // πŸ“Š Get activity summary
  getActivitySummary(): ActivitySummary {
    const now = new Date();
    const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
    
    const recentActivities = this.activities.filter(
      activity => activity.timestamp >= oneHourAgo
    );
    
    const summary: ActivitySummary = {
      totalActivities: recentActivities.length,
      uniqueUsers: new Set(recentActivities.map(a => a.userId)).size,
      activityBreakdown: this.groupByAction(recentActivities),
      timestamp: now
    };
    
    return summary;
  }
  
  // πŸ”„ Subscribe to updates
  onUpdate(callback: (data: ActivitySummary) => void): void {
    this.updateCallbacks.push(callback);
  }
  
  private groupByAction(activities: UserActivity[]): Record<string, number> {
    return activities.reduce((acc, activity) => {
      acc[activity.action] = (acc[activity.action] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
  }
  
  private notifySubscribers(): void {
    const summary = this.getActivitySummary();
    this.updateCallbacks.forEach(callback => callback(summary));
  }
}

// πŸ“ˆ Activity summary type
interface ActivitySummary {
  totalActivities: number;
  uniqueUsers: number;
  activityBreakdown: Record<string, number>;
  timestamp: Date;
}

Example 3: Performance Metrics Dashboard πŸš€

Build a dashboard for monitoring application performance:

// ⚑ Performance monitoring dashboard
interface PerformanceMetric {
  endpoint: string;
  responseTime: number; // milliseconds
  statusCode: number;
  timestamp: Date;
}

class PerformanceDashboard {
  private metrics: PerformanceMetric[] = [];
  private alertThreshold = 1000; // 1 second
  
  // πŸ“Š Add metric with validation
  recordMetric(metric: PerformanceMetric): void {
    if (metric.responseTime < 0) {
      throw new Error('Response time cannot be negative! 🚫');
    }
    
    this.metrics.push(metric);
    
    // 🚨 Check for performance issues
    if (metric.responseTime > this.alertThreshold) {
      this.triggerAlert(metric);
    }
  }
  
  // πŸ“ˆ Calculate average response time
  getAverageResponseTime(endpoint?: string): number {
    const relevantMetrics = endpoint
      ? this.metrics.filter(m => m.endpoint === endpoint)
      : this.metrics;
    
    if (relevantMetrics.length === 0) return 0;
    
    const total = relevantMetrics.reduce((sum, m) => sum + m.responseTime, 0);
    return Math.round(total / relevantMetrics.length);
  }
  
  // πŸ“Š Get performance distribution
  getPerformanceDistribution(): PerformanceDistribution {
    const distribution: PerformanceDistribution = {
      fast: 0,      // < 200ms
      normal: 0,    // 200-500ms
      slow: 0,      // 500-1000ms
      critical: 0   // > 1000ms
    };
    
    this.metrics.forEach(metric => {
      if (metric.responseTime < 200) distribution.fast++;
      else if (metric.responseTime < 500) distribution.normal++;
      else if (metric.responseTime < 1000) distribution.slow++;
      else distribution.critical++;
    });
    
    return distribution;
  }
  
  // 🎯 Get endpoint statistics
  getEndpointStats(): Map<string, EndpointStats> {
    const statsMap = new Map<string, EndpointStats>();
    
    // Group metrics by endpoint
    const grouped = this.groupByEndpoint();
    
    // Calculate stats for each endpoint
    grouped.forEach((metrics, endpoint) => {
      const responseTimes = metrics.map(m => m.responseTime);
      
      statsMap.set(endpoint, {
        endpoint,
        avgResponseTime: this.average(responseTimes),
        minResponseTime: Math.min(...responseTimes),
        maxResponseTime: Math.max(...responseTimes),
        requestCount: metrics.length,
        errorRate: this.calculateErrorRate(metrics)
      });
    });
    
    return statsMap;
  }
  
  private groupByEndpoint(): Map<string, PerformanceMetric[]> {
    const grouped = new Map<string, PerformanceMetric[]>();
    
    this.metrics.forEach(metric => {
      const existing = grouped.get(metric.endpoint) || [];
      grouped.set(metric.endpoint, [...existing, metric]);
    });
    
    return grouped;
  }
  
  private average(numbers: number[]): number {
    return numbers.reduce((a, b) => a + b, 0) / numbers.length;
  }
  
  private calculateErrorRate(metrics: PerformanceMetric[]): number {
    const errors = metrics.filter(m => m.statusCode >= 400).length;
    return (errors / metrics.length) * 100;
  }
  
  private triggerAlert(metric: PerformanceMetric): void {
    console.log(`🚨 Performance Alert: ${metric.endpoint} took ${metric.responseTime}ms!`);
  }
}

// πŸ“Š Type definitions
interface PerformanceDistribution {
  fast: number;
  normal: number;
  slow: number;
  critical: number;
}

interface EndpointStats {
  endpoint: string;
  avgResponseTime: number;
  minResponseTime: number;
  maxResponseTime: number;
  requestCount: number;
  errorRate: number;
}

πŸš€ Advanced Concepts

Creating Reusable Chart Components

Let’s build a flexible, type-safe charting system:

// 🎨 Advanced chart system with generics
interface DataPoint<T = any> {
  x: number | Date | string;
  y: number;
  metadata?: T;
}

interface ChartProps<T> {
  data: DataPoint<T>[];
  type: ChartType;
  options?: ChartOptions;
  onDataPointClick?: (point: DataPoint<T>) => void;
}

class TypedChart<T = any> {
  private props: ChartProps<T>;
  private container: HTMLElement;
  
  constructor(container: HTMLElement, props: ChartProps<T>) {
    this.container = container;
    this.props = props;
  }
  
  // 🎯 Type-safe data transformation
  transformData(): TransformedData {
    return {
      labels: this.props.data.map(d => String(d.x)),
      values: this.props.data.map(d => d.y),
      metadata: this.props.data.map(d => d.metadata)
    };
  }
  
  // πŸ“Š Render with proper event handling
  render(): void {
    const transformed = this.transformData();
    
    // Simulated rendering with click handling
    this.props.data.forEach((point, index) => {
      // Create visual element
      const element = this.createChartElement(point, index);
      
      // Add type-safe event listener
      element.addEventListener('click', () => {
        this.props.onDataPointClick?.(point);
      });
      
      this.container.appendChild(element);
    });
  }
  
  private createChartElement(point: DataPoint<T>, index: number): HTMLElement {
    const element = document.createElement('div');
    element.className = 'chart-point';
    element.dataset.value = String(point.y);
    return element;
  }
}

interface TransformedData {
  labels: string[];
  values: number[];
  metadata: any[];
}

// πŸš€ Usage with custom metadata
interface SalesMetadata {
  product: string;
  salesperson: string;
}

const salesChart = new TypedChart<SalesMetadata>(
  document.getElementById('chart')!,
  {
    type: 'bar',
    data: [
      { x: 'Jan', y: 1000, metadata: { product: 'Widget', salesperson: 'Alice' } },
      { x: 'Feb', y: 1500, metadata: { product: 'Gadget', salesperson: 'Bob' } }
    ],
    onDataPointClick: (point) => {
      console.log(`Clicked: ${point.metadata?.product} sold by ${point.metadata?.salesperson}`);
    }
  }
);

Real-Time Data Streaming

Handle streaming data with proper types:

// 🌊 Real-time data streaming
interface StreamConfig<T> {
  source: string;
  onData: (data: T) => void;
  onError?: (error: Error) => void;
  bufferSize?: number;
}

class DataStream<T> {
  private buffer: T[] = [];
  private config: StreamConfig<T>;
  private isActive = false;
  
  constructor(config: StreamConfig<T>) {
    this.config = {
      bufferSize: 100,
      ...config
    };
  }
  
  // πŸš€ Start streaming
  start(): void {
    this.isActive = true;
    this.simulateDataStream();
  }
  
  // πŸ›‘ Stop streaming
  stop(): void {
    this.isActive = false;
  }
  
  // πŸ“Š Get buffer snapshot
  getBuffer(): readonly T[] {
    return [...this.buffer];
  }
  
  private simulateDataStream(): void {
    if (!this.isActive) return;
    
    // Simulate incoming data
    setTimeout(() => {
      try {
        const newData = this.generateMockData();
        this.addToBuffer(newData);
        this.config.onData(newData);
        this.simulateDataStream();
      } catch (error) {
        this.config.onError?.(error as Error);
      }
    }, 1000);
  }
  
  private addToBuffer(data: T): void {
    this.buffer.push(data);
    
    // Maintain buffer size
    if (this.buffer.length > this.config.bufferSize!) {
      this.buffer.shift();
    }
  }
  
  private generateMockData(): T {
    // This would be replaced with actual data source
    return {
      timestamp: new Date(),
      value: Math.random() * 100
    } as unknown as T;
  }
}

⚠️ Common Pitfalls and Solutions

❌ Wrong: Untyped chart data

// 😱 No type safety for chart data
const chartData = {
  labels: ['Jan', 'Feb', 'Mar'],
  datasets: [{
    data: [10, 20, '30'], // Oops! String instead of number
    label: 'Sales'
  }]
};

βœ… Right: Properly typed chart data

// 🎯 Full type safety
interface ChartDataset {
  data: number[];
  label: string;
  backgroundColor?: string;
  borderColor?: string;
}

interface ChartData {
  labels: string[];
  datasets: ChartDataset[];
}

const chartData: ChartData = {
  labels: ['Jan', 'Feb', 'Mar'],
  datasets: [{
    data: [10, 20, 30], // TypeScript ensures all are numbers!
    label: 'Sales'
  }]
};

❌ Wrong: Loose event handling

// 😱 No type checking for events
element.addEventListener('click', (e) => {
  const value = e.target.dataset.value; // What type is value?
  processData(value); // Might fail at runtime!
});

βœ… Right: Type-safe event handling

// 🎯 Properly typed events
interface ChartClickEvent extends MouseEvent {
  target: HTMLElement & {
    dataset: {
      value?: string;
      index?: string;
    };
  };
}

element.addEventListener('click', (e: ChartClickEvent) => {
  const value = e.target.dataset.value;
  if (value) {
    const numValue = parseFloat(value);
    if (!isNaN(numValue)) {
      processData(numValue); // Safe!
    }
  }
});

πŸ› οΈ Best Practices

1. Use Discriminated Unions for Chart Types

// 🎯 Type-safe chart configurations
type LineChartConfig = {
  type: 'line';
  smooth?: boolean;
  tension?: number;
};

type BarChartConfig = {
  type: 'bar';
  stacked?: boolean;
  horizontal?: boolean;
};

type PieChartConfig = {
  type: 'pie';
  donut?: boolean;
  startAngle?: number;
};

type ChartConfig = LineChartConfig | BarChartConfig | PieChartConfig;

// 🎨 Type-safe chart factory
function createChart(config: ChartConfig): Chart {
  switch (config.type) {
    case 'line':
      return new LineChart(config); // TypeScript knows it's LineChartConfig
    case 'bar':
      return new BarChart(config); // TypeScript knows it's BarChartConfig
    case 'pie':
      return new PieChart(config); // TypeScript knows it's PieChartConfig
  }
}

2. Create Reusable Data Transformers

// πŸ”„ Type-safe data transformation utilities
class DataTransformer {
  // πŸ“Š Group data by key
  static groupBy<T, K extends keyof T>(
    data: T[],
    key: K
  ): Map<T[K], T[]> {
    const grouped = new Map<T[K], T[]>();
    
    data.forEach(item => {
      const keyValue = item[key];
      const existing = grouped.get(keyValue) || [];
      grouped.set(keyValue, [...existing, item]);
    });
    
    return grouped;
  }
  
  // πŸ“ˆ Calculate moving average
  static movingAverage(
    data: number[],
    windowSize: number
  ): number[] {
    if (windowSize <= 0 || windowSize > data.length) {
      throw new Error('Invalid window size! 🚫');
    }
    
    const result: number[] = [];
    
    for (let i = windowSize - 1; i < data.length; i++) {
      const window = data.slice(i - windowSize + 1, i + 1);
      const average = window.reduce((a, b) => a + b) / windowSize;
      result.push(average);
    }
    
    return result;
  }
  
  // 🎯 Aggregate data
  static aggregate<T>(
    data: T[],
    getValue: (item: T) => number,
    operation: 'sum' | 'avg' | 'min' | 'max'
  ): number {
    const values = data.map(getValue);
    
    switch (operation) {
      case 'sum':
        return values.reduce((a, b) => a + b, 0);
      case 'avg':
        return values.reduce((a, b) => a + b, 0) / values.length;
      case 'min':
        return Math.min(...values);
      case 'max':
        return Math.max(...values);
    }
  }
}

3. Implement Proper Error Boundaries

// πŸ›‘οΈ Error handling for dashboards
class DashboardError extends Error {
  constructor(
    message: string,
    public code: string,
    public component?: string
  ) {
    super(message);
    this.name = 'DashboardError';
  }
}

class ErrorBoundary {
  private errorHandlers: Map<string, (error: DashboardError) => void> = new Map();
  
  // 🎯 Register error handler
  onError(code: string, handler: (error: DashboardError) => void): void {
    this.errorHandlers.set(code, handler);
  }
  
  // πŸ›‘οΈ Wrap function with error handling
  wrap<T extends (...args: any[]) => any>(
    fn: T,
    component: string
  ): T {
    return ((...args: Parameters<T>) => {
      try {
        return fn(...args);
      } catch (error) {
        const dashboardError = new DashboardError(
          error instanceof Error ? error.message : 'Unknown error',
          'RUNTIME_ERROR',
          component
        );
        
        this.handleError(dashboardError);
        throw dashboardError;
      }
    }) as T;
  }
  
  private handleError(error: DashboardError): void {
    const handler = this.errorHandlers.get(error.code);
    
    if (handler) {
      handler(error);
    } else {
      console.error(`🚨 Unhandled dashboard error in ${error.component}:`, error);
    }
  }
}

πŸ§ͺ Hands-On Exercise

Ready to build your own analytics dashboard? Let’s create a comprehensive e-commerce analytics system! πŸ›οΈ

Your Challenge:

Build a type-safe e-commerce dashboard that tracks:

  1. Product sales by category
  2. Customer demographics
  3. Revenue trends over time
  4. Top performing products

Here’s your starter code:

// πŸͺ E-commerce analytics challenge
interface Product {
  id: string;
  name: string;
  category: 'Electronics' | 'Clothing' | 'Books' | 'Home';
  price: number;
}

interface Customer {
  id: string;
  age: number;
  location: string;
}

interface Order {
  orderId: string;
  customerId: string;
  productId: string;
  quantity: number;
  orderDate: Date;
}

// TODO: Create your analytics dashboard!
// 1. Define a Dashboard class
// 2. Add methods to calculate metrics
// 3. Create visualization data structures
// 4. Implement real-time updates

// Start coding here! πŸ’ͺ
πŸ’‘ Click here for the solution
// πŸŽ‰ Complete e-commerce analytics solution!
class EcommerceDashboard {
  private products: Map<string, Product> = new Map();
  private customers: Map<string, Customer> = new Map();
  private orders: Order[] = [];
  
  // πŸ“¦ Add product catalog
  addProduct(product: Product): void {
    this.products.set(product.id, product);
  }
  
  // πŸ‘€ Add customer
  addCustomer(customer: Customer): void {
    this.customers.set(customer.id, customer);
  }
  
  // πŸ›’ Process order
  processOrder(order: Order): void {
    // Validate order
    if (!this.products.has(order.productId)) {
      throw new Error(`Product ${order.productId} not found! 🚫`);
    }
    if (!this.customers.has(order.customerId)) {
      throw new Error(`Customer ${order.customerId} not found! 🚫`);
    }
    
    this.orders.push(order);
  }
  
  // πŸ“Š Get sales by category
  getSalesByCategory(): CategorySales[] {
    const categoryMap = new Map<string, number>();
    
    this.orders.forEach(order => {
      const product = this.products.get(order.productId)!;
      const revenue = product.price * order.quantity;
      
      const current = categoryMap.get(product.category) || 0;
      categoryMap.set(product.category, current + revenue);
    });
    
    return Array.from(categoryMap.entries()).map(([category, revenue]) => ({
      category,
      revenue,
      percentage: (revenue / this.getTotalRevenue()) * 100
    }));
  }
  
  // πŸ‘₯ Get customer demographics
  getCustomerDemographics(): Demographics {
    const ageGroups = {
      '18-25': 0,
      '26-35': 0,
      '36-45': 0,
      '46-55': 0,
      '56+': 0
    };
    
    const locationMap = new Map<string, number>();
    
    // Count unique customers who made orders
    const activeCustomers = new Set(this.orders.map(o => o.customerId));
    
    activeCustomers.forEach(customerId => {
      const customer = this.customers.get(customerId)!;
      
      // Age groups
      if (customer.age >= 18 && customer.age <= 25) ageGroups['18-25']++;
      else if (customer.age <= 35) ageGroups['26-35']++;
      else if (customer.age <= 45) ageGroups['36-45']++;
      else if (customer.age <= 55) ageGroups['46-55']++;
      else ageGroups['56+']++;
      
      // Locations
      const count = locationMap.get(customer.location) || 0;
      locationMap.set(customer.location, count + 1);
    });
    
    return {
      totalCustomers: activeCustomers.size,
      ageDistribution: ageGroups,
      topLocations: Array.from(locationMap.entries())
        .sort((a, b) => b[1] - a[1])
        .slice(0, 5)
        .map(([location, count]) => ({ location, count }))
    };
  }
  
  // πŸ“ˆ Get revenue trends
  getRevenueTrends(period: 'daily' | 'weekly' | 'monthly'): TrendData[] {
    const trendMap = new Map<string, number>();
    
    this.orders.forEach(order => {
      const product = this.products.get(order.productId)!;
      const revenue = product.price * order.quantity;
      const dateKey = this.getDateKey(order.orderDate, period);
      
      const current = trendMap.get(dateKey) || 0;
      trendMap.set(dateKey, current + revenue);
    });
    
    return Array.from(trendMap.entries())
      .sort((a, b) => a[0].localeCompare(b[0]))
      .map(([date, revenue]) => ({ date, revenue }));
  }
  
  // πŸ† Get top products
  getTopProducts(limit: number = 10): TopProduct[] {
    const productRevenue = new Map<string, number>();
    const productQuantity = new Map<string, number>();
    
    this.orders.forEach(order => {
      const product = this.products.get(order.productId)!;
      const revenue = product.price * order.quantity;
      
      const currentRevenue = productRevenue.get(order.productId) || 0;
      const currentQuantity = productQuantity.get(order.productId) || 0;
      
      productRevenue.set(order.productId, currentRevenue + revenue);
      productQuantity.set(order.productId, currentQuantity + order.quantity);
    });
    
    return Array.from(productRevenue.entries())
      .map(([productId, revenue]) => {
        const product = this.products.get(productId)!;
        return {
          product,
          revenue,
          unitsSold: productQuantity.get(productId)!,
          averageOrderValue: revenue / productQuantity.get(productId)!
        };
      })
      .sort((a, b) => b.revenue - a.revenue)
      .slice(0, limit);
  }
  
  // πŸ’° Helper: Get total revenue
  private getTotalRevenue(): number {
    return this.orders.reduce((total, order) => {
      const product = this.products.get(order.productId)!;
      return total + (product.price * order.quantity);
    }, 0);
  }
  
  // πŸ“… Helper: Format date based on period
  private getDateKey(date: Date, period: 'daily' | 'weekly' | 'monthly'): string {
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    
    switch (period) {
      case 'daily':
        return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
      case 'weekly':
        const week = Math.ceil(day / 7);
        return `${year}-W${week}`;
      case 'monthly':
        return `${year}-${month.toString().padStart(2, '0')}`;
    }
  }
}

// πŸ“Š Type definitions for dashboard data
interface CategorySales {
  category: string;
  revenue: number;
  percentage: number;
}

interface Demographics {
  totalCustomers: number;
  ageDistribution: Record<string, number>;
  topLocations: { location: string; count: number }[];
}

interface TrendData {
  date: string;
  revenue: number;
}

interface TopProduct {
  product: Product;
  revenue: number;
  unitsSold: number;
  averageOrderValue: number;
}

// πŸŽ‰ Usage example
const dashboard = new EcommerceDashboard();

// Add products
dashboard.addProduct({
  id: 'p1',
  name: 'Laptop',
  category: 'Electronics',
  price: 999
});

// Add customers
dashboard.addCustomer({
  id: 'c1',
  age: 28,
  location: 'New York'
});

// Process orders
dashboard.processOrder({
  orderId: 'o1',
  customerId: 'c1',
  productId: 'p1',
  quantity: 1,
  orderDate: new Date()
});

// Get insights! πŸ“ˆ
console.log('Sales by Category:', dashboard.getSalesByCategory());
console.log('Top Products:', dashboard.getTopProducts(5));

πŸŽ“ Key Takeaways

You’ve just mastered analytics dashboards with TypeScript! Here’s what you’ve learned:

  1. Type-Safe Data Structures πŸ“Š - Define clear interfaces for your analytics data
  2. Reusable Chart Components πŸ“ˆ - Build flexible, generic visualization components
  3. Real-Time Data Handling 🌊 - Stream and process data with proper typing
  4. Error Boundaries πŸ›‘οΈ - Protect your dashboard from runtime errors
  5. Data Transformation πŸ”„ - Create type-safe utility functions for data processing

Remember:

  • Always define your data structures with interfaces
  • Use discriminated unions for different chart types
  • Leverage generics for reusable components
  • Handle real-time updates with proper buffering
  • Test your dashboards with various data scenarios

🀝 Next Steps

Ready to take your analytics skills further? Here’s what to explore next:

  1. Learn Advanced Charting Libraries πŸ“Š - Dive into D3.js, Chart.js, or Recharts with TypeScript
  2. Explore Real-Time Technologies 🌊 - WebSockets, Server-Sent Events for live data
  3. Study Data Processing πŸ”„ - Learn about data aggregation and transformation patterns
  4. Master State Management 🎯 - Use Redux or MobX for complex dashboard state
  5. Build Full-Stack Dashboards πŸš€ - Connect to real databases and APIs

You’re now equipped to build powerful, type-safe analytics dashboards that transform data into insights! Keep practicing, and soon you’ll be creating dashboards that not only look amazing but are also maintainable and bug-free.

Happy visualizing! πŸŽ‰ Your journey into data visualization with TypeScript has just begun, and the possibilities are endless! πŸš€