Skip to content

ryan-io/simple-plotting

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

151 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simple Plotting

A wrapper for parsing, plotting, serializing CSV data and processing bitmaps.

Report Bug · Request Feature


Table of Contents
  1. Overview
  2. Getting Started
  3. Usage & Examples
  4. Roadmap
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgments

Overview

Simple Plotting is a library that acts as a wrapper for ScottPlot (C# MatPlotLib wrapper) and CsvHelper. This library has been used in the Automotive industry to parse CSV files and plot the data containing data relating to temperature, relative humidity, voltage and current output. This library can be used in conjecture with all types of data. In addition to generating plots, the API is also used to create interactive plots in various .NET environments. These interactive plots can be annotated, have draggable shapes added to them, have their axes labels, dimensions, colors, ticks, etc. all changed. It is versatile in this regard. The primary goals for this project are:

  1. Parse CSV files and load into memory for manipulation
  2. Define how CSV files are parsed
  3. Generate plots from the parsed CSV data using MatPlotLib
  4. Make the plots interactive (WPF, WinForms, Avalonia)
  5. Save generated plots to disk
Features
  1. Define a 'strategy' for how CSV files are parsed. A default strategy is provided, but the consumer should define a strategy that aligns with the formatting of the files they are working with.
  2. Define the type of plot. ScottPlot supports many.. this library currently supports: - Scatter - Signal - Constant signal - Line - Image (annotations) - 'Crosshair' - Markers
  3. An abstracted plotting API and an abstracted photo annotation API
  4. Plotting API: - Set title, axes labels, font (size, color, family, style), ticks, dimensions, colors, output format. - Add annotations and shapes to point out critical points in a plot
  5. Photo Annotation API: - Add images, text and annotations to photos

(back to top)

Built With

  • JetBrains Rider
  • Tested with WPF & Blazor WASM

(back to top)

Getting Started

Clone/fork this repository and add a reference to the simple-plotting project. OR, add this as a Nuget package to your project.

Csv Parsing
  • Start by defining a 'strategy' for how to parse Csv files. This does require knowledge of CsvHelper and how the API works.
    • The examples provided by Josh (CsvHelper author) is an excellent source for quickly learning what you need
  • A default strategy is provided ONLY as an example:
public class DefaultCsvParseStrategy : CsvParseStrategy {  
    StringBuilder Sb { get; } = new();  
  
    double? SampleRate { get; set; }  
  
    public override async Task StrategyAsync(  
       List<PlotChannel> output,  
       CsvReader csvr,  
       CancellationToken? cancellationToken = default) {  
       const int skipFourRows = 4;  
       const int skipSixRows  = 6;  
  
       try {  
          SkipRowsNumberOfRows(csvr, skipSixRows);  
  
          await csvr.ReadAsync();  
  
          SampleRate = CsvParserHelper.ExtractSampleRate(csvr[1]); // we could ignore this entirely  
  
          SkipRowsNumberOfRows(csvr, skipFourRows);  
  
          csvr.ReadHeader();  
  
          if (csvr.HeaderRecord == null)  
             throw new Exception(Message.EXCEPTION_NO_HEADER);  
  
          var channelsToParse = csvr.HeaderRecord.Length - 3;  
  
          for (var i = 0; i < channelsToParse; i++) {  
             if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested)  
                break;  
  
             output.Add(new PlotChannel(csvr.HeaderRecord[3 + i], PlotChannelType.Temperature, SampleRate));  
          }  
  
          while (await csvr.ReadAsync()) {  
             if (cancellationToken.WasCancelled())  
                break;  
  
             ParseCurrentReaderIndex(output, csvr, cancellationToken, channelsToParse);  
          }  
       }  
       catch (Exception e) {  
          throw new Exception(Message.EXCEPTION_STRATEGY_FAILED, e);  
       }  
    }
  • To implement your own strategy, created a class that derives from CsvParseStrategy or implements ICsvParseStrategy
    • I'd love to help you define a strategy for your needs
  • Once implemented, simply invoke
var initialStrategy = new DefaultCsvParseStrategy(-65d, 165d);  
var source          = CsvParserFactory.StartNew(initialStrategy, path);
var output = await source.ExtractAsync(file);
Plotting
  • Once a csv file has been parsed, you can now feed the parsed data into the PlotBuilderFluent API.
    • This API is fluent, meaning you can chain method calls
PlotBuilderFluent.StartNewPlot(output, numOfPlots)
  • Review the methods included with the API. These methods help you customize the plot
.WithType<SignalPlot>()  
.WithTitle("Test title", 24)  
.WithSize(PlotSize.S1280X800)  
.WithPrimaryXAxisLabel(Constants.X_AXIS_LABEL_DATE_TIME, 20)  
.WithPrimaryYAxisLabel(Constants.Y_AXIS_LABEL_TEMP, 20)  
.WithSecondaryYAxisLabel(Constants.Y_AXIS_LABEL_RH, 20)  
.ShowLegend(PlotAlignment.UpperRight)  
.SetDataPadding(valueY: .2, valueX: 0.0)

product.GotoPlottables().AddDraggableLine(0, 50, 40000, out _);  
product.GotoPostProcess().TrySetLabel("Test label", 0);  
product.GotoPlottables().WithAnnotationAt("Test annotation", 1, 100, 100, out _);
Image Processing
  • The image processing API is analogous to the PlotBuilderFluent API and functions identically.
  • Start by creating a new instance of BitmapParser. Once this is instantiated, simply query the PlotBuilderFluent API to create a new canvas.
var parser  = new BitmapParser(ref paths);  
var builder = PlotBuilderFluent.StartNewCanvas(ref parser.GetAllBitmaps());
  • You are ready to manipulate the image(s)
parser.ModifyRgbUnsafe(0, (ref int red, ref int green, ref int blue) => {  
                              red   -= 25;  
                              green += 10;  
                              blue  += 20;  
                          });

(back to top)

Prerequisites and Dependencies

  • .NET 6
Please feel free to contact me with any issues or concerns in regards to the dependencies defined above. We can work around the majority of them if needed.

(back to top)

Installation

  • Clone or fork this repository. Once done, add a reference to this library in your project
  • Download the latest dll and create a reference to it in your project
  • Install via NPM

(back to top)

Usage

The following is a demonstration of the API within a console application:

using ScottPlot.Plottable;  
using simple_plotting;  
using simple_plotting.runtime;
// Program.cs entry point

await GeneratePlots();  
await ModifyImages(); 

return 0;
Plotting
async Task<int> GeneratePlots() {  
    try {  
       // define a file name w/ extension
       var file = "my-test-file.csv";  
	   // define a directory; this is where your data resides
       var path = @"C:\test-data";  
  
       const int numOfPlots = 50;  
  
       // can use CsvParser.StartNew(path) to skip SetSource(path)  
       // this example uses the builtin DefaultCsvParseStrategy       var initialStrategy = new DefaultCsvParseStrategy(-65d, 165d);  
       var source          = CsvParserFactory.StartNew(initialStrategy, path);  
  
       // if you prefer to set the pat manually, you can do the following  
       // var initialStrategy = new DefaultCsvParseStrategy(-65d, 165d);       // var source          = CsvParser.StartNew(initialStrategy);       // source.SetSource(path);  
       // invoke source.ExtractAsync(file) to generate output       var output = await source.ExtractAsync(file);  
  
       // check if output is null  
       if (output == null) {  
          Console.WriteLine("Could not extract data from CSV file...");  
          return -1;  
       }  
  
       // begin building the plot via fluent builder  
       var product = PlotBuilderFluent.StartNewPlot(output, numOfPlots)  
                                      .WithType<SignalPlot>()  
                                      .WithTitle("Test title", 24)  
                                      .WithSize(PlotSize.S1280X800)  
                                      .WithPrimaryXAxisLabel(Constants.X_AXIS_LABEL_DATE_TIME, 20)  
                                      .WithPrimaryYAxisLabel(Constants.Y_AXIS_LABEL_TEMP, 20)  
                                      .WithSecondaryYAxisLabel(Constants.Y_AXIS_LABEL_RH, 20)  
                                      .ShowLegend(PlotAlignment.UpperRight)  
                                      .SetDataPadding(valueY: .2, valueX: 0.0)  
                                      .DefineSource(source)  
                                      .RotatePrimaryXAxisTicks(PlotAxisRotation.FortyFive)  
                                      .RotatePrimaryYAxisTicks(PlotAxisRotation.Zero)  
                                      .SetLayout(right: 123, top: 123)  
                                      .FinalizeConfiguration()  
                                      .Produce();  
  
       product.GotoPlottables().AddDraggableLine(0, 50, 40000, out _);  
       product.GotoPostProcess().TrySetLabel("Test label", 0);  
       product.GotoPlottables().WithAnnotationAt("Test annotation", 1, 100, 100, out _);  
       var status = product.TrySaveAtSource(@"C:\output");  
  
       var set    = new HashSet<SignalPlotXYConst<double, double>>();  
       product.GetPlottablesAsCache(0, ref set);  
         
       Console.WriteLine(status.State ? "Successfully saved plot!" : "Could not save plot...");  
       return 0;  
    }  
    catch (Exception e) {  
       Console.WriteLine(e);  
       throw;  
    }  
}
Photo Annotation
  • The API for photo annotation currently requires unsafe context.
  • There is an API to process images in a managed environment that I can include.
  • Thanks to Turgay at 'csharpexamples.com' for inspiration on fast Bitmap processing.
async Task ModifyImages() {  
	// get location of the image you want to annotate/process
    var path  = @"C:\my-folder\";
	var file = path + "\my-pic.my-ext";
	
	// define a path array; include as many image paths as you want
    var paths = new[] { file };  

	// instantiate a new instance of the BitmapParser
	// the default constructor takes a reference to an allocated array of strings
    var parser  = new BitmapParser(ref paths);  

	// query the fluent builder to start a new canvas plot
	// A canvas is a type of plot that does not look like a traditional plot
	// It is simply a window for containing your images for manipulation
    var builder = PlotBuilderFluent.StartNewCanvas(ref parser.GetAllBitmaps());  

	// invoke parser.ModifyRgbUnsafe
	// this method takes a delegate that must match:
	// public delegate void BitmapRgbDelegate(ref int red, ref int green, ref int blue);
	// red, green and blue parameters are enumerated for each pixel in the bitmap
	// each parameter is passed by reference; simply modify thes as you see fit
	// modifications to thes values result in a new RGB value for that enumerated pixel
    parser.ModifyRgbUnsafe(0, (ref int red, ref int green, ref int blue) => {  
                                 red   -= 25;  
                                 green += 10;  
                                 blue  += 20;  
                              });  
	// Save the processed bitmaps
    await parser.SaveBitmapsAsync(path);  
	
	// this example manually invokes Dipose
	// a 'using' statement is also appropriate
    parser.Dispose();  
}

Roadmap

  1. Provide a default implementation for processing Bitmaps in a managed (safe) context

(back to top)

Contributing

Contributions are absolutely welcome. This is an open source project.

  1. Fork the repository
  2. Create a feature branch
git checkout -b feature/your-feature-branch
  1. Commit changes on your feature branch
git commit -m 'Summary feature'
  1. Push your changes to your branch
git push origin feature/your-feature-branch
  1. Open a pull request to merge/incorporate your feature

(back to top)

License

Distributed under the MIT License.

(back to top)

Contact

RyanIO

[Email]
[LinkedIn]
[GitHub]

(back to top)

Acknowledgments and Credit

  • Scott over at ScottPlot for his library.
  • Turgay for fast image processing

(back to top)

About

A library wrapper for ScottPlot & CsvHelper. This library parses CSV files & interactively plots them in WPF, WinForms, etc.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages