📊 BeepStatCard
Professional statistical data display card with trend indicators, icons, and comprehensive theming support
📚 Table of Contents
📋 Overview
BeepStatCard is a sophisticated data visualization control designed for displaying key performance indicators (KPIs), metrics, and statistical data in dashboard and analytics applications. It combines multiple visual elements including header text, main values, percentage changes, trend indicators, and descriptive information in a cohesive, professionally styled card layout.
Built on the BeepControl foundation, BeepStatCard automatically adapts to themes, provides smooth layout management, and supports both SVG and traditional image formats for icons and trend indicators.
🚀 Key Features
📈 Trend Visualization
Built-in support for upward and downward trend indicators with customizable SVG icons
🎯 Multi-Component Layout
Header, main value, percentage change, trend text, and information sections
🖼️ Flexible Iconography
Support for card icons and trend icons with SVG file path configuration
🎨 Professional Theming
Full integration with Beep theming system for consistent dashboard appearance
📊 Data Binding Ready
BoundProperty support for seamless integration with data sources
📱 Responsive Layout
Automatic layout adjustment based on card size and content
👁️ Visual Preview
Card Layout Structure
Component Structure
┌─────────────────────────────────────┐
│ [Header Text] [Icon] │ ← Header Section
├─────────────────────────────────────┤
│ [+12.5%] │ ← Percentage Section
│ │
│ [$1,250.00] │ ← Main Value (Center)
│ │
│ [Trending up] ──────────── [📈] │ ← Trend Section
│ │
│ [Additional Information] │ ← Info Section
└─────────────────────────────────────┘
🚀 Basic Usage
Simple Stat Card
// Create a basic statistical card
var revenueCard = new BeepStatCard
{
Size = new Size(300, 150),
Location = new Point(10, 10),
// Basic content
HeaderText = "Monthly Revenue",
ValueText = "$45,231.00",
PercentageText = "+8.2%",
TrendText = "Up from last month",
InfoText = "Based on confirmed sales",
// Trend configuration
IsTrendingUp = true
};
// Add to form
this.Controls.Add(revenueCard);
Card with Custom Icons
// Create card with custom SVG icons
var userCard = new BeepStatCard
{
Size = new Size(280, 160),
Location = new Point(320, 10),
// Content
HeaderText = "Active Users",
ValueText = "2,847",
PercentageText = "+15.3%",
TrendText = "Growing steadily",
InfoText = "Users online in last 24h",
// Custom icons
Icon = @"Assets\icons\users.svg",
TrendUpSvgPath = @"Assets\icons\trend-up.svg",
TrendDownSvgPath = @"Assets\icons\trend-down.svg",
// Trend state
IsTrendingUp = true
};
this.Controls.Add(userCard);
Multiple Cards Dashboard
public void CreateDashboardCards()
{
var cardData = new[]
{
new { Header = "Revenue", Value = "$125,430", Percent = "+12.5%",
Trend = "Strong growth", Info = "Monthly total", Up = true },
new { Header = "Orders", Value = "1,847", Percent = "+5.8%",
Trend = "Steady increase", Info = "This week", Up = true },
new { Header = "Customers", Value = "23,591", Percent = "-2.1%",
Trend = "Slight decline", Info = "Active users", Up = false },
new { Header = "Conversion", Value = "3.24%", Percent = "+0.8%",
Trend = "Improving", Info = "Sales funnel", Up = true }
};
for (int i = 0; i < cardData.Length; i++)
{
var data = cardData[i];
var card = new BeepStatCard
{
Size = new Size(250, 140),
Location = new Point(10 + (i * 260), 10),
HeaderText = data.Header,
ValueText = data.Value,
PercentageText = data.Percent,
TrendText = data.Trend,
InfoText = data.Info,
IsTrendingUp = data.Up,
// Apply theme
Theme = "MaterialDesignTheme"
};
this.Controls.Add(card);
}
}
BeepStatCard automatically adjusts its internal layout based on the control size. For best results, use sizes between 200x120 and 400x180 pixels.
🧩 Card Components
Component Breakdown
| Component | Property | Position | Purpose |
|---|---|---|---|
| Header Label | HeaderText |
Top-left | Main title/category of the metric |
| Card Icon | Icon |
Top-right | Visual representation of the metric type |
| Percentage Label | PercentageText |
Upper-right area | Change percentage with trend coloring |
| Value Label | ValueText |
Center | Main statistical value (large, prominent) |
| Trend Label | TrendText |
Lower-left | Descriptive trend information |
| Trend Icon | TrendUpSvgPath/TrendDownSvgPath |
Lower-right | Visual trend indicator |
| Info Label | InfoText |
Bottom | Additional context or time period |
Individual Component Configuration
var salesCard = new BeepStatCard();
// Configure each component individually
salesCard.HeaderText = "Q4 Sales Target"; // Header section
salesCard.ValueText = "87.3%"; // Main value (center)
salesCard.PercentageText = "+23.1%"; // Percentage change
salesCard.TrendText = "Ahead of schedule"; // Trend description
salesCard.InfoText = "Target: $500K by Dec 31"; // Additional info
// Icon configuration
salesCard.Icon = @"Assets\sales-target.svg"; // Main card icon
salesCard.TrendUpSvgPath = @"Assets\arrow-up.svg"; // Positive trend
salesCard.TrendDownSvgPath = @"Assets\arrow-down.svg"; // Negative trend
// Set trend direction
salesCard.IsTrendingUp = true; // Will use TrendUpSvgPath and green color
Dynamic Component Updates
// Method to update card with new data
public void UpdateStatCard(BeepStatCard card, StatData data)
{
// Update main content
card.HeaderText = data.Title;
card.ValueText = data.FormattedValue;
card.PercentageText = $"{(data.ChangePercent >= 0 ? "+" : "")}{data.ChangePercent:F1}%";
// Update trend information
card.IsTrendingUp = data.ChangePercent >= 0;
card.TrendText = data.ChangePercent >= 0 ? "Trending up" : "Trending down";
// Update time context
card.InfoText = $"Data from {data.Period}";
// The card will automatically refresh its layout
card.Invalidate();
}
public class StatData
{
public string Title { get; set; }
public double Value { get; set; }
public string FormattedValue => Value.ToString("N0");
public double ChangePercent { get; set; }
public string Period { get; set; }
}
📈 Trend Indicators
Trend Configuration
// Configure trend indicators
var performanceCard = new BeepStatCard
{
HeaderText = "Website Performance",
ValueText = "98.7%",
PercentageText = "+0.3%",
// Trend state affects multiple elements
IsTrendingUp = true, // Controls icon and percentage color
// Custom trend icons (SVG recommended)
TrendUpSvgPath = @"Assets\icons\trending-up.svg",
TrendDownSvgPath = @"Assets\icons\trending-down.svg"
};
// Dynamically change trend direction
public void UpdateTrend(BeepStatCard card, double changeValue)
{
card.PercentageText = $"{(changeValue >= 0 ? "+" : "")}{changeValue:F1}%";
card.IsTrendingUp = changeValue >= 0;
// Trend text changes automatically based on direction
card.TrendText = changeValue >= 0
? "Performance improving"
: "Needs attention";
}
Advanced Trend Logic
public enum TrendType
{
StronglyPositive, // +5% or more
Positive, // +0.1% to +4.9%
Neutral, // -0.1% to +0.1%
Negative, // -0.1% to -4.9%
StronglyNegative // -5% or less
}
public void ApplyTrendStyling(BeepStatCard card, double changePercent)
{
var trendType = GetTrendType(changePercent);
// Update percentage text with appropriate formatting
card.PercentageText = FormatPercentageChange(changePercent);
// Set trend direction for icon selection
card.IsTrendingUp = changePercent >= 0;
// Apply trend-specific styling
switch (trendType)
{
case TrendType.StronglyPositive:
card.TrendText = "Excellent growth";
break;
case TrendType.Positive:
card.TrendText = "Growing steadily";
break;
case TrendType.Neutral:
card.TrendText = "Stable";
break;
case TrendType.Negative:
card.TrendText = "Declining";
break;
case TrendType.StronglyNegative:
card.TrendText = "Needs immediate attention";
break;
}
}
private TrendType GetTrendType(double changePercent)
{
return changePercent switch
{
>= 5.0 => TrendType.StronglyPositive,
>= 0.1 => TrendType.Positive,
>= -0.1 and <= 0.1 => TrendType.Neutral,
>= -5.0 => TrendType.Negative,
_ => TrendType.StronglyNegative
};
}
private string FormatPercentageChange(double changePercent)
{
string sign = changePercent >= 0 ? "+" : "";
return $"{sign}{changePercent:F1}%";
}
- Color Consistency: Green for positive trends, red for negative trends
- Icon Clarity: Use clear, recognizable trend icons (arrows, charts)
- Context Provision: Always provide context for what the trend represents
- Threshold Management: Define clear thresholds for trend categories
🖼️ Icons & Assets
SVG Icon Configuration
// Configure SVG icons for professional appearance
var analyticsCard = new BeepStatCard
{
HeaderText = "Analytics",
ValueText = "15,847",
// Main card icon (top-right)
Icon = @"Assets\icons\analytics.svg",
// Trend icons (bottom-right, contextual)
TrendUpSvgPath = @"Assets\icons\trend-up.svg",
TrendDownSvgPath = @"Assets\icons\trend-down.svg"
};
// Icons are automatically themed and sized
// Card icon: 32x32 pixels
// Trend icon: 32x32 pixels
Icon Asset Management
public static class CardIconAssets
{
public const string IconsPath = @"Assets\icons";
// Main card icons
public static readonly Dictionary CardIcons = new()
{
{ "revenue", $@"{IconsPath}\money.svg" },
{ "users", $@"{IconsPath}\users.svg" },
{ "orders", $@"{IconsPath}\shopping-cart.svg" },
{ "performance", $@"{IconsPath}\gauge.svg" },
{ "growth", $@"{IconsPath}\growth.svg" },
{ "traffic", $@"{IconsPath}\traffic.svg" }
};
// Trend icons
public static readonly Dictionary TrendIcons = new()
{
{ "up", $@"{IconsPath}\arrow-up.svg" },
{ "down", $@"{IconsPath}\arrow-down.svg" },
{ "up-strong", $@"{IconsPath}\trending-up.svg" },
{ "down-strong", $@"{IconsPath}\trending-down.svg" }
};
public static void ApplyIconSet(BeepStatCard card, string iconType, bool strongTrend = false)
{
// Apply main card icon
if (CardIcons.TryGetValue(iconType, out string cardIcon))
{
card.Icon = cardIcon;
}
// Apply trend icons
if (strongTrend)
{
card.TrendUpSvgPath = TrendIcons["up-strong"];
card.TrendDownSvgPath = TrendIcons["down-strong"];
}
else
{
card.TrendUpSvgPath = TrendIcons["up"];
card.TrendDownSvgPath = TrendIcons["down"];
}
}
}
// Usage
var salesCard = new BeepStatCard();
CardIconAssets.ApplyIconSet(salesCard, "revenue", strongTrend: true);
Dynamic Icon Loading
public class IconManager
{
private readonly Dictionary _iconCache = new();
public void SetCardIcon(BeepStatCard card, string category)
{
string iconPath = GetIconPath(category);
if (!string.IsNullOrEmpty(iconPath) && File.Exists(iconPath))
{
card.Icon = iconPath;
}
else
{
// Fallback to default icon
card.Icon = @"Assets\icons\default.svg";
}
}
private string GetIconPath(string category)
{
if (_iconCache.TryGetValue(category, out string cached))
{
return cached;
}
// Build icon path
string iconPath = Path.Combine("Assets", "icons", $"{category}.svg");
// Cache the result
_iconCache[category] = iconPath;
return iconPath;
}
public void PreloadIcons(params string[] categories)
{
foreach (string category in categories)
{
GetIconPath(category); // Loads into cache
}
}
}
- Format: SVG files are recommended for scalability and theming
- Size: Icons are automatically sized to 32x32 pixels
- Style: Use consistent icon style across all cards (outline, filled, etc.)
- Colors: SVG icons can be automatically themed to match card colors
- Fallbacks: Always provide fallback icons for missing assets
🔗 Data Binding
Simple Data Binding
public class KPIData
{
public string Title { get; set; }
public decimal Value { get; set; }
public double ChangePercent { get; set; }
public string Description { get; set; }
public DateTime LastUpdated { get; set; }
public string FormattedValue => Value.ToString("C0");
public string FormattedChange => $"{(ChangePercent >= 0 ? "+" : "")}{ChangePercent:F1}%";
public bool IsTrendingUp => ChangePercent >= 0;
}
// Bind data to card
public void BindDataToCard(BeepStatCard card, KPIData data)
{
card.HeaderText = data.Title;
card.ValueText = data.FormattedValue;
card.PercentageText = data.FormattedChange;
card.IsTrendingUp = data.IsTrendingUp;
card.TrendText = data.IsTrendingUp ? "Trending up" : "Trending down";
card.InfoText = $"Updated {data.LastUpdated:MMM dd, yyyy}";
}
// Example usage
var revenueData = new KPIData
{
Title = "Monthly Revenue",
Value = 125430m,
ChangePercent = 12.5,
Description = "Total sales revenue",
LastUpdated = DateTime.Now
};
BindDataToCard(revenueCard, revenueData);
Observable Data Binding
public class ObservableKPIData : INotifyPropertyChanged
{
private decimal _value;
private double _changePercent;
private string _title;
public string Title
{
get => _title;
set { _title = value; OnPropertyChanged(); }
}
public decimal Value
{
get => _value;
set { _value = value; OnPropertyChanged(); OnPropertyChanged(nameof(FormattedValue)); }
}
public double ChangePercent
{
get => _changePercent;
set { _changePercent = value; OnPropertyChanged(); OnPropertyChanged(nameof(FormattedChange)); }
}
public string FormattedValue => Value.ToString("C0");
public string FormattedChange => $"{(ChangePercent >= 0 ? "+" : "")}{ChangePercent:F1}%";
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Data-bound card wrapper
public class DataBoundStatCard : BeepStatCard
{
private ObservableKPIData _dataSource;
public ObservableKPIData DataSource
{
get => _dataSource;
set
{
if (_dataSource != null)
_dataSource.PropertyChanged -= OnDataChanged;
_dataSource = value;
if (_dataSource != null)
{
_dataSource.PropertyChanged += OnDataChanged;
UpdateFromDataSource();
}
}
}
private void OnDataChanged(object sender, PropertyChangedEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(UpdateFromDataSource));
}
else
{
UpdateFromDataSource();
}
}
private void UpdateFromDataSource()
{
if (_dataSource == null) return;
HeaderText = _dataSource.Title;
ValueText = _dataSource.FormattedValue;
PercentageText = _dataSource.FormattedChange;
IsTrendingUp = _dataSource.ChangePercent >= 0;
}
}
Real-Time Data Updates
public class RealTimeKPIManager
{
private readonly Timer _updateTimer;
private readonly List _cards;
private readonly Random _random = new();
public RealTimeKPIManager()
{
_cards = new List();
_updateTimer = new Timer(5000); // Update every 5 seconds
_updateTimer.Elapsed += UpdateData;
_updateTimer.Start();
}
public void RegisterCard(DataBoundStatCard card, string dataSource)
{
card.Tag = dataSource; // Store data source identifier
_cards.Add(card);
}
private void UpdateData(object sender, ElapsedEventArgs e)
{
foreach (var card in _cards)
{
if (card.DataSource != null && card.Tag is string dataSourceId)
{
// Simulate real-time data update
var newData = FetchLatestData(dataSourceId);
// Update data source (will automatically update card)
card.DataSource.Value = newData.Value;
card.DataSource.ChangePercent = newData.ChangePercent;
}
}
}
private (decimal Value, double ChangePercent) FetchLatestData(string dataSourceId)
{
// Simulate API call or database query
return dataSourceId switch
{
"revenue" => (125000m + (decimal)(_random.NextDouble() * 10000), _random.NextDouble() * 20 - 10),
"users" => (2500 + _random.Next(100), _random.NextDouble() * 10 - 5),
"orders" => (450 + _random.Next(50), _random.NextDouble() * 15 - 7.5),
_ => (0, 0)
};
}
}
🎨 Theming
Theme Integration
// BeepStatCard automatically applies theme colors:
public override void ApplyTheme()
{
if (_currentTheme == null) return;
// Card background
BackColor = _currentTheme.CardBackColor;
ParentBackColor = _currentTheme.CardBackColor;
// Header styling
headerLabel.ForeColor = _currentTheme.CardHeaderStyle.TextColor;
headerLabel.TextFont = BeepThemesManager.ToFont(_currentTheme.CardHeaderStyle);
headerLabel.BackColor = _currentTheme.CardBackColor;
// Percentage with trend-based coloring
percentageLabel.ForeColor = IsTrendingUp ? Color.Green : Color.Red;
percentageLabel.TextFont = BeepThemesManager.ToFont(_currentTheme.SmallText);
// Main value (prominent display)
valueLabel.ForeColor = _currentTheme.CardTextForeColor;
valueLabel.TextFont = new Font(
BeepThemesManager.ToFont(_currentTheme.CardparagraphStyle).FontFamily,
24, FontStyle.Bold);
// Trend text
trendLabel.ForeColor = _currentTheme.CardTextForeColor;
trendLabel.TextFont = BeepThemesManager.ToFont(_currentTheme.CardparagraphStyle);
// Info text (smaller)
infoLabel.ForeColor = _currentTheme.CardTextForeColor;
infoLabel.TextFont = new Font(
BeepThemesManager.ToFont(_currentTheme.CardparagraphStyle).FontFamily, 8);
}
Custom Theme Overrides
public class CustomStyledStatCard : BeepStatCard
{
private Color _positiveColor = Color.Green;
private Color _negativeColor = Color.Red;
private Color _neutralColor = Color.Gray;
public Color PositiveColor
{
get => _positiveColor;
set { _positiveColor = value; UpdateTrendColors(); }
}
public Color NegativeColor
{
get => _negativeColor;
set { _negativeColor = value; UpdateTrendColors(); }
}
public Color NeutralColor
{
get => _neutralColor;
set { _neutralColor = value; UpdateTrendColors(); }
}
private void UpdateTrendColors()
{
if (percentageLabel != null)
{
percentageLabel.ForeColor = IsTrendingUp ? PositiveColor : NegativeColor;
}
}
public override void ApplyTheme()
{
base.ApplyTheme();
UpdateTrendColors();
}
// Custom styling for different card types
public void ApplyCardTypeStyle(StatCardType cardType)
{
switch (cardType)
{
case StatCardType.Revenue:
PositiveColor = Color.FromArgb(40, 167, 69); // Success green
NegativeColor = Color.FromArgb(220, 53, 69); // Danger red
break;
case StatCardType.Performance:
PositiveColor = Color.FromArgb(0, 123, 255); // Primary blue
NegativeColor = Color.FromArgb(255, 193, 7); // Warning yellow
break;
case StatCardType.Users:
PositiveColor = Color.FromArgb(111, 66, 193); // Purple
NegativeColor = Color.FromArgb(108, 117, 125); // Gray
break;
}
UpdateTrendColors();
}
}
public enum StatCardType
{
Revenue,
Performance,
Users,
Orders,
Traffic
}
- Consistency: Use the same theme across all stat cards in a dashboard
- Contrast: Ensure adequate contrast between text and background colors
- Brand Colors: Customize trend colors to match your brand palette
- Accessibility: Test with high contrast themes for accessibility
🏢 Real-World Examples
Executive Dashboard
public class ExecutiveDashboard : UserControl
{
private BeepStatCard[] kpiCards;
public ExecutiveDashboard()
{
InitializeDashboard();
LoadRealTimeData();
}
private void InitializeDashboard()
{
var kpiDefinitions = new[]
{
new KPIDefinition
{
Title = "Quarterly Revenue",
Icon = "revenue",
Format = "C0",
DataSource = "sales.quarterly_revenue",
Target = 2500000m
},
new KPIDefinition
{
Title = "Customer Acquisition",
Icon = "users",
Format = "N0",
DataSource = "marketing.new_customers",
Target = 1500
},
new KPIDefinition
{
Title = "Operational Efficiency",
Icon = "performance",
Format = "P1",
DataSource = "operations.efficiency_score",
Target = 0.95m
},
new KPIDefinition
{
Title = "Market Share",
Icon = "growth",
Format = "P2",
DataSource = "market.share_percentage",
Target = 0.125m
}
};
kpiCards = new BeepStatCard[kpiDefinitions.Length];
for (int i = 0; i < kpiDefinitions.Length; i++)
{
var def = kpiDefinitions[i];
var card = new BeepStatCard
{
Size = new Size(280, 160),
Location = new Point(20 + (i % 2) * 300, 20 + (i / 2) * 180),
HeaderText = def.Title,
Theme = "CorporateTheme"
};
CardIconAssets.ApplyIconSet(card, def.Icon);
kpiCards[i] = card;
this.Controls.Add(card);
}
}
private async void LoadRealTimeData()
{
var dataService = new KPIDataService();
while (this.IsHandleCreated)
{
try
{
var kpiData = await dataService.GetLatestKPIsAsync();
for (int i = 0; i < kpiCards.Length && i < kpiData.Count; i++)
{
var card = kpiCards[i];
var data = kpiData[i];
if (InvokeRequired)
{
Invoke(() => UpdateKPICard(card, data));
}
else
{
UpdateKPICard(card, data);
}
}
}
catch (Exception ex)
{
// Log error and continue
// Console.WriteLine($"Error updating KPI data: {ex.Message}");
}
await Task.Delay(30000); // Update every 30 seconds
}
}
private void UpdateKPICard(BeepStatCard card, KPIData data)
{
card.ValueText = data.Value.ToString(data.Format ?? "N0");
card.PercentageText = $"{(data.ChangePercent >= 0 ? "+" : "")}{data.ChangePercent:F1}%";
card.IsTrendingUp = data.ChangePercent >= 0;
// Contextual trend messages
if (Math.Abs(data.ChangePercent) < 1)
card.TrendText = "Stable performance";
else if (data.ChangePercent >= 5)
card.TrendText = "Excellent growth";
else if (data.ChangePercent >= 1)
card.TrendText = "Positive trend";
else if (data.ChangePercent <= -5)
card.TrendText = "Immediate attention needed";
else
card.TrendText = "Below expectations";
card.InfoText = $"Updated {DateTime.Now:HH:mm}";
}
}
E-commerce Analytics
public class EcommerceAnalytics : UserControl
{
private readonly Dictionary _metricCards;
private readonly Timer _refreshTimer;
public EcommerceAnalytics()
{
_metricCards = new Dictionary();
CreateAnalyticsCards();
_refreshTimer = new Timer(60000); // Refresh every minute
_refreshTimer.Elapsed += RefreshMetrics;
_refreshTimer.Start();
}
private void CreateAnalyticsCards()
{
var metrics = new[]
{
new { Key = "revenue", Title = "Today's Revenue", Icon = "money" },
new { Key = "orders", Title = "Orders Placed", Icon = "shopping-cart" },
new { Key = "visitors", Title = "Unique Visitors", Icon = "users" },
new { Key = "conversion", Title = "Conversion Rate", Icon = "percentage" },
new { Key = "avgOrder", Title = "Average Order Value", Icon = "receipt" },
new { Key = "returnRate", Title = "Return Rate", Icon = "return" }
};
int col = 0, row = 0;
foreach (var metric in metrics)
{
var card = new BeepStatCard
{
Size = new Size(250, 140),
Location = new Point(20 + col * 270, 20 + row * 160),
HeaderText = metric.Title,
Theme = "MaterialDesignTheme"
};
CardIconAssets.ApplyIconSet(card, metric.Icon);
_metricCards[metric.Key] = card;
this.Controls.Add(card);
col++;
if (col >= 3) { col = 0; row++; }
}
}
private async void RefreshMetrics(object sender, ElapsedEventArgs e)
{
try
{
var analytics = await GetEcommerceAnalytics();
if (InvokeRequired)
{
Invoke(() => UpdateAnalyticsDisplay(analytics));
}
else
{
UpdateAnalyticsDisplay(analytics);
}
}
catch (Exception ex)
{
// Handle error gracefully
LogError("Failed to refresh analytics", ex);
}
}
private void UpdateAnalyticsDisplay(EcommerceAnalytics analytics)
{
// Revenue card
UpdateCard("revenue",
analytics.TodayRevenue.ToString("C0"),
analytics.RevenueChangePercent,
GetRevenueContext(analytics.TodayRevenue, analytics.YesterdayRevenue));
// Orders card
UpdateCard("orders",
analytics.OrdersToday.ToString("N0"),
analytics.OrdersChangePercent,
$"vs {analytics.OrdersYesterday} yesterday");
// Visitors card
UpdateCard("visitors",
analytics.UniqueVisitors.ToString("N0"),
analytics.VisitorsChangePercent,
"24-hour unique visitors");
// Conversion rate
UpdateCard("conversion",
analytics.ConversionRate.ToString("P2"),
analytics.ConversionChangePercent,
"Visitor to purchase rate");
// Average order value
UpdateCard("avgOrder",
analytics.AverageOrderValue.ToString("C0"),
analytics.AOVChangePercent,
"Per transaction average");
// Return rate
UpdateCard("returnRate",
analytics.ReturnRate.ToString("P1"),
analytics.ReturnRateChangePercent,
"30-day return rate");
}
private void UpdateCard(string key, string value, double changePercent, string context)
{
if (!_metricCards.TryGetValue(key, out var card)) return;
card.ValueText = value;
card.PercentageText = $"{(changePercent >= 0 ? "+" : "")}{changePercent:F1}%";
card.IsTrendingUp = changePercent >= 0;
card.InfoText = context;
// Dynamic trend messaging
card.TrendText = GetTrendMessage(changePercent);
}
private string GetTrendMessage(double changePercent)
{
return Math.Abs(changePercent) switch
{
< 1 => "Steady",
< 5 => changePercent > 0 ? "Growing" : "Declining",
< 10 => changePercent > 0 ? "Strong growth" : "Sharp decline",
_ => changePercent > 0 ? "Exceptional growth" : "Critical decline"
};
}
}
⭐ Best Practices
📊 Data Presentation
- Clear Hierarchy: Use font sizes and colors to establish visual hierarchy
- Meaningful Metrics: Display only actionable and relevant metrics
- Context Provision: Always provide context for what the numbers represent
- Consistent Formatting: Use consistent number formatting across similar metrics
🎨 Visual Design
- Color Psychology: Use green for positive trends, red for negative, gray for neutral
- Icon Consistency: Maintain consistent icon style and sizing across all cards
- White Space: Provide adequate spacing between elements for readability
- Responsive Layout: Design cards to work well at different sizes
⚡ Performance
- Efficient Updates: Batch updates and use appropriate refresh intervals
- Data Caching: Cache frequently accessed data to reduce API calls
- Lazy Loading: Load card data only when cards become visible
- Memory Management: Properly dispose of timers and event handlers
🔄 Real-Time Updates
- Update Frequency: Choose appropriate refresh intervals (not too frequent)
- Visual Feedback: Indicate when data is being updated
- Error Handling: Gracefully handle network errors and data issues
- Thread Safety: Use proper thread synchronization for UI updates
- Information Overload: Don't crowd too much information into a single card
- Misleading Trends: Ensure trend indicators accurately represent the data
- Poor Context: Avoid showing percentages without indicating what they're relative to
- Update Frequency: Don't update too frequently; it can be distracting
- Quick Comprehension: Users can understand the metric within 2-3 seconds
- Actionable Insights: Cards provide enough context for decision-making
- Visual Consistency: All cards follow the same design patterns
- Responsive Updates: Data updates smoothly without disrupting user experience