# MetricView
*Display key performance indicators (KPIs) and metrics with trend indicators, goal progress tracking, and data loading via UseQuery hooks for dashboard [applications](../../01_Onboarding/02_Concepts/10_Apps.md).*
The `MetricView` [widget](../../01_Onboarding/02_Concepts/03_Widgets.md) is a specialized dashboard component built on top of [Card](04_Card.md) that displays business metrics with visual indicators for performance trends and goal achievement. It uses [UseQuery](../../03_Hooks/02_Core/09_UseQuery.md) hooks for data fetching and automatically handles loading states, error handling, and provides a consistent layout for KPI dashboards.
## Basic Usage
Here's a simple example of a metric view showing total sales with a trend indicator and goal progress. The second parameter is an optional icon, and the third parameter is a hook function that receives an `IViewContext` and returns a `QueryResult<MetricRecord>`.
```csharp
new MetricView(
"Total Sales",
Icons.DollarSign,
ctx => ctx.UseQuery(
key: "total-sales",
fetcher: () => Task.FromResult(new MetricRecord(
"$84,250", // Current metric value
0.21, // 21% increase from previous period
0.21, // 21% of goal achieved
"$800,000" // Goal target
))
)
)
```
### Negative Trends
Negative trend values automatically display with a downward arrow and destructive color styling.
> **Info:** Trend Arrows: Green up arrow for positive trends, red down arrow for negative trends
```csharp
new MetricView(
"Stock Price",
Icons.CircleDollarSign,
ctx => ctx.UseQuery(
key: "stock-price",
fetcher: () => Task.FromResult(new MetricRecord(
"$42.30",
-0.15, // 15% decrease (negative trend)
0.45,
"$95.00 target"
))
)
)
```
### Using MetricView in Layouts
Combine multiple MetricViews in grid [layouts](../../01_Onboarding/02_Concepts/02_Views.md) to create comprehensive dashboards.
> **Info:** MetricRecord takes four parameters: MetricFormatted (string) for the value, TrendComparedToPreviousPeriod (decimal, e.g. 0.21 for 21%) for trend arrows, GoalAchieved (0 to 1) for progress bars, and GoalFormatted (string) for goal text. All except MetricFormatted are optional.
```csharp
Layout.Grid().Columns(2)
| new MetricView("Total Sales", Icons.DollarSign,
ctx => ctx.UseQuery(key: "sales", fetcher: () => Task.FromResult(new MetricRecord("$84,250", 0.21, 0.21, "$800,000"))))
| new MetricView("Post Engagement", Icons.Heart,
ctx => ctx.UseQuery(key: "engagement", fetcher: () => Task.FromResult(new MetricRecord("1,012.50%", 0.381, 1.25, "806.67%"))))
| new MetricView("User Comments", Icons.UserCheck,
ctx => ctx.UseQuery(key: "comments", fetcher: () => Task.FromResult(new MetricRecord("2.25", 0.381, 0.90, "2.50"))))
| new MetricView("System Health", Icons.Activity,
ctx => ctx.UseQuery(key: "health", fetcher: () => Task.FromResult(new MetricRecord("99.9%", null, 0.99, "100% uptime"))))
```
### Async Data Loading
The MetricView uses [UseQuery](../../03_Hooks/02_Core/09_UseQuery.md) hooks for data fetching, which automatically handle loading states with a skeleton loader. This is useful when fetching metrics from [databases](../../01_Onboarding/02_Concepts/01_Program.md) or APIs.
```csharp
new MetricView(
"Database Query",
Icons.Database,
ctx => ctx.UseQuery(
key: "db-query",
fetcher: async ct => {
await Task.Delay(1000, ct); // Simulate API call
return new MetricRecord("1,247 records", 0.125, 0.75, "1,500 records");
}
)
)
```
### Error Handling
When the data fetching fails, the MetricView automatically displays an error state from the QueryResult.
```csharp
new MetricView(
"Failed Metric",
Icons.TriangleAlert,
ctx => ctx.UseQuery<MetricRecord, string>(
key: "failing-metric",
fetcher: async ct => {
await Task.Delay(500, ct);
throw new Exception("Failed to load metric data");
}
)
)
```
## API
[View Source: MetricView.cs](https://github.com/Ivy-Interactive/Ivy-Framework/blob/main/src/Ivy/Views/Dashboards/MetricView.cs)
### Constructors
| Signature |
|-----------|
| `new MetricView(string title, Icons? icon, Func<IViewContext, QueryResult<MetricRecord>> useMetricData)` |
## Examples
### E-Commerce Analytics Dashboard
A complete e-commerce dashboard showing sales metrics, customer engagement, and inventory status with async data loading from a [database](../../01_Onboarding/02_Concepts/01_Program.md).
```csharp
public class ECommerceDashboard : ViewBase
{
public record SalesData(decimal Revenue, decimal PreviousRevenue, int Orders, int PreviousOrders, decimal ConversionRate, decimal PreviousConversionRate);
private QueryResult<MetricRecord> UseRevenueMetric(IViewContext context)
{
return context.UseQuery(
key: "revenue-metric",
fetcher: async ct =>
{
await Task.Delay(800, ct); // Simulate database query
var data = new SalesData(
Revenue: 284750.50m,
PreviousRevenue: 235000m,
Orders: 1247,
PreviousOrders: 1089,
ConversionRate: 3.45m,
PreviousConversionRate: 2.87m
);
var trend = (double)((data.Revenue - data.PreviousRevenue) / data.PreviousRevenue);
var goalAchieved = (double)(data.Revenue / 400000m); // Monthly goal: $400k
return new MetricRecord(
data.Revenue.ToString("C0"),
trend,
goalAchieved,
"$400,000 target"
);
}
);
}
private QueryResult<MetricRecord> UseOrdersMetric(IViewContext context)
{
return context.UseQuery(
key: "orders-metric",
fetcher: async ct =>
{
await Task.Delay(600, ct);
var orders = 1247;
var previousOrders = 1089;
var trend = (double)(orders - previousOrders) / previousOrders;
return new MetricRecord(
orders.ToString("N0"),
trend,
(double)orders / 1500, // Goal: 1500 orders
"1,500 orders target"
);
}
);
}
private QueryResult<MetricRecord> UseConversionMetric(IViewContext context)
{
return context.UseQuery(
key: "conversion-metric",
fetcher: async ct =>
{
await Task.Delay(700, ct);
var rate = 3.45;
var previous = 2.87;
var trend = (rate - previous) / previous;
return new MetricRecord(
rate.ToString("F2") + "%",
trend,
rate / 5.0, // Target: 5% conversion
"5% target"
);
}
);
}
private QueryResult<MetricRecord> UseAverageOrderValue(IViewContext context)
{
return context.UseQuery(
key: "aov-metric",
fetcher: async ct =>
{
await Task.Delay(500, ct);
var aov = 228.45m;
var previous = 215.80m;
return new MetricRecord(
aov.ToString("C2"),
(double)((aov - previous) / previous),
null,
null
);
}
);
}
public override object? Build()
{
return Layout.Vertical().Gap(4)
| Text.H2("E-Commerce Dashboard")
| (Layout.Grid().Columns(2).Gap(3)
| new MetricView("Total Revenue", Icons.DollarSign, UseRevenueMetric)
| new MetricView("Total Orders", Icons.ShoppingCart, UseOrdersMetric)
| new MetricView("Conversion Rate", Icons.TrendingUp, UseConversionMetric)
| new MetricView("Avg Order Value", Icons.CreditCard, UseAverageOrderValue)
);
}
}
```
### SaaS Metrics Dashboard
Track key SaaS metrics including MRR, churn rate, active users, and customer lifetime value with [UseQuery](../../03_Hooks/02_Core/09_UseQuery.md) hooks for data caching and automatic revalidation.
```csharp
public class SaaSDashboard : ViewBase
{
private QueryResult<MetricRecord> UseMrrMetric(IViewContext context)
{
return context.UseQuery(
key: "mrr-metric",
fetcher: async ct =>
{
await Task.Delay(800, ct); // Simulate API call
var mrr = 125430m;
var previousMrr = 108750m;
return new MetricRecord(
mrr.ToString("C0"),
(double)((mrr - previousMrr) / previousMrr),
(double)(mrr / 150000m),
"$150K target"
);
}
);
}
private QueryResult<MetricRecord> UseActiveUsersMetric(IViewContext context)
{
return context.UseQuery(
key: "active-users-metric",
fetcher: async ct =>
{
await Task.Delay(600, ct);
var activeUsers = 3847;
var previousActiveUsers = 3520;
return new MetricRecord(
activeUsers.ToString("N0"),
(double)(activeUsers - previousActiveUsers) / previousActiveUsers,
(double)activeUsers / 5000,
"5,000 users goal"
);
}
);
}
private QueryResult<MetricRecord> UseChurnRateMetric(IViewContext context)
{
return context.UseQuery(
key: "churn-rate-metric",
fetcher: async ct =>
{
await Task.Delay(700, ct);
var churnRate = 2.3;
var previousChurnRate = 3.1;
return new MetricRecord(
churnRate.ToString("F1") + "%",
-(churnRate - previousChurnRate) / previousChurnRate, // Negative is good for churn
1 - (churnRate / 5.0), // Lower is better
"Target: <2%"
);
}
);
}
private QueryResult<MetricRecord> UseLtvMetric(IViewContext context)
{
return context.UseQuery(
key: "ltv-metric",
fetcher: async ct =>
{
await Task.Delay(500, ct);
var ltv = 8450m;
var previousLtv = 7890m;
return new MetricRecord(
ltv.ToString("C0"),
(double)((ltv - previousLtv) / previousLtv),
null,
null
);
}
);
}
public override object? Build()
{
return Layout.Vertical().Gap(4)
| Text.H2("SaaS Metrics Dashboard")
| Text.Muted("Real-time business metrics and KPIs")
| (Layout.Grid().Columns(2).Gap(3)
| new MetricView("Monthly Recurring Revenue", Icons.DollarSign, UseMrrMetric)
| new MetricView("Active Users", Icons.Users, UseActiveUsersMetric)
| new MetricView("Churn Rate", Icons.UserMinus, UseChurnRateMetric)
| new MetricView("Customer LTV", Icons.Gem, UseLtvMetric)
);
}
}
```