├── .gitignore ├── Images ├── BizUnity.jpeg ├── StockPlot3.gif ├── StockPlot5.gif ├── StockPlot6.gif ├── ouinex.png └── stockplot4.gif ├── README.md ├── StockPlot.Charts ├── Controls │ ├── AvaPlot.axaml │ ├── AvaPlot.axaml.cs │ ├── PropertyGrid.axaml │ ├── PropertyGrid.axaml.cs │ ├── StockChart.axaml │ ├── StockChart.axaml.cs │ ├── SubIndicator.axaml │ └── SubIndicator.axaml.cs ├── Delegates.cs ├── Drawings │ ├── HorizontalLine.cs │ ├── TrendLine.cs │ └── VerticalLine.cs ├── Enums.cs ├── Helpers │ ├── ColorHelper.cs │ ├── CrossHairHelper.cs │ ├── IndicatorsHelper.cs │ └── PlotHelper.cs ├── Models │ ├── DrawingManager.cs │ ├── IndicatorItemManager.cs │ ├── IndicatorsManager.cs │ └── StockPricesModel.cs ├── OverideSeries │ ├── OScatterPlot.cs │ └── OScatterPlotDraggable.cs └── StockPlot.Charts.csproj ├── StockPlot.Indicators ├── Enums.cs ├── Fill.cs ├── Formulas.cs ├── IndicatorBase.cs ├── IndicatorLevel.cs ├── IndicatorParameter.cs ├── Indicators │ ├── ATR.cs │ ├── ATRStop.cs │ ├── BollingerBands.cs │ ├── CCI.cs │ ├── Donchian.cs │ ├── EMA.cs │ ├── Ichimoku.cs │ ├── MACD.cs │ ├── RagheeWave.cs │ └── Stochastic.cs ├── IndicatorsList.cs ├── StockPlot.Indicators.csproj ├── XYSerie.cs └── XYYSerie.cs ├── StockPlot.sln └── StockPlot ├── App.axaml ├── App.axaml.cs ├── MainWindow.axaml ├── MainWindow.axaml.cs ├── Program.cs ├── StockPlot.csproj └── app.manifest /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | /desktop.ini 238 | FxcmPriceHistory 239 | Avatars 240 | Tickets 241 | Temp 242 | -------------------------------------------------------------------------------- /Images/BizUnity.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BizUnity/StockPlot/39fcde4ebbd6eaf646b03a0914902388f98f621c/Images/BizUnity.jpeg -------------------------------------------------------------------------------- /Images/StockPlot3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BizUnity/StockPlot/39fcde4ebbd6eaf646b03a0914902388f98f621c/Images/StockPlot3.gif -------------------------------------------------------------------------------- /Images/StockPlot5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BizUnity/StockPlot/39fcde4ebbd6eaf646b03a0914902388f98f621c/Images/StockPlot5.gif -------------------------------------------------------------------------------- /Images/StockPlot6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BizUnity/StockPlot/39fcde4ebbd6eaf646b03a0914902388f98f621c/Images/StockPlot6.gif -------------------------------------------------------------------------------- /Images/ouinex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BizUnity/StockPlot/39fcde4ebbd6eaf646b03a0914902388f98f621c/Images/ouinex.png -------------------------------------------------------------------------------- /Images/stockplot4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BizUnity/StockPlot/39fcde4ebbd6eaf646b03a0914902388f98f621c/Images/stockplot4.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | An initiative of [Ouinex Exchange](http://ouinex.com/ "Ouinex Exchange") and [BizUnity](https://www.linkedin.com/company/bizunity/ "BizUnity") 3 | 4 | ![](/Images/ouinex.png) 5 | 6 | ![](/Images/BizUnity.jpeg) 7 | 8 | # StockPlot 9 | ![](/Images/StockPlot6.gif) 10 | 11 | A Technical analysis library for [AvaloniaUI](https://avaloniaui.net/ "AvaloniaUI"), based on [ScottPlot](https://scottplot.net/ "ScottPlot") DataVisualization Library (v 4.1.63). 12 | 13 | StockPlot *(will)* allow you to have a full Stock Market Analysis module in your application only by using a single UserControl and a single class. 14 | 15 | StockPlot are in a very early stage (started in April 2023). A lot of features need to be created before deploying a proper and working Nuget Package. 16 | Some refactoring may need to be done a couple of time, **do not use it in a production yet. ** 17 | Play around, do not hesitate to contribute <3 . 18 | 19 | 20 | 21 | ## Current features 22 | + Display the price using Candlesticks or Bars (OHLC) with a full live datas update support : 23 | + Update the last bar (OHLC). 24 | + Add a new bar. 25 | + Automaticaly update the indicator(s) on price updating. 26 | + Working using Background dispatcher. 27 | + Display studies. 28 | + Add indicator on price. 29 | + Add sub indicator. 30 | + Auto hide or show the X axis. (Only the last Plot X axis need to be shown). 31 | + Auto link the X axis with sub indicators and main price area. 32 | + Shared cross hair between price area and sub charts. 33 | + Display & modify properties of an indicator. 34 | 35 | 36 | ## Features planned 37 | 38 | + ##### Price displaying 39 | + Allowing the user to choose the price type (candle, OHLC or line). 40 | + Add the line type chart. 41 | + Allowing the user to customize the charts and price colors. 42 | + ##### Indicators : 43 | + Ability to change the parametters of an indicator. 44 | + Ability to change the visual parametter of an indicator (eg : line style, line color, ...). 45 | + For both of those previous point, we need to create and add a PropertyGrid Control. 46 | + Display the list of working indicators on price. 47 | + ##### Drawing tools 48 | + Add an horizontal line on a specified price (Y Axis). 49 | + Add a vertical line on a specified time (X Axis). 50 | + Add a limited line (X1,Y1,X2,Y2). 51 | + Add Fibonacci retracement. 52 | + Add Andrew Pitchfork. 53 | + ... 54 | + #### Presets 55 | + Save and load a preset of indicators. 56 | + Save and load a preset of draws. 57 | 58 | # How to use 59 | 1) From you IDE, add the reference to StockPlot.Charts. 60 | This library contains all the controls and logics. 61 | 62 | 2) In your Window or UserControl add the reference to StockPlot.Charts : 63 | `xmlns:stockPlot="using:StockPlot.Charts.Controls"` 64 | 65 | 3) Add the StockChart control in your axaml code : 66 | ```xml 67 | 73 | ``` 74 | 4) In your C# code, find the StockChart control : 75 | ```csharp 76 | StockChart _chart = this.Find("StockChart"); 77 | ``` 78 | 5) Create a new StockPricesModel and provide the StockChart control with it : 79 | ```csharp 80 | var model = new StockPricesModel(); 81 | _chart.PricesModel = model; 82 | ``` 83 | 6) FullFill the model with datas. For this exemple we will use Binance API using [Binance.NET](https://github.com/JKorf/Binance.Net "Binance.NET") library. 84 | With this exemple, price is working in live using WebSocket. 85 | ```csharp 86 | var client = new BinanceClient(); 87 | 88 | var request = await client.SpotApi.ExchangeData.GetUiKlinesAsync("BTCUSDT", Binance.Net.Enums.KlineInterval.OneMinute, limit: 500); 89 | 90 | if (request.Success) 91 | { 92 | var bars = request.Data.Select(x => new OHLC((double)x.OpenPrice, 93 | (double)x.HighPrice, 94 | (double)x.LowPrice, 95 | (double)x.ClosePrice, 96 | x.OpenTime, 97 | TimeSpan.FromMinutes(1))).ToArray(); 98 | 99 | // Append the all bars 100 | model.Append(bars); 101 | 102 | var socket = new BinanceSocketClient(); 103 | await socket.SpotStreams.SubscribeToKlineUpdatesAsync("BTCUSDT", Binance.Net.Enums.KlineInterval.OneMinute, async (data) => 104 | { 105 | await Dispatcher.UIThread.InvokeAsync(() => 106 | { 107 | var candle = data.Data.Data; 108 | 109 | var toUpdate = model.Prices.FirstOrDefault(x => x.DateTime == candle.OpenTime); 110 | 111 | // Check if the data time are the same as the last. If not, it means we have to add a new bar 112 | if (toUpdate != null) 113 | { 114 | toUpdate.Volume = (double)candle.Volume; 115 | toUpdate.High = (double)candle.HighPrice; 116 | toUpdate.Close = (double)candle.ClosePrice; 117 | toUpdate.Low = (double)candle.LowPrice; 118 | 119 | // Update the last bar 120 | model.UpdateBar(toUpdate); 121 | } 122 | else 123 | { 124 | var newBar = new OHLC((double)candle.OpenPrice, 125 | (double)candle.HighPrice, 126 | (double)candle.LowPrice, 127 | (double)candle.ClosePrice, 128 | candle.OpenTime, TimeSpan.FromMinutes(1)); 129 | 130 | // Append the new bar 131 | model.Append(newBar); 132 | } 133 | }, DispatcherPriority.Background); 134 | }); 135 | } 136 | ``` 137 | 138 | It is very easy to fullfill the chart with datas just by using Append() and UpdateBar(). 139 | ** 140 | -------------------------------------------------------------------------------- /StockPlot.Charts/Controls/AvaPlot.axaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /StockPlot.Charts/Controls/AvaPlot.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Input; 4 | using Avalonia.Interactivity; 5 | using Avalonia.Markup.Xaml; 6 | using Avalonia.Threading; 7 | using System.ComponentModel; 8 | using Ava = Avalonia; 9 | 10 | #pragma warning disable IDE1006 // lowercase public properties 11 | #pragma warning disable CS0067 // unused events 12 | 13 | namespace ScottPlot.Avalonia 14 | { 15 | /// 16 | /// Interaction logic for AvaPlot.axaml 17 | /// 18 | 19 | [System.ComponentModel.ToolboxItem(true)] 20 | [System.ComponentModel.DesignTimeVisible(true)] 21 | public partial class AvaPlot : UserControl, ScottPlot.Control.IPlotControl 22 | { 23 | public Plot Plot => Backend.Plot; 24 | public ScottPlot.Control.Configuration Configuration { get; } 25 | 26 | /// 27 | /// This event is invoked any time the axis limits are modified. 28 | /// 29 | public event EventHandler AxesChanged; 30 | 31 | /// 32 | /// This event is invoked any time the plot is right-clicked. 33 | /// 34 | public event EventHandler RightClicked; 35 | 36 | /// 37 | /// This event is invoked any time the plot is left-clicked. 38 | /// It is typically used to interact with custom plot types. 39 | /// 40 | public event EventHandler LeftClicked; 41 | 42 | /// 43 | /// This event is invoked when a plottable is left-clicked. 44 | /// 45 | public event EventHandler LeftClickedPlottable; 46 | 47 | /// 48 | /// This event is invoked after the mouse moves while dragging a draggable plottable. 49 | /// The object passed is the plottable being dragged. 50 | /// 51 | public event EventHandler PlottableDragged; 52 | 53 | [Obsolete("use 'PlottableDragged' instead", error: true)] 54 | public event EventHandler MouseDragPlottable; 55 | 56 | /// 57 | /// This event is invoked right after a draggable plottable was dropped. 58 | /// The object passed is the plottable that was just dropped. 59 | /// 60 | public event EventHandler PlottableDropped; 61 | 62 | [Obsolete("use 'PlottableDropped' instead", error: true)] 63 | public event EventHandler MouseDropPlottable; 64 | 65 | private readonly Control.ControlBackEnd Backend; 66 | private readonly Dictionary Cursors; 67 | private readonly Ava.Controls.Image PlotImage = new Ava.Controls.Image(); 68 | private float ScaledWidth => (float)(Bounds.Width * Configuration.DpiStretchRatio); 69 | private float ScaledHeight => (float)(Bounds.Height * Configuration.DpiStretchRatio); 70 | 71 | [Obsolete("Reference Plot instead of plt")] 72 | public ScottPlot.Plot plt => Plot; 73 | 74 | static AvaPlot() 75 | { 76 | 77 | } 78 | 79 | public AvaPlot() 80 | { 81 | InitializeComponent(); 82 | 83 | Cursors = new Dictionary() 84 | { 85 | [ScottPlot.Cursor.Arrow] = new Ava.Input.Cursor(StandardCursorType.Arrow), 86 | [ScottPlot.Cursor.WE] = new Ava.Input.Cursor(StandardCursorType.SizeWestEast), 87 | [ScottPlot.Cursor.NS] = new Ava.Input.Cursor(StandardCursorType.SizeNorthSouth), 88 | [ScottPlot.Cursor.All] = new Ava.Input.Cursor(StandardCursorType.SizeAll), 89 | [ScottPlot.Cursor.Crosshair] = new Ava.Input.Cursor(StandardCursorType.Cross), 90 | [ScottPlot.Cursor.Hand] = new Ava.Input.Cursor(StandardCursorType.Hand), 91 | [ScottPlot.Cursor.Question] = new Ava.Input.Cursor(StandardCursorType.Help), 92 | }; 93 | 94 | Backend = new ScottPlot.Control.ControlBackEnd((float)Bounds.Width, (float)Bounds.Height, GetType().Name); 95 | Backend.BitmapChanged += new EventHandler(OnBitmapChanged); 96 | Backend.BitmapUpdated += new EventHandler(OnBitmapUpdated); 97 | Backend.CursorChanged += new EventHandler(OnCursorChanged); 98 | Backend.RightClicked += new EventHandler(OnRightClicked); 99 | Backend.LeftClicked += new EventHandler(OnLeftClicked); 100 | Backend.LeftClickedPlottable += new EventHandler(OnLeftClickedPlottable); 101 | Backend.AxesChanged += new EventHandler(OnAxesChanged); 102 | Backend.PlottableDragged += new EventHandler(OnPlottableDragged); 103 | Backend.PlottableDropped += new EventHandler(OnPlottableDropped); 104 | Backend.Configuration.ScaleChanged += new EventHandler(OnScaleChanged); 105 | Configuration = Backend.Configuration; 106 | 107 | 108 | InitializeLayout(); 109 | Backend.StartProcessingEvents(); 110 | } 111 | 112 | public (double x, double y) GetMouseCoordinates(int xAxisIndex = 0, int yAxisIndex = 0) => Backend.GetMouseCoordinates(xAxisIndex, yAxisIndex); 113 | 114 | public (float x, float y) GetMousePixel() => Backend.GetMousePixel(); 115 | public void Reset() => Backend.Reset(ScaledWidth, ScaledHeight); 116 | public void Reset(Plot newPlot) => Backend.Reset(ScaledWidth, ScaledHeight, newPlot); 117 | public void Refresh() => Refresh(false); 118 | public void Refresh(bool lowQuality = false) 119 | { 120 | Backend.WasManuallyRendered = true; 121 | Backend.Render(lowQuality); 122 | } 123 | public void RefreshRequest(RenderType renderType = RenderType.LowQualityThenHighQualityDelayed) 124 | { 125 | Backend.WasManuallyRendered = true; 126 | Backend.RenderRequest(renderType); 127 | } 128 | 129 | // TODO: mark this obsolete in ScottPlot 5.0 (favor Refresh) 130 | public void Render(bool lowQuality = false) => Refresh(lowQuality); 131 | 132 | // TODO: mark this obsolete in ScottPlot 5.0 (favor Refresh) 133 | public void RenderRequest(RenderType renderType = RenderType.LowQualityThenHighQualityDelayed) => RefreshRequest(renderType); 134 | 135 | private Task SetImagePlot(Func getBmp) 136 | { 137 | return Task.Run(() => 138 | { 139 | Dispatcher.UIThread.InvokeAsync(() => 140 | { 141 | PlotImage.Source = getBmp(); 142 | }); 143 | }); 144 | } 145 | 146 | private void OnBitmapChanged(object sender, EventArgs e) => SetImagePlot(() => BmpImageFromBmp(Backend.GetLatestBitmap())); 147 | private void OnCursorChanged(object sender, EventArgs e) { PlotImage.Cursor = Cursors[Backend.Cursor]; } 148 | private void OnBitmapUpdated(object sender, EventArgs e) => SetImagePlot(() => BmpImageFromBmp(Backend.GetLatestBitmap())); 149 | private void OnRightClicked(object sender, EventArgs e) => RightClicked?.Invoke(this, e); 150 | private void OnLeftClicked(object sender, EventArgs e) => LeftClicked?.Invoke(this, e); 151 | private void OnLeftClickedPlottable(object sender, EventArgs e) => LeftClickedPlottable?.Invoke(sender, e); 152 | private void OnPlottableDragged(object sender, EventArgs e) => PlottableDragged?.Invoke(sender, e); 153 | private void OnPlottableDropped(object sender, EventArgs e) => PlottableDropped?.Invoke(sender, e); 154 | private void OnAxesChanged(object sender, EventArgs e) => AxesChanged?.Invoke(this, e); 155 | private void OnSizeChanged(object sender, EventArgs e) => Backend.Resize(ScaledWidth, ScaledHeight, useDelayedRendering: true); 156 | private void OnScaleChanged(object sender, EventArgs e) { System.Diagnostics.Debug.WriteLine("SCALECHANGED"); OnSizeChanged(null, null); } 157 | private void OnMouseDown(object sender, PointerEventArgs e) { CaptureMouse(e.Pointer); Backend.MouseDown(GetInputState(e)); } 158 | private void OnMouseUp(object sender, PointerEventArgs e) { Backend.MouseUp(GetInputState(e)); UncaptureMouse(e.Pointer); } 159 | private void OnDoubleClick(object sender, RoutedEventArgs e) => Backend.DoubleClick(); 160 | private void OnMouseWheel(object sender, PointerWheelEventArgs e) => Backend.MouseWheel(GetInputState(e, e.Delta.Y)); 161 | private void OnMouseMove(object sender, PointerEventArgs e) { Backend.MouseMove(GetInputState(e)); base.OnPointerMoved(e); } 162 | private void OnMouseEnter(object sender, PointerEventArgs e) => base.OnPointerEntered(e); 163 | private void OnMouseLeave(object sender, PointerEventArgs e) => base.OnPointerExited(e); 164 | private void CaptureMouse(IPointer pointer) => pointer.Capture(this); 165 | private void UncaptureMouse(IPointer pointer) => pointer.Capture(null); 166 | 167 | private ScottPlot.Control.InputState GetInputState(PointerEventArgs e, double? delta = null) => 168 | new ScottPlot.Control.InputState() 169 | { 170 | X = (float)e.GetPosition(this).X * Configuration.DpiStretchRatio, 171 | Y = (float)e.GetPosition(this).Y * Configuration.DpiStretchRatio, 172 | LeftWasJustPressed = e.GetCurrentPoint(null).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonPressed, 173 | RightWasJustPressed = e.GetCurrentPoint(null).Properties.PointerUpdateKind == PointerUpdateKind.RightButtonPressed, 174 | MiddleWasJustPressed = e.GetCurrentPoint(null).Properties.PointerUpdateKind == PointerUpdateKind.MiddleButtonPressed, 175 | ShiftDown = e.KeyModifiers.HasFlag(KeyModifiers.Shift), 176 | CtrlDown = e.KeyModifiers.HasFlag(KeyModifiers.Control), 177 | AltDown = e.KeyModifiers.HasFlag(KeyModifiers.Alt), 178 | WheelScrolledUp = delta.HasValue && delta > 0, 179 | WheelScrolledDown = delta.HasValue && delta < 0, 180 | }; 181 | 182 | private void InitializeComponent() 183 | { 184 | AvaloniaXamlLoader.Load(this); 185 | this.Focusable = true; 186 | 187 | PointerPressed += OnMouseDown; 188 | PointerMoved += OnMouseMove; 189 | // Note: PointerReleased is handled in OnPointerReleased override instead 190 | PointerWheelChanged += OnMouseWheel; 191 | PointerEntered += OnMouseEnter; 192 | PointerExited += OnMouseLeave; 193 | DoubleTapped += OnDoubleClick; 194 | PropertyChanged += AvaPlot_PropertyChanged; 195 | } 196 | 197 | private void InitializeLayout() 198 | { 199 | Grid mainGrid = this.Find("MainGrid"); 200 | 201 | bool isDesignerMode = Design.IsDesignMode; 202 | if (isDesignerMode) 203 | { 204 | try 205 | { 206 | Plot.Title($"ScottPlot {Plot.Version}"); 207 | Plot.Render(); 208 | } 209 | catch (Exception e) 210 | { 211 | InitializeComponent(); 212 | this.Find("ErrorLabel").Text = "ERROR: ScottPlot failed to render in design mode.\n\n" + 213 | "This may be due to incompatible System.Drawing.Common versions or a 32-bit/64-bit mismatch.\n\n" + 214 | "Although rendering failed at design time, it may still function normally at runtime.\n\n" + 215 | $"Exception details:\n{e}"; 216 | return; 217 | } 218 | } 219 | 220 | this.Find("ErrorLabel").IsVisible = false; 221 | 222 | Canvas canvas = new Canvas(); 223 | mainGrid.Children.Add(canvas); 224 | canvas.Children.Add(PlotImage); 225 | } 226 | 227 | public static Ava.Media.Imaging.Bitmap BmpImageFromBmp(System.Drawing.Bitmap bmp) 228 | { 229 | using var memory = new System.IO.MemoryStream(); 230 | bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png); 231 | memory.Position = 0; 232 | var bitmapImage = new Ava.Media.Imaging.Bitmap(memory); 233 | return bitmapImage; 234 | } 235 | 236 | private void InhibitContextMenuIfMouseDragged(object sender, CancelEventArgs e) 237 | { 238 | e.Cancel = Backend.MouseDownDragged; 239 | } 240 | 241 | protected override void OnPointerReleased(PointerReleasedEventArgs e) 242 | { 243 | // First, make sure backend sees that we are no longer pressing mouse button. 244 | // Otherwise, after selecting an item from the context menu, the control 245 | // will still think we are right-click-dragging even though the button 246 | // is no longer down. 247 | OnMouseUp(this, e); 248 | 249 | // Then allow Avalonia's own click handling to allow the context menu 250 | // to be displayed if needed. 251 | base.OnPointerReleased(e); 252 | } 253 | 254 | private void AvaPlot_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) 255 | { 256 | if (e.Property.Name == "Bounds") 257 | { 258 | Backend.Resize(ScaledWidth, ScaledHeight, useDelayedRendering: true); 259 | PlotImage.Width = ScaledWidth; 260 | PlotImage.Height = ScaledHeight; 261 | Refresh(); 262 | } 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /StockPlot.Charts/Controls/PropertyGrid.axaml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 21 |