Skip to content

Commit 7497ea1

Browse files
committed
PivotDataGrid dynamic data support added
1 parent d68bb34 commit 7497ea1

File tree

5 files changed

+244
-19
lines changed

5 files changed

+244
-19
lines changed

Radzen.Blazor/RadzenPivotDataGrid.razor.cs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,14 @@ protected RenderFragment RenderFooter()
950950

951951
for (int i = 0; i < colPath.Count; i++)
952952
{
953-
items = items.Where($"i => i.{pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
953+
if (pivotColumns[i].Property.Contains("it["))
954+
{
955+
items = items.Where($"it => {pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
956+
}
957+
else
958+
{
959+
items = items.Where($"i => i.{pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
960+
}
954961
}
955962

956963
var total = GetAggregateValue(items, aggregate);
@@ -1023,7 +1030,14 @@ private IQueryable<TItem> GetRowItems(PivotBodyRow pivotRow)
10231030
continue; // Skip padding cells added to align depths
10241031
}
10251032

1026-
items = items.Where($@"i => i.{pivotRows[i].Property} == {ExpressionSerializer.FormatValue(cell.Value)}");
1033+
if (pivotRows[i].Property.Contains("it["))
1034+
{
1035+
items = items.Where($@"it => {pivotRows[i].Property} == {ExpressionSerializer.FormatValue(cell.Value)}");
1036+
}
1037+
else
1038+
{
1039+
items = items.Where($@"i => i.{pivotRows[i].Property} == {ExpressionSerializer.FormatValue(cell.Value)}");
1040+
}
10271041
}
10281042

10291043
return items;
@@ -1131,7 +1145,7 @@ public object GetAggregateValue(IQueryable<TItem> items, RadzenPivotAggregate<TI
11311145
}
11321146
else
11331147
{
1134-
values = items.Select(aggregate.Property).Cast(propertyType);
1148+
values = propertyType != null ? items.Select(aggregate.Property).Cast(propertyType) : items.Select(aggregate.Property);
11351149
}
11361150

11371151
switch (aggregate.Aggregate)
@@ -1249,7 +1263,14 @@ private void FlattenRowHeaderTree(RowHeaderNode node, List<RowHeaderCell> prefix
12491263
var items = node.Items;
12501264
for (int i = 0; i < colPath.Count; i++)
12511265
{
1252-
items = items.Where($"i => i.{pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
1266+
if (pivotColumns[i].Property.Contains("it["))
1267+
{
1268+
items = items.Where($"it => {pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
1269+
}
1270+
else
1271+
{
1272+
items = items.Where($"i => i.{pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
1273+
}
12531274
}
12541275
row.ValueCells.Add(items.Count() > 0 ? GetAggregateValue(items, aggregate) : null);
12551276
}
@@ -1289,12 +1310,26 @@ private void FlattenRowHeaderTree(RowHeaderNode node, List<RowHeaderCell> prefix
12891310
// Filter by row path
12901311
for (int i = 0; i < newPrefix.Count && i < pivotRows.Count; i++)
12911312
{
1292-
items = items.Where($@"i => i.{pivotRows[i].Property} == {ExpressionSerializer.FormatValue(newPrefix[i].Value)}");
1313+
if (pivotRows[i].Property.Contains("it["))
1314+
{
1315+
items = items.Where($@"it => {pivotRows[i].Property} == {ExpressionSerializer.FormatValue(newPrefix[i].Value)}");
1316+
}
1317+
else
1318+
{
1319+
items = items.Where($@"i => i.{pivotRows[i].Property} == {ExpressionSerializer.FormatValue(newPrefix[i].Value)}");
1320+
}
12931321
}
12941322
// Filter by column path
12951323
for (int i = 0; i < colPath.Count; i++)
12961324
{
1297-
items = items.Where($"i => i.{pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
1325+
if (pivotColumns[i].Property.Contains("it["))
1326+
{
1327+
items = items.Where($"it => {pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
1328+
}
1329+
else
1330+
{
1331+
items = items.Where($"i => i.{pivotColumns[i].Property} == {ExpressionSerializer.FormatValue(colPath[i])}");
1332+
}
12981333
}
12991334
collapsedRow.ValueCells.Add(items.Count() > 0 ? GetAggregateValue(items, aggregate) : null);
13001335
}
@@ -1960,12 +1995,12 @@ internal List<CompositeFilterDescriptor> GetFilters()
19601995

19611996
var innerFilterExpressions = new List<CompositeFilterDescriptor>();
19621997

1963-
var filterExpression = BuildFilterExpression(property, filterValue, filterOperator);
1998+
var filterExpression = BuildFilterExpression(property, filterValue, filterOperator, column.Type);
19641999
innerFilterExpressions.Add(filterExpression);
19652000

19662001
if (secondFilterValue != null)
19672002
{
1968-
var secondFilterExpression = BuildFilterExpression(property, secondFilterValue, secondFilterOperator);
2003+
var secondFilterExpression = BuildFilterExpression(property, secondFilterValue, secondFilterOperator, column.Type);
19692004
innerFilterExpressions.Add(secondFilterExpression);
19702005
}
19712006

@@ -1992,12 +2027,12 @@ internal List<CompositeFilterDescriptor> GetFilters()
19922027

19932028
var innerFilterExpressions = new List<CompositeFilterDescriptor>();
19942029

1995-
var filterExpression = BuildFilterExpression(property, filterValue, filterOperator);
2030+
var filterExpression = BuildFilterExpression(property, filterValue, filterOperator, row.Type);
19962031
innerFilterExpressions.Add(filterExpression);
19972032

19982033
if (secondFilterValue != null)
19992034
{
2000-
var secondFilterExpression = BuildFilterExpression(property, secondFilterValue, secondFilterOperator);
2035+
var secondFilterExpression = BuildFilterExpression(property, secondFilterValue, secondFilterOperator, row.Type);
20012036
innerFilterExpressions.Add(secondFilterExpression);
20022037
}
20032038

@@ -2016,13 +2051,14 @@ internal List<CompositeFilterDescriptor> GetFilters()
20162051
/// <summary>
20172052
/// Builds a filter expression for a property and value.
20182053
/// </summary>
2019-
private CompositeFilterDescriptor BuildFilterExpression(string property, object value, FilterOperator filterOperator)
2054+
private CompositeFilterDescriptor BuildFilterExpression(string property, object value, FilterOperator filterOperator, Type type)
20202055
{
20212056
return new CompositeFilterDescriptor
20222057
{
20232058
Property = property,
20242059
FilterValue = value,
2025-
FilterOperator = filterOperator
2060+
FilterOperator = filterOperator,
2061+
Type = type
20262062
};
20272063
}
20282064

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
@using System
2+
@using System.Collections.Generic
3+
@using System.Linq
4+
@using Microsoft.EntityFrameworkCore
5+
@using Radzen
6+
@using Radzen.Blazor
7+
@inherits RadzenBlazorDemos.Shared.DbContextPage
8+
9+
<RadzenCard Variant="Variant.Outlined" class="rz-my-4">
10+
<RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" AlignItems="AlignItems.Center" Style="margin:1rem">
11+
<RadzenSwitch @bind-Value="@showColumnTotals" Name="ShowColumnTotals" />
12+
<RadzenLabel Text="Show column totals" Component="ShowColumnTotals" />
13+
<RadzenSwitch @bind-Value="@showRowTotals" Name="ShowRowTotals" />
14+
<RadzenLabel Text="Show row totals" Component="ShowRowTotals" />
15+
<RadzenSwitch @bind-Value="@allowDrillDown" Name="AllowDrillDown" />
16+
<RadzenLabel Text="Allow drill-down" Component="AllowDrillDown" />
17+
<RadzenSwitch @bind-Value="@allowPaging" Name="AllowPaging" />
18+
<RadzenLabel Text="Allow paging" Component="AllowPaging" />
19+
<RadzenDropDown @bind-Value="@pagerPosition"
20+
Visible="@allowPaging"
21+
TextProperty="Text"
22+
ValueProperty="Value"
23+
Data="@(Enum.GetValues(typeof(PagerPosition)).Cast<PagerPosition>().Select(t => new { Text = $"{t}", Value = t }))" />
24+
</RadzenStack>
25+
<RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" AlignItems="AlignItems.Center" Style="margin:1rem">
26+
<RadzenSwitch @bind-Value="@allowFieldsPicking" Name="AllowFieldsPicking" />
27+
<RadzenLabel Text="Allow fields picking" Component="AllowFieldsPicking" />
28+
<RadzenSwitch @bind-Value="@allowSorting" Name="AllowSorting" Visible="@allowFieldsPicking" />
29+
<RadzenLabel Text="Allow sorting" Component="AllowSorting" Visible="@allowFieldsPicking" />
30+
<RadzenSwitch @bind-Value="@allowFiltering" Name="AllowFiltering" Visible="@allowFieldsPicking" />
31+
<RadzenLabel Text="Allow filtering" Component="AllowFiltering" Visible="@allowFieldsPicking" />
32+
</RadzenStack>
33+
</RadzenCard>
34+
35+
<RadzenPivotDataGrid Data="@data"
36+
TItem="IDictionary<string, object>"
37+
AllowSorting="@allowSorting"
38+
AllowFiltering="@allowFiltering"
39+
AllowDrillDown="@allowDrillDown"
40+
AllowFieldsPicking="@allowFieldsPicking"
41+
ShowColumnsTotals="@showColumnTotals"
42+
ShowRowsTotals="@showRowTotals"
43+
AllowPaging="@allowPaging"
44+
PagerPosition="@pagerPosition"
45+
PageSize="20"
46+
AllowAlternatingRows="true"
47+
GridLines="Radzen.DataGridGridLines.Default"
48+
Style="height: 700px;">
49+
<Columns>
50+
@foreach (var field in columnFields)
51+
{
52+
<RadzenPivotColumn @key="@field.Property"
53+
Title="@field.Title"
54+
Width="@field.Width"
55+
Type="@field.Type"
56+
Property="@PropertyAccess.GetDynamicPropertyExpression(field.Property, field.Type)" />
57+
}
58+
</Columns>
59+
<Rows>
60+
@foreach (var field in rowFields)
61+
{
62+
<RadzenPivotRow @key="@field.Property"
63+
Title="@field.Title"
64+
Type="@field.Type"
65+
Property="@PropertyAccess.GetDynamicPropertyExpression(field.Property, field.Type)" />
66+
}
67+
</Rows>
68+
<Aggregates>
69+
@foreach (var aggregate in aggregateFields)
70+
{
71+
<RadzenPivotAggregate @key="@aggregate.Property"
72+
Title="@aggregate.Title"
73+
Type="@aggregate.Type"
74+
Property="@PropertyAccess.GetDynamicPropertyExpression(aggregate.Property, aggregate.Type)"
75+
Aggregate="@aggregate.Aggregate"
76+
FormatString="@aggregate.FormatString"
77+
TextAlign="@aggregate.TextAlign" />
78+
}
79+
</Aggregates>
80+
</RadzenPivotDataGrid>
81+
82+
@code {
83+
private readonly IReadOnlyList<PivotFieldDescriptor> columnFields = new[]
84+
{
85+
new PivotFieldDescriptor("OrderYear", "Order Year", typeof(int?), "140px"),
86+
new PivotFieldDescriptor("ShipCountry", "Ship Country", typeof(string), "180px")
87+
};
88+
89+
private readonly IReadOnlyList<PivotFieldDescriptor> rowFields = new[]
90+
{
91+
new PivotFieldDescriptor("Category", "Category", typeof(string)),
92+
new PivotFieldDescriptor("Product", "Product", typeof(string))
93+
};
94+
95+
private readonly IReadOnlyList<PivotAggregateDescriptor> aggregateFields = new[]
96+
{
97+
new PivotAggregateDescriptor("TotalSales", "Total Sales", typeof(double), AggregateFunction.Sum, "{0:C}", TextAlign.Right),
98+
new PivotAggregateDescriptor("Quantity", "Quantity", typeof(int), AggregateFunction.Sum, "{0:N0}", TextAlign.Right),
99+
new PivotAggregateDescriptor("Discount", "Average Discount", typeof(double), AggregateFunction.Average, "{0:P1}", TextAlign.Right),
100+
new PivotAggregateDescriptor("UnitPrice", "Average Unit Price", typeof(double), AggregateFunction.Average, "{0:C}", TextAlign.Right)
101+
};
102+
103+
private List<IDictionary<string, object>> data = new List<IDictionary<string, object>>();
104+
private bool allowDrillDown = true;
105+
private bool allowFieldsPicking = true;
106+
private bool allowSorting = true;
107+
private bool allowFiltering = true;
108+
private bool allowPaging = true;
109+
private bool showColumnTotals = true;
110+
private bool showRowTotals = true;
111+
private PagerPosition pagerPosition = PagerPosition.Bottom;
112+
113+
protected override async Task OnInitializedAsync()
114+
{
115+
await base.OnInitializedAsync();
116+
117+
var sales = await (from od in dbContext.OrderDetails
118+
join o in dbContext.Orders on od.OrderID equals o.OrderID
119+
join p in dbContext.Products on od.ProductID equals p.ProductID
120+
join c in dbContext.Categories on p.CategoryID equals c.CategoryID
121+
select new
122+
{
123+
CategoryName = c.CategoryName,
124+
ProductName = p.ProductName,
125+
OrderYear = o.OrderDate.HasValue ? o.OrderDate.Value.Year : 0,
126+
ShipCountry = o.ShipCountry,
127+
UnitPrice = od.UnitPrice ?? 0,
128+
Quantity = od.Quantity ?? 0,
129+
Discount = od.Discount ?? 0,
130+
TotalAmount = (od.UnitPrice ?? 0) * (od.Quantity ?? 0) * (1 - (od.Discount ?? 0))
131+
}).ToListAsync();
132+
133+
data = sales.Select(result =>
134+
{
135+
var row = new Dictionary<string, object>
136+
{
137+
["OrderYear"] = result.OrderYear,
138+
["ShipCountry"] = string.IsNullOrEmpty(result.ShipCountry) ? "Unknown" : result.ShipCountry,
139+
["Category"] = result.CategoryName ?? "(no category)",
140+
["Product"] = result.ProductName ?? "(no product)",
141+
["Quantity"] = result.Quantity,
142+
["UnitPrice"] = result.UnitPrice,
143+
["Discount"] = result.Discount,
144+
["TotalSales"] = result.TotalAmount
145+
};
146+
147+
return (IDictionary<string, object>)row;
148+
}).ToList();
149+
}
150+
151+
152+
private sealed record PivotFieldDescriptor(string Property, string Title, Type Type, string? Width = null);
153+
154+
private sealed record PivotAggregateDescriptor(string Property,
155+
string Title,
156+
Type Type,
157+
AggregateFunction Aggregate,
158+
string? FormatString,
159+
TextAlign TextAlign);
160+
}
161+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@page "/pivot-data-grid-dynamic"
2+
3+
<RadzenText TextStyle="TextStyle.H2" TagName="TagName.H1" class="rz-pt-8">
4+
PivotDataGrid <strong>dynamic</strong> data support
5+
</RadzenText>
6+
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P" class="rz-pb-4">
7+
Schema-less datasets are common when data comes from external APIs. This demo shows how to build a pivot table on top of
8+
<code>IDictionary&lt;string, object&gt;</code> records without defining C# models.
9+
</RadzenText>
10+
11+
<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.P" class="rz-pb-4">
12+
The example generates 200 rows with dynamic columns (year, region, category, product, quantity, unit price, discount, total sales) and uses
13+
<code>PropertyAccess.GetDynamicPropertyExpression</code> to configure pivot rows, columns, and aggregates at runtime while enabling totals, drill-down,
14+
filtering, sorting, and field picking.
15+
</RadzenText>
16+
17+
<RadzenExample ComponentName="PivotDataGrid" Example="PivotDataGridDynamicData">
18+
<PivotDataGridDynamicData />
19+
</RadzenExample>
20+

RadzenBlazorDemos/Pages/PivotDataGridIQueryable.razor

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,23 @@
77

88
<RadzenCard Variant="Variant.Outlined" class="rz-my-4">
99
<RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" AlignItems="AlignItems.Center" Style="margin:1rem">
10-
<RadzenSwitch @bind-Value=@showColumnsTotals Name="ShowColumnsTotals" TValue="bool" />
10+
<RadzenSwitch @bind-Value=@showColumnsTotals Name="ShowColumnsTotals" />
1111
<RadzenLabel Text="Show columns totals" Component="ShowColumnsTotals" />
12-
<RadzenSwitch @bind-Value=@showRowsTotals Name="ShowRowsTotals" TValue="bool" />
12+
<RadzenSwitch @bind-Value=@showRowsTotals Name="ShowRowsTotals" />
1313
<RadzenLabel Text="Show rows totals" Component="ShowRowsTotals" />
14-
<RadzenSwitch @bind-Value=@allowDrillDown Name="AllowDrillDown" TValue="bool" />
14+
<RadzenSwitch @bind-Value=@allowDrillDown Name="AllowDrillDown" />
1515
<RadzenLabel Text="Allow drill-down" Component="AllowDrillDown" />
16-
<RadzenSwitch @bind-Value=@allowPaging Name="AllowPaging" TValue="bool" />
16+
<RadzenSwitch @bind-Value=@allowPaging Name="AllowPaging" />
1717
<RadzenLabel Text="AllowPaging" Component="AllowPaging" />
1818
<RadzenDropDown @bind-Value="@pagerPosition" Visible="@allowPaging" TextProperty="Text" Name="PagerPosition" ValueProperty="Value"
1919
Data="@(Enum.GetValues(typeof(PagerPosition)).Cast<PagerPosition>().Select(t => new { Text = $"{t}", Value = t }))" />
2020
</RadzenStack>
2121
<RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" AlignItems="AlignItems.Center" Style="margin:1rem">
22-
<RadzenSwitch @bind-Value=@allowFieldsPicking Name="AllowFieldsPicking" TValue="bool" />
22+
<RadzenSwitch @bind-Value=@allowFieldsPicking Name="AllowFieldsPicking" />
2323
<RadzenLabel Text="Allow fields picking" Component="AllowFieldsPicking" />
24-
<RadzenSwitch @bind-Value=@allowSorting Name="AllowSorting" TValue="bool" Visible=@allowFieldsPicking />
24+
<RadzenSwitch @bind-Value=@allowSorting Name="AllowSorting" Visible=@allowFieldsPicking />
2525
<RadzenLabel Text="Allow sorting" Component="AllowSorting" Visible=@allowFieldsPicking />
26-
<RadzenSwitch @bind-Value=@allowFiltering Name="AllowFiltering" TValue="bool" Visible=@allowFieldsPicking />
26+
<RadzenSwitch @bind-Value=@allowFiltering Name="AllowFiltering" Visible=@allowFieldsPicking />
2727
<RadzenLabel Text="Allow filtering" Component="AllowFiltering" Visible=@allowFieldsPicking />
2828
</RadzenStack>
2929
</RadzenCard>

0 commit comments

Comments
 (0)