diff --git a/samples/AvaloniaSample/AvaloniaSample.csproj b/samples/AvaloniaSample/AvaloniaSample.csproj index 29543f8a1..7e2afc6bc 100644 --- a/samples/AvaloniaSample/AvaloniaSample.csproj +++ b/samples/AvaloniaSample/AvaloniaSample.csproj @@ -118,6 +118,9 @@ %(Filename) + + %(Filename) + %(Filename) diff --git a/samples/AvaloniaSample/General/VisualElements/View.axaml b/samples/AvaloniaSample/General/VisualElements/View.axaml new file mode 100644 index 000000000..a02b83b96 --- /dev/null +++ b/samples/AvaloniaSample/General/VisualElements/View.axaml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/samples/AvaloniaSample/General/VisualElements/View.axaml.cs b/samples/AvaloniaSample/General/VisualElements/View.axaml.cs new file mode 100644 index 000000000..6d3b64875 --- /dev/null +++ b/samples/AvaloniaSample/General/VisualElements/View.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace AvaloniaSample.General.VisualElements; + +public class View : UserControl +{ + public View() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} diff --git a/samples/BlazorSample/Pages/Design/LinearGradients.razor b/samples/BlazorSample/Pages/Design/LinearGradients.razor index 113e31ef1..a6e9756c9 100644 --- a/samples/BlazorSample/Pages/Design/LinearGradients.razor +++ b/samples/BlazorSample/Pages/Design/LinearGradients.razor @@ -3,7 +3,8 @@ @using ViewModelsSamples.Design.LinearGradients + Series="ViewModel.Series" + LegendPosition="LiveChartsCore.Measure.LegendPosition.Right"> @code { diff --git a/samples/BlazorSample/Pages/Design/RadialGradients.razor b/samples/BlazorSample/Pages/Design/RadialGradients.razor index 905ed32b0..a09d79de1 100644 --- a/samples/BlazorSample/Pages/Design/RadialGradients.razor +++ b/samples/BlazorSample/Pages/Design/RadialGradients.razor @@ -3,7 +3,8 @@ @using ViewModelsSamples.Design.RadialGradients + Series="ViewModel.Series" + LegendPosition="LiveChartsCore.Measure.LegendPosition.Right"> @code { diff --git a/samples/BlazorSample/Pages/General/VisualElements.razor b/samples/BlazorSample/Pages/General/VisualElements.razor new file mode 100644 index 000000000..ac516582b --- /dev/null +++ b/samples/BlazorSample/Pages/General/VisualElements.razor @@ -0,0 +1,12 @@ +@page "/General/VisualElements" +@using LiveChartsCore.SkiaSharpView.Blazor +@using ViewModelsSamples.General.VisualElements + + + + +@code { + public ViewModel ViewModel { get; set; } = new(); +} diff --git a/samples/ConsoleSample/ConsoleSample/Program.cs b/samples/ConsoleSample/ConsoleSample/Program.cs index 20de92c33..e907a77df 100644 --- a/samples/ConsoleSample/ConsoleSample/Program.cs +++ b/samples/ConsoleSample/ConsoleSample/Program.cs @@ -12,7 +12,8 @@ { new LineSeries { Values = new int[] { 1, 5, 4, 6 } }, new ColumnSeries { Values = new int[] { 4, 8, 2, 4 } } - } + }, + LegendPosition = LiveChartsCore.Measure.LegendPosition.Right }; // you can save the image to png (by default) @@ -28,7 +29,8 @@ new PieSeries { Values = new int[] { 10, } }, new PieSeries { Values = new int[] { 6 } }, new PieSeries { Values = new int[] { 4 } } - } + }, + LegendPosition = LiveChartsCore.Measure.LegendPosition.Right }; pieChart.SaveImage("pieChart.png"); diff --git a/samples/EtoFormsSample/General/VisualElements/View.cs b/samples/EtoFormsSample/General/VisualElements/View.cs new file mode 100644 index 000000000..87bfeb277 --- /dev/null +++ b/samples/EtoFormsSample/General/VisualElements/View.cs @@ -0,0 +1,21 @@ +using Eto.Forms; +using LiveChartsCore.SkiaSharpView.Eto; +using ViewModelsSamples.General.VisualElements; + +namespace EtoFormsSample.General.VisualElements; + +public class View : Panel +{ + public View() + { + var viewModel = new ViewModel(); + + var cartesianChart = new CartesianChart + { + Series = viewModel.Series, + VisualElements = viewModel.VisualElements, + }; + + Content = cartesianChart; + } +} diff --git a/samples/MauiSample/General/VisualElements/View.xaml b/samples/MauiSample/General/VisualElements/View.xaml new file mode 100644 index 000000000..cf19c1780 --- /dev/null +++ b/samples/MauiSample/General/VisualElements/View.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/samples/MauiSample/General/VisualElements/View.xaml.cs b/samples/MauiSample/General/VisualElements/View.xaml.cs new file mode 100644 index 000000000..6908f34ff --- /dev/null +++ b/samples/MauiSample/General/VisualElements/View.xaml.cs @@ -0,0 +1,10 @@ +namespace MauiSample.General.VisualElements; + +[XamlCompilation(XamlCompilationOptions.Compile)] +public partial class View : ContentPage +{ + public View() + { + InitializeComponent(); + } +} diff --git a/samples/MauiSample/MauiSample.csproj b/samples/MauiSample/MauiSample.csproj index f209e36ef..ea5c8c7b7 100644 --- a/samples/MauiSample/MauiSample.csproj +++ b/samples/MauiSample/MauiSample.csproj @@ -51,6 +51,9 @@ + + %(Filename) + %(Filename) @@ -162,6 +165,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile diff --git a/samples/UWPSample/General/VisualElements/View.xaml b/samples/UWPSample/General/VisualElements/View.xaml new file mode 100644 index 000000000..026728614 --- /dev/null +++ b/samples/UWPSample/General/VisualElements/View.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/samples/UWPSample/General/VisualElements/View.xaml.cs b/samples/UWPSample/General/VisualElements/View.xaml.cs new file mode 100644 index 000000000..49e931815 --- /dev/null +++ b/samples/UWPSample/General/VisualElements/View.xaml.cs @@ -0,0 +1,12 @@ +using Windows.UI.Xaml.Controls; + +namespace UWPSample.General.VisualElements +{ + public sealed partial class View : UserControl + { + public View() + { + InitializeComponent(); + } + } +} diff --git a/samples/UWPSample/UWPSample.csproj b/samples/UWPSample/UWPSample.csproj index 7af746017..586f9bf62 100644 --- a/samples/UWPSample/UWPSample.csproj +++ b/samples/UWPSample/UWPSample.csproj @@ -223,6 +223,9 @@ View.xaml + + View.xaml + View.xaml @@ -546,6 +549,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/Sections2/View.xaml b/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/Sections2/View.xaml index af4f6954b..1d48b10b0 100644 --- a/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/Sections2/View.xaml +++ b/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/Sections2/View.xaml @@ -13,6 +13,6 @@ + Sections="{Binding Sections}"> diff --git a/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/VisualElements/View.xaml b/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/VisualElements/View.xaml new file mode 100644 index 000000000..f19a122d8 --- /dev/null +++ b/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/VisualElements/View.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/VisualElements/View.xaml.cs b/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/VisualElements/View.xaml.cs new file mode 100644 index 000000000..979d66f63 --- /dev/null +++ b/samples/UnoSample/UnoSample.Shared/LiveChartsSamples/General/VisualElements/View.xaml.cs @@ -0,0 +1,11 @@ +using Windows.UI.Xaml.Controls; + +namespace UnoSample.General.VisualElements; + +public sealed partial class View : UserControl +{ + public View() + { + InitializeComponent(); + } +} diff --git a/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.projitems b/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.projitems index c0be98bbb..02ebb0541 100644 --- a/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.projitems +++ b/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.projitems @@ -123,6 +123,9 @@ View.xaml + + View.xaml + View.xaml @@ -674,7 +677,9 @@ Designer MSBuild:Compile - + + Designer + <_Globbed_Compile Include="$(MSBuildThisFileDirectory)**/*.xaml.cs" Exclude="@(Compile)"> %(Filename) @@ -684,4 +689,10 @@ <_Globbed_Content Include="$(MSBuildThisFileDirectory)Assets/**/*.*" Exclude="@(Content)" /> + + + Designer + MSBuild:Compile + + \ No newline at end of file diff --git a/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.shproj b/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.shproj index 75fff3f88..491347104 100644 --- a/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.shproj +++ b/samples/UnoSample/UnoSample.Shared/UnoSample.Shared.shproj @@ -100,6 +100,7 @@ <_Globbed_Compile Remove="LiveChartsSamples\General\MultiThreading2\View.xaml.cs" /> <_Globbed_Compile Remove="LiveChartsSamples\General\MultiThreading\View.xaml.cs" /> <_Globbed_Compile Remove="LiveChartsSamples\General\NullPoints\View.xaml.cs" /> + <_Globbed_Compile Remove="LiveChartsSamples\General\Sections - Copy\View.xaml.cs" /> <_Globbed_Compile Remove="LiveChartsSamples\General\Sections2\View.xaml.cs" /> <_Globbed_Compile Remove="LiveChartsSamples\General\Sections\View.xaml.cs" /> <_Globbed_Compile Remove="LiveChartsSamples\General\TemplatedLegends\View.xaml.cs" /> diff --git a/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/LiveChartsSamples/General/VisualElements/View.xaml b/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/LiveChartsSamples/General/VisualElements/View.xaml new file mode 100644 index 000000000..e97aca2a0 --- /dev/null +++ b/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/LiveChartsSamples/General/VisualElements/View.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/LiveChartsSamples/General/VisualElements/View.xaml.cs b/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/LiveChartsSamples/General/VisualElements/View.xaml.cs new file mode 100644 index 000000000..70fbc4805 --- /dev/null +++ b/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/LiveChartsSamples/General/VisualElements/View.xaml.cs @@ -0,0 +1,11 @@ +using Microsoft.UI.Xaml.Controls; + +namespace UnoWinUISample.General.VisualElements; + +public sealed partial class View : UserControl +{ + public View() + { + InitializeComponent(); + } +} diff --git a/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/UnoWinUISample.Shared.projitems b/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/UnoWinUISample.Shared.projitems index e13133f1d..58ea3f4b8 100644 --- a/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/UnoWinUISample.Shared.projitems +++ b/samples/UnoWinUISample/UnoWinUISample/UnoWinUISample.Shared/UnoWinUISample.Shared.projitems @@ -674,12 +674,12 @@ Designer MSBuild:Compile - + <_Globbed_Compile Include="$(MSBuildThisFileDirectory)**/*.xaml.cs" Exclude="@(Compile)"> %(Filename) <_Globbed_Compile Include="$(MSBuildThisFileDirectory)**/*.cs" Exclude="@(Compile);@(_Globbed_Compile)" /> - + <_Globbed_PRIResource Include="$(MSBuildThisFileDirectory)**/*.resw" Exclude="@(PRIResource)" /> <_Globbed_Content Include="$(MSBuildThisFileDirectory)Assets/**/*.*" Exclude="@(Content)" /> @@ -690,4 +690,16 @@ + + + View.xaml + + + + + WinUI + Designer + MSBuild:Compile + + \ No newline at end of file diff --git a/samples/ViewModelsSamples/Axes/LabelsRotation/ViewModel.cs b/samples/ViewModelsSamples/Axes/LabelsRotation/ViewModel.cs index 1ec9ad66b..1e9258c18 100644 --- a/samples/ViewModelsSamples/Axes/LabelsRotation/ViewModel.cs +++ b/samples/ViewModelsSamples/Axes/LabelsRotation/ViewModel.cs @@ -26,7 +26,7 @@ public partial class ViewModel { // Use the Label property to indicate the format of the labels in the axis // The Labeler takes the value of the label as parameter and must return it as string - Labeler = (value) => "Day " + value, + Labeler = (value) => "so long label with this Day " + value, // The MinStep property lets you define the minimum separation (in chart values scale) // between every axis separator, in this case we don't want decimals, @@ -44,6 +44,7 @@ public partial class ViewModel { new Axis { + IsVisible = false, LabelsRotation = 15, // Now the Y axis we will display it as currency diff --git a/samples/ViewModelsSamples/Axes/Multiple/ViewModel.cs b/samples/ViewModelsSamples/Axes/Multiple/ViewModel.cs index ddc8418d3..63d6299ce 100644 --- a/samples/ViewModelsSamples/Axes/Multiple/ViewModel.cs +++ b/samples/ViewModelsSamples/Axes/Multiple/ViewModel.cs @@ -66,16 +66,26 @@ public partial class ViewModel Name = "Tens", NameTextSize = 14, NamePaint = new SolidColorPaint(s_blue), + NamePadding = new LiveChartsCore.Drawing.Padding(0, 20), + Padding = new LiveChartsCore.Drawing.Padding(0, 0, 20, 0), TextSize = 12, LabelsPaint = new SolidColorPaint(s_blue), + TicksPaint = new SolidColorPaint(s_blue), + SubticksPaint = new SolidColorPaint(s_blue), + DrawTicksPath = true }, new Axis // the "hundreds" series will be scaled on this axis { Name = "Hundreds", NameTextSize = 14, NamePaint = new SolidColorPaint(s_red), + NamePadding = new LiveChartsCore.Drawing.Padding(0, 20), + Padding = new LiveChartsCore.Drawing.Padding(20, 0, 0, 0), TextSize = 12, LabelsPaint = new SolidColorPaint(s_red), + TicksPaint = new SolidColorPaint(s_red), + SubticksPaint = new SolidColorPaint(s_red), + DrawTicksPath = true, ShowSeparatorLines = false, Position = LiveChartsCore.Measure.AxisPosition.End }, @@ -83,9 +93,14 @@ public partial class ViewModel { Name = "Thousands", NameTextSize = 14, + NamePadding = new LiveChartsCore.Drawing.Padding(0, 20), + Padding = new LiveChartsCore.Drawing.Padding(20, 0, 0, 0), NamePaint = new SolidColorPaint(s_yellow), TextSize = 12, LabelsPaint = new SolidColorPaint(s_yellow), + TicksPaint = new SolidColorPaint(s_yellow), + SubticksPaint = new SolidColorPaint(s_yellow), + DrawTicksPath = true, ShowSeparatorLines = false, Position = LiveChartsCore.Measure.AxisPosition.End } diff --git a/samples/ViewModelsSamples/General/VisualElements/MyGeometry.cs b/samples/ViewModelsSamples/General/VisualElements/MyGeometry.cs new file mode 100644 index 000000000..b7ed66be5 --- /dev/null +++ b/samples/ViewModelsSamples/General/VisualElements/MyGeometry.cs @@ -0,0 +1,25 @@ +using SkiaSharp; + +namespace ViewModelsSamples.General.VisualElements; + +public class MyGeometry : LiveChartsCore.SkiaSharpView.Drawing.Geometries.SVGPathGeometry +{ + // https://fonts.google.com/icons?icon.query=sun + public static SKPath svgPath = SKPath.ParseSvgPathData( + "M24 9.5q-.65 0-1.075-.425Q22.5 8.65 22.5 8V3.5q0-.65.425-1.075Q23.35 2 24 2q.65 0 1.075.425.425.425.425 1.075V8q0 " + + ".65-.425 1.075Q24.65 9.5 24 9.5Zm10.25 4.25q-.45-.45-.45-1.05 0-.6.45-1.05l3.15-3.2Q37.85 8 38.475 8t1.075.45Q40 8.9 40 9.5q0 " + + ".6-.45 1.05l-3.2 3.2q-.45.45-1.05.45-.6 0-1.05-.45ZM40 25.5q-.65 0-1.075-.425Q38.5 24.65 38.5 24q0-.65.425-1.075Q39.35 " + + "22.5 40 22.5h4.5q.65 0 1.075.425Q46 23.35 46 24q0 .65-.425 1.075-.425.425-1.075.425ZM24 46q-.65 " + + "0-1.075-.425-.425-.425-.425-1.075V40q0-.65.425-1.075Q23.35 38.5 24 38.5q.65 0 1.075.425.425.425.425 1.075v4.5q0 " + + ".65-.425 1.075Q24.65 46 24 46ZM11.65 13.75l-3.2-3.15Q8 10.15 8 9.525t.45-1.075Q8.9 8 9.5 8q.6 0 1.05.45l3.2 " + + "3.2q.45.45.45 1.05 0 .6-.45 1.05-.45.4-1.075.4t-1.025-.4Zm25.8 25.8-3.2-3.2q-.45-.45-.45-1.05 0-.6.45-1.05.4-.4 1.025-.4.625 " + + "0 1.075.4l3.25 3.15q.45.45.425 1.075Q40 39.1 39.6 39.55q-.45.45-1.075.45t-1.075-.45ZM3.5 25.5q-.65 0-1.075-.425Q2 24.65 2 " + + "24q0-.65.425-1.075Q2.85 22.5 3.5 22.5H8q.65 0 1.075.425Q9.5 23.35 9.5 24q0 .65-.425 1.075Q8.65 25.5 8 25.5Zm4.95 14.05Q8 " + + "39.1 8 38.5q0-.6.45-1.05l3.2-3.2q.4-.4 1.025-.4.625 0 1.075.4.45.45.45 1.075t-.45 1.075l-3.15 3.15q-.45.45-1.075.45t-1.075-.45ZM24 " + + "36q-5 0-8.5-3.5T12 24q0-5 3.5-8.5T24 12q5 0 8.5 3.5T36 24q0 5-3.5 8.5T24 36Zm0-3q3.75 0 6.375-2.625T33 24q0-3.75-2.625-6.375T24 " + + "15q-3.75 0-6.375 2.625T15 24q0 3.75 2.625 6.375T24 33Z"); + + public MyGeometry() + : base(svgPath) + { } +} diff --git a/samples/ViewModelsSamples/General/VisualElements/ViewModel.cs b/samples/ViewModelsSamples/General/VisualElements/ViewModel.cs new file mode 100644 index 000000000..f554087cd --- /dev/null +++ b/samples/ViewModelsSamples/General/VisualElements/ViewModel.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; +using LiveChartsCore; +using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel; +using LiveChartsCore.SkiaSharpView; +using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.Drawing.Geometries; +using LiveChartsCore.SkiaSharpView.Painting; +using LiveChartsCore.SkiaSharpView.VisualElements; +using SkiaSharp; + +namespace ViewModelsSamples.General.VisualElements; + +[ObservableObject] +public partial class ViewModel +{ + public IEnumerable> VisualElements { get; set; } = new List> + { + new GeometryVisual + { + X = 2.5, + Y = 3.5, + LocationUnit = MeasureUnit.ChartValues, + Width = 4, + Height = 2, + SizeUnit = MeasureUnit.ChartValues, + Fill = new SolidColorPaint(new SKColor(239, 83, 80, 50)) { ZIndex = 10 }, + Stroke = new SolidColorPaint(new SKColor(239, 83, 80)) { ZIndex = 10, StrokeThickness = 1.5f }, + }, + new GeometryVisual + { + X = 5.5, + Y = 6, + LocationUnit = MeasureUnit.ChartValues, + Width = 4, + Height = 5, + SizeUnit = MeasureUnit.ChartValues, + Fill = new SolidColorPaint(new SKColor(100, 221, 23, 50)) { ZIndex = - 10 }, + Stroke = new SolidColorPaint(new SKColor(100, 221, 23)) { ZIndex = -10, StrokeThickness = 1.5f }, + }, + new GeometryVisual + { + X = 18, + Y = 6, + LocationUnit = MeasureUnit.ChartValues, + Width = 100, + Height = 100, + SizeUnit = MeasureUnit.Pixels, + Fill = new SolidColorPaint(new SKColor(251, 192, 45, 50)) { ZIndex = 10 }, + Stroke = new SolidColorPaint(new SKColor(251, 192, 45)) { ZIndex = 10, StrokeThickness = 1.5f }, + }, + new LabelVisual + { + Text = "What happened here?", + X = 11, + Y = 1, + TextSize = 16, + Paint = new SolidColorPaint(new SKColor(250, 250, 250)) { ZIndex = 11 }, + BackgroundColor = new LvcColor(55, 71, 79), + Padding = new Padding(12), + LocationUnit = MeasureUnit.ChartValues, + Translate = new LvcPoint(0, -35) + } + }; + + public ISeries[] Series { get; set; } = + { + new LineSeries + { + GeometrySize = 13, + Values = new int[] { 2,2,3,4,2,2,3,6,3,5,2,1,4,5,2,3,2,4,5,3,2,6 } + } + }; +} diff --git a/samples/ViewModelsSamples/Index.cs b/samples/ViewModelsSamples/Index.cs index 4474da96c..b6325e8bb 100644 --- a/samples/ViewModelsSamples/Index.cs +++ b/samples/ViewModelsSamples/Index.cs @@ -88,6 +88,7 @@ public static class Index "General/Sections", "General/Sections2", + "General/VisualElements", "General/ChartToImage", "General/Tooltips", "General/Legends", diff --git a/samples/WPFSample/General/VisualElements/View.xaml b/samples/WPFSample/General/VisualElements/View.xaml new file mode 100644 index 000000000..666d8f2c6 --- /dev/null +++ b/samples/WPFSample/General/VisualElements/View.xaml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/samples/WPFSample/General/VisualElements/View.xaml.cs b/samples/WPFSample/General/VisualElements/View.xaml.cs new file mode 100644 index 000000000..41332e3e3 --- /dev/null +++ b/samples/WPFSample/General/VisualElements/View.xaml.cs @@ -0,0 +1,14 @@ +using System.Windows.Controls; + +namespace WPFSample.General.VisualElements; + +/// +/// Interaction logic for View.xaml +/// +public partial class View : UserControl +{ + public View() + { + InitializeComponent(); + } +} diff --git a/samples/WPFSample/WPFSample.csproj b/samples/WPFSample/WPFSample.csproj index 8927025d2..f7af80729 100644 --- a/samples/WPFSample/WPFSample.csproj +++ b/samples/WPFSample/WPFSample.csproj @@ -116,6 +116,9 @@ Code + + Code + Code @@ -387,6 +390,10 @@ $(DefaultXamlRuntime) Designer + + $(DefaultXamlRuntime) + Designer + $(DefaultXamlRuntime) Designer diff --git a/samples/WinFormsSample/General/VisualElements/View.Designer.cs b/samples/WinFormsSample/General/VisualElements/View.Designer.cs new file mode 100644 index 000000000..cd651000f --- /dev/null +++ b/samples/WinFormsSample/General/VisualElements/View.Designer.cs @@ -0,0 +1,46 @@ + +namespace WinFormsSample.General.VisualElements +{ + partial class View + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // View + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Name = "View"; + this.Size = new System.Drawing.Size(427, 375); + this.ResumeLayout(false); + + } + + #endregion + } +} diff --git a/samples/WinFormsSample/General/VisualElements/View.cs b/samples/WinFormsSample/General/VisualElements/View.cs new file mode 100644 index 000000000..039d2a38c --- /dev/null +++ b/samples/WinFormsSample/General/VisualElements/View.cs @@ -0,0 +1,29 @@ +using System.Windows.Forms; +using LiveChartsCore.SkiaSharpView.WinForms; +using ViewModelsSamples.General.VisualElements; + +namespace WinFormsSample.General.VisualElements; + +public partial class View : UserControl +{ + public View() + { + InitializeComponent(); + Size = new System.Drawing.Size(50, 50); + + var viewModel = new ViewModel(); + + var cartesianChart = new CartesianChart + { + Series = viewModel.Series, + VisualElements = viewModel.VisualElements, + + // out of livecharts properties... + Location = new System.Drawing.Point(0, 0), + Size = new System.Drawing.Size(50, 50), + Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom + }; + + Controls.Add(cartesianChart); + } +} diff --git a/samples/WinFormsSample/General/VisualElements/View.resx b/samples/WinFormsSample/General/VisualElements/View.resx new file mode 100644 index 000000000..f298a7be8 --- /dev/null +++ b/samples/WinFormsSample/General/VisualElements/View.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/samples/WinFormsSample/WinFormsSample.csproj b/samples/WinFormsSample/WinFormsSample.csproj index 48348c405..1bb46cb55 100644 --- a/samples/WinFormsSample/WinFormsSample.csproj +++ b/samples/WinFormsSample/WinFormsSample.csproj @@ -111,6 +111,9 @@ UserControl + + UserControl + UserControl diff --git a/samples/WinUISample/WinUISample/General/VisualElements/View.xaml b/samples/WinUISample/WinUISample/General/VisualElements/View.xaml new file mode 100644 index 000000000..79df8e7c3 --- /dev/null +++ b/samples/WinUISample/WinUISample/General/VisualElements/View.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/samples/WinUISample/WinUISample/General/VisualElements/View.xaml.cs b/samples/WinUISample/WinUISample/General/VisualElements/View.xaml.cs new file mode 100644 index 000000000..bb10350f4 --- /dev/null +++ b/samples/WinUISample/WinUISample/General/VisualElements/View.xaml.cs @@ -0,0 +1,11 @@ +using Microsoft.UI.Xaml.Controls; + +namespace WinUISample.General.VisualElements; + +public sealed partial class View : UserControl +{ + public View() + { + InitializeComponent(); + } +} diff --git a/samples/WinUISample/WinUISample/WinUISample.csproj b/samples/WinUISample/WinUISample/WinUISample.csproj index 552277b13..5877d2092 100644 --- a/samples/WinUISample/WinUISample/WinUISample.csproj +++ b/samples/WinUISample/WinUISample/WinUISample.csproj @@ -47,6 +47,7 @@ + @@ -237,6 +238,9 @@ $(DefaultXamlRuntime) + + $(DefaultXamlRuntime) + $(DefaultXamlRuntime) diff --git a/samples/XamarinSample/XamarinSample/XamarinSample/General/VisualElements/View.xaml b/samples/XamarinSample/XamarinSample/XamarinSample/General/VisualElements/View.xaml new file mode 100644 index 000000000..2fb9061c6 --- /dev/null +++ b/samples/XamarinSample/XamarinSample/XamarinSample/General/VisualElements/View.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/samples/XamarinSample/XamarinSample/XamarinSample/General/VisualElements/View.xaml.cs b/samples/XamarinSample/XamarinSample/XamarinSample/General/VisualElements/View.xaml.cs new file mode 100644 index 000000000..38e28f2e7 --- /dev/null +++ b/samples/XamarinSample/XamarinSample/XamarinSample/General/VisualElements/View.xaml.cs @@ -0,0 +1,13 @@ +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace XamarinSample.General.VisualElements; + +[XamlCompilation(XamlCompilationOptions.Compile)] +public partial class View : ContentPage +{ + public View() + { + InitializeComponent(); + } +} diff --git a/samples/XamarinSample/XamarinSample/XamarinSample/XamarinSample.csproj b/samples/XamarinSample/XamarinSample/XamarinSample/XamarinSample.csproj index 7949b2814..471dcb48e 100644 --- a/samples/XamarinSample/XamarinSample/XamarinSample/XamarinSample.csproj +++ b/samples/XamarinSample/XamarinSample/XamarinSample/XamarinSample.csproj @@ -128,6 +128,9 @@ %(Filename) + + %(Filename) + %(Filename) diff --git a/src/LiveChartsCore/Axis.cs b/src/LiveChartsCore/Axis.cs index c9bc31d62..11d74ffe2 100644 --- a/src/LiveChartsCore/Axis.cs +++ b/src/LiveChartsCore/Axis.cs @@ -23,7 +23,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using LiveChartsCore.Drawing; @@ -54,8 +53,8 @@ public abstract class Axis /// protected readonly Dictionary>> activeSeparators = new(); - // xo (x origin) and yo (y origin) are the distance to the center of the axis to the control bounds internal float _xo = 0f, _yo = 0f; + internal LvcSize _size; internal AxisOrientation _orientation; internal AnimatableAxisBounds _animatableBounds = new(); internal Bounds _dataBounds = new(); @@ -78,11 +77,12 @@ public abstract class Axis private double _textSize = 16; private IPaint? _separatorsPaint; private IPaint? _subseparatorsPaint; + private bool _drawTicksPath; + private ILineGeometry? _ticksPath; private IPaint? _ticksPaint; private IPaint? _subticksPaint; private IPaint? _zeroPaint; private ILineGeometry? _zeroLine; - private bool _showTicks = true; private bool _showSeparatorLines = true; private bool _isVisible = true; private bool _isInverted; @@ -96,6 +96,7 @@ public abstract class Axis float ICartesianAxis.Xo { get => _xo; set => _xo = value; } float ICartesianAxis.Yo { get => _yo; set => _yo = value; } + LvcSize ICartesianAxis.Size { get => _size; set => _size = value; } LvcRectangle ICartesianAxis.LabelsDesiredSize { get => _labelsDesiredSize; set => _labelsDesiredSize = value; } LvcRectangle ICartesianAxis.NameDesiredSize { get => _nameDesiredSize; set => _nameDesiredSize = value; } @@ -158,6 +159,9 @@ public abstract class Axis /// public IPaint? SubseparatorsPaint { get => _subseparatorsPaint; set { _subseparatorsPaint = value; OnPropertyChanged(); } } + /// + public bool DrawTicksPath { get => _drawTicksPath; set { _drawTicksPath = value; OnPropertyChanged(); } } + /// public IPaint? TicksPaint { get => _ticksPaint; set { _ticksPaint = value; OnPropertyChanged(); } } @@ -167,9 +171,6 @@ public abstract class Axis /// public IPaint? ZeroPaint { get => _zeroPaint; set { _zeroPaint = value; OnPropertyChanged(); } } - /// - public bool ShowTicks { get => _showTicks; set { _showTicks = value; OnPropertyChanged(); } } - /// public bool IsVisible { get => _isVisible; set { _isVisible = value; OnPropertyChanged(); } } @@ -260,7 +261,7 @@ public override void Measure(Chart chart) var scale = this.GetNextScaler(cartesianChart); var actualScale = this.GetActualScalerScaler(cartesianChart) ?? scale; - var axisTick = this.GetTick(drawMarginSize); + var axisTick = this.GetTick(drawMarginSize, null, GetPossibleMaxLabelSize()); var labeler = Labeler; if (Labels is not null) @@ -376,16 +377,43 @@ NamePadding is not null || SeparatorsPaint is not null || LabelsPaint is not nul { _zeroLine = new TLineGeometry(); ZeroPaint.AddGeometryToPaintTask(cartesianChart.Canvas, _zeroLine); - InitializeLine(_zeroLine, cartesianChart); - UpdateSeparator(_zeroLine!, x, y, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndComplete); + UpdateSeparator(_zeroLine, x, y, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndComplete); } - if (min <= 0 && max >= 0) + UpdateSeparator(_zeroLine, x, y, lxi, lxj, lyi, lyj, UpdateMode.Update); + } + + if (TicksPaint is not null && _drawTicksPath) + { + if (_ticksPath is null) + { + _ticksPath = new TLineGeometry(); + InitializeLine(_ticksPath, cartesianChart); + TicksPaint.AddGeometryToPaintTask(cartesianChart.Canvas, _ticksPath); + } + + if (_orientation == AxisOrientation.X) + { + var yp = yoo + _size.Height * 0.5f * (_position == AxisPosition.Start ? -1 : 1); + _ticksPath.X = lxi; + _ticksPath.X1 = lxj; + _ticksPath.Y = yp; + _ticksPath.Y1 = yp; + } + else { - UpdateSeparator(_zeroLine!, x, y, lxi, lxj, lyi, lyj, UpdateMode.Update); + var xp = xoo + _size.Width * 0.5f * (_position == AxisPosition.Start ? 1 : -1); + _ticksPath.X = xp; + _ticksPath.X1 = xp; + _ticksPath.Y = lyi; + _ticksPath.Y1 = lyj; } + + if (!_animatableBounds.HasPreviousState) _ticksPath.CompleteTransition(null); } + if (TicksPaint is not null && _ticksPath is not null && !_drawTicksPath) + TicksPaint.RemoveGeometryFromPainTask(cartesianChart.Canvas, _ticksPath); for (var i = start - s; i <= max + s; i += s) { @@ -416,8 +444,6 @@ NamePadding is not null || SeparatorsPaint is not null || LabelsPaint is not nul yc = actualScale.ToPixels(i); } - if (_orientation == AxisOrientation.Y) Trace.WriteLine($"@{i:N2}"); - if (!separators.TryGetValue(separatorKey, out var visualSeparator)) { visualSeparator = new AxisVisualSeprator() { Value = i }; @@ -426,22 +452,21 @@ NamePadding is not null || SeparatorsPaint is not null || LabelsPaint is not nul { InitializeSeparator(visualSeparator, cartesianChart); UpdateSeparator(visualSeparator.Separator!, xc, yc, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndComplete); - if (_orientation == AxisOrientation.Y) Trace.WriteLine($"{i:N2} => {yc:N2}"); } if (SubseparatorsPaint is not null) { InitializeSubseparators(visualSeparator, cartesianChart); UpdateSubseparators(visualSeparator.Subseparators!, actualScale, s, xc, yc, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndComplete); } - if (TicksPaint is not null && ShowTicks) + if (TicksPaint is not null) { InitializeTick(visualSeparator, cartesianChart); - UpdateTick(visualSeparator.Tick!, _tickLength, xc, yc, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndComplete); + UpdateTick(visualSeparator.Tick!, _tickLength, xc, yc, UpdateMode.UpdateAndComplete); } if (SubticksPaint is not null && _subSections > 0) { InitializeSubticks(visualSeparator, cartesianChart); - UpdateSubticks(visualSeparator.Subticks!, actualScale, s, xc, yc, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndComplete); + UpdateSubticks(visualSeparator.Subticks!, actualScale, s, xc, yc, UpdateMode.UpdateAndComplete); } if (LabelsPaint is not null) { @@ -452,8 +477,13 @@ NamePadding is not null || SeparatorsPaint is not null || LabelsPaint is not nul separators.Add(separatorKey, visualSeparator); } - if (SeparatorsPaint is not null && ShowSeparatorLines && visualSeparator.Separator is not null) - SeparatorsPaint.AddGeometryToPaintTask(cartesianChart.Canvas, visualSeparator.Separator); + if (SeparatorsPaint is not null && visualSeparator.Separator is not null) + { + if (ShowSeparatorLines) + SeparatorsPaint.AddGeometryToPaintTask(cartesianChart.Canvas, visualSeparator.Separator); + else + SeparatorsPaint.RemoveGeometryFromPainTask(cartesianChart.Canvas, visualSeparator.Separator); + } if (SubseparatorsPaint is not null && visualSeparator.Subseparators is not null) foreach (var subtick in visualSeparator.Subseparators) SubseparatorsPaint.AddGeometryToPaintTask(cartesianChart.Canvas, subtick); if (LabelsPaint is not null && visualSeparator.Label is not null) @@ -465,8 +495,8 @@ NamePadding is not null || SeparatorsPaint is not null || LabelsPaint is not nul if (visualSeparator.Separator is not null) UpdateSeparator(visualSeparator.Separator, x, y, lxi, lxj, lyi, lyj, UpdateMode.Update); if (visualSeparator.Subseparators is not null) UpdateSubseparators(visualSeparator.Subseparators, scale, s, x, y, lxi, lxj, lyi, lyj, UpdateMode.Update); - if (visualSeparator.Tick is not null) UpdateTick(visualSeparator.Tick, _tickLength, x, y, lxi, lxj, lyi, lyj, UpdateMode.Update); - if (visualSeparator.Subticks is not null) UpdateSubticks(visualSeparator.Subticks, scale, s, x, y, lxi, lxj, lyi, lyj, UpdateMode.Update); + if (visualSeparator.Tick is not null) UpdateTick(visualSeparator.Tick, _tickLength, x, y, UpdateMode.Update); + if (visualSeparator.Subticks is not null) UpdateSubticks(visualSeparator.Subticks, scale, s, x, y, UpdateMode.Update); if (visualSeparator.Label is not null) UpdateLabel(visualSeparator.Label, x, y, labelContent, hasRotation, r, UpdateMode.Update); if (hasActivePaint) _ = measured.Add(visualSeparator); @@ -491,8 +521,8 @@ NamePadding is not null || SeparatorsPaint is not null || LabelsPaint is not nul if (separator.Separator is not null) UpdateSeparator(separator.Separator, x, y, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndRemove); if (separator.Subseparators is not null) UpdateSubseparators(separator.Subseparators, scale, s, x, y, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndRemove); - if (separator.Tick is not null) UpdateTick(separator.Tick, _tickLength, x, y, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndRemove); - if (separator.Subticks is not null) UpdateSubticks(separator.Subticks, scale, s, x, y, lxi, lxj, lyi, lyj, UpdateMode.UpdateAndRemove); + if (separator.Tick is not null) UpdateTick(separator.Tick, _tickLength, x, y, UpdateMode.UpdateAndRemove); + if (separator.Subticks is not null) UpdateSubticks(separator.Subticks, scale, s, x, y, UpdateMode.UpdateAndRemove); if (separator.Label is not null) UpdateLabel(separator.Label, x, y, labeler(separator.Value - 1d + 1d), hasRotation, r, UpdateMode.UpdateAndRemove); _ = separators.Remove(separatorValueKey.Key); @@ -618,6 +648,7 @@ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnPropertyChanged(propertyName); } @@ -630,6 +661,43 @@ protected override void OnPaintChanged(string? propertyName) return new[] { _separatorsPaint, _labelsPaint, _namePaint, _zeroPaint, _ticksPaint, _subticksPaint, _subseparatorsPaint }; } + private LvcSize GetPossibleMaxLabelSize() + { + if (LabelsPaint is null) return new LvcSize(); + + var labeler = Labeler; + + if (Labels is not null) + { + labeler = Labelers.BuildNamedLabeler(Labels).Function; + _minStep = 1; + } + + var max = MaxLimit is null ? _visibleDataBounds.Max : MaxLimit.Value; + var min = MinLimit is null ? _visibleDataBounds.Min : MinLimit.Value; + var s = (max - min) / 20d; + + var maxLabelSize = new LvcSize(); + for (var i = min; i <= max; i += s) + { + var textGeometry = new TTextGeometry + { + Text = labeler(i), + TextSize = (float)TextSize, + RotateTransform = (float)LabelsRotation, + Padding = _padding + }; + + var m = textGeometry.Measure(LabelsPaint); + + maxLabelSize = new LvcSize( + maxLabelSize.Width > m.Width ? maxLabelSize.Width : m.Width, + maxLabelSize.Height > m.Height ? maxLabelSize.Height : m.Height); + } + + return maxLabelSize; + } + private void DrawName( CartesianChart cartesianChart, float size, @@ -815,19 +883,23 @@ private void UpdateSeparator( } private void UpdateTick( - ILineGeometry tick, float length, float x, float y, float lxi, float lxj, float lyi, float lyj, UpdateMode mode) + ILineGeometry tick, float length, float x, float y, UpdateMode mode) { if (_orientation == AxisOrientation.X) { + var lyi = y + _size.Height * 0.5f; + var lyj = y - _size.Height * 0.5f; tick.X = x; tick.X1 = x; - tick.Y = lyj; - tick.Y1 = lyj + length; + tick.Y = _position == AxisPosition.Start ? lyj : lyi - length; + tick.Y1 = _position == AxisPosition.Start ? lyj + length : lyi; } else { - tick.X = lxi; - tick.X1 = lxi - length; + var lxi = x + _size.Width * 0.5f; + var lxj = x - _size.Width * 0.5f; + tick.X = _position == AxisPosition.Start ? lxi : lxj + length; + tick.X1 = _position == AxisPosition.Start ? lxi - length : lxj; tick.Y = y; tick.Y1 = y; } @@ -858,15 +930,15 @@ private void UpdateSubseparators( } private void UpdateSubticks( - ILineGeometry[] subticks, Scaler scale, double s, float x, float y, float lxi, float lxj, float lyi, float lyj, UpdateMode mode) + ILineGeometry[] subticks, Scaler scale, double s, float x, float y, UpdateMode mode) { for (var j = 0; j < subticks.Length; j++) { var subtick = subticks[j]; - var k = 0.6f; + var k = 0.5f; var kl = (j + 1) / (double)(_subSections + 1); - if (Math.Abs(kl - 0.5f) < 0.01) k += 0.5f; + if (Math.Abs(kl - 0.5f) < 0.01) k += 0.25f; float xs = 0f, ys = 0f; if (_orientation == AxisOrientation.X) @@ -878,7 +950,7 @@ private void UpdateSubticks( ys = scale.MeasureInPixels(s * kl); } - UpdateTick(subtick, _tickLength * k, x + xs, y + ys, lxi, lxj, lyi, lyj, mode); + UpdateTick(subtick, _tickLength * k, x + xs, y + ys, mode); } } @@ -905,7 +977,7 @@ private void SetUpdateMode(IGeometry geometry, UpdateMode mode) switch (mode) { case Axis.UpdateMode.UpdateAndComplete: - if(_animatableBounds.HasPreviousState) geometry.Opacity = 0; + if (_animatableBounds.HasPreviousState) geometry.Opacity = 0; geometry.CompleteTransition(null); break; case Axis.UpdateMode.UpdateAndRemove: diff --git a/src/LiveChartsCore/CartesianChart.cs b/src/LiveChartsCore/CartesianChart.cs index 66bb943d6..c796d25fc 100644 --- a/src/LiveChartsCore/CartesianChart.cs +++ b/src/LiveChartsCore/CartesianChart.cs @@ -43,11 +43,11 @@ public class CartesianChart : Chart internal readonly HashSet _everMeasuredSeries = new(); internal readonly HashSet> _everMeasuredAxes = new(); internal readonly HashSet> _everMeasuredSections = new(); + internal readonly HashSet> _everMeasuredVisuals = new(); private readonly ICartesianChartView _chartView; private readonly ISizedGeometry _zoomingSection; private int _nextSeries = 0; private double _zoomingSpeed = 0; - private readonly bool _requiresLegendMeasureAlways = false; private ZoomAndPanMode _zoomMode; private DrawMarginFrame? _previousDrawMarginFrame; private const double MaxAxisBound = 0.05; @@ -60,17 +60,14 @@ public class CartesianChart : Chart /// The default platform configuration. /// The canvas. /// The zooming section. - /// Forces the legends to redraw with every measure request. public CartesianChart( ICartesianChartView view, Action defaultPlatformConfig, MotionCanvas canvas, - ISizedGeometry? zoomingSection, - bool requiresLegendMeasureAlways = false) + ISizedGeometry? zoomingSection) : base(canvas, defaultPlatformConfig, view) { _chartView = view; - _requiresLegendMeasureAlways = requiresLegendMeasureAlways; _zoomingSection = zoomingSection ?? throw new Exception($"{nameof(zoomingSection)} is required."); _zoomingSection.X = -1; _zoomingSection.Y = -1; @@ -110,6 +107,14 @@ public CartesianChart( /// public Section[] Sections { get; private set; } = Array.Empty>(); + /// + /// Gets the visual elements. + /// + /// + /// The visual elements. + /// + public ChartElement[] VisualElements { get; private set; } = Array.Empty>(); + /// /// Gets the drawable series. /// @@ -443,6 +448,7 @@ protected internal override void Measure() .ToArray(); Sections = _chartView.Sections?.Where(x => x.IsVisible).ToArray() ?? Array.Empty>(); + VisualElements = _chartView.VisualElements?.ToArray() ?? Array.Empty>(); #endregion @@ -535,17 +541,19 @@ protected internal override void Measure() #endregion - if (Legend is not null && (SeriesMiniatureChanged(Series, LegendPosition) || (_requiresLegendMeasureAlways && SizeChanged()))) + if (Legend is not null && (SeriesMiniatureChanged(Series, LegendPosition) || SizeChanged())) { Legend.Draw(this); - Update(); PreviousLegendPosition = LegendPosition; PreviousSeriesAtLegend = Series.Where(x => x.IsVisibleAtLegend).ToList(); + foreach (var series in PreviousSeriesAtLegend.Cast()) series.PaintsChanged = false; preserveFirstDraw = IsFirstDraw; + SetPreviousSize(); + Measure(); + return; } // calculate draw margin - var m = new Margin(); float ts = 0f, bs = 0f, ls = 0f, rs = 0f; SetDrawMargin(ControlSize, m); @@ -566,6 +574,8 @@ protected internal override void Measure() var drawablePlane = (IPlane)axis; var ns = drawablePlane.GetNameLabelSize(this); var s = drawablePlane.GetPossibleSize(this); + axis.Size = s; + if (axis.Position == AxisPosition.Start) { // X Bottom @@ -611,6 +621,7 @@ protected internal override void Measure() var drawablePlane = (IPlane)axis; var ns = drawablePlane.GetNameLabelSize(this); var s = drawablePlane.GetPossibleSize(this); + axis.Size = s; var w = s.Width; if (axis.Position == AxisPosition.Start) @@ -715,6 +726,15 @@ protected internal override void Measure() _ = toDeleteSections.Remove(section); } + var toDeleteVisualElements = new HashSet>(_everMeasuredVisuals); + foreach (var visual in VisualElements) + { + visual.Measure(this); + visual.RemoveOldPaints(View); + _ = _everMeasuredVisuals.Add(visual); + _ = toDeleteVisualElements.Remove(visual); + } + var toDeleteSeries = new HashSet(_everMeasuredSeries); foreach (var series in Series) { @@ -751,6 +771,11 @@ protected internal override void Measure() section.RemoveFromUI(this); _ = _everMeasuredSections.Remove(section); } + foreach (var visual in toDeleteVisualElements) + { + visual.RemoveFromUI(this); + _ = _everMeasuredVisuals.Remove(visual); + } foreach (var axis in totalAxes) { @@ -783,6 +808,8 @@ public override void Unload() _everMeasuredAxes.Clear(); foreach (var item in _everMeasuredSections) item.RemoveFromUI(this); _everMeasuredSections.Clear(); + foreach (var item in _everMeasuredVisuals) item.RemoveFromUI(this); + _everMeasuredVisuals.Clear(); foreach (var item in _everMeasuredSeries) ((ChartElement)item).RemoveFromUI(this); _everMeasuredSeries.Clear(); @@ -845,6 +872,18 @@ internal override void InvokePointerUp(LvcPoint point, bool isSecondaryAction) { if (_sectionZoomingStart is not null) { + var xy = Math.Sqrt(Math.Pow(point.X - _sectionZoomingStart.Value.X, 2) + Math.Pow(point.Y - _sectionZoomingStart.Value.Y, 2)); + if (xy < 15) + { + _zoomingSection.X = -1; + _zoomingSection.Y = -1; + _zoomingSection.Width = 0; + _zoomingSection.Height = 0; + Update(); + _sectionZoomingStart = null; + return; + } + if ((_zoomMode & ZoomAndPanMode.X) == ZoomAndPanMode.X) { for (var i = 0; i < XAxes.Length; i++) @@ -916,7 +955,6 @@ internal override void InvokePointerUp(LvcPoint point, bool isSecondaryAction) _zoomingSection.Width = 0; _zoomingSection.Height = 0; Update(); - _sectionZoomingStart = null; return; } diff --git a/src/LiveChartsCore/Chart.cs b/src/LiveChartsCore/Chart.cs index a6059b9f6..c470a0ac9 100644 --- a/src/LiveChartsCore/Chart.cs +++ b/src/LiveChartsCore/Chart.cs @@ -408,6 +408,14 @@ protected void SetDrawMargin(LvcSize controlSize, Margin margin) DrawMarginLocation = new LvcPoint(margin.Left, margin.Top); } + /// + /// Saves the previous size of the chart. + /// + protected void SetPreviousSize() + { + _previousSize = ControlSize; + } + /// /// Invokes the event. /// @@ -423,7 +431,7 @@ protected void InvokeOnMeasuring() /// protected void InvokeOnUpdateStarted() { - _previousSize = ControlSize; + SetPreviousSize(); UpdateStarted?.Invoke(View); } @@ -465,7 +473,6 @@ protected virtual bool SeriesMiniatureChanged(IReadOnlyList? Fill /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnPropertyChanged(propertyName); } diff --git a/src/LiveChartsCore/Drawing/Animatable.cs b/src/LiveChartsCore/Drawing/Animatable.cs index 2ead3b2ff..62de9f6d0 100644 --- a/src/LiveChartsCore/Drawing/Animatable.cs +++ b/src/LiveChartsCore/Drawing/Animatable.cs @@ -51,8 +51,7 @@ protected Animatable() { } public void SetTransition(Animation? animation, params string[]? propertyName) { var a = animation?.Duration == 0 ? null : animation; - - if (propertyName is null) propertyName = MotionProperties.Keys.ToArray(); + if (propertyName is null || propertyName.Length == 0) propertyName = MotionProperties.Keys.ToArray(); foreach (var name in propertyName) { @@ -63,7 +62,7 @@ public void SetTransition(Animation? animation, params string[]? propertyName) /// public void RemoveTransition(params string[]? propertyName) { - if (propertyName is null) propertyName = MotionProperties.Keys.ToArray(); + if (propertyName is null || propertyName.Length == 0) propertyName = MotionProperties.Keys.ToArray(); foreach (var name in propertyName) { @@ -74,7 +73,7 @@ public void RemoveTransition(params string[]? propertyName) /// public virtual void CompleteTransition(params string[]? propertyName) { - if (propertyName is null) propertyName = MotionProperties.Keys.ToArray(); + if (propertyName is null || propertyName.Length == 0) propertyName = MotionProperties.Keys.ToArray(); foreach (var property in propertyName) { diff --git a/src/LiveChartsCore/Drawing/DrawingContext.cs b/src/LiveChartsCore/Drawing/DrawingContext.cs index 4c864205c..85085889c 100644 --- a/src/LiveChartsCore/Drawing/DrawingContext.cs +++ b/src/LiveChartsCore/Drawing/DrawingContext.cs @@ -28,7 +28,14 @@ namespace LiveChartsCore.Drawing; public abstract class DrawingContext { /// - /// Clears the canvas. + /// Called when the frame starts. /// - public abstract void ClearCanvas(); + public virtual void OnBegingDraw() + { } + + /// + /// Called when the frame ends. + /// + public virtual void OnEndDraw() + { } } diff --git a/src/LiveChartsCore/Drawing/TransitionBuilder.cs b/src/LiveChartsCore/Drawing/TransitionBuilder.cs index 048d344db..9609d0162 100644 --- a/src/LiveChartsCore/Drawing/TransitionBuilder.cs +++ b/src/LiveChartsCore/Drawing/TransitionBuilder.cs @@ -21,6 +21,7 @@ // SOFTWARE. using System; +using LiveChartsCore.Kernel.Sketches; namespace LiveChartsCore.Drawing; @@ -66,6 +67,18 @@ public TransitionBuilder WithAnimation(Action animationBuilder) return WithAnimation(animation); } + /// + /// Sets the animations based on the chart and properties. + /// + /// The chart. + /// The transition + public TransitionBuilder WithAnimation(IChart chart) + { + return WithAnimation(new Animation() + .WithDuration(chart.View.AnimationsSpeed) + .WithEasingFunction(chart.View.EasingFunction)); + } + /// /// Sets the current transitions. /// diff --git a/src/LiveChartsCore/FinancialSeries.cs b/src/LiveChartsCore/FinancialSeries.cs index 59f263f2b..28cd151bb 100644 --- a/src/LiveChartsCore/FinancialSeries.cs +++ b/src/LiveChartsCore/FinancialSeries.cs @@ -420,6 +420,7 @@ protected internal override void SoftDeleteOrDisposePoint(ChartPoint point, Scal /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnSeriesMiniatureChanged(); OnPropertyChanged(); } diff --git a/src/LiveChartsCore/ISeries.cs b/src/LiveChartsCore/ISeries.cs index 87c3a0127..30ba3ac26 100644 --- a/src/LiveChartsCore/ISeries.cs +++ b/src/LiveChartsCore/ISeries.cs @@ -43,7 +43,12 @@ public interface ISeries : IStopNPC /// /// Gets the properties of the series. /// - SeriesProperties SeriesProperties { get; } + SeriesProperties SeriesProperties { get; } + + /// + /// Gets a value indicating whether any paint changed. + /// + bool PaintsChanged { get; set; } /// /// Gets the active pints. diff --git a/src/LiveChartsCore/Kernel/ChartElement.cs b/src/LiveChartsCore/Kernel/ChartElement.cs index af1312d8b..1c442ed63 100644 --- a/src/LiveChartsCore/Kernel/ChartElement.cs +++ b/src/LiveChartsCore/Kernel/ChartElement.cs @@ -35,6 +35,9 @@ public abstract class ChartElement : IChartElement> _deletingTasks = new(); + /// + public object? Tag { get; set; } + /// public abstract void Measure(Chart chart); diff --git a/src/LiveChartsCore/Kernel/Extensions.cs b/src/LiveChartsCore/Kernel/Extensions.cs index aedc535b7..db7e02bb4 100644 --- a/src/LiveChartsCore/Kernel/Extensions.cs +++ b/src/LiveChartsCore/Kernel/Extensions.cs @@ -110,33 +110,19 @@ public static class Extensions /// /// The axis. /// Size of the control. + /// The bounds. + /// The max label size. /// - public static AxisTick GetTick(this ICartesianAxis axis, LvcSize controlSize) + public static AxisTick GetTick(this ICartesianAxis axis, LvcSize controlSize, Bounds? bounds = null, LvcSize? maxLabelSize = null) { - return GetTick(axis, controlSize, axis.VisibleDataBounds); - } + bounds ??= axis.VisibleDataBounds; - /// - /// Gets the tick. - /// - /// The axis. - /// The chart. - /// - public static AxisTick GetTick(this IPolarAxis axis, PolarChart chart) - where TDrawingContext : DrawingContext - { - return GetTick(axis, chart, axis.VisibleDataBounds); - } + var w = (maxLabelSize?.Width ?? 0d) * 0.60; + if (w < 20 * Cf) w = 20 * Cf; + + var h = maxLabelSize?.Height ?? 0d; + if (h < 12 * Cf) h = 12 * Cf; - /// - /// Gets the tick. - /// - /// The axis. - /// Size of the control. - /// The bounds. - /// - public static AxisTick GetTick(this ICartesianAxis axis, LvcSize controlSize, Bounds bounds) - { var max = axis.MaxLimit is null ? bounds.Max : axis.MaxLimit.Value; var min = axis.MinLimit is null ? bounds.Min : axis.MinLimit.Value; @@ -144,8 +130,8 @@ public static AxisTick GetTick(this ICartesianAxis axis, LvcSize controlSize, Bo if (range == 0) range = min; var separations = axis.Orientation == AxisOrientation.Y - ? Math.Round(controlSize.Height / (12 * Cf), 0) - : Math.Round(controlSize.Width / (20 * Cf), 0); + ? Math.Round(controlSize.Height / h, 0) + : Math.Round(controlSize.Width / w, 0); var minimum = range / separations; @@ -164,9 +150,11 @@ public static AxisTick GetTick(this ICartesianAxis axis, LvcSize controlSize, Bo /// The chart. /// The bounds. /// - public static AxisTick GetTick(this IPolarAxis axis, PolarChart chart, Bounds bounds) + public static AxisTick GetTick(this IPolarAxis axis, PolarChart chart, Bounds? bounds = null) where TDrawingContext : DrawingContext { + bounds ??= axis.VisibleDataBounds; + var max = axis.MaxLimit is null ? bounds.Max : axis.MaxLimit.Value; var min = axis.MinLimit is null ? bounds.Min : axis.MinLimit.Value; diff --git a/src/LiveChartsCore/Kernel/IChartElement.cs b/src/LiveChartsCore/Kernel/IChartElement.cs index 9d6da960e..48c0f987b 100644 --- a/src/LiveChartsCore/Kernel/IChartElement.cs +++ b/src/LiveChartsCore/Kernel/IChartElement.cs @@ -31,6 +31,11 @@ namespace LiveChartsCore.Kernel; public interface IChartElement where TDrawingContext : DrawingContext { + /// + /// Gets or sets the object that contains data about the control. + /// + object? Tag { get; set; } + /// /// Measures and schedule the draw of the element in the user interface. /// diff --git a/src/LiveChartsCore/Kernel/Sketches/ICartesianAxis.cs b/src/LiveChartsCore/Kernel/Sketches/ICartesianAxis.cs index 7afadd66f..8d65208e8 100644 --- a/src/LiveChartsCore/Kernel/Sketches/ICartesianAxis.cs +++ b/src/LiveChartsCore/Kernel/Sketches/ICartesianAxis.cs @@ -64,6 +64,14 @@ public interface ICartesianAxis : IPlane, INotifyPropertyChanged /// float Yo { get; set; } + /// + /// Gets or sets the size of the axis, this value is used internally to calculate the axis position. + /// + /// + /// The length. + /// + LvcSize Size { get; set; } + /// /// Gets or sets the reserved area for the labels. /// @@ -109,6 +117,11 @@ public interface ICartesianAxis : ICartesianAxis /// IPaint? SubseparatorsPaint { get; set; } + /// + /// Gets or sets whether the ticks path should be drawn. + /// + bool DrawTicksPath { get; set; } + /// /// Gets or sets the separators paint. /// @@ -132,12 +145,4 @@ public interface ICartesianAxis : ICartesianAxis /// The separators paint. /// IPaint? ZeroPaint { get; set; } - - /// - /// Gets or sets the separators paint. - /// - /// - /// The separators paint. - /// - bool ShowTicks { get; set; } } diff --git a/src/LiveChartsCore/Kernel/Sketches/ICartesianChartView.cs b/src/LiveChartsCore/Kernel/Sketches/ICartesianChartView.cs index 629d07c7f..bfef33960 100644 --- a/src/LiveChartsCore/Kernel/Sketches/ICartesianChartView.cs +++ b/src/LiveChartsCore/Kernel/Sketches/ICartesianChartView.cs @@ -66,6 +66,11 @@ public interface ICartesianChartView : IChartView IEnumerable> Sections { get; set; } + /// + /// Gets or sets the visual elements. + /// + IEnumerable> VisualElements { get; set; } + /// /// Gets or sets the series to plot in the user interface. /// diff --git a/src/LiveChartsCore/Kernel/Sketches/IPieChartView.cs b/src/LiveChartsCore/Kernel/Sketches/IPieChartView.cs index 7607c72b7..8da06a4a7 100644 --- a/src/LiveChartsCore/Kernel/Sketches/IPieChartView.cs +++ b/src/LiveChartsCore/Kernel/Sketches/IPieChartView.cs @@ -49,6 +49,11 @@ public interface IPieChartView : IChartView /// IEnumerable Series { get; set; } + /// + /// Gets or sets the visual elements. + /// + IEnumerable> VisualElements { get; set; } + /// /// Gets or sets the initial rotation in degrees, this angle specifies where the first pie slice will be drawn, then the remaining /// slices will stack according to its corresponding position. diff --git a/src/LiveChartsCore/Kernel/Sketches/IPolarChartView.cs b/src/LiveChartsCore/Kernel/Sketches/IPolarChartView.cs index aa6d16a15..047c9acc0 100644 --- a/src/LiveChartsCore/Kernel/Sketches/IPolarChartView.cs +++ b/src/LiveChartsCore/Kernel/Sketches/IPolarChartView.cs @@ -87,13 +87,10 @@ public interface IPolarChartView : IChartView /// IEnumerable RadiusAxes { get; set; } - ///// - ///// Gets or sets the sections. - ///// - ///// - ///// The sections. - ///// - //IEnumerable> Sections { get; set; } + /// + /// Gets or sets the visual elements. + /// + IEnumerable> VisualElements { get; set; } /// /// Gets or sets the series to plot in the user interface. @@ -103,22 +100,6 @@ public interface IPolarChartView : IChartView /// IEnumerable Series { get; set; } - ///// - ///// Gets or sets the tool tip finding strategy. - ///// - ///// - ///// The tool tip finding strategy. - ///// - //TooltipFindingStrategy TooltipFindingStrategy { get; set; } - - ///// - ///// Gets or sets the zooming speed from 0 to 1, where 0 is the fastest and 1 the slowest. - ///// - ///// - ///// The zooming speed. - ///// - //double ZoomingSpeed { get; set; } - /// /// Scales the UI point. /// diff --git a/src/LiveChartsCore/LineSeries.cs b/src/LiveChartsCore/LineSeries.cs index 45854a291..01ac1f771 100644 --- a/src/LiveChartsCore/LineSeries.cs +++ b/src/LiveChartsCore/LineSeries.cs @@ -22,7 +22,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using LiveChartsCore.Drawing; using LiveChartsCore.Drawing.Segments; @@ -534,6 +533,16 @@ protected override void OnSeriesMiniatureChanged() CanvasSchedule = context; } + /// + public override bool MiniatureEquals(IChartSeries series) + { + return series is LineSeries lineSeries && + Name == series.Name && + !((ISeries)this).PaintsChanged && + Fill == lineSeries.Fill && Stroke == lineSeries.Stroke && + GeometryFill == lineSeries.GeometryFill && GeometryStroke == lineSeries.GeometryStroke; + } + /// /// Builds an spline from the given points. /// diff --git a/src/LiveChartsCore/LiveChartsCore.csproj b/src/LiveChartsCore/LiveChartsCore.csproj index 89fa556b0..2c85d9c0c 100644 --- a/src/LiveChartsCore/LiveChartsCore.csproj +++ b/src/LiveChartsCore/LiveChartsCore.csproj @@ -17,7 +17,7 @@ LiveChartsCore LiveChartsCore - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for .Net, this is the core package probably you need another package also unless you are building your own backed. MIT diff --git a/src/LiveChartsCore/Motion/MotionCanvas.cs b/src/LiveChartsCore/Motion/MotionCanvas.cs index e59d51561..10e3a9a61 100644 --- a/src/LiveChartsCore/Motion/MotionCanvas.cs +++ b/src/LiveChartsCore/Motion/MotionCanvas.cs @@ -49,13 +49,12 @@ public MotionCanvas() _stopwatch.Start(); } + internal bool DisableAnimations { get; set; } + /// - /// Gets a value indicating whether the animations are disabled. + /// Gets or sets the point where the draw starts. /// - /// - /// true if [disable animations]; otherwise, false. - /// - internal bool DisableAnimations { get; set; } + public LvcPoint? StartPoint { get; set; } /// /// Occurs when the visual is invalidated. @@ -104,9 +103,10 @@ public void DrawFrame(TDrawingContext context) lock (Sync) { + context.OnBegingDraw(); + var isValid = true; var frameTime = _stopwatch.ElapsedMilliseconds; - context.ClearCanvas(); var toRemoveGeometries = new List, IDrawable>>(); @@ -170,6 +170,8 @@ public void DrawFrame(TDrawingContext context) _previousFrameTime = frameTime; IsValid = isValid; + + context.OnEndDraw(); } if (IsValid) Validated?.Invoke(this); diff --git a/src/LiveChartsCore/PieChart.cs b/src/LiveChartsCore/PieChart.cs index d3c896ef0..06df7db6d 100644 --- a/src/LiveChartsCore/PieChart.cs +++ b/src/LiveChartsCore/PieChart.cs @@ -41,6 +41,7 @@ public class PieChart : Chart where TDrawingContext : DrawingContext { private readonly HashSet _everMeasuredSeries = new(); + internal readonly HashSet> _everMeasuredVisuals = new(); private readonly IPieChartView _chartView; private int _nextSeries = 0; private readonly bool _requiresLegendMeasureAlways = false; @@ -71,6 +72,14 @@ public PieChart( /// public IPieSeries[] Series { get; private set; } = Array.Empty>(); + /// + /// Gets the visual elements. + /// + /// + /// The visual elements. + /// + public ChartElement[] VisualElements { get; private set; } = Array.Empty>(); + /// /// Gets the drawable series. /// @@ -163,6 +172,8 @@ protected internal override void Measure() .Cast>() .ToArray(); + VisualElements = _chartView.VisualElements?.ToArray() ?? Array.Empty>(); + LegendPosition = _chartView.LegendPosition; LegendOrientation = _chartView.LegendOrientation; Legend = _chartView.Legend; @@ -221,6 +232,15 @@ protected internal override void Measure() // or it is initializing in the UI and has no dimensions yet if (DrawMarginSize.Width <= 0 || DrawMarginSize.Height <= 0) return; + var toDeleteVisualElements = new HashSet>(_everMeasuredVisuals); + foreach (var visual in VisualElements) + { + visual.Measure(this); + visual.RemoveOldPaints(View); + _ = _everMeasuredVisuals.Add(visual); + _ = toDeleteVisualElements.Remove(visual); + } + var toDeleteSeries = new HashSet(_everMeasuredSeries); foreach (var series in Series) { @@ -235,6 +255,11 @@ protected internal override void Measure() series.SoftDeleteOrDispose(View); _ = _everMeasuredSeries.Remove(series); } + foreach (var visual in toDeleteVisualElements) + { + visual.RemoveFromUI(this); + _ = _everMeasuredVisuals.Remove(visual); + } InvokeOnUpdateStarted(); IsFirstDraw = false; @@ -252,6 +277,8 @@ public override void Unload() foreach (var item in _everMeasuredSeries) ((ChartElement)item).RemoveFromUI(this); _everMeasuredSeries.Clear(); + foreach (var item in _everMeasuredVisuals) item.RemoveFromUI(this); + _everMeasuredVisuals.Clear(); IsFirstDraw = true; } } diff --git a/src/LiveChartsCore/PieSeries.cs b/src/LiveChartsCore/PieSeries.cs index fd95d62fd..a511302b9 100644 --- a/src/LiveChartsCore/PieSeries.cs +++ b/src/LiveChartsCore/PieSeries.cs @@ -37,12 +37,14 @@ namespace LiveChartsCore; /// The type of the model. /// The type of the visual. /// The type of the label. +/// The type of the miniature geometry, used in tool tips and legends. /// The type of the drawing context. -public abstract class PieSeries +public abstract class PieSeries : ChartSeries, IPieSeries where TDrawingContext : DrawingContext where TVisual : class, IDoughnutVisualChartPoint, new() where TLabel : class, ILabelGeometry, new() + where TMiniatureGeometry : ISizedGeometry, new() { private IPaint? _stroke = null; private IPaint? _fill = null; @@ -60,7 +62,7 @@ public abstract class PieSeries private PolarLabelsPosition _labelsPosition = PolarLabelsPosition.Middle; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected PieSeries(bool isGauge = false, bool isGaugeFill = false) : base(SeriesProperties.PieSeries | SeriesProperties.Stacked | @@ -456,16 +458,12 @@ protected override void OnSeriesMiniatureChanged() strokeClone.StrokeThickness = MaxSeriesStroke; } - var visual = new TVisual + var visual = new TMiniatureGeometry { X = st + MaxSeriesStroke - st, Y = st + MaxSeriesStroke - st, Height = (float)LegendShapeSize, - Width = (float)LegendShapeSize, - CenterX = (float)LegendShapeSize * 0.5f, - CenterY = (float)LegendShapeSize * 0.5f, - StartAngle = 0, - SweepAngle = 359.9999f + Width = (float)LegendShapeSize }; sh = st; strokeClone.ZIndex = 1; @@ -475,16 +473,12 @@ protected override void OnSeriesMiniatureChanged() if (Fill is not null) { var fillClone = Fill.CloneTask(); - var visual = new TVisual + var visual = new TMiniatureGeometry { X = sh + MaxSeriesStroke - sh, Y = sh + MaxSeriesStroke - sh, Height = (float)LegendShapeSize, - Width = (float)LegendShapeSize, - CenterX = (float)LegendShapeSize * 0.5f, - CenterY = (float)LegendShapeSize * 0.5f, - StartAngle = 0, - SweepAngle = 359.9999f + Width = (float)LegendShapeSize }; context.PaintSchedules.Add(new PaintSchedule(fillClone, visual)); } @@ -538,6 +532,7 @@ protected override void WhenPointerLeaves(ChartPoint point) /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnSeriesMiniatureChanged(); OnPropertyChanged(propertyName); } @@ -545,7 +540,7 @@ protected override void OnPaintChanged(string? propertyName) /// public override bool MiniatureEquals(IChartSeries instance) { - return instance is PieSeries pieSeries && + return instance is PieSeries pieSeries && Name == pieSeries.Name && Fill == pieSeries.Fill && Stroke == pieSeries.Stroke; } diff --git a/src/LiveChartsCore/PolarAxis.cs b/src/LiveChartsCore/PolarAxis.cs index cb9910899..4237ca060 100644 --- a/src/LiveChartsCore/PolarAxis.cs +++ b/src/LiveChartsCore/PolarAxis.cs @@ -656,6 +656,7 @@ protected virtual void SoftDeleteSeparator( /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnPropertyChanged(propertyName); } diff --git a/src/LiveChartsCore/PolarChart.cs b/src/LiveChartsCore/PolarChart.cs index 3ce0a1bc7..5a5833623 100644 --- a/src/LiveChartsCore/PolarChart.cs +++ b/src/LiveChartsCore/PolarChart.cs @@ -43,6 +43,7 @@ public class PolarChart : Chart internal readonly HashSet _everMeasuredSeries = new(); internal readonly HashSet> _everMeasuredAxes = new(); internal readonly HashSet> _everMeasuredSections = new(); + internal readonly HashSet> _everMeasuredVisuals = new(); private readonly IPolarChartView _chartView; private int _nextSeries = 0; private readonly bool _requiresLegendMeasureAlways = false; @@ -89,6 +90,14 @@ public PolarChart( /// public IPolarSeries[] Series { get; private set; } = Array.Empty>(); + /// + /// Gets the visual elements. + /// + /// + /// The visual elements. + /// + public ChartElement[] VisualElements { get; private set; } = Array.Empty>(); + /// /// Gets whether the series fit to bounds or not. /// @@ -208,6 +217,8 @@ protected internal override void Measure() .Cast>() .ToArray(); + VisualElements = _chartView.VisualElements?.ToArray() ?? Array.Empty>(); + #endregion SeriesContext = new SeriesContext(Series); @@ -476,6 +487,15 @@ protected internal override void Measure() drawablePlane.RemoveOldPaints(View); } + var toDeleteVisualElements = new HashSet>(_everMeasuredVisuals); + foreach (var visual in VisualElements) + { + visual.Measure(this); + visual.RemoveOldPaints(View); + _ = _everMeasuredVisuals.Add(visual); + _ = toDeleteVisualElements.Remove(visual); + } + var toDeleteSeries = new HashSet(_everMeasuredSeries); foreach (var series in Series) { @@ -495,6 +515,11 @@ protected internal override void Measure() axis.RemoveFromUI(this); _ = _everMeasuredAxes.Remove(axis); } + foreach (var visual in toDeleteVisualElements) + { + visual.RemoveFromUI(this); + _ = _everMeasuredVisuals.Remove(visual); + } foreach (var axis in totalAxes) { @@ -545,6 +570,8 @@ public override void Unload() _everMeasuredAxes.Clear(); foreach (var item in _everMeasuredSections) item.RemoveFromUI(this); _everMeasuredSections.Clear(); + foreach (var item in _everMeasuredVisuals) item.RemoveFromUI(this); + _everMeasuredVisuals.Clear(); foreach (var item in _everMeasuredSeries) ((ChartElement)item).RemoveFromUI(this); _everMeasuredSeries.Clear(); IsFirstDraw = true; diff --git a/src/LiveChartsCore/PolarLineSeries.cs b/src/LiveChartsCore/PolarLineSeries.cs index 6e8c24f8f..f06f3963d 100644 --- a/src/LiveChartsCore/PolarLineSeries.cs +++ b/src/LiveChartsCore/PolarLineSeries.cs @@ -489,6 +489,7 @@ public override bool MiniatureEquals(IChartSeries series) /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnSeriesMiniatureChanged(); OnPropertyChanged(); } diff --git a/src/LiveChartsCore/Section.cs b/src/LiveChartsCore/Section.cs index 3b6b4e874..5eab64bbd 100644 --- a/src/LiveChartsCore/Section.cs +++ b/src/LiveChartsCore/Section.cs @@ -160,6 +160,7 @@ public IPaint? Fill /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnPropertyChanged(propertyName); } diff --git a/src/LiveChartsCore/Series.cs b/src/LiveChartsCore/Series.cs index 3e19b1377..d424831fd 100644 --- a/src/LiveChartsCore/Series.cs +++ b/src/LiveChartsCore/Series.cs @@ -117,6 +117,8 @@ protected Series(SeriesProperties properties) (sender, e) => NotifySubscribers()); } + bool ISeries.PaintsChanged { get; set; } + /// public HashSet ActivePoints => everFetched; @@ -455,6 +457,13 @@ protected virtual void WhenPointerLeaves(ChartPoint point) ChartPointPointerHoverLost?.Invoke(point.Context.Chart, new ChartPoint(point)); } + /// + protected override void OnPaintChanged(string? propertyName) + { + base.OnPaintChanged(propertyName); + ((ISeries)this).PaintsChanged = true; + } + /// public override void RemoveFromUI(Chart chart) { diff --git a/src/LiveChartsCore/StepLineSeries.cs b/src/LiveChartsCore/StepLineSeries.cs index 39300a3a4..0d3d8defc 100644 --- a/src/LiveChartsCore/StepLineSeries.cs +++ b/src/LiveChartsCore/StepLineSeries.cs @@ -483,6 +483,16 @@ protected override void OnSeriesMiniatureChanged() CanvasSchedule = context; } + /// + public override bool MiniatureEquals(IChartSeries series) + { + return series is StepLineSeries stepSeries && + Name == series.Name && + !((ISeries)this).PaintsChanged && + Fill == stepSeries.Fill && Stroke == stepSeries.Stroke && + GeometryFill == stepSeries.GeometryFill && GeometryStroke == stepSeries.GeometryStroke; + } + /// protected override void SetDefaultPointTransitions(ChartPoint chartPoint) { diff --git a/src/LiveChartsCore/StrokeAndFillCartesianSeries.cs b/src/LiveChartsCore/StrokeAndFillCartesianSeries.cs index 91a33da77..15ef2e5c6 100644 --- a/src/LiveChartsCore/StrokeAndFillCartesianSeries.cs +++ b/src/LiveChartsCore/StrokeAndFillCartesianSeries.cs @@ -82,6 +82,7 @@ public IPaint? Fill /// protected override void OnPaintChanged(string? propertyName) { + base.OnPaintChanged(propertyName); OnSeriesMiniatureChanged(); OnPropertyChanged(); } @@ -100,6 +101,6 @@ protected override void OnPaintChanged(string? propertyName) public override bool MiniatureEquals(IChartSeries series) { return series is StrokeAndFillCartesianSeries sfSeries && - Name == series.Name && Fill == sfSeries.Fill && Stroke == sfSeries.Stroke; + Name == series.Name && !((ISeries)this).PaintsChanged && Fill == sfSeries.Fill && Stroke == sfSeries.Stroke; } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.axaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.axaml.cs index 937f8420c..e6444db4a 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.axaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.axaml.cs @@ -67,6 +67,7 @@ public class CartesianChart : UserControl, ICartesianChartView _xObserver; private readonly CollectionDeepObserver _yObserver; private readonly CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver> _visualsObserver; #endregion @@ -99,6 +100,8 @@ public CartesianChart() _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); XAxes = new List() { @@ -109,6 +112,7 @@ public CartesianChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultCartesianAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); PointerPressed += CartesianChart_PointerPressed; PointerMoved += CartesianChart_PointerMoved; @@ -158,6 +162,13 @@ public CartesianChart() AvaloniaProperty.Register>>( nameof(Sections), Enumerable.Empty>(), inherits: true); + /// + /// The visual elements property + /// + public static readonly AvaloniaProperty>> VisualElementsProperty = + AvaloniaProperty.Register>>( + nameof(VisualElements), Enumerable.Empty>(), inherits: true); + /// /// The draw margin frame property /// @@ -418,6 +429,13 @@ public IEnumerable> Sections set => SetValue(SectionsProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public DrawMarginFrame? DrawMarginFrame { @@ -801,6 +819,12 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs _sectionsObserver?.Initialize((IEnumerable>)change.NewValue.Value); } + if (change.Property.Name == nameof(VisualElementsProperty)) + { + _visualsObserver?.Dispose((IEnumerable>)change.OldValue.Value); + _visualsObserver?.Initialize((IEnumerable>)change.NewValue.Value); + } + _core.Update(); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj index 66efb3895..faedf3156 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj @@ -6,7 +6,7 @@ netcoreapp2.0;netstandard2.0;net462; LiveChartsCore.SkiaSharpView.Avalonia LiveChartsCore.SkiaSharpView.Avalonia - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for AvaloniaUI. MIT diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PieChart.axaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PieChart.axaml.cs index 26955ce9a..1516bc9d3 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PieChart.axaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PieChart.axaml.cs @@ -25,6 +25,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using System.Linq; using System.Windows.Input; using Avalonia; using Avalonia.Controls; @@ -59,7 +60,8 @@ public class PieChart : UserControl, IPieChartView, IAv protected IChartTooltip? tooltip; private Chart? _core; - private CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver> _visualsObserver; private MotionCanvas? _avaloniaCanvas; #endregion @@ -98,9 +100,21 @@ public PieChart() { if (_core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; _core.Update(); - }); + }, true); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (_core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + _core.Update(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (_core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + _core.Update(); + }, true); Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); PointerLeave += Chart_PointerLeave; PointerMoved += Chart_PointerMoved; @@ -128,6 +142,13 @@ public PieChart() public static readonly AvaloniaProperty> SeriesProperty = AvaloniaProperty.Register>(nameof(Series), new List(), inherits: true); + /// + /// The visual elements property + /// + public static readonly AvaloniaProperty>> VisualElementsProperty = + AvaloniaProperty.Register>>( + nameof(VisualElements), Enumerable.Empty>(), inherits: true); + /// /// The initial rotation property /// @@ -356,6 +377,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public double InitialRotation { @@ -695,6 +723,13 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs return; } + if (change.Property.Name == nameof(VisualElements)) + { + _visualsObserver?.Dispose((IEnumerable>)change.OldValue.Value); + _visualsObserver?.Initialize((IEnumerable>)change.NewValue.Value); + return; + } + _core.Update(); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PolarChart.axaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PolarChart.axaml.cs index bdf997ff7..54e2b0c18 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PolarChart.axaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/PolarChart.axaml.cs @@ -62,9 +62,10 @@ public class PolarChart : UserControl, IPolarChartView, private MotionCanvas? _avaloniaCanvas; private Chart? _core; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _angleObserver; - private CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _angleObserver; + private readonly CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver> _visualsObserver; #endregion @@ -95,6 +96,8 @@ public PolarChart() _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); AngleAxes = new List() { @@ -105,6 +108,7 @@ public PolarChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultPolarAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); PointerWheelChanged += PolarChart_PointerWheelChanged; PointerPressed += PolarChart_PointerPressed; @@ -128,6 +132,13 @@ public PolarChart() public static readonly AvaloniaProperty> SeriesProperty = AvaloniaProperty.Register>(nameof(Series), Enumerable.Empty(), inherits: true); + /// + /// The visual elements property + /// + public static readonly AvaloniaProperty>> VisualElementsProperty = + AvaloniaProperty.Register>>( + nameof(VisualElements), Enumerable.Empty>(), inherits: true); + /// /// The fit to bounds property. /// @@ -421,6 +432,13 @@ public IEnumerable RadiusAxes set => SetValue(RadiusAxesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public TimeSpan AnimationsSpeed { @@ -760,6 +778,12 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs _radiusObserver?.Initialize((IEnumerable)change.NewValue.Value); } + if (change.Property.Name == nameof(VisualElements)) + { + _visualsObserver?.Dispose((IEnumerable>)change.OldValue.Value); + _visualsObserver?.Initialize((IEnumerable>)change.NewValue.Value); + } + _core.Update(); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/CartesianChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/CartesianChart.cs index 0c439c1ef..c59eefeaa 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/CartesianChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/CartesianChart.cs @@ -46,6 +46,7 @@ public class CartesianChart : Chart, ICartesianChartView _xObserver; private readonly CollectionDeepObserver _yObserver; private readonly CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver> _visualsObserver; private readonly RectangleGeometry _zoomingSection = new(); #endregion @@ -65,6 +66,8 @@ public CartesianChart() _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); SetCurrentValue(XAxesProperty, new ObservableCollection() { @@ -76,6 +79,7 @@ public CartesianChart() }); SetCurrentValue(SeriesProperty, new ObservableCollection()); SetCurrentValue(SectionsProperty, new ObservableCollection>()); + SetCurrentValue(VisualElementsProperty, new ObservableCollection>()); MouseWheel += OnMouseWheel; MouseDown += OnMouseDown; @@ -178,6 +182,28 @@ public CartesianChart() : new List>(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(CartesianChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (CartesianChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart.core is null) return; + chart.core.Update(); + }, + (DependencyObject o, object value) => + { + return value is IEnumerable> + ? value + : new List>(); + })); + /// /// The zoom mode property /// @@ -244,6 +270,13 @@ public IEnumerable> Sections set => SetValue(SectionsProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public DrawMarginFrame? DrawMarginFrame { @@ -340,21 +373,21 @@ private void OnDeepCollectionPropertyChanged(object? sender, PropertyChangedEven core.Update(); } - private void OnMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + private void OnMouseDown(object sender, MouseButtonEventArgs e) { _ = CaptureMouse(); var p = e.GetPosition(this); core?.InvokePointerDown(new LvcPoint((float)p.X, (float)p.Y), e.ChangedButton == MouseButton.Right); } - private void OnMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) + private void OnMouseUp(object sender, MouseButtonEventArgs e) { var p = e.GetPosition(this); core?.InvokePointerUp(new LvcPoint((float)p.X, (float)p.Y), e.ChangedButton == MouseButton.Right); ReleaseMouseCapture(); } - private void OnMouseWheel(object? sender, System.Windows.Input.MouseWheelEventArgs e) + private void OnMouseWheel(object? sender, MouseWheelEventArgs e) { if (core is null) throw new Exception("core not found"); var c = (CartesianChart)core; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj index 41ce05dff..1a9c29352 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj @@ -1,4 +1,4 @@ - + enable @@ -7,7 +7,7 @@ net462;netcoreapp3.1 LiveChartsCore.SkiaSharpView.WPF LiveChartsCore.SkiaSharpView.WPF - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for WPF. MIT @@ -23,7 +23,7 @@ - + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PieChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PieChart.cs index 2c7f65839..0863d4435 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PieChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PieChart.cs @@ -37,7 +37,8 @@ namespace LiveChartsCore.SkiaSharpView.WPF; /// public class PieChart : Chart, IPieChartView { - private CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver> _visualsObserver; static PieChart() { @@ -59,9 +60,21 @@ public PieChart() { if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; core.Update(); - }); + }, true); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + core.Update(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + core.Update(); + }, true); SetCurrentValue(SeriesProperty, new ObservableCollection()); + SetCurrentValue(VisualElementsProperty, new ObservableCollection>()); MouseDown += OnMouseDown; } @@ -85,6 +98,28 @@ public PieChart() return value is IEnumerable ? value : new ObservableCollection(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(PieChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (PieChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart.core is null) return; + chart.core.Update(); + }, + (DependencyObject o, object value) => + { + return value is IEnumerable> + ? value + : new List>(); + })); + /// /// The initial rotation property /// @@ -115,6 +150,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public double InitialRotation { @@ -155,7 +197,7 @@ protected override void OnUnloaded() core?.Unload(); } - private void OnMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + private void OnMouseDown(object sender, MouseButtonEventArgs e) { var p = e.GetPosition(this); core?.InvokePointerDown(new LvcPoint((float)p.X, (float)p.Y), e.ChangedButton == MouseButton.Right); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PolarChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PolarChart.cs index febdf2ac8..a03e6ddb5 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PolarChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/PolarChart.cs @@ -39,9 +39,10 @@ public class PolarChart : Chart, IPolarChartView { #region fields - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _angleObserver; - private CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _angleObserver; + private readonly CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver> _visualsObserver; #endregion @@ -58,6 +59,8 @@ public PolarChart() _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); SetCurrentValue(AngleAxesProperty, new ObservableCollection() { @@ -68,6 +71,7 @@ public PolarChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultPolarAxis() }); SetCurrentValue(SeriesProperty, new ObservableCollection()); + SetCurrentValue(VisualElementsProperty, new ObservableCollection>()); MouseWheel += OnMouseWheel; MouseDown += OnMouseDown; @@ -124,6 +128,28 @@ public PolarChart() return value is IEnumerable ? value : new ObservableCollection(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(PolarChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (PolarChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart.core is null) return; + chart.core.Update(); + }, + (DependencyObject o, object value) => + { + return value is IEnumerable> + ? value + : new List>(); + })); + /// /// The x axes property. /// @@ -216,6 +242,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public IEnumerable AngleAxes { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/CartesianChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/CartesianChart.cs index ae2e93c7e..7ca979a40 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/CartesianChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/CartesianChart.cs @@ -39,14 +39,16 @@ namespace LiveChartsCore.SkiaSharpView.WinForms; /// public class CartesianChart : Chart, ICartesianChartView { - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _xObserver; - private CollectionDeepObserver _yObserver; - private CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _xObserver; + private readonly CollectionDeepObserver _yObserver; + private readonly CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver> _visualsObserver; private IEnumerable _series = new List(); private IEnumerable _xAxes = new List { new() }; private IEnumerable _yAxes = new List { new() }; private IEnumerable> _sections = new List>(); + private IEnumerable> _visuals = new List>(); private DrawMarginFrame? _drawMarginFrame; private TooltipFindingStrategy _tooltipFindingStrategy = LiveCharts.CurrentSettings.DefaultTooltipFindingStrategy; @@ -68,6 +70,8 @@ public CartesianChart(IChartTooltip? tooltip = null, IC _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); XAxes = new List() { @@ -78,6 +82,7 @@ public CartesianChart(IChartTooltip? tooltip = null, IC LiveCharts.CurrentSettings.GetProvider().GetDefaultCartesianAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); var c = Controls[0].Controls[0]; @@ -145,6 +150,20 @@ public IEnumerable> Sections } } + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public DrawMarginFrame? DrawMarginFrame @@ -185,7 +204,7 @@ protected override void InitializeCore() motionCanvas.CanvasCore.AddDrawableTask(zoomingSectionPaint); core = new CartesianChart( - this, LiveChartsSkiaSharp.DefaultPlatformBuilder, motionCanvas.CanvasCore, zoomingSection, true); + this, LiveChartsSkiaSharp.DefaultPlatformBuilder, motionCanvas.CanvasCore, zoomingSection); if (((IChartView)this).DesignerMode) return; core.Update(); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj index 881289651..e3f890092 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj @@ -9,7 +9,7 @@ net462;netcoreapp3.1 LiveChartsCore.SkiaSharpView.WinForms LiveChartsCore.SkiaSharpView.WinForms - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for WindowsForms. MIT @@ -25,7 +25,7 @@ - + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PieChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PieChart.cs index 64fea2698..b5b874ef8 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PieChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PieChart.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Windows.Forms; @@ -35,8 +36,10 @@ namespace LiveChartsCore.SkiaSharpView.WinForms; /// public class PieChart : Chart, IPieChartView { - private CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver> _visualsObserver; private IEnumerable _series = new List(); + private IEnumerable> _visuals = new List>(); private double _initialRotation; private double _maxAngle = 360; private double? _total; @@ -66,6 +69,21 @@ public PieChart(IChartTooltip? tooltip = null, IChartLe OnPropertyChanged(); }, true); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (sender is IStopNPC stop && !stop.IsNotifyingChanges) return; + OnPropertyChanged(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (sender is IStopNPC stop && !stop.IsNotifyingChanges) return; + OnPropertyChanged(); + }, + true); + + Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); var c = Controls[0].Controls[0]; c.MouseDown += OnMouseDown; @@ -88,6 +106,20 @@ public IEnumerable Series } } + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// public double InitialRotation { get => _initialRotation; set { _initialRotation = value; OnPropertyChanged(); } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PolarChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PolarChart.cs index 1f683cfcf..49423c121 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PolarChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/PolarChart.cs @@ -40,12 +40,14 @@ public class PolarChart : Chart, IPolarChartView private double _totalAngle = 360; private double _innerRadius; private double _initialRotation = LiveCharts.CurrentSettings.PolarInitialRotation; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _angleObserver; - private CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _angleObserver; + private readonly CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver> _visualsObserver; private IEnumerable _series = new List(); private IEnumerable _angleAxes = new List(); private IEnumerable _radiusAxes = new List(); + private IEnumerable> _visuals = new List>(); /// /// Initializes a new instance of the class. @@ -63,6 +65,8 @@ public PolarChart(IChartTooltip? tooltip = null, IChart _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); AngleAxes = new List() { @@ -73,6 +77,7 @@ public PolarChart(IChartTooltip? tooltip = null, IChart LiveCharts.CurrentSettings.GetProvider().GetDefaultPolarAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); var c = Controls[0].Controls[0]; @@ -174,6 +179,20 @@ public IEnumerable RadiusAxes } } + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// /// Initializes the core. /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/CartesianChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/CartesianChart.xaml.cs index cc53d8772..11e6fb3c4 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/CartesianChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/CartesianChart.xaml.cs @@ -40,7 +40,6 @@ using Xamarin.Essentials; using Xamarin.Forms; using Xamarin.Forms.Xaml; -using c = Xamarin.Forms.Color; namespace LiveChartsCore.SkiaSharpView.Xamarin.Forms; @@ -55,10 +54,11 @@ public partial class CartesianChart : ContentView, ICartesianChartView protected Chart? core; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _xObserver; - private CollectionDeepObserver _yObserver; - private CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _xObserver; + private readonly CollectionDeepObserver _yObserver; + private readonly CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver> _visualsObserver; private Grid? _grid; private double _lastScale = 0; private DateTime _panLocketUntil; @@ -91,6 +91,8 @@ public CartesianChart() _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); XAxes = new List() { @@ -101,6 +103,7 @@ public CartesianChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultCartesianAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); canvas.SkCanvasView.EnableTouchEvents = true; canvas.SkCanvasView.Touch += OnSkCanvasTouched; @@ -191,6 +194,22 @@ public CartesianChart() chart.core.Update(); }); + /// + /// The visual elements property. + /// + public static readonly BindableProperty VisualElementsProperty = + BindableProperty.Create( + nameof(VisualElements), typeof(IEnumerable>), typeof(CartesianChart), new List>(), + BindingMode.Default, null, (BindableObject o, object oldValue, object newValue) => + { + var chart = (CartesianChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)oldValue); + observer?.Initialize((IEnumerable>)newValue); + if (chart.core is null) return; + chart.core.Update(); + }); + /// /// The draw margin frame property. /// @@ -415,7 +434,7 @@ LvcColor IChartView.BackColor ? new LvcColor() : LvcColor.FromArgb( (byte)(b.Color.R * 255), (byte)(b.Color.G * 255), (byte)(b.Color.B * 255), (byte)(b.Color.A * 255)); - set => Background = new SolidColorBrush(new c(value.R / 255, value.G / 255, value.B / 255, value.A / 255)); + set => Background = new SolidColorBrush(new Color(value.R / 255, value.G / 255, value.B / 255, value.A / 255)); } CartesianChart ICartesianChartView.Core => core is null ? throw new Exception("core not found") : (CartesianChart)core; @@ -477,6 +496,13 @@ public IEnumerable> Sections set => SetValue(SectionsProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public DrawMarginFrame? DrawMarginFrame { @@ -758,8 +784,8 @@ LvcPoint IMobileChart.GetCanvasPosition() /// public void SetTooltipStyle(LvcColor background, LvcColor textColor) { - TooltipBackground = new c(background.R, background.G, background.B, background.A); - TooltipTextBrush = new c(textColor.R, textColor.G, textColor.B, textColor.A); + TooltipBackground = new Color(background.R, background.G, background.B, background.A); + TooltipTextBrush = new Color(textColor.R, textColor.G, textColor.B, textColor.A); } void IChartView.InvokeOnUIThread(Action action) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/LiveChartsCore.SkiaSharpView.Xamarin.Forms.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/LiveChartsCore.SkiaSharpView.Xamarin.Forms.csproj index d1d8dcaf5..22357b7ff 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/LiveChartsCore.SkiaSharpView.Xamarin.Forms.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/LiveChartsCore.SkiaSharpView.Xamarin.Forms.csproj @@ -1,4 +1,4 @@ - + 10.0 @@ -6,7 +6,7 @@ netstandard2.0; LiveChartsCore.SkiaSharpView.XamarinForms LiveChartsCore.SkiaSharpView.XamarinForms - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for XamarinForms. MIT @@ -22,7 +22,7 @@ - + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PieChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PieChart.xaml.cs index 2e1fce041..8c675f309 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PieChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PieChart.xaml.cs @@ -38,7 +38,6 @@ using Xamarin.Essentials; using Xamarin.Forms; using Xamarin.Forms.Xaml; -using c = Xamarin.Forms.Color; namespace LiveChartsCore.SkiaSharpView.Xamarin.Forms; @@ -52,7 +51,8 @@ public partial class PieChart : ContentView, IPieChartView protected Chart? core; - private CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver> _visualsObserver; private Grid? _grid; #endregion @@ -87,8 +87,20 @@ public PieChart() if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; core.Update(); }); + _visualsObserver = new CollectionDeepObserver>( + (object sender, NotifyCollectionChangedEventArgs e) => + { + if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + core.Update(); + }, + (object sender, PropertyChangedEventArgs e) => + { + if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + core.Update(); + }); Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); canvas.SkCanvasView.EnableTouchEvents = true; canvas.SkCanvasView.Touch += OnSkCanvasTouched; @@ -131,6 +143,22 @@ public PieChart() chart.core.Update(); }); + /// + /// The visual elements property. + /// + public static readonly BindableProperty VisualElementsProperty = + BindableProperty.Create( + nameof(VisualElements), typeof(IEnumerable>), typeof(PieChart), new List>(), + BindingMode.Default, null, (BindableObject o, object oldValue, object newValue) => + { + var chart = (PieChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)oldValue); + observer?.Initialize((IEnumerable>)newValue); + if (chart.core is null) return; + chart.core.Update(); + }); + /// /// The initial rotation property /// @@ -215,7 +243,7 @@ public PieChart() /// public static readonly BindableProperty LegendTextBrushProperty = BindableProperty.Create( - nameof(LegendTextBrush), typeof(c), typeof(CartesianChart), + nameof(LegendTextBrush), typeof(Color), typeof(CartesianChart), new Color(35 / 255d, 35 / 255d, 35 / 255d), propertyChanged: OnBindablePropertyChanged); /// @@ -223,7 +251,7 @@ public PieChart() /// public static readonly BindableProperty LegendBackgroundProperty = BindableProperty.Create( - nameof(LegendBackground), typeof(c), typeof(CartesianChart), + nameof(LegendBackground), typeof(Color), typeof(CartesianChart), new Color(250 / 255d, 250 / 255d, 250 / 255d), propertyChanged: OnBindablePropertyChanged); /// @@ -343,7 +371,7 @@ LvcColor IChartView.BackColor ? new LvcColor() : LvcColor.FromArgb( (byte)(b.Color.A * 255), (byte)(b.Color.R * 255), (byte)(b.Color.G * 255), (byte)(b.Color.B * 255)); - set => Background = new SolidColorBrush(new c(value.R / 255, value.G / 255, value.B / 255, value.A / 255)); + set => Background = new SolidColorBrush(new Color(value.R / 255, value.G / 255, value.B / 255, value.A / 255)); } PieChart IPieChartView.Core => @@ -385,6 +413,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public double InitialRotation { @@ -651,8 +686,8 @@ LvcPoint IMobileChart.GetCanvasPosition() /// public void SetTooltipStyle(LvcColor background, LvcColor textColor) { - TooltipBackground = new c(background.R, background.G, background.B, background.A); - TooltipTextBrush = new c(textColor.R, textColor.G, textColor.B, textColor.A); + TooltipBackground = new Color(background.R, background.G, background.B, background.A); + TooltipTextBrush = new Color(textColor.R, textColor.G, textColor.B, textColor.A); } void IChartView.InvokeOnUIThread(Action action) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PolarChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PolarChart.xaml.cs index 5581dbbfa..90e4d14c3 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PolarChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Xamarin.Forms/PolarChart.xaml.cs @@ -38,7 +38,6 @@ using Xamarin.Essentials; using Xamarin.Forms; using Xamarin.Forms.Xaml; -using c = Xamarin.Forms.Color; namespace LiveChartsCore.SkiaSharpView.Xamarin.Forms; @@ -53,9 +52,10 @@ public partial class PolarChart : ContentView, IPolarChartView protected Chart? core; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _angleObserver; - private CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _angleObserver; + private readonly CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver> _visualsObserver; private Grid? _grid; #endregion @@ -82,6 +82,8 @@ public PolarChart() _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); AngleAxes = new List() { @@ -92,6 +94,7 @@ public PolarChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultPolarAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); canvas.SkCanvasView.EnableTouchEvents = true; canvas.SkCanvasView.Touch += OnSkCanvasTouched; @@ -162,6 +165,22 @@ public PolarChart() chart.core.Update(); }); + /// + /// The visual elements property. + /// + public static readonly BindableProperty VisualElementsProperty = + BindableProperty.Create( + nameof(VisualElements), typeof(IEnumerable>), typeof(PolarChart), new List>(), + BindingMode.Default, null, (BindableObject o, object oldValue, object newValue) => + { + var chart = (PolarChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)oldValue); + observer?.Initialize((IEnumerable>)newValue); + if (chart.core is null) return; + chart.core.Update(); + }); + /// /// The x axes property. /// @@ -387,7 +406,7 @@ LvcColor IChartView.BackColor ? new LvcColor() : LvcColor.FromArgb( (byte)(b.Color.R * 255), (byte)(b.Color.G * 255), (byte)(b.Color.B * 255), (byte)(b.Color.A * 255)); - set => Background = new SolidColorBrush(new c(value.R / 255, value.G / 255, value.B / 255, value.A / 255)); + set => Background = new SolidColorBrush(new Color(value.R / 255, value.G / 255, value.B / 255, value.A / 255)); } PolarChart IPolarChartView.Core @@ -457,6 +476,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public IEnumerable AngleAxes { @@ -725,8 +751,8 @@ LvcPoint IMobileChart.GetCanvasPosition() /// public void SetTooltipStyle(LvcColor background, LvcColor textColor) { - TooltipBackground = new c(background.R, background.G, background.B, background.A); - TooltipTextBrush = new c(textColor.R, textColor.G, textColor.B, textColor.A); + TooltipBackground = new Color(background.R, background.G, background.B, background.A); + TooltipTextBrush = new Color(textColor.R, textColor.G, textColor.B, textColor.A); } void IChartView.InvokeOnUIThread(Action action) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/AvaloniaDrawingContext.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/AvaloniaDrawingContext.cs index 52e557687..e68ee1387 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/AvaloniaDrawingContext.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/AvaloniaDrawingContext.cs @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using SkiaSharp; @@ -42,8 +43,10 @@ public AvaloniaDrawingContext(MotionCanvas motionCanvas : base(motionCanvas, info, surface, canvas) { } - /// - /// Clears the canvas. - /// - public override void ClearCanvas() { } + /// + public override void OnBegingDraw() + { + if (MotionCanvas.StartPoint is null) return; + Canvas.Translate(new SKPoint(MotionCanvas.StartPoint.Value.X, MotionCanvas.StartPoint.Value.Y)); + } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/DoughnutGeometry.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/DoughnutGeometry.cs index b0ed7a61f..384d022df 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/DoughnutGeometry.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/DoughnutGeometry.cs @@ -125,8 +125,8 @@ public override void OnDraw(SkiaSharpDrawingContext context, SKPaint paint) (float)(cx + Math.Cos(startAngle * toRadians) * wedge), (float)(cy + Math.Sin(startAngle * toRadians) * wedge)); path.LineTo( - (float)(cx + Math.Cos(startAngle * toRadians) * (r + pushout)), - (float)(cy + Math.Sin(startAngle * toRadians) * (r + pushout))); + (float)(cx + Math.Cos(startAngle * toRadians) * r), + (float)(cy + Math.Sin(startAngle * toRadians) * r)); path.ArcTo( new SKRect { Left = X, Top = Y, Size = new SKSize { Width = Width, Height = Height } }, startAngle, diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/LabelGeometry.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/LabelGeometry.cs index fc31f9bdd..42b4dd81c 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/LabelGeometry.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/LabelGeometry.cs @@ -77,26 +77,24 @@ public LabelGeometry() /// public override void OnDraw(SkiaSharpDrawingContext context, SKPaint paint) { + var size = OnMeasure(context.PaintTask); + var bg = Background; if (bg != LvcColor.Empty) { - var m = OnMeasure(context.PaintTask); using (var bgPaint = new SKPaint { Color = new SKColor(bg.R, bg.G, bg.B, (byte)(bg.A * Opacity)) }) { var p = Padding; - context.Canvas.DrawRect(X - p.Left, Y - m.Height + p.Top, m.Width, m.Height, bgPaint); + context.Canvas.DrawRect(X - p.Left, Y - size.Height + p.Top, size.Width, size.Height, bgPaint); } } - if (paint.Typeface != null) - { - using (var eventTextShaper = new SKShaper(paint.Typeface)) - { - context.Canvas.DrawShapedText(eventTextShaper, Text ?? "", new SKPoint(X, Y), paint); - } - } - else + + var lines = GetLines(Text); + double linesCount = lines.Length; + var lineNumber = 0; + foreach (var line in lines) { - context.Canvas.DrawText(Text ?? "", new SKPoint(X, Y), paint); + DrawLine(line, -size.Height + (float)(++lineNumber / linesCount * size.Height), context, paint); } } @@ -112,20 +110,18 @@ protected override LvcSize OnMeasure(Paint drawable) TextSize = TextSize }; - var bounds = new SKRect(); + var bounds = MeasureLines(p); - _ = p.MeasureText(Text, ref bounds); - return new LvcSize(bounds.Size.Width + Padding.Left + Padding.Right, bounds.Size.Height + Padding.Top + Padding.Bottom); + return new LvcSize(bounds.Width + Padding.Left + Padding.Right, bounds.Height + Padding.Top + Padding.Bottom); } /// protected override void ApplyCustomGeometryTransform(SkiaSharpDrawingContext context) { - var size = new SKRect(); context.Paint.TextSize = TextSize; - _ = context.Paint.MeasureText(Text, ref size); - const double toRadians = Math.PI / 180d; + var size = MeasureLines(context.Paint); + const double toRadians = Math.PI / 180d; var p = Padding; float w = 0.5f, h = 0.5f; @@ -157,4 +153,37 @@ protected override void ApplyCustomGeometryTransform(SkiaSharpDrawingContext con // and also translate according to the vertical an horizontal alignment properties context.Canvas.Translate((float)xp, (float)yp); } + + private void DrawLine(string content, float yLine, SkiaSharpDrawingContext context, SKPaint paint) + { + if (paint.Typeface is not null) + { + using var eventTextShaper = new SKShaper(paint.Typeface); + context.Canvas.DrawShapedText(content, new SKPoint(X, Y + yLine), paint); + return; + } + + context.Canvas.DrawText(content, new SKPoint(X, Y + yLine), paint); + } + + private LvcSize MeasureLines(SKPaint paint) + { + float w = 0f, h = 0f; + + foreach (var line in GetLines(Text)) + { + var bounds = new SKRect(); + _ = paint.MeasureText(line, ref bounds); + + if (bounds.Width > w) w = bounds.Width; + h += bounds.Height; + } + + return new LvcSize(w, h); + } + + private string[] GetLines(string multiLineText) + { + return multiLineText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); + } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs index 6f0f063cc..fa40fc931 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs @@ -102,19 +102,19 @@ public SkiaSharpDrawingContext( /// public SKPaint Paint { get; set; } - /// - /// Gets or sets the color of the clear. - /// - /// - /// The color of the clear. - /// - public SKColor ClearColor { get; set; } = SKColor.Empty; + /// + public override void OnBegingDraw() + { + Canvas.Clear(); - /// - /// Clears the canvas. - /// - public override void ClearCanvas() + if (MotionCanvas.StartPoint is null) return; + Canvas.Translate(new SKPoint(MotionCanvas.StartPoint.Value.X, MotionCanvas.StartPoint.Value.Y)); + } + + /// + public override void OnEndDraw() { - Canvas.Clear(ClearColor); + if (MotionCanvas.StartPoint is null) return; + Canvas.Restore(); } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/FluentDrawingExtensions.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/FluentDrawingExtensions.cs new file mode 100644 index 000000000..65cb867ac --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/FluentDrawingExtensions.cs @@ -0,0 +1,112 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using LiveChartsCore.Drawing; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.Painting; +using SkiaSharp; + +namespace LiveChartsCore.SkiaSharpView; + +/// +/// Defines the drawing extensions. +/// +public static class DrawingFluentExtensions +{ + /// + /// Initializes a drawing in the given canvas. + /// + /// The canvas. + public static Drawing Draw(this MotionCanvas canvas) + { + return new Drawing(canvas); + } + + /// + /// Defines the Drawing class. + /// + public class Drawing + { + private IPaint? _selectedPaint; + + /// + /// Initializes a new instance of the Drawing class. + /// + /// The canvas. + public Drawing(MotionCanvas canvas) + { + Canvas = canvas; + } + + /// + /// Gets the canvas. + /// + public MotionCanvas Canvas { get; } + + /// + /// Selects the specified paint. + /// + /// The paint. + /// The current drawing instance. + public Drawing SelectPaint(IPaint paint) + { + _selectedPaint = paint; + Canvas.AddDrawableTask(_selectedPaint); + + return this; + } + + /// + /// Selects the specified color. + /// + /// The color to draw with. + /// The stroke width. + /// Indicates whether the geometries are filled with the specified color. + /// The current drawing instance. + public Drawing SelectColor(SKColor color, float? strokeWidth = null, bool? isFill = null) + { + strokeWidth ??= 1; + isFill ??= false; + var paint = new SolidColorPaint(color, strokeWidth.Value) { IsFill = isFill.Value }; + + return SelectPaint(paint); + } + + /// + /// Draws the specified object. + /// + /// The drawable. + /// + public Drawing Draw(IDrawable drawable) + { + if (_selectedPaint is null) + throw new Exception( + "There is no paint selected, please select a paint (By calling a Select method) to add the geometry to."); + + _selectedPaint.AddGeometryToPaintTask(Canvas, drawable); + + return this; + } + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj index 568a5e3a7..103ef9992 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj @@ -17,7 +17,7 @@ LiveChartsCore.SkiaSharpView LiveChartsCore.SkiaSharpView - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for .Net, this package contains the SkiaSharp backend. MIT @@ -37,8 +37,8 @@ - - + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/PieSeries.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/PieSeries.cs index ba25e6bfc..7920275b1 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/PieSeries.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/PieSeries.cs @@ -89,7 +89,7 @@ public PieSeries(bool isGauge = false, bool isGaugeFill = false) : base(isGauge, /// /// The type of the data label of every point. /// -public class PieSeries : PieSeries +public class PieSeries : PieSeries where TVisual : class, IDoughnutVisualChartPoint, new() where TLabel : class, ILabelGeometry, new() { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/IDrawnLegend.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/IDrawnLegend.cs new file mode 100644 index 000000000..b9f59e229 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/IDrawnLegend.cs @@ -0,0 +1,42 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Drawing; +using LiveChartsCore.SkiaSharpView.Drawing; + +namespace LiveChartsCore.SkiaSharpView.SKCharts; + +/// +/// A chart with a drawn legend using SkiaSharp. +/// +public interface IDrawnLegend +{ + /// + /// Gets or sets the font paint. + /// + IPaint? LegendFontPaint { get; set; } + + /// + /// Gets or sets the legend font size. + /// + double LegendFontSize { get; set; } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/IImageControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/IImageControl.cs new file mode 100644 index 000000000..79c81b692 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/IImageControl.cs @@ -0,0 +1,36 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Drawing; + +namespace LiveChartsCore.SkiaSharpView.SKCharts; + +/// +/// An image control +/// +public interface IImageControl +{ + /// + /// Gets the sized. + /// + public LvcSize Size { get; set; } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs index 73750c536..ac15cd503 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs @@ -31,6 +31,7 @@ using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; using LiveChartsCore.SkiaSharpView.Drawing.Geometries; +using LiveChartsCore.SkiaSharpView.Painting; using SkiaSharp; namespace LiveChartsCore.SkiaSharpView.SKCharts; @@ -38,7 +39,7 @@ namespace LiveChartsCore.SkiaSharpView.SKCharts; /// /// In-memory chart that is able to generate a chart images. /// -public class SKCartesianChart : ICartesianChartView, ISkiaSharpChart +public class SKCartesianChart : ICartesianChartView, ISkiaSharpChart, IDrawnLegend { private LvcColor _backColor; @@ -73,6 +74,7 @@ public SKCartesianChart(ICartesianChartView view) : thi Series = view.Series; Sections = view.Sections; DrawMarginFrame = view.DrawMarginFrame; + LegendPosition = view.LegendPosition; } /// @@ -117,6 +119,9 @@ public SKCartesianChart(ICartesianChartView view) : thi /// public IEnumerable> Sections { get; set; } = Array.Empty(); + /// + public IEnumerable> VisualElements { get; set; } = Array.Empty>(); + /// public IEnumerable Series { get; set; } = Array.Empty(); @@ -139,7 +144,13 @@ public SKCartesianChart(ICartesianChartView view) : thi public MotionCanvas CoreCanvas { get; } = new(); /// - public IChartLegend? Legend => null; + public IChartLegend? Legend { get; } = new SKDefaultLegend(); + + /// + public IPaint? LegendFontPaint { get; set; } = new SolidColorPaint { FontFamily = "Arial", Color = new SKColor(40, 40, 40) }; + + /// + public double LegendFontSize { get; set; } = 13; /// public IChartTooltip? Tooltip => null; @@ -157,7 +168,7 @@ LvcColor IChartView.BackColor } } - LvcSize IChartView.ControlSize => new(Width, Height); + LvcSize IChartView.ControlSize => GetControlSize(); /// public Margin? DrawMargin { get; set; } @@ -241,10 +252,7 @@ public SKImage GetImage() CoreCanvas, new SKImageInfo(Height, Width), surface, - canvas) - { - ClearColor = Background - }); + canvas)); Core.Unload(); @@ -275,6 +283,25 @@ private void OnCoreMeasuring(IChartView chart) Measuring?.Invoke(this); } + private LvcSize GetControlSize() + { + if (LegendPosition == LegendPosition.Hidden || Legend is null) return new(Width, Height); + + if (LegendPosition is LegendPosition.Left or LegendPosition.Right) + { + var imageControl = (IImageControl)Legend; + return new(Width - imageControl.Size.Width, Height); + } + + if (LegendPosition is LegendPosition.Top or LegendPosition.Bottom) + { + var imageControl = (IImageControl)Legend; + return new(Width, Height - imageControl.Size.Height); + } + + return new(Width, Height); + } + void IChartView.OnDataPointerDown(IEnumerable points, LvcPoint pointer) { DataPointerDown?.Invoke(this, points); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKDefaultLegend.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKDefaultLegend.cs new file mode 100644 index 000000000..d4b09734a --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKDefaultLegend.cs @@ -0,0 +1,330 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Linq; +using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel.Sketches; +using LiveChartsCore.Measure; +using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.Drawing.Geometries; + +namespace LiveChartsCore.SkiaSharpView.SKCharts; + +/// +public class SKDefaultLegend : IChartLegend, IImageControl +{ + /// + public LvcSize Size { get; set; } + + void IChartLegend.Draw(Chart chart) + { + if (chart.LegendPosition is LegendPosition.Left or LegendPosition.Right) + { + DrawOrMeasureVertical(chart, false); + DrawOrMeasureVertical(chart, true); + } + else + { + DrawOrMeasureHorizontal(chart, false); + DrawOrMeasureHorizontal(chart, true); + } + } + + private void DrawOrMeasureVertical(Chart chart, bool draw) + { + float xi = 0f, yi = 0f; + var p = 8f; + + if (draw) + { + var actualChartSize = chart.View.ControlSize; + + if (chart.LegendPosition == LegendPosition.Left) + { + chart.Canvas.StartPoint = new LvcPoint(Size.Width, 0); + xi = -Size.Width + p; + yi = actualChartSize.Height * 0.5f - Size.Height * 0.5f; + } + if (chart.LegendPosition == LegendPosition.Right) + { + xi = actualChartSize.Width; + yi = actualChartSize.Height * 0.5f - Size.Height * 0.5f; + } + } + else + { + xi = 0f; + yi = 0f; + } + + var drawnLegendChart = (IDrawnLegend)chart.View; + if (drawnLegendChart.LegendFontPaint is null) return; + + var series = chart.ChartSeries.Where(x => x.IsVisibleAtLegend); + var legendOrientation = chart.LegendOrientation; + var legendPosition = chart.LegendPosition; + + var drawing = draw ? chart.Canvas.Draw() : null; + + var miniatureMaxSize = series + .Aggregate(new LvcSize(), (current, s) => + { + var maxScheduleSize = s.CanvasSchedule.PaintSchedules + .Aggregate(new LvcSize(), (current, schedule) => + { + var maxGeometrySize = schedule.Geometries.OfType>() + .Aggregate(new LvcSize(), (current, geometry) => + { + var size = geometry.Measure(schedule.PaintTask); + var t = schedule.PaintTask.StrokeThickness; + + return GetMaxSize(current, new LvcSize(size.Width + t, size.Height + t)); + }); + + return GetMaxSize(current, maxGeometrySize); + }); + + return GetMaxSize(current, maxScheduleSize); + }); + + var labelMaxSize = series + .Aggregate(new LvcSize(), (current, s) => + { + var label = new LabelGeometry + { + HorizontalAlign = Align.Start, + VerticalAlign = Align.Start, + Text = s.Name ?? string.Empty, + TextSize = (float)drawnLegendChart.LegendFontSize + }; + + return GetMaxSize(current, label.Measure(drawnLegendChart.LegendFontPaint)); + }); + + // set a padding + miniatureMaxSize = new LvcSize(miniatureMaxSize.Width, miniatureMaxSize.Height); + labelMaxSize = new LvcSize(labelMaxSize.Width + p, labelMaxSize.Height + p); + + var maxY = miniatureMaxSize.Height > labelMaxSize.Height ? miniatureMaxSize.Height : labelMaxSize.Height; + + var y = yi; + foreach (var s in series) + { + var x = xi; + + foreach (var miniatureSchedule in s.CanvasSchedule.PaintSchedules) + { + if (draw) _ = drawing!.SelectPaint(miniatureSchedule.PaintTask); + + foreach (var geometry in miniatureSchedule.Geometries.Cast>()) + { + var size = geometry.Measure(miniatureSchedule.PaintTask); + var t = miniatureSchedule.PaintTask.StrokeThickness; + + // distance to center (in miniatureMaxSize) in the x and y axis + //var cx = 0;// (miniatureMaxSize.Width - (size.Width + t)) * 0.5f; + var cy = (maxY - (size.Height + t)) * 0.5f; + + geometry.X = x; // + cx; // this is already centered by the LiveCharts API + geometry.Y = y + cy; + + if (draw) _ = drawing!.Draw(geometry); + } + } + + x += miniatureMaxSize.Width + p; + + if (drawnLegendChart.LegendFontPaint is not null) + { + var label = new LabelGeometry + { + X = x, + Y = y, + HorizontalAlign = Align.Start, + VerticalAlign = Align.Start, + Text = s.Name ?? string.Empty, + TextSize = (float)drawnLegendChart.LegendFontSize + }; + var size = label.Measure(drawnLegendChart.LegendFontPaint); + + var cy = (maxY - size.Height) * 0.5f; + label.Y = y + cy; + + if (draw) + _ = drawing! + .SelectPaint(drawnLegendChart.LegendFontPaint) + .Draw(label); + + x += labelMaxSize.Width + p; + } + + y += maxY; + if (!draw) Size = GetMaxSize(Size, new LvcSize(x - xi, y - yi)); + } + } + + private void DrawOrMeasureHorizontal(Chart chart, bool draw) + { + float xi = 0f, yi = 0f; + var p = 8f; + + if (draw) + { + var actualChartSize = chart.View.ControlSize; + + if (chart.LegendPosition == LegendPosition.Top) + { + chart.Canvas.StartPoint = new LvcPoint(0, Size.Height); + xi = actualChartSize.Width * 0.5f - Size.Width * 0.5f; + yi = -Size.Height + p; + } + if (chart.LegendPosition == LegendPosition.Bottom) + { + xi = actualChartSize.Width * 0.5f - Size.Width * 0.5f; + yi = actualChartSize.Height + p; + } + } + else + { + xi = 0f; + yi = 0f; + } + + var drawnLegendChart = (IDrawnLegend)chart.View; + if (drawnLegendChart.LegendFontPaint is null) return; + + var series = chart.ChartSeries.Where(x => x.IsVisibleAtLegend); + var legendOrientation = chart.LegendOrientation; + var legendPosition = chart.LegendPosition; + + var drawing = draw ? chart.Canvas.Draw() : null; + + var miniatureMaxSize = series + .Aggregate(new LvcSize(), (current, s) => + { + var maxScheduleSize = s.CanvasSchedule.PaintSchedules + .Aggregate(new LvcSize(), (current, schedule) => + { + var maxGeometrySize = schedule.Geometries.OfType>() + .Aggregate(new LvcSize(), (current, geometry) => + { + var size = geometry.Measure(schedule.PaintTask); + var t = schedule.PaintTask.StrokeThickness; + + return GetMaxSize(current, new LvcSize(size.Width + t, size.Height + t)); + }); + + return GetMaxSize(current, maxGeometrySize); + }); + + return GetMaxSize(current, maxScheduleSize); + }); + + var labelMaxSize = series + .Aggregate(new LvcSize(), (current, s) => + { + var label = new LabelGeometry + { + HorizontalAlign = Align.Start, + VerticalAlign = Align.Start, + Text = s.Name ?? string.Empty, + TextSize = (float)drawnLegendChart.LegendFontSize + }; + + return GetMaxSize(current, label.Measure(drawnLegendChart.LegendFontPaint)); + }); + + // set a padding + miniatureMaxSize = new LvcSize(miniatureMaxSize.Width, miniatureMaxSize.Height); + labelMaxSize = new LvcSize(labelMaxSize.Width + p, labelMaxSize.Height + p); + + var maxY = miniatureMaxSize.Height > labelMaxSize.Height ? miniatureMaxSize.Height : labelMaxSize.Height; + + var y = yi; + var x = xi; + + foreach (var s in series) + { + foreach (var miniatureSchedule in s.CanvasSchedule.PaintSchedules) + { + if (draw) _ = drawing!.SelectPaint(miniatureSchedule.PaintTask); + + foreach (var geometry in miniatureSchedule.Geometries.Cast>()) + { + var size = geometry.Measure(miniatureSchedule.PaintTask); + var t = miniatureSchedule.PaintTask.StrokeThickness; + + // distance to center (in miniatureMaxSize) in the x and y axis + //var cx = 0;// (miniatureMaxSize.Width - (size.Width + t)) * 0.5f; + var cy = (maxY - (size.Height + t)) * 0.5f; + + geometry.X = x; // + cx; // this is already centered by the LiveCharts API + geometry.Y = y + cy; + + if (draw) _ = drawing!.Draw(geometry); + } + } + + x += miniatureMaxSize.Width + p; + + if (drawnLegendChart.LegendFontPaint is not null) + { + var label = new LabelGeometry + { + X = x, + Y = y, + HorizontalAlign = Align.Start, + VerticalAlign = Align.Start, + Text = s.Name ?? string.Empty, + TextSize = (float)drawnLegendChart.LegendFontSize + }; + var size = label.Measure(drawnLegendChart.LegendFontPaint); + + var cy = (maxY - size.Height) * 0.5f; + label.Y = y + cy; + + if (draw) + _ = drawing! + .SelectPaint(drawnLegendChart.LegendFontPaint) + .Draw(label); + + x += labelMaxSize.Width + p; + } + + x += p; + } + + y += maxY + 2 * p; + if (!draw) Size = GetMaxSize(Size, new LvcSize(x - xi, y - yi)); + } + + private LvcSize GetMaxSize(LvcSize size1, LvcSize size2) + { + var w = size1.Width; + var h = size1.Height; + + if (size2.Width > w) w = size2.Width; + if (size2.Height > h) h = size2.Height; + + return new LvcSize(w, h); + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs index b3b8fedd2..435954895 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs @@ -159,10 +159,7 @@ public SKImage GetImage() Canvas, new SKImageInfo(Height, Width), surface, - canvas) - { - ClearColor = Background - }); + canvas)); _core.Unload(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs index d6e237a2d..bf8f50f98 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs @@ -30,6 +30,7 @@ using LiveChartsCore.Measure; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.Painting; using SkiaSharp; namespace LiveChartsCore.SkiaSharpView.SKCharts; @@ -37,7 +38,7 @@ namespace LiveChartsCore.SkiaSharpView.SKCharts; /// /// In-memory chart that is able to generate a chart images. /// -public class SKPieChart : IPieChartView, ISkiaSharpChart +public class SKPieChart : IPieChartView, ISkiaSharpChart, IDrawnLegend { private LvcColor _backColor; @@ -70,6 +71,7 @@ public SKPieChart(IPieChartView view) : this() InitialRotation = view.InitialRotation; MaxAngle = view.MaxAngle; Total = view.Total; + LegendPosition = view.LegendPosition; } /// @@ -108,6 +110,9 @@ public SKPieChart(IPieChartView view) : this() /// public IEnumerable Series { get; set; } = Array.Empty(); + /// + public IEnumerable> VisualElements { get; set; } = Array.Empty>(); + /// public double InitialRotation { get; set; } @@ -124,7 +129,13 @@ public SKPieChart(IPieChartView view) : this() public MotionCanvas CoreCanvas { get; } = new(); /// - public IChartLegend? Legend => null; + public IChartLegend? Legend { get; } = new SKDefaultLegend(); + + /// + public IPaint? LegendFontPaint { get; set; } = new SolidColorPaint { FontFamily = "Arial", Color = new SKColor(40, 40, 40) }; + + /// + public double LegendFontSize { get; set; } = 13; /// public IChartTooltip? Tooltip => null; @@ -142,7 +153,7 @@ LvcColor IChartView.BackColor } } - LvcSize IChartView.ControlSize => new(Width, Height); + LvcSize IChartView.ControlSize => GetControlSize(); /// public Margin? DrawMargin { get; set; } @@ -219,10 +230,7 @@ public SKImage GetImage() CoreCanvas, new SKImageInfo(Height, Width), surface, - canvas) - { - ClearColor = Background - }); + canvas)); Core.Unload(); return surface.Snapshot(); @@ -252,6 +260,25 @@ private void OnCoreMeasuring(IChartView chart) Measuring?.Invoke(this); } + private LvcSize GetControlSize() + { + if (LegendPosition == LegendPosition.Hidden || Legend is null) return new(Width, Height); + + if (LegendPosition is LegendPosition.Left or LegendPosition.Right) + { + var imageControl = (IImageControl)Legend; + return new(Width - imageControl.Size.Width, Height); + } + + if (LegendPosition is LegendPosition.Top or LegendPosition.Bottom) + { + var imageControl = (IImageControl)Legend; + return new(Width, Height - imageControl.Size.Height); + } + + return new(Width, Height); + } + void IChartView.OnDataPointerDown(IEnumerable points, LvcPoint pointer) { DataPointerDown?.Invoke(this, points); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs index 659b684cb..9282cf627 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs @@ -30,6 +30,7 @@ using LiveChartsCore.Measure; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.Painting; using SkiaSharp; namespace LiveChartsCore.SkiaSharpView.SKCharts; @@ -37,7 +38,7 @@ namespace LiveChartsCore.SkiaSharpView.SKCharts; /// /// In-memory chart that is able to generate a chart images. /// -public class SKPolarChart : IPolarChartView, ISkiaSharpChart +public class SKPolarChart : IPolarChartView, ISkiaSharpChart, IDrawnLegend { private LvcColor _backColor; @@ -73,6 +74,7 @@ public SKPolarChart(IPolarChartView view) : this() TotalAngle = view.TotalAngle; InnerRadius = view.InnerRadius; InitialRotation = view.InitialRotation; + LegendPosition = view.LegendPosition; } /// @@ -117,11 +119,20 @@ public SKPolarChart(IPolarChartView view) : this() /// public IEnumerable Series { get; set; } = Array.Empty(); + /// + public IEnumerable> VisualElements { get; set; } = Array.Empty>(); + /// public MotionCanvas CoreCanvas { get; } = new(); /// - public IChartLegend? Legend => null; + public IChartLegend? Legend { get; } = new SKDefaultLegend(); + + /// + public IPaint? LegendFontPaint { get; set; } = new SolidColorPaint { FontFamily = "Arial", Color = new SKColor(40, 40, 40) }; + + /// + public double LegendFontSize { get; set; } = 13; /// public IChartTooltip? Tooltip => null; @@ -139,7 +150,7 @@ LvcColor IChartView.BackColor } } - LvcSize IChartView.ControlSize => new(Width, Height); + LvcSize IChartView.ControlSize => GetControlSize(); /// public Margin? DrawMargin { get; set; } @@ -237,10 +248,7 @@ public SKImage GetImage() CoreCanvas, new SKImageInfo(Height, Width), surface, - canvas) - { - ClearColor = Background - }); + canvas)); Core.Unload(); return surface.Snapshot(); @@ -270,6 +278,25 @@ private void OnCoreMeasuring(IChartView chart) Measuring?.Invoke(this); } + private LvcSize GetControlSize() + { + if (LegendPosition == LegendPosition.Hidden || Legend is null) return new(Width, Height); + + if (LegendPosition is LegendPosition.Left or LegendPosition.Right) + { + var imageControl = (IImageControl)Legend; + return new(Width - imageControl.Size.Width, Height); + } + + if (LegendPosition is LegendPosition.Top or LegendPosition.Bottom) + { + var imageControl = (IImageControl)Legend; + return new(Width, Height - imageControl.Size.Height); + } + + return new(Width, Height); + } + void IChartView.OnDataPointerDown(IEnumerable points, LvcPoint pointer) { DataPointerDown?.Invoke(this, points); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/BaseGeometryVisual.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/BaseGeometryVisual.cs new file mode 100644 index 000000000..f5af434a8 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/BaseGeometryVisual.cs @@ -0,0 +1,105 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel; +using LiveChartsCore.SkiaSharpView.Drawing; + +namespace LiveChartsCore.SkiaSharpView.VisualElements; + +/// +/// Defines a visual element with stroke and fill properties. +/// +public abstract class BaseGeometryVisual : BaseVisual +{ + private double _x; + private double _y; + private double _width; + private double _height; + private IPaint? _fill; + private IPaint? _stroke; + + /// + /// Gets or sets the X coordinate [in Pixels or ChartValues, see ]. + /// + public double X { get => _x; set { _x = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the Y coordinate [in Pixels or ChartValues, see ]. + /// + public double Y { get => _y; set { _y = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the unit of the and properties. + /// + public MeasureUnit LocationUnit { get; set; } = MeasureUnit.Pixels; + + /// + /// Gets or sets the height of the rectangle [in Pixels or ChartValues, see ]. + /// + public double Width { get => _width; set { _width = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the width of the rectangle [in Pixels or ChartValues, see ]. + /// + public double Height { get => _height; set { _height = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the unit of the and properties. + /// + public MeasureUnit SizeUnit { get; set; } = MeasureUnit.Pixels; + + /// + /// Gets or sets the fill paint. + /// + public IPaint? Fill + { + get => _fill; + set => SetPaintProperty(ref _fill, value); + } + + /// + /// Gets or sets the stroke paint. + /// + public IPaint? Stroke + { + get => _stroke; + set => SetPaintProperty(ref _stroke, value, true); + } + + /// + protected override IPaint?[] GetPaintTasks() + { + return new[] { _fill, _stroke }; + } + + /// + /// Called when [paint changed]. + /// + /// Name of the property. + /// + protected override void OnPaintChanged(string? propertyName) + { + base.OnPaintChanged(propertyName); + OnPropertyChanged(propertyName); + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/BaseVisual.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/BaseVisual.cs new file mode 100644 index 000000000..97cd88443 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/BaseVisual.cs @@ -0,0 +1,134 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel; +using LiveChartsCore.Kernel.Sketches; +using LiveChartsCore.Measure; +using LiveChartsCore.SkiaSharpView.Drawing; + +namespace LiveChartsCore.SkiaSharpView.VisualElements; + +/// +/// Defines the base visual element class, inheriting from this class makes it easy to implement a visual element. +/// +public abstract class BaseVisual : ChartElement, INotifyPropertyChanged +{ + private int _scalesXAt; + private int _scalesYAt; + + /// + /// Called when a property changes. + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + /// Gets or sets the axis index where the series is scaled in the X plane, the index must exist + /// in the collection. + /// + /// + /// The index of the axis. + /// + public int ScalesXAt { get => _scalesXAt; set { _scalesXAt = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the axis index where the series is scaled in the Y plane, the index must exist + /// in the collection. + /// + /// + /// The index of the axis. + /// + public int ScalesYAt { get => _scalesYAt; set { _scalesYAt = value; OnPropertyChanged(); } } + + /// + public override void Measure(Chart chart) + { + Scaler? primary = null; + Scaler? secondary = null; + + CartesianChart? cartesianChart = null; + + if (chart is CartesianChart cc) + { + cartesianChart = cc; + var primaryAxis = cartesianChart.YAxes[ScalesYAt]; + var secondaryAxis = cartesianChart.XAxes[ScalesXAt]; + secondary = secondaryAxis.GetNextScaler(cartesianChart); + primary = primaryAxis.GetNextScaler(cartesianChart); + } + + foreach (var paintTask in GetPaintTasks()) + { + if (paintTask is null) continue; + + if (cartesianChart is not null) + { + paintTask.SetClipRectangle( + cartesianChart.Canvas, + new LvcRectangle(cartesianChart.DrawMarginLocation, cartesianChart.DrawMarginSize)); + } + + chart.Canvas.AddDrawableTask(paintTask); + } + + if (primary is null || secondary is null) throw new Exception($"This chart does not support VisualElements"); + + Draw(chart, primary, secondary); + } + + /// + /// Called when [paint changed]. + /// + /// Name of the property. + /// + protected override void OnPaintChanged(string? propertyName) + { + base.OnPaintChanged(propertyName); + OnPropertyChanged(propertyName); + } + + /// + /// Called when a property changes. + /// + /// Name of the property. + /// + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Called when the visual is drawn. + /// + /// The chart. + /// + /// The primary axis scaler, normally the Y axis. If the chart is Polar then it is the Angle scaler. If the chart is a pie chart + /// then it is the Values Scaler. + /// + /// + /// The secondary axis scaler, normally the X axis. If the chart is Polar then it is the Radius scaler. If the chart is a pie chart + /// then it is the index Scaler. + protected abstract void Draw(Chart chart, Scaler primaryAxisScale, Scaler secondaryAxisScale); +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/GeometryVisual.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/GeometryVisual.cs new file mode 100644 index 000000000..c7d241b34 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/GeometryVisual.cs @@ -0,0 +1,77 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel; +using LiveChartsCore.Measure; +using LiveChartsCore.SkiaSharpView.Drawing; + +namespace LiveChartsCore.SkiaSharpView.VisualElements; + +/// +/// Defines a visual element in a chart that draws a rectangle geometry in the user interface. +/// +public class GeometryVisual : BaseGeometryVisual + where TGeometry : ISizedGeometry, new() +{ + private TGeometry? _rectangleGeometry; + + /// + protected override void Draw(Chart chart, Scaler primaryAxisScale, Scaler secondaryAxisScale) + { + var x = (float)X; + var y = (float)Y; + var w = (float)Width; + var h = (float)Height; + + if (SizeUnit == MeasureUnit.ChartValues) + { + w = secondaryAxisScale.MeasureInPixels(w); + h = primaryAxisScale.MeasureInPixels(h); + } + + if (LocationUnit == MeasureUnit.ChartValues) + { + x = secondaryAxisScale.ToPixels(x); + y = primaryAxisScale.ToPixels(y); + } + + if (_rectangleGeometry is null) + { + _rectangleGeometry = new TGeometry { X = x, Y = y, Width = w, Height = h }; + + _ = _rectangleGeometry + .TransitionateProperties() + .WithAnimation(chart) + .CompleteCurrentTransitions(); + } + + _rectangleGeometry.X = x; + _rectangleGeometry.Y = y; + _rectangleGeometry.Width = w; + _rectangleGeometry.Height = h; + + var drawing = chart.Canvas.Draw(); + if (Fill is not null) _ = drawing.SelectPaint(Fill).Draw(_rectangleGeometry); + if (Stroke is not null) _ = drawing.SelectPaint(Stroke).Draw(_rectangleGeometry); + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/LabelVisual.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/LabelVisual.cs new file mode 100644 index 000000000..aa7ef81b3 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/LabelVisual.cs @@ -0,0 +1,166 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel; +using LiveChartsCore.Measure; +using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.Drawing.Geometries; + +namespace LiveChartsCore.SkiaSharpView.VisualElements; + +/// +/// Defines a visual element with stroke and fill properties. +/// +public class LabelVisual : BaseVisual +{ + private LabelGeometry? _labelGeometry; + private IPaint? _paint; + private double _x; + private double _y; + private string _text = string.Empty; + private double _textSize = 12; + private Align _verticalAlignment = Align.Middle; + private Align _horizontalAlignment = Align.Middle; + private LvcColor _backgroundColor; + private Padding _padding = new(0); + private double _rotation; + private LvcPoint _translate = new(); + + /// + /// Gets or sets the fill paint. + /// + public IPaint? Paint + { + get => _paint; + set => SetPaintProperty(ref _paint, value); + } + + /// + /// Gets or sets the X coordinate [in Pixels or ChartValues, see ]. + /// + public double X { get => _x; set { _x = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the Y coordinate [in Pixels or ChartValues, see ]. + /// + public double Y { get => _y; set { _y = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the label text. + /// + public string Text { get => _text; set { _text = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the font size. + /// + public double TextSize { get => _textSize; set { _textSize = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the rotation. + /// + public double Rotation { get => _rotation; set { _rotation = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the translate transform. + /// + public LvcPoint Translate { get => _translate; set { _translate = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the vertical alignment. + /// + public Align VerticalAlignment { get => _verticalAlignment; set { _verticalAlignment = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the horizontal alignment. + /// + public Align HorizontalAlignment { get => _horizontalAlignment; set { _horizontalAlignment = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the background color. + /// + public LvcColor BackgroundColor { get => _backgroundColor; set { _backgroundColor = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the padding. + /// + public Padding Padding { get => _padding; set { _padding = value; OnPropertyChanged(); } } + + /// + /// Gets or sets the unit of the and properties. + /// + public MeasureUnit LocationUnit { get; set; } = MeasureUnit.Pixels; + + /// + protected override IPaint?[] GetPaintTasks() + { + return new[] { _paint }; + } + + /// + protected override void Draw(Chart chart, Scaler primaryAxisScale, Scaler secondaryAxisScale) + { + var x = (float)X; + var y = (float)Y; + + if (LocationUnit == MeasureUnit.ChartValues) + { + x = secondaryAxisScale.ToPixels(x); + y = primaryAxisScale.ToPixels(y); + } + + if (_labelGeometry is null) + { + _labelGeometry = new LabelGeometry + { + TextSize = (float)TextSize, + X = x, + Y = y, + RotateTransform = (float)Rotation, + TranslateTransform = Translate, + VerticalAlign = VerticalAlignment, + HorizontalAlign = HorizontalAlignment, + Background = BackgroundColor, + Padding = Padding + }; + + _ = _labelGeometry + .TransitionateProperties() + .WithAnimation(chart) + .CompleteCurrentTransitions(); + } + + _labelGeometry.Text = Text; + _labelGeometry.TextSize = (float)TextSize; + _labelGeometry.X = x; + _labelGeometry.Y = y; + _labelGeometry.RotateTransform = (float)Rotation; + _labelGeometry.TranslateTransform = Translate; + _labelGeometry.VerticalAlign = VerticalAlignment; + _labelGeometry.HorizontalAlign = HorizontalAlignment; + _labelGeometry.Background = BackgroundColor; + _labelGeometry.Padding = Padding; + + var drawing = chart.Canvas.Draw(); + if (Paint is not null) _ = drawing.SelectPaint(Paint).Draw(_labelGeometry); + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/MeasureUnit.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/MeasureUnit.cs new file mode 100644 index 000000000..8ca3e7704 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/VisualElements/MeasureUnit.cs @@ -0,0 +1,39 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.SkiaSharpView.VisualElements; + +/// +/// Defines measurement units. +/// +public enum MeasureUnit +{ + /// + /// Indicates that the unit is in pixels. + /// + Pixels, + + /// + /// Indicates that the unit is in CahrtValues. + /// + ChartValues +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/CartesianChart.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/CartesianChart.razor.cs index a4cb3f599..c895d7e89 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/CartesianChart.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/CartesianChart.razor.cs @@ -42,10 +42,12 @@ public partial class CartesianChart : Chart, ICartesianChartView? _xObserver; private CollectionDeepObserver? _yObserver; private CollectionDeepObserver>? _sectionsObserver; + private CollectionDeepObserver>? _visualsObserver; private IEnumerable _series = new ObservableCollection(); private IEnumerable? _xAxes; private IEnumerable? _yAxes; private IEnumerable> _sections = new List>(); + private IEnumerable> _visuals = new List>(); private DrawMarginFrame? _drawMarginFrame; private TooltipFindingStrategy _tooltipFindingStrategy = LiveCharts.CurrentSettings.DefaultTooltipFindingStrategy; @@ -59,6 +61,8 @@ protected override void OnInitialized() _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); if (_xAxes is null) XAxes = new List() @@ -139,6 +143,20 @@ public IEnumerable> Sections } } + /// + [Parameter] + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// [Parameter] public DrawMarginFrame? DrawMarginFrame @@ -225,10 +243,12 @@ protected override void OnDisposing() XAxes = Array.Empty(); YAxes = Array.Empty(); Sections = Array.Empty(); + VisualElements = Array.Empty>(); _seriesObserver = null!; _xObserver = null!; _yObserver = null!; _sectionsObserver = null!; + _visuals = null!; } private void OnDeepCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj index d905b09cc..2c4d63270 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj @@ -17,7 +17,7 @@ net6.0 enable enable - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for Blazor. MIT @@ -44,7 +44,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PieChart.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PieChart.razor.cs index 356e8fedf..7f0bed070 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PieChart.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PieChart.razor.cs @@ -33,7 +33,9 @@ namespace LiveChartsCore.SkiaSharpView.Blazor; public partial class PieChart : Chart, IPieChartView { private CollectionDeepObserver? _seriesObserver; + private CollectionDeepObserver>? _visualsObserver; private IEnumerable _series = new List(); + private IEnumerable> _visuals = new List>(); private double _initialRotation; private double _maxAngle = 360; private double? _total; @@ -57,6 +59,18 @@ protected override void OnInitialized() OnPropertyChanged(); }, true); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (sender is IStopNPC stop && !stop.IsNotifyingChanges) return; + OnPropertyChanged(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (sender is IStopNPC stop && !stop.IsNotifyingChanges) return; + OnPropertyChanged(); + }, + true); } PieChart IPieChartView.Core => @@ -76,6 +90,20 @@ public IEnumerable Series } } + /// + [Parameter] + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// [Parameter] public double InitialRotation { get => _initialRotation; set { _initialRotation = value; OnPropertyChanged(); } } @@ -108,5 +136,7 @@ protected override void OnDisposing() Series = Array.Empty(); _seriesObserver = null!; + VisualElements = Array.Empty>(); + _visualsObserver = null!; } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PolarChart.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PolarChart.razor.cs index f70171570..7364031d6 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PolarChart.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/PolarChart.razor.cs @@ -40,9 +40,11 @@ public partial class PolarChart : Chart, IPolarChartView? _seriesObserver; private CollectionDeepObserver? _angleObserver; private CollectionDeepObserver? _radiusObserver; + private CollectionDeepObserver>? _visualsObserver; private IEnumerable _series = new List(); private IEnumerable? _angleAxes; private IEnumerable? _radiusAxes; + private IEnumerable> _visuals = new List>(); /// /// Called when the control is initalized. @@ -54,6 +56,8 @@ protected override void OnInitialized() _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); if (_angleAxes is null) AngleAxes = new List() @@ -167,8 +171,22 @@ public IEnumerable RadiusAxes } } + /// + [Parameter] + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// - /// Called then the core is intialized. + /// Called then the core is initialized. /// /// protected override void InitializeCore() @@ -198,9 +216,11 @@ protected override void OnDisposing() Series = Array.Empty(); AngleAxes = Array.Empty(); RadiusAxes = Array.Empty(); + VisualElements = Array.Empty>(); _seriesObserver = null!; _angleObserver = null!; _radiusObserver = null!; + _visuals = null!; } private void OnDeepCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/CartesianChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/CartesianChart.cs index ac7243097..5807b20f5 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/CartesianChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/CartesianChart.cs @@ -43,10 +43,12 @@ public class CartesianChart : Chart, ICartesianChartView _xObserver; private CollectionDeepObserver _yObserver; private CollectionDeepObserver> _sectionsObserver; + private CollectionDeepObserver> _visualsObserver; private IEnumerable _series = new List(); private IEnumerable _xAxes = new List { new() }; private IEnumerable _yAxes = new List { new() }; private IEnumerable> _sections = new List>(); + private IEnumerable> _visuals = new List>(); private DrawMarginFrame? _drawMarginFrame; private TooltipFindingStrategy _tooltipFindingStrategy = LiveCharts.CurrentSettings.DefaultTooltipFindingStrategy; @@ -68,6 +70,8 @@ public CartesianChart(IChartTooltip? tooltip = null, IC _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); XAxes = new List() { @@ -141,6 +145,19 @@ public IEnumerable> Sections } } + /// + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// public DrawMarginFrame? DrawMarginFrame { @@ -177,7 +194,7 @@ protected override void InitializeCore() motionCanvas.CanvasCore.AddDrawableTask(zoomingSectionPaint); core = new CartesianChart( - this, LiveChartsSkiaSharp.DefaultPlatformBuilder, motionCanvas.CanvasCore, zoomingSection, true); + this, LiveChartsSkiaSharp.DefaultPlatformBuilder, motionCanvas.CanvasCore, zoomingSection); core.Update(); } @@ -188,10 +205,12 @@ protected override void OnUnloading() XAxes = Array.Empty(); YAxes = Array.Empty(); Sections = Array.Empty(); + VisualElements = Array.Empty>(); _seriesObserver = null!; _xObserver = null!; _yObserver = null!; _sectionsObserver = null!; + _visualsObserver = null!; } /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj index 6ae6fa6e0..85ca7aa66 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj @@ -1,4 +1,4 @@ - + Library @@ -7,7 +7,7 @@ netstandard2.0 LiveChartsCore.SkiaSharpView.Eto LiveChartsCore.SkiaSharpView.Eto - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for Eto.Forms. MIT diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PieChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PieChart.cs index de3e03f49..1657b0bca 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PieChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PieChart.cs @@ -37,6 +37,8 @@ public class PieChart : Chart, IPieChartView { private CollectionDeepObserver _seriesObserver; private IEnumerable _series = new List(); + private CollectionDeepObserver> _visualsObserver; + private IEnumerable> _visuals = new List>(); private double _initialRotation; private double _maxAngle = 360; private double? _total; @@ -66,6 +68,18 @@ public PieChart(IChartTooltip? tooltip = null, IChartLe OnPropertyChanged(); }, true); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (sender is IStopNPC stop && !stop.IsNotifyingChanges) return; + OnPropertyChanged(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (sender is IStopNPC stop && !stop.IsNotifyingChanges) return; + OnPropertyChanged(); + }, + true); motionCanvas.MouseDown += OnMouseDown; } @@ -86,6 +100,20 @@ public IEnumerable Series } } + /// + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + + /// public double InitialRotation { get => _initialRotation; set { _initialRotation = value; OnPropertyChanged(); } } @@ -110,6 +138,8 @@ protected override void OnUnloading() { Series = Array.Empty(); _seriesObserver = null!; + VisualElements = Array.Empty>(); + _visualsObserver = null!; } private void OnMouseDown(object? sender, MouseEventArgs e) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PolarChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PolarChart.cs index ff9e73293..22cd5b60e 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PolarChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/PolarChart.cs @@ -43,9 +43,11 @@ public class PolarChart : Chart, IPolarChartView private CollectionDeepObserver _seriesObserver; private CollectionDeepObserver _angleObserver; private CollectionDeepObserver _radiusObserver; + private CollectionDeepObserver> _visualsObserver; private IEnumerable _series = new List(); private IEnumerable _angleAxes = new List(); private IEnumerable _radiusAxes = new List(); + private IEnumerable> _visuals = new List>(); /// /// Initializes a new instance of the class. @@ -63,6 +65,8 @@ public PolarChart(IChartTooltip? tooltip = null, IChart _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); AngleAxes = new List() { @@ -141,6 +145,19 @@ public IEnumerable Series } } + /// + public IEnumerable> VisualElements + { + get => _visuals; + set + { + _visualsObserver?.Dispose(_visuals); + _visualsObserver?.Initialize(value); + _visuals = value; + OnPropertyChanged(); + } + } + /// public IEnumerable AngleAxes { @@ -183,9 +200,11 @@ protected override void OnUnloading() Series = Array.Empty(); AngleAxes = Array.Empty(); RadiusAxes = Array.Empty(); + VisualElements = Array.Empty>(); _seriesObserver = null!; _angleObserver = null!; _radiusObserver = null!; + _visualsObserver = null!; } /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.xaml.cs index f461b1a12..1934230be 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.xaml.cs @@ -57,10 +57,11 @@ public partial class CartesianChart : ContentView, ICartesianChartView protected Chart? core; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _xObserver; - private CollectionDeepObserver _yObserver; - private CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _xObserver; + private readonly CollectionDeepObserver _yObserver; + private readonly CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver> _visualsObserver; private double _lastScale = 0; private DateTime _panLocketUntil; private double _lastPanX = 0; @@ -92,6 +93,8 @@ public CartesianChart() _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); XAxes = new List() { @@ -102,6 +105,7 @@ public CartesianChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultCartesianAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); canvas.SkCanvasView.EnableTouchEvents = true; canvas.SkCanvasView.Touch += OnSkCanvasTouched; @@ -192,6 +196,22 @@ public CartesianChart() chart.core.Update(); }); + /// + /// The visual elements property. + /// + public static readonly BindableProperty VisualElementsProperty = + BindableProperty.Create( + nameof(VisualElements), typeof(IEnumerable>), typeof(CartesianChart), new List>(), + BindingMode.Default, null, (BindableObject o, object oldValue, object newValue) => + { + var chart = (CartesianChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)oldValue); + observer?.Initialize((IEnumerable>)newValue); + if (chart.core is null) return; + chart.core.Update(); + }); + /// /// The draw margin frame property. /// @@ -477,6 +497,13 @@ public IEnumerable> Sections set => SetValue(SectionsProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public DrawMarginFrame? DrawMarginFrame { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj index cbc3c64aa..e1544635b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj @@ -1,4 +1,4 @@ - + net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst @@ -14,7 +14,7 @@ 10.0.17763.0 10.0.17763.0 - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for Maui. MIT @@ -27,7 +27,7 @@ - + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.nuspec b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.nuspec index bd3d42cdd..4a6542e2b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.nuspec +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.nuspec @@ -2,7 +2,7 @@ LiveChartsCore.SkiaSharpView.Maui - 2.0.0-beta.361 + 2.0.0-beta.400 LiveChartsCore.SkiaSharpView.Maui BetoRodriguez true @@ -16,24 +16,24 @@ - - + + - - + + - - + + - - + + - - + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PieChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PieChart.xaml.cs index d7070e17c..a96cd93b7 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PieChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PieChart.xaml.cs @@ -52,7 +52,8 @@ public partial class PieChart : ContentView, IPieChartView protected Chart? core; - private CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver> _visualsObserver; #endregion @@ -86,8 +87,20 @@ public PieChart() if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; core.Update(); }); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + core.Update(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (core is null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + core.Update(); + }); Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); canvas.SkCanvasView.EnableTouchEvents = true; canvas.SkCanvasView.Touch += OnSkCanvasTouched; @@ -130,6 +143,22 @@ public PieChart() chart.core.Update(); }); + /// + /// The visual elements property. + /// + public static readonly BindableProperty VisualElementsProperty = + BindableProperty.Create( + nameof(VisualElements), typeof(IEnumerable>), typeof(PieChart), new List>(), + BindingMode.Default, null, (BindableObject o, object oldValue, object newValue) => + { + var chart = (PieChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)oldValue); + observer?.Initialize((IEnumerable>)newValue); + if (chart.core is null) return; + chart.core.Update(); + }); + /// /// The initial rotation property /// @@ -382,6 +411,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public double InitialRotation { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PolarChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PolarChart.xaml.cs index 87cdfa3a1..59e880ae8 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PolarChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PolarChart.xaml.cs @@ -53,9 +53,10 @@ public partial class PolarChart : ContentView, IPolarChartView protected Chart? core; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _angleObserver; - private CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _angleObserver; + private readonly CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver> _visualsObserver; #endregion @@ -81,6 +82,8 @@ public PolarChart() _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); AngleAxes = new List() { @@ -91,6 +94,7 @@ public PolarChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultPolarAxis() }; Series = new ObservableCollection(); + VisualElements = new ObservableCollection>(); canvas.SkCanvasView.EnableTouchEvents = true; canvas.SkCanvasView.Touch += OnSkCanvasTouched; @@ -161,6 +165,22 @@ public PolarChart() chart.core.Update(); }); + /// + /// The visual elements property. + /// + public static readonly BindableProperty VisualElementsProperty = + BindableProperty.Create( + nameof(VisualElements), typeof(IEnumerable>), typeof(PolarChart), new List>(), + BindingMode.Default, null, (BindableObject o, object oldValue, object newValue) => + { + var chart = (PolarChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)oldValue); + observer?.Initialize((IEnumerable>)newValue); + if (chart.core is null) return; + chart.core.Update(); + }); + /// /// The x axes property. /// @@ -468,6 +488,13 @@ public IEnumerable RadiusAxes set => SetValue(RadiusAxesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public TimeSpan AnimationsSpeed { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/CartesianChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/CartesianChart.xaml.cs index 422804291..317342550 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/CartesianChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/CartesianChart.xaml.cs @@ -55,14 +55,12 @@ public sealed partial class CartesianChart : UserControl, ICartesianChartView? _core; private MotionCanvas? _motionCanvas; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _xObserver; - private CollectionDeepObserver _yObserver; - private CollectionDeepObserver> _sectionsObserver; - //private double _lastScale = 0; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _xObserver; + private readonly CollectionDeepObserver _yObserver; + private readonly CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver> _visualsObserver; private DateTime _panLocketUntil; - //private double _lastPanX = 0; - //private double _lastPanY = 0; #endregion @@ -86,6 +84,8 @@ public CartesianChart() _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); Loaded += OnLoaded; Unloaded += OnUnloaded; @@ -100,6 +100,7 @@ public CartesianChart() }); SetValue(SeriesProperty, new ObservableCollection()); SetValue(SectionsProperty, new ObservableCollection>()); + SetValue(VisualElementsProperty, new ObservableCollection>()); } #region dependency properties @@ -168,6 +169,22 @@ public CartesianChart() chart._core.Update(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(CartesianChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (CartesianChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart._core == null) return; + chart._core.Update(); + })); + /// /// The sync context property /// @@ -499,6 +516,13 @@ public IEnumerable> Sections set => SetValue(SectionsProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public DrawMarginFrame? DrawMarginFrame { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index 1d482a424..e867b2e0a 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -6,7 +6,7 @@ enable 10.0 - 2.0.0-beta.361 + 2.0.0-beta.400 icon.png Simple, flexible, interactive and powerful data visualization for Uno.WinUI. MIT @@ -21,8 +21,8 @@ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - - + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PieChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PieChart.xaml.cs index 906f470f1..5108a2a08 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PieChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PieChart.xaml.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Windows.Input; @@ -49,6 +50,7 @@ public sealed partial class PieChart : UserControl, IPieChartView? _core; private MotionCanvas? _canvas; private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver> _visualsObserver; /// /// Initializes a new instance of the class. @@ -76,9 +78,23 @@ public PieChart() if (_core == null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; _core.Update(); }); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (_core == null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + _core.Update(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (_core == null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + _core.Update(); + }); Loaded += OnLoaded; Unloaded += OnUnloaded; + + SetValue(SeriesProperty, new ObservableCollection()); + SetValue(VisualElementsProperty, new ObservableCollection>()); } #region dependency properties @@ -99,6 +115,22 @@ public PieChart() chart._core.Update(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(PieChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (PieChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart._core == null) return; + chart._core.Update(); + })); + /// /// The sync context property /// @@ -379,6 +411,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public double InitialRotation { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PolarChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PolarChart.xaml.cs index 765516c33..9cc63220d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PolarChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/PolarChart.xaml.cs @@ -55,6 +55,7 @@ public sealed partial class PolarChart : UserControl, IPolarChartView _seriesObserver; private readonly CollectionDeepObserver _angleObserver; private readonly CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver> _visualsObserver; #endregion @@ -76,6 +77,8 @@ public PolarChart() _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); Loaded += OnLoaded; Unloaded += OnUnloaded; @@ -89,6 +92,7 @@ public PolarChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultPolarAxis() }); SetValue(SeriesProperty, new ObservableCollection()); + SetValue(VisualElementsProperty, new ObservableCollection>()); } #region dependency properties @@ -138,6 +142,22 @@ public PolarChart() chart._core.Update(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(PolarChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (PolarChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart._core == null) return; + chart._core.Update(); + })); + /// /// The x axes property. /// @@ -492,6 +512,13 @@ public IEnumerable RadiusAxes set => SetValue(RadiusAxesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public TimeSpan AnimationsSpeed { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/CartesianChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/CartesianChart.xaml.cs index 257d82fbf..2566967e3 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/CartesianChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/CartesianChart.xaml.cs @@ -54,14 +54,12 @@ public sealed partial class CartesianChart : UserControl, ICartesianChartView? _core; private MotionCanvas? _motionCanvas; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _xObserver; - private CollectionDeepObserver _yObserver; - private CollectionDeepObserver> _sectionsObserver; - //private double _lastScale = 0; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _xObserver; + private readonly CollectionDeepObserver _yObserver; + private readonly CollectionDeepObserver> _sectionsObserver; + private readonly CollectionDeepObserver> _visualsObserver; private DateTime _panLocketUntil; - //private double _lastPanX = 0; - //private double _lastPanY = 0; #endregion @@ -85,6 +83,8 @@ public CartesianChart() _yObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _sectionsObserver = new CollectionDeepObserver>( OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); Loaded += OnLoaded; Unloaded += OnUnloaded; @@ -99,6 +99,7 @@ public CartesianChart() }); SetValue(SeriesProperty, new ObservableCollection()); SetValue(SectionsProperty, new ObservableCollection>()); + SetValue(VisualElementsProperty, new ObservableCollection>()); } #region dependency properties @@ -167,6 +168,22 @@ public CartesianChart() chart._core.Update(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(CartesianChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (CartesianChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart._core == null) return; + chart._core.Update(); + })); + /// /// The sync context property /// @@ -498,6 +515,13 @@ public IEnumerable> Sections set => SetValue(SectionsProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public DrawMarginFrame? DrawMarginFrame { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/LiveChartsCore.SkiaSharpView.Uno.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/LiveChartsCore.SkiaSharpView.Uno.csproj index 2f030d9e1..c8b48134d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/LiveChartsCore.SkiaSharpView.Uno.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno/LiveChartsCore.SkiaSharpView.Uno.csproj @@ -1,4 +1,4 @@ - + - - - + + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PieChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PieChart.xaml.cs index 9c18b466c..d9b0a5314 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PieChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PieChart.xaml.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Windows.Input; @@ -45,7 +46,8 @@ public sealed partial class PieChart : UserControl, IPieChartView? _core; private MotionCanvas? _canvas; - private CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver> _visualsObserver; /// /// Initializes a new instance of the class. @@ -73,9 +75,23 @@ public PieChart() if (_core == null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; _core.Update(); }); + _visualsObserver = new CollectionDeepObserver>( + (object? sender, NotifyCollectionChangedEventArgs e) => + { + if (_core == null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + _core.Update(); + }, + (object? sender, PropertyChangedEventArgs e) => + { + if (_core == null || (sender is IStopNPC stop && !stop.IsNotifyingChanges)) return; + _core.Update(); + }); Loaded += OnLoaded; Unloaded += OnUnloaded; + + SetValue(SeriesProperty, new ObservableCollection()); + SetValue(VisualElementsProperty, new ObservableCollection>()); } #region dependency properties @@ -96,6 +112,22 @@ public PieChart() chart._core.Update(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(PieChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (PieChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart._core == null) return; + chart._core.Update(); + })); + /// /// The sync context property /// @@ -375,6 +407,13 @@ public IEnumerable Series set => SetValue(SeriesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public double InitialRotation { @@ -785,6 +824,7 @@ private void OnLoaded(object sender, RoutedEventArgs e) PointerMoved += OnPointerMoved; PointerExited += OnPointerExited; PointerPressed += OnPointerPressed; + PointerReleased += OnPointerReleased; } _core.Load(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PolarChart.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PolarChart.xaml.cs index e9f49136b..bce6e1299 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PolarChart.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/PolarChart.xaml.cs @@ -48,9 +48,10 @@ public sealed partial class PolarChart : UserControl, IPolarChartView? _core; private MotionCanvas? _canvas; - private CollectionDeepObserver _seriesObserver; - private CollectionDeepObserver _angleObserver; - private CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver _seriesObserver; + private readonly CollectionDeepObserver _angleObserver; + private readonly CollectionDeepObserver _radiusObserver; + private readonly CollectionDeepObserver> _visualsObserver; #endregion @@ -72,6 +73,8 @@ public PolarChart() _seriesObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _angleObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); _radiusObserver = new CollectionDeepObserver(OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); + _visualsObserver = new CollectionDeepObserver>( + OnDeepCollectionChanged, OnDeepCollectionPropertyChanged, true); Loaded += OnLoaded; Unloaded += OnUnloaded; @@ -85,6 +88,7 @@ public PolarChart() LiveCharts.CurrentSettings.GetProvider().GetDefaultPolarAxis() }); SetValue(SeriesProperty, new ObservableCollection()); + SetValue(VisualElementsProperty, new ObservableCollection>()); } #region dependency properties @@ -137,6 +141,22 @@ public PolarChart() chart._core.Update(); })); + /// + /// The visual elements property + /// + public static readonly DependencyProperty VisualElementsProperty = + DependencyProperty.Register( + nameof(VisualElements), typeof(IEnumerable>), typeof(PolarChart), new PropertyMetadata(null, + (DependencyObject o, DependencyPropertyChangedEventArgs args) => + { + var chart = (PolarChart)o; + var observer = chart._visualsObserver; + observer?.Dispose((IEnumerable>)args.OldValue); + observer?.Initialize((IEnumerable>)args.NewValue); + if (chart._core == null) return; + chart._core.Update(); + })); + /// /// The x axes property. /// @@ -482,6 +502,13 @@ public IEnumerable RadiusAxes set => SetValue(RadiusAxesProperty, value); } + /// + public IEnumerable> VisualElements + { + get => (IEnumerable>)GetValue(VisualElementsProperty); + set => SetValue(VisualElementsProperty, value); + } + /// public TimeSpan AnimationsSpeed {