Skip to content

Create Squirrel#286

Open
ArtemLazarchuk wants to merge 25 commits intomainfrom
284-squirrel-create-an-ivy-example
Open

Create Squirrel#286
ArtemLazarchuk wants to merge 25 commits intomainfrom
284-squirrel-create-an-ivy-example

Conversation

@ArtemLazarchuk
Copy link
Collaborator

Squirrel

image image image

One-Click Development Environment

Open in GitHub Codespaces

Click the badge above to open this example in GitHub Codespaces with:

  • .NET 9.0 SDK pre-installed
  • Ready-to-run environment (no local setup)

Created Using Ivy

Web application created using Ivy-Framework.

Ivy unifies front-end and back-end into a single C# codebase for building internal tools and dashboards.

What This Application Does

This example demonstrates data processing using the Squirrel library integrated with Ivy:

  • Load CSV: Reads fashion_products.csv into a Squirrel Table
  • Filter & Sort: Interactive controls for rating, price, brand, category, and sort order
  • Column Visibility: Toggle which columns are shown/exported
  • Export Filtered CSV: Download the current filtered view
  • Pagination: Navigate results with page controls (default page size: 25)
  • Charts: Brand-level line chart showing counts per brand and overall average for a selected product
  • Details Table: Tabular breakdown of brand counts for the selected product

Technical Implementation

  • Uses Squirrel Table APIs for CSV load, sort, and iteration
  • UI built with Ivy components and reactive state (UseState, UseEffect)
  • Pagination via Pagination widget bound to state (top/bottom of the table)
  • Chart built with Ivy LineChart using dimensions/measures
  • Key files:
    • squirrel/Apps/SquirrelCsvApp.cs – data editor with filtering, sorting, pagination, and CSV export
    • squirrel/Apps/PhysicsSimulationApp.cs – chart and details view for brand counts

