Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WPF PlotView based on SkiaSharp (#1515) #1533

Merged
merged 1 commit into from
May 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file.
- Renderer based on SkiaSharp, including exporters for PNG, JPEG, PDF and SVG (#1509)
- Example for Issue #1524: HitTracker IndexOutOfRangeException with HeatMapSeries
- Text shaping support to SkiaRenderContext (#1520)
- PlotView based on SkiaRenderContext (OxyPlot.SkiaSharp.Wpf) (#1515)

### Changed
- Legends model (#644)
Expand All @@ -29,6 +30,8 @@ All notable changes to this project will be documented in this file.
- Upgrade to .NET Core 3.1 (#1488)
- DrawRectangle(...), DrawLine(...), DrawEllipse(...), DrawPolygon(...) and related overloads in IRenderContext and related extensions in RenderingExtensions now require an EdgeRenderingMode
- Default color palette for LinearColorAxis from Jet to Viridis (#1505)
- Extract most of the functionality from OxyPlot.Wpf into OxyPlot.Wpf.Shared to allow code sharing with other WPF PlotViews (#1515)
- WPF ExampleBrowser can switch between Canvas and SkiaSharp renderers (#1515)

### Removed
- Remove PlotModel.Legends (#644)
Expand Down
3 changes: 3 additions & 0 deletions Source/Examples/WPF/ExampleBrowser/ExampleBrowser.WPF.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
<OutputType>WinExe</OutputType>
<RuntimeIdentifiers>win</RuntimeIdentifiers>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\OxyPlot.SkiaSharp.Wpf\OxyPlot.SkiaSharp.Wpf.csproj" />
<ProjectReference Include="..\..\..\OxyPlot.Wpf\OxyPlot.Wpf.csproj" />
<ProjectReference Include="..\..\..\OxyPlot\OxyPlot.csproj" />
<ProjectReference Include="..\..\ExampleLibrary\ExampleLibrary.csproj" />
Expand Down
74 changes: 31 additions & 43 deletions Source/Examples/WPF/ExampleBrowser/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<Window x:Class="ExampleBrowser.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:oxyWpf="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:oxySkia="clr-namespace:OxyPlot.SkiaSharp.Wpf;assembly=OxyPlot.SkiaSharp.Wpf"
xmlns:local="clr-namespace:ExampleBrowser"
Title="OxyPlot.WPF Example Browser"
Height="720" Width="1280" Icon="OxyPlot_64.png">
<Window.Resources>
Expand Down Expand Up @@ -66,26 +68,9 @@
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid Background="#20000000">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding ExamplesView}" ItemContainerStyle="{DynamicResource ListboxItemStyle}"
SelectedItem="{Binding SelectedExample}" BorderThickness="0,0,0,1">
<ListBox.GroupStyle>
<!-- GroupStyle with headers -->
<!--<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Text="{Binding Name}" Padding="2 0 8 0"/>
<TextBlock Text="{Binding ItemCount, StringFormat='({0})'}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>-->

<!-- GroupStyle with expanders -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
Expand All @@ -109,32 +94,35 @@
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="8">
<CheckBox IsChecked="{Binding MeasureFrameRate}" Content="Measure frame rate"/>
<TextBlock Margin="20 0 0 0" Text="{Binding FrameRate, StringFormat='{}{0:0.0} fps'}"/>
</Grid>
<Grid Grid.Column="1">
<TabControl TabStripPlacement="Bottom">
<TabControl.Resources>
<Style TargetType="{x:Type TextBox}" x:Key="CodeTextBox">
<Setter Property="AcceptsReturn" Value="True"></Setter>
<Setter Property="FontFamily" Value="Consolas"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"></Setter>
</Style>
</TabControl.Resources>
<TabItem Header="Plot">
<Grid>
<Grid.Resources>
<local:NotNullVisibilityConverter x:Key="NotNullVisibilityConverter" />
</Grid.Resources>
<oxySkia:PlotView Model="{Binding SkiaModel}" Controller="{Binding SelectedExample.Controller}" Visibility="{Binding SkiaModel, Converter={StaticResource NotNullVisibilityConverter}}" />
<oxyWpf:PlotView x:Name="Plot1" Model="{Binding CanvasModel}" Controller="{Binding SelectedExample.Controller}" Visibility="{Binding CanvasModel, Converter={StaticResource NotNullVisibilityConverter}}" />
</Grid>
</TabItem>
<TabItem Header="Code">
<TextBox Text="{Binding Code, Mode=OneWay}" Style="{StaticResource CodeTextBox}" />
</TabItem>
</TabControl>
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock Text="Renderer:" VerticalAlignment="Center" />
<ComboBox SelectedItem="{Binding Renderer}" ItemsSource="{Binding Renderers}" Margin="3,0,0,0" Width="80" />
<CheckBox IsChecked="{Binding Transposed}" Content="Transposed" VerticalAlignment="Center" Margin="10,0,5,0" IsEnabled="{Binding CanTranspose}" />
</StackPanel>
</Grid>
<TabControl Grid.Column="1" TabStripPlacement="Bottom">
<TabControl.Resources>
<Style TargetType="{x:Type TextBox}" x:Key="CodeTextBox">
<Setter Property="AcceptsReturn" Value="True"></Setter>
<Setter Property="FontFamily" Value="Consolas"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"></Setter>
</Style>
</TabControl.Resources>
<TabItem Header="Plot">
<oxy:PlotView x:Name="Plot1" Model="{Binding SelectedExample.PlotModel}" Controller="{Binding SelectedExample.Controller}"/>
</TabItem>
<TabItem Header="Plot (Transposed)" IsEnabled="{Binding SelectedExample.IsTransposable}">
<oxy:PlotView x:Name="Plot2" Model="{Binding SelectedExample.TransposedPlotModel}" Controller="{Binding SelectedExample.TransposedPlotController}"/>
</TabItem>
<TabItem Header="Code">
<TextBox Text="{Binding SelectedExample.Code, Mode=OneWay}" Style="{StaticResource CodeTextBox}" />
</TabItem>
<TabItem Header="Code (Transposed)" IsEnabled="{Binding SelectedExample.IsTransposable}">
<TextBox Text="{Binding SelectedExample.TransposedCode, Mode=OneWay}" Style="{StaticResource CodeTextBox}" />
</TabItem>
</TabControl>
</Grid>
</Window>
43 changes: 6 additions & 37 deletions Source/Examples/WPF/ExampleBrowser/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,18 @@

namespace ExampleBrowser
{
using System;
using System.Diagnostics;
using Microsoft.Win32;
using System.Windows;
using System.Windows.Media;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// The frame count.
/// </summary>
private int frameCount;

/// <summary>
/// The vm.
/// </summary>
private MainWindowViewModel vm = new MainWindowViewModel();

/// <summary>
/// The watch.
/// </summary>
private Stopwatch watch = new Stopwatch();
private readonly MainWindowViewModel vm = new MainWindowViewModel();

/// <summary>
/// Initializes a new instance of the <see cref="MainWindow" /> class.
Expand All @@ -41,31 +29,12 @@ public MainWindow()
{
this.InitializeComponent();
this.DataContext = this.vm;
CompositionTarget.Rendering += this.CompositionTargetRendering;
this.watch.Start();
SystemEvents.DisplaySettingsChanged += this.SystemEvents_DisplaySettingsChanged;
}

/// <summary>
/// Handles the Rendering event of the CompositionTarget control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
private void CompositionTargetRendering(object sender, EventArgs e)
private void SystemEvents_DisplaySettingsChanged(object sender, System.EventArgs e)
{
this.frameCount++;
if (this.watch.ElapsedMilliseconds > 1000 && this.frameCount > 1)
{
this.vm.FrameRate = this.frameCount / (this.watch.ElapsedMilliseconds * 0.001);
this.frameCount = 0;
this.watch.Reset();
this.watch.Start();
}

if (this.vm.MeasureFrameRate)
{
this.Plot1.InvalidatePlot(true);
}
this.vm.ActiveModel?.PlotView?.InvalidatePlot(false);
}

}
}
}
119 changes: 109 additions & 10 deletions Source/Examples/WPF/ExampleBrowser/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@

namespace ExampleBrowser
{
using ExampleLibrary;
using OxyPlot;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;

using ExampleLibrary;

public class MainWindowViewModel : INotifyPropertyChanged
{
private bool _CanTranspose;
private PlotModel _CanvasModel;
private string _Code;
private Renderer _Renderer;
private PlotModel _SkiaModel;
private bool _Transposed;
private IEnumerable<ExampleInfo> examples;

private double frameRate;

private ExampleInfo selectedExample;

public MainWindowViewModel()
Expand All @@ -30,15 +34,35 @@ public MainWindowViewModel()

public event PropertyChangedEventHandler PropertyChanged;

public bool MeasureFrameRate { get; set; }
public PlotModel ActiveModel => this.CanvasModel ?? this.SkiaModel;

public bool CanTranspose
{
get => this._CanTranspose;
private set
{
this._CanTranspose = value;
this.RaisePropertyChanged(nameof(this.CanTranspose));
}
}

public PlotModel CanvasModel
{
get => this._CanvasModel;
set
{
this._CanvasModel = value;
this.RaisePropertyChanged(nameof(this.CanvasModel));
}
}

public double FrameRate
public string Code
{
get => this.frameRate;
get => this._Code;
set
{
this.frameRate = value;
this.RaisePropertyChanged(nameof(this.FrameRate));
this._Code = value;
this.RaisePropertyChanged(nameof(this.Code));
}
}

Expand All @@ -54,19 +78,94 @@ public IEnumerable<ExampleInfo> Examples

public ICollectionView ExamplesView { get; set; }

public Renderer Renderer
{
get => this._Renderer;
set
{
this._Renderer = value;
this.UpdatePlotModels();
this.RaisePropertyChanged(nameof(this.Renderer));
}
}

public IEnumerable<Renderer> Renderers => Enum.GetValues(typeof(Renderer)).Cast<Renderer>();

public ExampleInfo SelectedExample
{
get => this.selectedExample;
set
{
this.selectedExample = value;
this.CoerceSelectedExample();
this.RaisePropertyChanged(nameof(this.SelectedExample));
}
}

public PlotModel SkiaModel
{
get => this._SkiaModel;
set
{
this._SkiaModel = value;
this.RaisePropertyChanged(nameof(this.SkiaModel));
}
}

public bool Transposed
{
get => this._Transposed;
set
{
this._Transposed = value;
this.UpdatePlotModels();
this.RaisePropertyChanged(nameof(this.Transposed));
}
}

protected void RaisePropertyChanged(string property)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}

private void CoerceSelectedExample()
{
this.CanTranspose = this.SelectedExample?.TransposedPlotModel != null;
if (!this.CanTranspose)
{
this._Transposed = false;
}

this.UpdatePlotModels();
}

private void UpdatePlotModels()
{
PlotModel model;
if (this.Transposed)
{
model = this.SelectedExample?.TransposedPlotModel;
this.Code = this.SelectedExample.TransposedCode;
}
else
{
model = this.SelectedExample?.PlotModel;
this.Code = this.SelectedExample?.Code;
}

switch (this.Renderer)
{
case Renderer.Canvas:
this.SkiaModel = null;
this.CanvasModel = model;
break;
case Renderer.SkiaSharp:
this.CanvasModel = null;
this.SkiaModel = model;
break;
default:
break;
}
}
}
}
28 changes: 28 additions & 0 deletions Source/Examples/WPF/ExampleBrowser/NotNullVisibilityConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NotNullVisibilityConverter.cs" company="OxyPlot">
// Copyright (c) 2020 OxyPlot contributors
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace ExampleBrowser
{
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

public sealed class NotNullVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null
? Visibility.Collapsed
: Visibility.Visible;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
}
Loading