Skip to content

kb(pivotgrid): Add KB for local date aggregations and explain differences between local and XMLA binding #2759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions components/pivotgrid/data-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,22 @@ The PivotGrid supports different data sources via its `DataProviderType` paramet
* [`Local`](#local) (default)
* [`Xmla`](#xmla)

### Usage Differences

The available data providers differ in several ways:

* XMLA binding is more complex to setup, but more flexible.
* Local data does not support aggregations by date periods for `DateTime` properties. If a `DateTime` property is added as a row or column, the PivotGrid will generate a separate row or column for each unique `DateTime` value. If you need [aggregations by year, month, week, and so on, then create additional `int` or `string` properties in the PivotGrid model class](slug:pivotgrid-kb-local-date-aggregates).
* XMLA binding supports [load on demand](slug:pivotgrid-overview#pivotgrid-parameters), which offloads all calculations to the external data source. Local binding receives all data at once and performs all aggregate calculations in-memory. Large amounts of local data may impact the performance, especially in WebAssembly apps.
* When using load on demand, XMLA binding supports custom aggregate functions that are defined and performed in the OLAP cube. Local data supports only the [predefined aggregate types in the `PivotGridAggregateType` enum](slug:telerik.blazor.pivotgridaggregatetype).
* When using local data, all defined measures in `<PivotGridMeasures>` render by default in the PivotGrid. Users can uncheck and hide the measures they don't need from the [PivotGrid Configurator](slug:pivotgrid-configurator).

## Local

When bound to local data, the Pivot Grid requires its `Data` parameter to provide all the data at once as `IEnumerable<TItem>`. The component will perform all aggregate calculations in-memory and there is no [load on demand](slug:pivotgrid-overview#pivotgrid-parameters).
When bound to local data, the Pivot Grid requires its `Data` parameter to provide all the data at once as `IEnumerable<TItem>`.

If the local data changes programmatically, you need to reset the collection instance or [call the PivotGrid `Rebind()` method](slug:pivotgrid-overview#pivotgrid-reference-and-methods). See the common documentation about [refreshing component data](slug:common-features-data-binding-overview#refresh-data) for details.

> Large amounts of local data may impact the performance, especially in WebAssembly applications.

>caption PivotGrid bound to Local data provider

<div class="skip-repl"></div>
Expand Down
169 changes: 169 additions & 0 deletions knowledge-base/pivotgrid-local-date-aggregates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
title: Use Date Aggregates with Local Data
description: Learn how to implement and configure aggregate calculations by date periods like year, month, week, and day, when using local data binding with the Telerik PivotGrid for Blazor.
type: how-to
page_title: How to Use Date Aggregates with Local Data
slug: pivotgrid-kb-local-date-aggregates
tags: blazor, pivotgrid, aggregates
ticketid: 1678109
res_type: kb
---

## Environment

<table>
<tbody>
<tr>
<td>Product</td>
<td>PivotGrid for Blazor</td>
</tr>
</tbody>
</table>

## Description

This KB answers the following questions:

* How to allow users to aggregate local data by date periods, such as years, months, weeks, and days?
* How to enable date aggregations with local PivotGrid data binding?
* How to calculate date aggregates by period with a local data provider?

## Solution

The [PivotGrid supports date aggregates by period](slug:pivotgrid-data-binding#usage-differences) only with [XMLA data binding](slug:pivotgrid-data-binding#xmla). Consider the following approach for local data scenarios:

1. Define all `DateTime` properties in the PivotGrid model class as `internal`, `protected`, or `private`, so that the PivotGrid ignores them.
1. Define additional helper properties in the PivotGrid model class, which will extract the year, month, week, and day values from the `DateTime` properties.
1. Use the helper properties in the PivotGrid definition and UI.
1. (optional) Use a [PivotGrid column header template](slug:pivotgrid-templates#column-header-template) to format the values of the helper properties. This may be necessary, because the PivotGrid sorts `string` values alphabetically in the column headers. In this case, you may also need to [get the correct localized value for the "TOTAL" label](slug:globalization-localization).

>caption Using date period aggregations in the Telerik PivotGrid for Blazor

````RAZOR.skip-repl
@using System.Globalization
@using Telerik.Blazor.Services

@inject ITelerikStringLocalizer TelerikStringLocalizer

<TelerikPivotGridContainer>

<TelerikPivotGridConfigurator />

<TelerikPivotGridConfiguratorButton />

<TelerikPivotGrid Data="@PivotData"
DataProviderType="@PivotGridDataProviderType.Local"
ColumnHeadersWidth="150px">
<PivotGridColumns>
<PivotGridColumn Name="@nameof(SalesModel.Year)" Title="Year" />
<PivotGridColumn Name="@nameof(SalesModel.Month)" Title="Month" />
<PivotGridColumn Name="@nameof(SalesModel.Week)" Title="Week" />
</PivotGridColumns>
<PivotGridRows>
<PivotGridRow Name="@nameof(SalesModel.Category)" Title="Category" />
<PivotGridRow Name="@nameof(SalesModel.Product)" />
</PivotGridRows>
<PivotGridMeasures>
<PivotGridMeasure Name="@nameof(SalesModel.Revenue)"
Title="Revenue"
Format="{0:C2}"
Aggregate="@PivotGridAggregateType.Sum" />
</PivotGridMeasures>
<ColumnHeaderTemplate>
@{ var c = (PivotGridColumnHeaderTemplateContext)context; }
@ConvertYearMonthString(c.Text)
</ColumnHeaderTemplate>
</TelerikPivotGrid>

</TelerikPivotGridContainer>

@code {
private List<SalesModel> PivotData { get; set; } = new List<SalesModel>();

protected override void OnInitialized()
{
TotalSuffix = " " + TelerikStringLocalizer["PivotGrid_TotalValueFormat"].Replace("{0}", string.Empty).Trim();

GenerateData();
}

/// <summary>
/// Gets or sets the correct localized TOTAL string.
/// </summary>
private string TotalSuffix { get; set; } = " Total";

/// <summary>
/// Convert a <c>yyyy MM</c> string to <c>MMM yyyy</c>.
/// </summary>
/// <param name="yearMonth">The PivotGrid column header text that will render without a template.</param>
/// <returns>If the <c>yearMonth</c> argument is valid, returns a DateTime string in format <c>MMM yyyy</c> plus the optional Total suffix.
/// Otherwise, return the argument as is.</returns>
private string ConvertYearMonthString(string yearMonth)
{
string yearMonthWithoutTotal = yearMonth.Replace(TotalSuffix, string.Empty, StringComparison.InvariantCultureIgnoreCase);
string totalSuffixConditional = yearMonth.Replace(yearMonthWithoutTotal, string.Empty);
string[] separateYearMonth = yearMonthWithoutTotal.Split(" ");

if (yearMonthWithoutTotal.Length == 7 &&
yearMonthWithoutTotal.IndexOf(" ") == 4 &&
separateYearMonth.Length == 2)
{
bool yearSuccess = int.TryParse(separateYearMonth[0], NumberStyles.None, CultureInfo.InvariantCulture, out int year);
bool monthSuccess = int.TryParse(separateYearMonth[1], NumberStyles.None, CultureInfo.InvariantCulture, out int month);

if (yearSuccess && monthSuccess)
{
DateTime actualDate = new DateTime(year, month, 1);

return $"{actualDate.ToString("MMM yyyy")} {totalSuffixConditional}";
}
}

return yearMonth;
}

private void GenerateData()
{
int dataItemCount = 100;
int categoryCount = 3;
int productCount = 7 + 1; // effectively 7
int cityCount = 3 + 1; // effectively 3
int daysBack = 6 * 30;
Random rnd = Random.Shared;

for (int i = 1; i <= dataItemCount; i++)
{
var productNumber = rnd.Next(1, productCount);

PivotData.Add(new SalesModel()
{
Category = $"Category {productNumber % categoryCount + 1}",
Product = $"Product {productNumber}",
City = $"City {rnd.Next(1, cityCount)}",
ContractDate = DateTime.Today.AddDays(-rnd.Next(1, daysBack)),
Revenue = rnd.Next(123, 987) * 1.23m
});

PivotData.OrderBy(p => p.ContractDate);
}
}

public class SalesModel
{
public string Category { get; set; } = null!;
public string Product { get; set; } = null!;
public string City { get; set; } = null!;
internal DateTime ContractDate { get; set; }

public int Year => ContractDate.Year;
public string Month => ContractDate.ToString("yyyy MM");
public string Week => $"Week {ISOWeek.GetWeekOfYear(ContractDate)} {ContractDate.ToString("yyyy")}";

public decimal Revenue { get; set; }
}
}
````

## See Also

* [Pivot Grid Data Providers](slug:pivotgrid-data-binding)
Loading