-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #72 from anmcgrath/range-sorting
Range sorting
- Loading branch information
Showing
38 changed files
with
1,067 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
using BlazorDatasheet.Core.Data; | ||
using BlazorDatasheet.Core.Data.Cells; | ||
using BlazorDatasheet.DataStructures.Geometry; | ||
using BlazorDatasheet.DataStructures.Store; | ||
using BlazorDatasheet.Formula.Core; | ||
|
||
namespace BlazorDatasheet.Core.Commands; | ||
|
||
public class SortRangeCommand : IUndoableCommand | ||
{ | ||
private readonly IRegion _region; | ||
public IRegion? SortedRegion; | ||
private readonly List<ColumnSortOptions> _sortOptions; | ||
public int[] OldIndices = Array.Empty<int>(); | ||
private readonly RegionRestoreData<string> _typeRestoreData = new(); | ||
|
||
/// <summary> | ||
/// Sorts the specified region on values using the specified sort options. | ||
/// </summary> | ||
/// <param name="region">The region to sort</param> | ||
/// <param name="sortOptions">The column sort options, if null the default sort (sort on column 0 ascending) will be used. | ||
/// If two column values are equal, then the next option will be used for the sort, equivalent to a ThenBy</param> | ||
public SortRangeCommand(IRegion region, List<ColumnSortOptions>? sortOptions = null) | ||
{ | ||
_region = region; | ||
_sortOptions = sortOptions ?? new List<ColumnSortOptions>() | ||
{ new(0, true) }; | ||
} | ||
|
||
/// <summary> | ||
/// Sorts the specified region on values using the specified sort options. | ||
/// </summary> | ||
/// <param name="region">The region to sort</param> | ||
/// <param name="sortOption">The column sort options, if null the default sort (sort on column 0 ascending) will be used. | ||
/// If two column values are equal, then the next option will be used for the sort, equivalent to a ThenBy</param> | ||
public SortRangeCommand(IRegion region, ColumnSortOptions sortOption) | ||
{ | ||
_region = region; | ||
_sortOptions = new List<ColumnSortOptions> { sortOption }; | ||
} | ||
|
||
public bool Execute(Sheet sheet) | ||
{ | ||
var store = sheet.Cells.GetCellDataStore(); | ||
|
||
var rowCollection = store.GetNonEmptyRowData(_region); | ||
var rowIndices = new Span<int>(rowCollection.RowIndicies); | ||
var rowData = new Span<RowData<CellValue>>(rowCollection.Rows); | ||
|
||
rowData.Sort(rowIndices, Comparison); | ||
|
||
SortedRegion = new Region(_region.Top, _region.Top + rowIndices.Length, _region.Left, _region.Right); | ||
SortedRegion = SortedRegion.GetIntersection(sheet.Region); | ||
|
||
if (SortedRegion == null) | ||
return true; | ||
|
||
sheet.BatchUpdates(); | ||
|
||
var formulaData = sheet.Cells.GetFormulaStore().GetSubStore(_region, false); | ||
var typeData = | ||
(ConsolidatedDataStore<string>)sheet.Cells.GetTypeStore().GetSubStore(_region, false); | ||
|
||
// clear any row data that has been shifted (which should be all non-empty rows) | ||
for (int i = 0; i < rowData.Length; i++) | ||
{ | ||
var row = rowData[i].Row; | ||
var rowReg = new Region(row, row, _region.Left, _region.Right); | ||
sheet.Cells.ClearCellsImpl(new[] { rowReg }); | ||
_typeRestoreData.Merge(sheet.Cells.GetTypeStore().Clear(rowReg)); | ||
} | ||
|
||
for (int i = 0; i < rowData.Length; i++) | ||
{ | ||
var newRowNo = _region.Top + i; | ||
var oldRowNo = rowData[i].Row; | ||
|
||
for (int j = 0; j < rowData[i].ColumnIndices.Length; j++) | ||
{ | ||
var col = rowData[i].ColumnIndices[j]; | ||
var val = rowData[i].Values[j]; | ||
var formula = formulaData.Get(oldRowNo, col); | ||
var type = typeData.Get(oldRowNo, col); | ||
|
||
sheet.Cells.SetCellTypeImpl(new Region(newRowNo, col), type); | ||
|
||
if (formula == null) | ||
sheet.Cells.SetValueImpl(newRowNo, col, val); | ||
else | ||
{ | ||
formula.ShiftReferences((newRowNo - oldRowNo), 0); | ||
sheet.Cells.SetFormulaImpl(newRowNo, col, formula); | ||
} | ||
} | ||
} | ||
|
||
sheet.EndBatchUpdates(); | ||
|
||
OldIndices = rowIndices.ToArray(); | ||
|
||
return true; | ||
} | ||
|
||
private int Comparison(RowData<CellValue> x, RowData<CellValue> y) | ||
{ | ||
for (int i = 0; i < _sortOptions.Count; i++) | ||
{ | ||
var sortOption = _sortOptions[i]; | ||
var xValue = x.GetColumnData(sortOption.ColumnIndex + _region.Left); | ||
var yValue = y.GetColumnData(sortOption.ColumnIndex + _region.Left); | ||
|
||
if (xValue?.Data == null && yValue?.Data == null) | ||
continue; | ||
|
||
// null comparisons shouldn't depend on the sort order - | ||
// null values always end up last. | ||
if (xValue?.Data == null) | ||
return 1; | ||
if (yValue?.Data == null) | ||
return -1; | ||
|
||
int comparison = xValue.CompareTo(yValue); | ||
|
||
if (comparison == 0) | ||
continue; | ||
|
||
comparison = sortOption.Ascending ? comparison : -comparison; | ||
|
||
return comparison; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
public bool Undo(Sheet sheet) | ||
{ | ||
if (SortedRegion == null) | ||
return true; | ||
|
||
var rowCollection = sheet.Cells.GetCellDataStore().GetRowData(SortedRegion); | ||
var formulaCollection = sheet.Cells.GetFormulaStore().GetSubStore(SortedRegion, false); | ||
var typeCollection = (ConsolidatedDataStore<string>)sheet.Cells.GetTypeStore().GetSubStore(SortedRegion); | ||
|
||
sheet.BatchUpdates(); | ||
sheet.Cells.ClearCellsImpl(new[] { SortedRegion }); | ||
sheet.Cells.GetTypeStore().Clear(SortedRegion); | ||
|
||
var rowData = rowCollection.Rows; | ||
var rowIndices = rowCollection.RowIndicies; | ||
for (int i = 0; i < OldIndices.Length; i++) | ||
{ | ||
var newRowNo = OldIndices[i]; | ||
for (int j = 0; j < rowData[i].ColumnIndices.Length; j++) | ||
{ | ||
var col = rowData[i].ColumnIndices[j]; | ||
var formula = formulaCollection.Get(rowIndices[i], col); | ||
var type = typeCollection.Get(rowIndices[i], col); | ||
if (formula == null) | ||
{ | ||
var val = rowData[i].Values[j]; | ||
sheet.Cells.SetValueImpl(newRowNo, col, val); | ||
} | ||
else | ||
{ | ||
formula.ShiftReferences((newRowNo - rowIndices[i]), 0); | ||
sheet.Cells.SetFormulaImpl(newRowNo, col, formula); | ||
} | ||
|
||
sheet.Cells.SetCellTypeImpl(new Region(newRowNo, col), type); | ||
} | ||
} | ||
|
||
var restoreData = new CellStoreRestoreData() | ||
{ | ||
TypeRestoreData = _typeRestoreData | ||
}; | ||
|
||
sheet.Cells.Restore(restoreData); | ||
sheet.EndBatchUpdates(); | ||
|
||
return true; | ||
} | ||
} | ||
|
||
public class ColumnSortOptions | ||
{ | ||
/// <summary> | ||
/// The column index, relative to the range being sorted. | ||
/// </summary> | ||
public int ColumnIndex { get; set; } | ||
|
||
/// <summary> | ||
/// Whether to sort in ascending order. | ||
/// </summary> | ||
public bool Ascending { get; set; } | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="columnIndex"></param> | ||
/// <param name="ascending"></param> | ||
public ColumnSortOptions(int columnIndex, bool ascending) | ||
{ | ||
ColumnIndex = columnIndex; | ||
Ascending = ascending; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/BlazorDatasheet.Core/Events/BeforeRangeSortEventArgs.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System.ComponentModel; | ||
using BlazorDatasheet.Core.Commands; | ||
using BlazorDatasheet.DataStructures.Geometry; | ||
|
||
namespace BlazorDatasheet.Core.Events; | ||
|
||
public class BeforeRangeSortEventArgs : CancelEventArgs | ||
{ | ||
public IRegion Region { get; } | ||
public IList<ColumnSortOptions>? SortOptions { get; } | ||
|
||
public BeforeRangeSortEventArgs(IRegion region, IList<ColumnSortOptions>? sortOptions) | ||
{ | ||
Region = region; | ||
SortOptions = sortOptions; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.