How to Run

  1. Prerequisites: .NET 9.0 SDK
  2. Navigate to this example:
    cd squirrel
  3. Restore dependencies:
    dotnet restore
  4. Run the application:
    dotnet watch
  5. Open your browser to the URL shown in the terminal (typically http://localhost:5010).

How to Deploy

  1. Navigate to this example:
    cd squirrel
  2. Deploy to Ivy hosting:
    ivy deploy

Learn More

  • Squirrel library: https://github.com/sudipto80/Squirrel
  • Ivy Documentation: https://docs.ivy.app

…V with visible columns and improved UI for column visibility management
…sorting functionality; add support for saving modified data back to the original file
…s from the original table, enhancing data integrity and filtering capabilities
…tering logic to use arrays for selected brands and categories
@ArtemLazarchuk ArtemLazarchuk self-assigned this Nov 5, 2025
@ArtemLazarchuk ArtemLazarchuk linked an issue Nov 5, 2025 that may be closed by this pull request
@rorychatt rorychatt requested a review from Copilot November 6, 2025 12:32
@rorychatt rorychatt moved this to In Progress in Current Sprint Ivy Framework Nov 6, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new example application that demonstrates integration between the Ivy Framework and the Squirrel library for CSV data manipulation. The application provides a data editor with filtering, sorting, pagination, and visualization capabilities for fashion product data.

Key Changes:

  • Adds a new Squirrel example project with CSV data processing capabilities
  • Implements interactive data filtering, sorting, column visibility, and CSV export
  • Includes data visualization with brand-level analysis charts

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
squirrel/fashion_products.csv Sample dataset with 1000 fashion product records for the example
squirrel/SquirrelExample.csproj Project configuration with .NET 9.0 and dependencies
squirrel/README.md Documentation covering setup, features, and deployment
squirrel/Program.cs Application entry point with server configuration
squirrel/Models/FashionProduct.cs Data model for fashion product records
squirrel/GlobalUsings.cs Global using directives for the project
squirrel/Apps/SquirrelCsvApp.cs Main data editor with filtering and pagination
squirrel/Apps/PhysicsSimulationApp.cs Chart visualization for brand analysis
squirrel/.gitignore Git ignore patterns for build artifacts
.devcontainer/squirrel/devcontainer.json GitHub Codespaces configuration for the example

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

| new Card(
Layout.Vertical()
| Text.H3("Squirrel Data Chart")
| Text.Muted($"Analysis of product quantities by brands and names ")
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammatical error and trailing whitespace. The text "Analysis of product quantities by brands and names " should be "Analysis of product counts by brand and name" (singular forms are more appropriate, and remove trailing space).

Suggested change
| Text.Muted($"Analysis of product quantities by brands and names ")
| Text.Muted("Analysis of product counts by brand and name")

Copilot uses AI. Check for mistakes.
</ItemGroup>

<ItemGroup>
<ProjectReference Include="C:\git\Squirrel\Squirrel\Squirrel\Squirrel.csproj" />
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded absolute path C:\git\Squirrel\Squirrel\Squirrel\Squirrel.csproj will not work in other development environments or on different machines. This should use a relative path or a more portable reference method.

Suggested change
<ProjectReference Include="C:\git\Squirrel\Squirrel\Squirrel\Squirrel.csproj" />
<ProjectReference Include="..\Squirrel\Squirrel.csproj" />

Copilot uses AI. Check for mistakes.
- **Filter & Sort**: Interactive controls for rating, price, brand, category, and sort order
- **Column Visibility**: Toggle which columns are shown/exported
- **Export Filtered CSV**: Download the current filtered view
- **Pagination**: Navigate results with page controls (default page size: 25)
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states "default page size: 25" but the code sets pageSize = 20. These values should be consistent.

Suggested change
- **Pagination**: Navigate results with page controls (default page size: 25)
- **Pagination**: Navigate results with page controls (default page size: 20)

Copilot uses AI. Check for mistakes.
- Chart built with Ivy `LineChart` using dimensions/measures
- Key files:
- `squirrel/Apps/SquirrelCsvApp.cs` – data editor with filtering, sorting, pagination, and CSV export
- `squirrel/Apps/PhysicsSimulationApp.cs` – chart and details view for brand counts
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation references PhysicsSimulationApp.cs which is misleading given the file's actual content. Update to match the actual app name or the corrected filename if it's renamed.

Suggested change
- `squirrel/Apps/PhysicsSimulationApp.cs` – chart and details view for brand counts
- `squirrel/Apps/SquirrelBrandChartApp.cs` – chart and details view for brand counts

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +16
var csvPath = Path.Combine(AppContext.BaseDirectory, "fashion_products.csv");

if (!File.Exists(csvPath))
{
return new Card(
Layout.Vertical()
| Text.H3("Error")
| Text.Muted($"CSV file not found: {csvPath}")
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call to 'System.IO.Path.Combine'.

Suggested change
var csvPath = Path.Combine(AppContext.BaseDirectory, "fashion_products.csv");
if (!File.Exists(csvPath))
{
return new Card(
Layout.Vertical()
| Text.H3("Error")
| Text.Muted($"CSV file not found: {csvPath}")
var baseDirPath = Path.Combine(AppContext.BaseDirectory, "fashion_products.csv");
var cwdPath = Path.Combine(Directory.GetCurrentDirectory(), "fashion_products.csv");
string? csvPath = null;
if (File.Exists(baseDirPath))
csvPath = baseDirPath;
else if (File.Exists(cwdPath))
csvPath = cwdPath;
if (csvPath == null)
{
return new Card(
Layout.Vertical()
| Text.H3("Error")
| Text.Muted($"CSV file not found in either location:\n{baseDirPath}\n{cwdPath}")

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +59
catch (Exception ex)
{
client.Error(ex);
}
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic catch clause.

Suggested change
catch (Exception ex)
{
client.Error(ex);
}
catch (IOException ex)
{
client.Error(ex);
}
catch (UnauthorizedAccessException ex)
{
client.Error(ex);
}
catch (FormatException ex)
{
client.Error(ex);
}

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +117
catch (Exception ex)
{
client.Error(ex);
}
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic catch clause.

Copilot uses AI. Check for mistakes.
if (!string.IsNullOrEmpty(value))
values.Add(value);
}
catch { }
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic catch clause.

Suggested change
catch { }
catch (KeyNotFoundException ex)
{
Console.WriteLine($"Key not found: {ex.Message}");
}
catch (InvalidCastException ex)
{
Console.WriteLine($"Invalid cast: {ex.Message}");
}
catch (FormatException ex)
{
Console.WriteLine($"Format error: {ex.Message}");
}

Copilot uses AI. Check for mistakes.
{
object? V(string col)
{
try { return table[r][col]; } catch { return null; }
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic catch clause.

Suggested change
try { return table[r][col]; } catch { return null; }
try { return table[r][col]; }
catch (KeyNotFoundException) { return null; }
catch (IndexOutOfRangeException) { return null; }
catch (ArgumentException) { return null; }

Copilot uses AI. Check for mistakes.
var values = AllColumns.Select(col =>
{
try { return EscapeCsvField(Convert.ToString(row[col])); }
catch { return ""; }
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic catch clause.

Suggested change
catch { return ""; }
catch (KeyNotFoundException) { return ""; }
catch (InvalidCastException) { return ""; }
catch (FormatException) { return ""; }
catch (Exception ex)
{
Console.Error.WriteLine($"Unexpected error exporting column '{col}': {ex}");
return "";
}

Copilot uses AI. Check for mistakes.

// Create detailed data with brand breakdown for each product using Squirrel Table
// Use Squirrel Table's Rows collection to process data
var detailedData = new Dictionary<string, Dictionary<string, int>>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be created using nested calls to SplitOn like this

  var brands = fashions.SplitOn("Brand");
        var detailedData = new Dictionary<string, Dictionary<string, int>>();
        brands.Select(t =>
                new
                {
                    Brand = t.Key,
                    Products = t.Value.SplitOn("Product Name")
                        .Select(z => new { Product = z.Key, Count = z.Value.RowCount })
                }
            ).ToList()
            .ForEach(z =>
            {
               detailedData.Add(z.Brand, new Dictionary<string, int>());
                foreach (var product in z.Products)
                {
                    detailedData[z.Brand].Add(product.Product, product.Count);
                }
            });

? detailedData[selectedProduct.Value]
: new Dictionary<string, int>();

var brandNames = selectedProductData.Keys.OrderBy(b => b).ToList();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get the values of a column, just use the column name as the index on the Squirrel.Table instance like

var brandNames = tab["Brand"]

@ArtemLazarchuk
Copy link
Collaborator Author

@sudipto80 Thank you for the feedback! I’ll fix it and let you know once it’s done.

@ArtemLazarchuk
Copy link
Collaborator Author

@sudipto80 Could you please review it again?

}

// Convert Squirrel Table to List<FashionProduct>
List<FashionProduct> ConvertTableToProducts(Squirrel.Table table)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function can be replaced with a call like this

var allProducts = RecordTable<FashionProduct>(table).Rows;

Please use Squirrel from the release https://github.com/sudipto80/Squirrel/releases/tag/1.0.4
The Nuget package is pretty old and doesn't have this new type (RecordTable<T>)

}

// Save Squirrel Table to CSV file
void SaveTableToFile(Squirrel.Table table, string filePath)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squirrel provides a built in function to convert a Squirrel.Table instance to CSV file. It is called ToCsv
So essentially you can use that in this like this

var writer = new  StreamWriter(filePath);
writer.WriteLine(table.ToCsv());
// If you are not using 'using pattern' 
writer.Close(); 

Copy link

@sudipto80 sudipto80 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have left couple of more comments around converting Squirrel table to strongly typed collection of products and saving to CSV. There is a old cheatsheet created for Squirrel. Might help

Here is a cheatsheet. I created long time ago
https://www.slideshare.net/slideshow/squirrel-do-morewithlesscodelightcheatsheet/48221385

Here is the darker version
https://www.slideshare.net/slideshow/squirrel-do-morewithlesscodecheatsheet1/48185568

Also try this code with your fashion data (change the path) and use the latest release version. There will be a Nuget soon but the current Nuget is old

using Squirrel;

namespace IvyFashion;

class Program
{
    static void Main(string[] args)
    {
        var fashions = DataAcquisition.LoadCsv(
            @"/Users/sudiptamukherjee/Documents/GitHub/Squirrel/SquirrelSolution/IvyFashion/fashion.csv");
        
        // Brand and Product Counts

        var brands = fashions.SplitOn("Brand");
        
        // How many products are there for each brand
        brands.Select ( t =>
            {
                var prices = 
                    t.Value["Price"].Select(z => Convert.ToDecimal(z.Trim()))
                        .ToList();
                return new
                {
                    Brand = t.Key,
                    Count = t.Value.RowCount,
                    // Highest and lowest price item for this brand
                    HighestPrice = prices.Max(),
                    LowestPrice = prices.Min()
                };
            })
         //Create a new table with the new columns from the Select projection 
        .ToTableFromAnonList()
        .PrettyDump(header: "Brand and Product Counts", rowColor: ConsoleColor.Blue);
        
        
        // Filter by product then by size 
        fashions.Filter("Product Name", "Dress")
                .SortInThisOrder("Size", ["S","M","L","XL"])
                .PrettyDump(header: "Dress", rowColor: ConsoleColor.Magenta);
        
        // Filter on multiple columns 
        fashions.Filter("Brand", "Adidas")
                .Filter("Product Name", "Dress")
                .Filter("Size", "M")
                .PrettyDump(header: "Adidas Dress M", rowColor: ConsoleColor.Magenta);
        
        // You can also filter by regex. find anything that has the word "Women" in it
        fashions.FilterByRegex("Category", "Women")
            .SortBy("Rating", how: SortDirection.Descending)
            .PrettyDump(header: "Women's fashion", rowColor: ConsoleColor.Red);;
        
        // Add column programmatically 
        fashions.AddColumn(columnName: "ValueForMoney", formula: "[Rating]/[Price]", decimalDigits: 2);
        var valueForMoney = fashions.SortBy("ValueForMoney", how: SortDirection.Descending)
            // Pick the columns we want 
            .Pick("Product Name","Brand","Category","Price", "Rating", "ValueForMoney");
            // Dump to console 
            valueForMoney
                //Pick the top 15
                .Top(15)
                .PrettyDump(header: "Top 5 fashion for money", rowColor: ConsoleColor.Green);
            
            valueForMoney
                //Pick the bottom 15
                .Bottom(15)
                .PrettyDump(header: "Bottom 5 fashion for money", rowColor: ConsoleColor.DarkRed);
    }
}

@ArtemLazarchuk
Copy link
Collaborator Author

@sudipto80 Thanks for the hint! I’ll check it out and make use of it.

@rorychatt rorychatt requested a review from sudipto80 November 8, 2025 09:54
@ArtemLazarchuk
Copy link
Collaborator Author

@sudipto80 could you please take another look?

I’ve addressed your comments, fixed those issues, and also added a demo application for the code you provided.

Copy link

@sudipto80 sudipto80 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks!

@ArtemLazarchuk
Copy link
Collaborator Author

@rorychatt this is ready to merge

@rorychatt
Copy link
Collaborator

@ArtemLazarchuk a new sideproject.

can you, using ivy widget init, create an example external widget which is a "smart" table that has Squirrel logic?
and then in the same place we build a sample demo app using this new component.

@sudipto80 maybe you have suggestions on what this could be

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

(squirrel): create an Ivy example

4 participants