mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
Top10Rule.GetFormat(Cell) ignored its own Count and Bottom properties and returned the rule's Format for any numeric cell, silently producing the wrong conditional-formatting result. Add a range-aware ConditionalFormatStore.Calculate path that detects Top10Rule and passes the range to a new internal Top10Rule.GetFormat overload. The overload computes the threshold using DISTINCT numeric values from range.GetCells() (Excel semantics — ties at the threshold include all tied values). Non-numeric cells skipped per Excel. Includes tests for top-N, bottom-N, ties at threshold, and non-numeric cells being excluded from the ranking.
122 lines
3.2 KiB
C#
122 lines
3.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Radzen.Documents.Spreadsheet;
|
|
|
|
#nullable enable
|
|
|
|
/// <summary>
|
|
/// Base class for conditional formatting rules.
|
|
/// </summary>
|
|
public abstract class ConditionalFormatBase
|
|
{
|
|
/// <summary>
|
|
/// Calculates the conditional <see cref="Format"/> for the provided <see cref="Cell"/>.
|
|
/// Return null when the rule does not apply.
|
|
/// </summary>
|
|
public abstract Format? GetFormat(Cell cell);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Store for conditional formatting rules per ranges.
|
|
/// </summary>
|
|
public class ConditionalFormatStore
|
|
{
|
|
private readonly Dictionary<RangeRef, List<ConditionalFormatBase>> formats = [];
|
|
|
|
/// <summary>
|
|
/// Adds conditional formatting rules for a specific range.
|
|
/// </summary>
|
|
public void Add(RangeRef range, params ConditionalFormatBase[] rules)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(rules);
|
|
foreach (var rule in rules)
|
|
{
|
|
Add(range, rule);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a conditional formatting rule for a specific range.
|
|
/// </summary>
|
|
public void Add(RangeRef range, ConditionalFormatBase rule)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(rule);
|
|
if (!formats.TryGetValue(range, out var list))
|
|
{
|
|
list = [];
|
|
formats[range] = list;
|
|
}
|
|
|
|
list.Add(rule);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a conditional formatting rule from a specific range.
|
|
/// </summary>
|
|
public bool Remove(RangeRef range, ConditionalFormatBase rule)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(rule);
|
|
if (formats.TryGetValue(range, out var list))
|
|
{
|
|
var removed = list.Remove(rule);
|
|
if (list.Count == 0)
|
|
{
|
|
formats.Remove(range);
|
|
}
|
|
return removed;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all rules for a specific range.
|
|
/// </summary>
|
|
public IReadOnlyList<ConditionalFormatBase> GetRules(RangeRef range)
|
|
{
|
|
return formats.TryGetValue(range, out var list) ? list : [];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all ranges that have conditional formatting rules.
|
|
/// </summary>
|
|
public IEnumerable<RangeRef> Ranges => formats.Keys;
|
|
|
|
/// <summary>
|
|
/// Calculates the merged conditional overlay format for a cell from all matching rules.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Rules are applied in the order they were added, later rules can override earlier ones.
|
|
/// </remarks>
|
|
public Format? Calculate(Cell cell)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(cell);
|
|
Format? overlay = null;
|
|
|
|
foreach (var kvp in formats)
|
|
{
|
|
if (!kvp.Key.Contains(cell.Address))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var rule in kvp.Value)
|
|
{
|
|
var candidate = rule is Top10Rule top
|
|
? top.GetFormat(cell, kvp.Key)
|
|
: rule.GetFormat(cell);
|
|
|
|
if (candidate is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
overlay = overlay is null ? candidate.Clone() : overlay.Merge(candidate);
|
|
}
|
|
}
|
|
|
|
return overlay;
|
|
}
|
|
}
|