Skip to content

ScottPlot5: development#1647

Merged
swharden merged 45 commits intomasterfrom
2022-02-06-maui-dev
Mar 4, 2022
Merged

ScottPlot5: development#1647
swharden merged 45 commits intomasterfrom
2022-02-06-maui-dev

Conversation

@swharden
Copy link
Member

@swharden swharden commented Feb 6, 2022

Axes can now Draw() themselves and measure their size.

A TightenLayout() method can be called to initiate measuring/fitting the layout to the size of the axis labels and ticks labels.

In GUI controls tightening does not occur while dragging, but does occur after the mouse is dropped.
@swharden swharden marked this pull request as ready for review March 3, 2022 23:55
@swharden
Copy link
Member Author

swharden commented Mar 4, 2022

This PR created a functional from-scratch MVP implementation of ScottPlot using a preview package of Microsoft.Maui.Graphics (and SkiaSharp) instead of System.Drawing to render plots. The self-contained .NET Standard library (and WinForms demo) are in in /dev/ScottPlot5 and can be run in isolation.

Things I Learned

  • This rewrite is necessary: Typically rewrites are an anti-pattern. I think a rewrite here is necessary because ScottPlot 5 isn't just an extension of ScottPlot 4, it really is a new thing. It uses a completely different rendering system, different units, different objects (e.g., no System.Drawing.Color), a different relationship between controls and the core library. Yeah you put data in one end and get an image out the other, but the API is changing appreciably and the internals are very different. Because ScottPlot 4 and ScottPlot 5 are different things (and may even use shared testing/documentation code) I'll keep directories for both projects in the /src folder as described in Repository: Rename primary branch main in March, 2022 #1691

  • Maui.Graphics Preview is not mature enough for me to continue developing against: The Maui.Graphics preview packages I referenced should always be publicly available, but that project is being actively developed (including breaking changes, like just this week renaming Rectangle to Rect in Maui.Graphics PR 318#). Recent limitations of the package that affected ScottPlot are the limited support for fonts (custom fonts and nuanced errors related to measuring strings). Related: Refactor: System.Drawing → SkiaSharp (or Maui.Graphics?) #1036

  • Passing configuration into Render() instead of storing it in the Plot is nice: The old (ScottPlot4) render and settings systems are extremely brittle. This is expected because earlier versions of ScottPlot had a much different architecture, and it evolved trying to minimize breaking changes. This experimental rewrite made it easy to test a new flow of configuration (immutable configuration objects passed into the render system) which proved very convenient and much simpler than retaining state in the Plot module.

  • Interface all the things: I spent so much work in old versions of ScottPlot hacking-in options to customize grid spacing, tick placement, and tick labels. If I make it simple for users to create a class that implements something like a ITickGenerator then I don't have to cover every corner case! This design makes it so much easier for users to create and test their own tick systems.

  • Recalculate the layout and ticks on every Render(): Recalculating ticks and layout isn't costly enough to affect framerate, so do it inside the Render() call instead of trying to store all that state in the Plot module then selectively out what updated and selectively change positioning or ticks in response to resize or axis manipulation events.

  • Use custom objects instead of instead of double, Point, Size, or Rect: It can be confusing when X/Y pairs these basic types it is not obvious if they're in pixel units or coordinate units. Structs can hold these measurements and leverage the type system to express units. Methods and operator overloads allow common operations like expanding rectangles or panning points by a given delta.

  • Favor immutable objects with fluent methods (but don't go nuts): I think it's fine for plottable objects to store state, but locations and sizes described in the previous bullet should be immutable where possible to minimize bugs and simplify testing.

  • Plot types can support generics, spans, etc. using inheritance: Create the base class to render and give it abstract methods defining how to request that data. Child classes can be created that support double[], <T>, and even advanced data indexing strategies like Span and Memory using preprocessor directives to selectively include them on supported .NET versions.

  • Controls should store state (not the Plot module): If the user left-click-drags to pan, it's much easier to pass-in the information like, pan such that these are axis limits before, here is where I clicked, and here is where I let go, and have the Render() method apply the necessary mutation inside the render loop. This totally eliminates the need to store state in the Plot module, it makes GUI interactions easier to test, and allows users to override functionality in the control to provide custom interaction behavior.

  • Generate the website locally: Everything can be rigged to run in GitHub Actions, but should it? As the cookbook grows in complexity jobs reach toward the limit of allowable duration (especially with large numbers of image uploads). Build and package the demo apps and generate the cookbook using a local build application. Generate the cookbook as Markdown files, then use Hugo to turn them into HTML, and upload them to the web from the local machine. Packages should still be build/deployed in the cloud though.

  • MSTest vs. NUnit? Most of the pain is probably behind me, but NUnit has made it difficult to run mixed .NET Framework / .NET Core projects, test them in cloud, and test them in Docker containers. NUnit has many fantastic features, but if MSTest is better supported on multi-platform systems let's consider it. It was inferior in the past, but in 2022 it may be worth considering.

Next Steps

  • Short answer: Put this down until .NET Maui matures.

  • Location: I'll probably leave ScottPlot 5 code in /dev/ScottPlot5 and keep poking at the API and primitive objects to refine them toward a design I like more. It's getting close, but there's a lot of complexity I think I can distill away with better designed primitives.

  • Plottables and advanced data types: I do not want to get distracted implementing new advanced plot types. I'll probably even delete the fancy ones that I created with this PR to ensure they do not become a distraction. There are many fantastic ideas for improving plot types, but those can be implemented later. Immediate next steps should focus on architecting the best system for control/plot interaction, testing, documentation, etc.

  • Consider SkiaSharp: When Maui.Graphics matures/releases, I'll pick this up again, and probably use it... However if it never matures (or it remains painful to work with), it won't be that difficult to render using SkiaSharp, and I can always swap it to Maui.Graphics later. I have a good understanding of the functionality supported by Maui.Graphics, and if we restrict SkiaSharp functionality to that limited feature set the transition won't be too bad. The biggest pain point would be argument types like SkiaSharp.SKColor vs Microsoft.Maui.Graphics.Color. There is even an option to accept Maui colors (keeping the public API stable) and convert them to SKColor under the hood.

  • Continue to Improve ScottPlot 4: There are a lot of benefits from the long list above that can be applied to ScottPlot 4. This will improve the existing library and make the transition easier when the time comes. I'll spend the next few months focusing on improving it in that way.

@swharden swharden merged commit 79d1738 into master Mar 4, 2022
@swharden swharden deleted the 2022-02-06-maui-dev branch March 4, 2022 01:10
@erichiller
Copy link
Contributor

One reason to go with SkiaSharp directly is that it supports being compiled to WebAssembly, whereas, as far as I know, Maui.Graphics does not support this. I'm not sure if running as WebAssembly is of interest to yourself or others, but it is to me at least.

@swharden
Copy link
Member Author

Hi @erichiller, thank you for pointing this out! Browser support would be fantastic, and it's impressive to see the recent work that has been done to get SkiaSharp working in Blazor apps using WebAssembly. I'll spend some time this morning researching more about what can be done using SkiaSharp/Blazor with existing packages.

However, it appears that Maui.Graphics has Blazor project that is being actively developed:
https://github.com/dotnet/Microsoft.Maui.Graphics/tree/main/src/Microsoft.Maui.Graphics.Blazor

I suspect that once Maui and Maui.Graphics releases, there will be NuGet packages for all the main platforms, including Blazor. I recognize targeting SkiaSharp directly would allow development to progress immediately, but I'm trying to hold-out a little more to see what Maui produces as it nears GA release.

The choice between one or the other affects so much code in this library, and influences the public API with things that seem trivial but are far-reaching like whether the arguments expect Color.Blue (System.Drawing), Colors.Blue (Microsoft.Maui.Graphics), or SKColor.Blue (SkiaSharp). Yeah I could make the public API permanent and do conversions under the hood, but I'm trying to avoid all that complexity. Hopefully over the next few months a clear winner will bubble-up to the top 🤞

@erichiller
Copy link
Contributor

Ok, that makes total sense. Is it safe to assume that ScottPlot 5 using Maui.Graphics would work on Avalonia & Linux using Microsoft.Maui.Graphics.Skia (or maybe it'd be with Microsoft.Maui.Graphics.Skia.Gtk)

@swharden
Copy link
Member Author

swharden commented Mar 23, 2022

Is it safe to assume that ScottPlot 5 using Maui.Graphics would work on Avalonia & Linux

I think so, because at its core Microsoft.Maui.Graphics is mostly just a collection of interfaces. They have an ICanvas interface with methods like DrawLine(), and any graphics platform (like SkiaSharp) can make a package that implements Maui's ICanvas.

My plan is to start by exclusively making ScottPlot5 controls that use SkiaSharp. I'll create a SkiaSharp SKCanvas, but really it's a Microsoft.Maui.Graphics.Skia.Canvas, and I can treat it simply as a Microsoft.Maui.Graphics.ICanvas, letting me use all of MAUI's drawing methods to interact with it, but I can still access the underlying Skia SKCanvas if I want to pull it back out after ScottPlot draws on it.

The win here is that later if a user wants to swap-out SkiaSharp with GDK or ImageSharp, they can, and none of ScottPlot's core drawing routines have to be modified.

This is the promise anyway. The library still isn't fully released, and the API is still implementing breaking changes. We'll see how it shakes out as it stabilizes!

PS: a working proof-of-concept is already in https://github.com/ScottPlot/ScottPlot/tree/main/dev/ScottPlot5 and it compiles on Linux but I haven't extensively tested it yet, nor have I made an Avalonia control for it.

@bclehmann
Copy link
Member

bclehmann commented May 23, 2022

FWIW Maui just hit a stable release, so this may be worth another look soon.

@swharden
Copy link
Member Author

swharden commented May 23, 2022

this may be worth another look soon.

I agree! I've been gearing-up to make this post for a little while... Maui.Graphics has been in RC for a few weeks now, and likely to have its first official release this week, so we know pretty much what to expect.

My Assessment

Conclusions

  • Overall it seems Maui.Graphics has implemented just enough drawing support to be able to draw basic controls and simple shapes in cross platform MAUI apps. I'm glad the library met the needs of the team.

  • This library (ScottPlot) has more demanding drawing needs, so I'm pretty convinced the way to go from here is to just lean on SkiaSharp which is widely used, actively developed, and extensively tested and documented.

  • I have more confidence now in the stability and maturity of SkiaSharp than I did when I first looked at it a few years back

Next Steps

  • It may be possible to migrate ScottPlot 4 from System.Drawing to SkiaSharp
    • It may be possible to abstract the rendering system and pass a custom IRenderable throughout the codebase, preventing breaking changes, and allowing simultaneous support of both systems.
    • This would be a tremendous amount of work, bordering on a full rewrite, and I'd rather devote that effort into ScottPlot 5
  • ScottPlot 5
    • I intend to continue working on and fully supporting ScottPlot 4 while I work on ScottPlot 5
    • I think ScottPlot 5 should exclusively use SkiaSharp and plottables will store objects like SKColor
    • I do not plan to support .NET Framework
    • I plan to initially target .NET 6 and lean on spans and records
    • I may soon target .NET 7 to utilize generic math (reducing our need for double[] input types)
    • I'm waiting for the timing to be right for me to get started

@swharden
Copy link
Member Author

Following-up,

A little time will help reveal the trajectory of .NET Maui, but the GA is pretty sketch. I find many comments on the official blog extremely negative (and I think are being pruned). .NET Maui has "released" but it's still not available in Visual Studio and requires a preview version of VS 😅

I'm going to hold my assessment for a few more months and give the dust a little time to settle. It's possible thing thing could still land in a good place, and wow I'd really love to use it, but we'll see how the next bit of time goes.

Biggest remaining sticking points are no support for accurate string measurement and extremely limited support for line styles.

I also realized a compromise could be to use Microsoft.Maui.Graphics for their colors and Microsoft.Maui.Graphics.Skia to provide the SkiaSharp I need to render everything with Skia. This would let me use SkiaSharp for now but have ScottPlot's API accept Maui colors, so if/when we switch to Maui.Graphics rendering, the user wouldn't know the difference.

@bclehmann
Copy link
Member

This would let me use SkiaSharp for now but have ScottPlot's API accept Maui colors, so if/when we switch to Maui.Graphics rendering, the user wouldn't know the difference.

I think if we have a reasonable expectation that we might switch libraries again we should write our own color classes rather than use the color classes of the most likely replacement. It prevents us from being tied to a specific library, especially an incomplete one. It may also be easier for us to port existing code if we write a (partial) re-implementation of System.Drawing.Color. An implementation of System.Drawing.Color is available here, however the license allows only "reference use", so we'd still have to reimplement it.

I haven't looked deeply into Microsoft.Maui.Graphics.Skia, but even if it allows calling arbitrary SkiaSharp functions, using Maui Graphics just to use SkiaSharp rather than using SkiaSharp directly seems troublesome. It sounds like we get none of the benefits of Maui Graphics, but still have all of the drawbacks. For example, we may not be able to upgrade SkiaSharp until the Maui team releases an update, we may have more complicated builds because of features we don't use, our build times and build sizes will be inflated, etc etc.

A lot of those concerns are minor, provided that we expect Maui Graphics to be supported and up-to-date for a long time (although we could've reasonably expected that from System.Drawing.Common, and the two libraries do share a parent company). Minor concerns that would be more than balanced if we were getting some benefit from Maui Graphics over SkiaSharp. But I don't think they are balanced simply using the library in order to use another.

@bclehmann
Copy link
Member

bclehmann commented May 26, 2022

EDIT: There's more up-to-date code under the MIT license

The full text of the Microsoft Reference Source License is as follows (with my interpretation afterwards):

License MICROSOFT REFERENCE SOURCE LICENSE (MS-RSL)

This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.

  1. Definitions

The terms "reproduce," "reproduction" and "distribution" have the same meaning here as under U.S. copyright law.

"You" means the licensee of the software.

"Your company" means the company you worked for when you downloaded the software.

"Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes of debugging your products, maintaining your products, or enhancing the interoperability of your products with the software, and specifically excludes the right to distribute the software outside of your company.

"Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor under this license.

  1. Grant of Rights

(A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free copyright license to reproduce the software for reference use.

(B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free patent license under licensed patents for reference use.

  1. Limitations

(A) No Trademark License- This license does not grant you any rights to use the Licensor's name, logo, or trademarks.

(B) If you begin patent litigation against the Licensor over patents that you think may apply to the software (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.

(C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.

The meat of my concerns is whether us referencing the reference source in order to partially reimplement the API of System.Drawing.Color fits under "enhancing the interoperability of your products with the software". "The software" in this context is presumably either .NET Framework 4.8 or System.Drawing, but I think that the distinction doesn't matter here. Referencing the reference source would enhance interoperability with (our) code written for System.Drawing, but maybe not System.Drawing itself. I see the interop clause applying if we were to create a library that was designed to link with or be in some way compatible with System.Drawing, but perhaps not for writing code to let us jump off of System.Drawing.

There is another interpretation of "enhancing the interoperability of your products with the software", it may mean either that we may reference it in enhancing how ScottPlot interops with System.Drawing, or we may reference the software to enhance the interop (without saying what it enhances the interoperability with). This interpretation is unconvincing to me, because it's not how a normal person would read it, nor is it how Microsoft likely intended it to be read. Especially given the history of this license as a way to encourage companies to stay in the Microsoft ecosystem. Ambiguity in a contract does benefit the party who did not draft it, but I'm unsure if this counts as ambiguous.

@bclehmann
Copy link
Member

There's more up-to-date source code under the MIT license, we could copy-paste it if we wanted to: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Drawing.Primitives/src/System/Drawing/Color.cs

You can safely ignore the above comment, unless you enjoy the pontification of someone with no legal education.

This was referenced Jun 6, 2022
@swharden
Copy link
Member Author

swharden commented Jun 6, 2022

ScottPlot 5 will use its own ScottPlot.Color

I think if we have a reasonable expectation that we might switch libraries again we should write our own color classes rather than use the color classes of the most likely replacement.

I was thinking about this lately and I recently independently came to the same conclusion! Funny how I did a full 360 on this one. A few reasons I now am leaning toward making this class is:

  • We are already using our own type for LineStyle
  • We can add colors that draw from the active Palette, like Colors.C0, Colors.C1, etc. (mimicking matplotlib language)
  • If we make Colors a static class (plural) with a Blue property that returns a new Color(0, 0, 255), ScottPlot's Colors.Blue won't conflict with Color.Blue from System.Drawing which Windows Forms applications bring in as a default using.
  • We can still import System.Drawing.Common.Primitives to make it easier to convert from System.Drawing.Color to ScottPlot.Color

Regarding the legality of lifting the System.Drawing.Common colors, lots of good info above, but when the time to do this comes it will be good to study how multiple libraries implement this so we can land on the ideal API from the start.

@swharden
Copy link
Member Author

swharden commented Jun 11, 2022

@bclehmann
Copy link
Member

That is cool, but I think users would expect them to match the named web colours, which are the same names that most graphics libraries use. Even though it means dark gray is lighter than gray.

@swharden
Copy link
Member Author

That is cool, but I think users would expect them to match the named web colours, which are the same names that most graphics libraries use.

True - web colors will definitely be the ScottPlot defaults because they're widely used and known.

According to WPF docs their colors are based on UNIX X11 named colors. It looks like web colors extend this set: https://en.wikipedia.org/wiki/Web_colors#X11_color_names

Even though it means dark gray is lighter than gray.

lol, this came up this week in Maui.Graphics land dotnet/Microsoft.Maui.Graphics#449

xkcd

I'd love to add XKCD colors somehow because they are so expressive. I'm thinking of accessing them like Colors.Xkcd.RadioactiveGreen.

swharden added a commit that referenced this pull request Jun 12, 2022
Color: struct to define R, G, B, and A values defining a color

Colors: quick access to common colors by name (defaults are web color keywords)

NamedColors: namespace for additional color packs (e.g., xkcd colors, windows colors, etc)

See color API discussion in #1647
@swharden swharden mentioned this pull request Jul 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants