diff --git a/Configuration/GlobalAssemblyInfo.cs b/Configuration/GlobalAssemblyInfo.cs index 36594fba8..2f0e628a2 100644 --- a/Configuration/GlobalAssemblyInfo.cs +++ b/Configuration/GlobalAssemblyInfo.cs @@ -15,7 +15,7 @@ internal static class RevisionClass public const string Major = "3"; public const string Minor = "8"; public const string Build = "0"; - public const string Revision = "2932"; + public const string Revision = "2934"; public const string MainVersion = Major + "." + Minor; public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision; diff --git a/Configuration/Version.defs b/Configuration/Version.defs index 1d054bb10..6dbe8a6f3 100644 --- a/Configuration/Version.defs +++ b/Configuration/Version.defs @@ -1,4 +1,4 @@ %COREVERSION%=0 -%REVISION%=2932 +%REVISION%=2934 %MINOR%=8 %MAJOR%=3 diff --git a/InteractiveDataDisplay_OneDll/.gitignore b/InteractiveDataDisplay_OneDll/.gitignore new file mode 100644 index 000000000..940794e60 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/.gitignore @@ -0,0 +1,288 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/InteractiveDataDisplay_OneDll/InteractiveDataDisplay.WPF.nuspec b/InteractiveDataDisplay_OneDll/InteractiveDataDisplay.WPF.nuspec new file mode 100644 index 000000000..d75774387 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/InteractiveDataDisplay.WPF.nuspec @@ -0,0 +1,43 @@ + + + + InteractiveDataDisplay.WPF + 1.1.0 + InteractiveDataDipslay.WPF + Sergey Berezin, Vassily Lyutsarev, Nikita Skoblov, Natalia Stepanova + Microsoft; MSU ITIS Lab + false + https://github.com/Microsoft/InteractiveDataDisplay.WPF/blob/master/LICENSE + https://github.com/Microsoft/InteractiveDataDisplay.WPF + Interactive Data Display for WPF is a set of controls for adding interactive visualization of dynamic data to your application. + Interactive Data Dipslay for WPF allows to create line graphs, bubble charts, heat maps and other complex 2D plots which are very common in scientific software. It integrates well with Bing Maps control to show data on a geographic map in latitude/longitude coordinates. The controls can also be operated programmatically. + Copyright 2017 Microsoft Corporation + idd wpf visualization plot plots plotting chart charting data interactive datavisualization + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/LICENSE b/InteractiveDataDisplay_OneDll/LICENSE new file mode 100644 index 000000000..5b677b6f6 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/LICENSE @@ -0,0 +1,23 @@ + InteractiveDataDisplay.WPF + + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/InteractiveDataDisplay_OneDll/README.md b/InteractiveDataDisplay_OneDll/README.md new file mode 100644 index 000000000..bd646c2c5 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/README.md @@ -0,0 +1,68 @@ +Interactive Data Display for WPF +================================ + +Interactive Data Display for WPF is a set of controls for adding interactive visualization of dynamic data to your application. It allows to create line graphs, bubble charts, heat maps and other complex 2D plots which are very common in scientific software. Interactive Data Display for WPF integrates well with Bing Maps control to show data on a geographic map in latitude/longitude coordinates. The controls can also be operated programmatically. + +How to install +---------- +The IDD for WPF can be [installed from NuGet](https://www.nuget.org/packages/InteractiveDataDisplay.WPF/): +``` +PM > Install-Package InteractiveDataDisplay.WPF -Version 1.0.0 +``` + +How to use +---------- +Drawing with Interactive Data Display is easy. + +One line of C# code to draw simple linegraph: +````c# +linegraph.Plot(x,y); // x and y are IEnumerable +```` +And corresponding XAML snippet: +````xaml + + + +```` +If we get arrays `x` and `y` as: +````c# +var x = Enumerable.Range(0, 1001).Select(i => i / 10.0).ToArray(); +var y = x.Select(v => Math.Abs(v) < 1e-10 ? 1 : Math.Sin(v) / v).ToArray(); +```` +We will receive: + + + +Other Interactive Data Display samples: + + + + + + + +See the source code [here](https://github.com/Microsoft/InteractiveDataDisplay.WPF/tree/master/samples). + +Licensing +--------- + +Interactive Data Display for WPF is under the [MIT license](https://github.com/Microsoft/InteractiveDataDisplay.WPF/blob/master/LICENSE). + +Other +----- +There is also [Interactive Data Display](https://github.com/predictionmachines/InteractiveDataDisplay) for Javascript. You can see interactive samples [here](http://predictionmachines.github.io/InteractiveDataDisplay). + +Contributing +------------ + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/InteractiveDataDisplay_OneDll/images/barchart.PNG b/InteractiveDataDisplay_OneDll/images/barchart.PNG new file mode 100644 index 000000000..aad5f5253 Binary files /dev/null and b/InteractiveDataDisplay_OneDll/images/barchart.PNG differ diff --git a/InteractiveDataDisplay_OneDll/images/heatmap.PNG b/InteractiveDataDisplay_OneDll/images/heatmap.PNG new file mode 100644 index 000000000..777fcaa53 Binary files /dev/null and b/InteractiveDataDisplay_OneDll/images/heatmap.PNG differ diff --git a/InteractiveDataDisplay_OneDll/images/line.PNG b/InteractiveDataDisplay_OneDll/images/line.PNG new file mode 100644 index 000000000..af5d6f065 Binary files /dev/null and b/InteractiveDataDisplay_OneDll/images/line.PNG differ diff --git a/InteractiveDataDisplay_OneDll/images/map.PNG b/InteractiveDataDisplay_OneDll/images/map.PNG new file mode 100644 index 000000000..724745bda Binary files /dev/null and b/InteractiveDataDisplay_OneDll/images/map.PNG differ diff --git a/InteractiveDataDisplay_OneDll/images/markers.PNG b/InteractiveDataDisplay_OneDll/images/markers.PNG new file mode 100644 index 000000000..bed2535ec Binary files /dev/null and b/InteractiveDataDisplay_OneDll/images/markers.PNG differ diff --git a/InteractiveDataDisplay_OneDll/images/sinline.PNG b/InteractiveDataDisplay_OneDll/images/sinline.PNG new file mode 100644 index 000000000..dcb12c7bc Binary files /dev/null and b/InteractiveDataDisplay_OneDll/images/sinline.PNG differ diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.config b/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.config new file mode 100644 index 000000000..88fa4027b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.xaml b/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.xaml new file mode 100644 index 000000000..b85fb66d1 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.xaml.cs b/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.xaml.cs new file mode 100644 index 000000000..04e2769ee --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace BarChartSample +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/BarChartSample.csproj b/InteractiveDataDisplay_OneDll/samples/BarChartSample/BarChartSample.csproj new file mode 100644 index 000000000..5348a1fda --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/BarChartSample.csproj @@ -0,0 +1,103 @@ + + + + + Debug + AnyCPU + {F36872BF-8818-4019-A207-2B381C33C370} + WinExe + BarChartSample + BarChartSample + v4.5.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {2d34544c-2df2-4b20-a43a-6c8d2df3dd82} + InteractiveDataDisplay.WPF + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/MainWindow.xaml b/InteractiveDataDisplay_OneDll/samples/BarChartSample/MainWindow.xaml new file mode 100644 index 000000000..55370aa49 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/MainWindow.xaml @@ -0,0 +1,17 @@ + + + + + Bar chart sample + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/MainWindow.xaml.cs b/InteractiveDataDisplay_OneDll/samples/BarChartSample/MainWindow.xaml.cs new file mode 100644 index 000000000..7612942f6 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/MainWindow.xaml.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; + +namespace BarChartSample +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + + int N = 100; + double[] y = new double[N]; + + Random r = new Random(); + double k; + + for (int i = 0; i < N; i++) + { + k = r.NextDouble(); + y[i] = k < 0.5 ? r.Next(100) : -r.Next(100); + } + barChart.PlotBars(y); + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/AssemblyInfo.cs b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..244a26027 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BarChartSample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BarChartSample")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Resources.Designer.cs b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Resources.Designer.cs new file mode 100644 index 000000000..bc4a8188b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BarChartSample.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BarChartSample.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Resources.resx b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Settings.Designer.cs b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Settings.Designer.cs new file mode 100644 index 000000000..1eaf99ed6 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BarChartSample.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Settings.settings b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BarChartSample/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.config b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.config new file mode 100644 index 000000000..88fa4027b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.xaml b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.xaml new file mode 100644 index 000000000..f26edcfb1 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.xaml.cs b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.xaml.cs new file mode 100644 index 000000000..24239e5e3 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace BingMapsSample +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/BingMapsSample.csproj b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/BingMapsSample.csproj new file mode 100644 index 000000000..e6f999861 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/BingMapsSample.csproj @@ -0,0 +1,117 @@ + + + + + Debug + AnyCPU + {C724F25D-6F77-48C0-A791-28E34E6B0F0C} + WinExe + Properties + BingMapsSample + BingMapsSample + v4.5.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\sln\packages\Microsoft.Maps.MapControl.WPF.1.0.0.3\lib\net40-Client\Microsoft.Maps.MapControl.WPF.dll + True + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + {2d34544c-2df2-4b20-a43a-6c8d2df3dd82} + InteractiveDataDisplay.WPF + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/MainWindow.xaml b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/MainWindow.xaml new file mode 100644 index 000000000..7c5773180 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/MainWindow.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/MainWindow.xaml.cs b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/MainWindow.xaml.cs new file mode 100644 index 000000000..c6f6c9cdd --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/MainWindow.xaml.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Windows; + +namespace BingMapsSample +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + plot.Tag = bingmap; + + List lat = new List(), lon = new List(), data = new List(); + using (var reader = new StreamReader(@"..\..\crop_area.csv")) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + var values = line.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + lon.Add(Double.Parse(values[0], CultureInfo.InvariantCulture)); + lat.Add(Double.Parse(values[1], CultureInfo.InvariantCulture)); + data.Add(Double.Parse(values[2], CultureInfo.InvariantCulture)); + } + } + + markers.PlotColor(lon.ToArray(), lat.ToArray(), data.ToArray()); + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/AssemblyInfo.cs b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..2cdf6ccce --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BingMapsSample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BingMapsSample")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Resources.Designer.cs b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Resources.Designer.cs new file mode 100644 index 000000000..9535f6e19 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BigMapsSample.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BigMapsSample.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Resources.resx b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Settings.Designer.cs b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Settings.Designer.cs new file mode 100644 index 000000000..670331bdd --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BigMapsSample.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Settings.settings b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/crop_area.csv b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/crop_area.csv new file mode 100644 index 000000000..47e4cf240 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/crop_area.csv @@ -0,0 +1,2946 @@ +-87.14859772;33.03834915;0 +-86.63339996;34.01269913;0.00217824 +-85.70469666;32.08162689;0.001499976 +-86.67725372;31.74399948;0.003620297 +-85.35935211;32.9185257;0 +-86.69689941;32.86545181;0.001801205 +-88.20185089;32.00394821;0 +-87.81974792;31.59149933;0 +-85.90750122;33.29457474;0 +-85.59629822;33.7167511;0.001394566 +-86.25894928;32.92907333;0 +-86.32274628;31.74575043;0.004357508 +-87.85154724;34.44314957;0.004178859 +-87.94387817;32.83895111;0.00145153 +-86.90245056;33.54432678;0 +-88.10984802;33.79135132;0.003616599 +-85.34899902;32.57804871;0 +-86.65589905;32.17839813;0.003917458 +-85.72805023;32.41439819;0.00409487 +-87.78990173;32.26077652;0.000639687 +-87.92079926;34.11780167;0 +-88.18650055;30.74489975;0.001013714 +-86.21414948;32.23270035;0.001978449 +-87.2716217;32.59090042;0.001954537 +-88.08865356;33.26255035;0.005318123 +-85.16970062;32.28604889;0.001705467 +-86.32160187;33.68040085;0.00078647 +-85.80102539;32.79990005;0 +-87.29485321;33.7574501;0 +-87.26184845;32.046875;0.001934048 +-87.37322235;34.14965057;0 +-109.5223007;35.24095154;0 +-112.0443497;35.63372421;0 +-110.8554993;33.73215103;0 +-109.7823486;33.03855133;0.000573788 +-109.2700272;33.10224915;0 +-113.647377;35.61334991;0 +-110.2884979;35.28625107;0 +-111.8898468;31.96990013;0 +-110.9089966;31.53199959;0 +-112.4041748;34.70529938;0 +-114.0678024;32.7504425;0.000538393 +-91.38365173;34.26305008;0.003161388 +-91.79750061;33.20220184;0.013569994 +-92.35449982;36.23625183;0 +-94.24210358;36.30054855;0 +-93.07749939;36.30625153;0 +-92.16917419;33.43534851;0 +-92.53822327;33.55054855;0 +-93.58204651;36.31034851;0 +-93.19419861;34.05889893;0 +-92.02185059;35.53522491;0 +-92.21414948;33.88410187;0 +-93.23435211;33.23625183;0 +-94.19239807;35.55602646;0.009185156 +-90.77185059;35.29624939;0.017760006 +-92.64745331;33.97534943;0 +-92.35684967;35.11989975;0 +-93.88870239;35.48685074;0 +-91.80449677;36.37625122;0 +-93.09919739;34.58124924;0 +-92.44317627;34.27759933;0 +-93.70670319;33.74240112;0 +-93.03829956;34.32772446;0 +-94.03794861;34.05157471;0 +-91.94454956;36.06415176;0 +-91.05357361;36.07097626;0 +-91.69879913;33.97850037;0.004176319 +-93.70414734;36.03350067;0 +-92.64982605;36.2804985;0 +-93.85820007;33.31639862;0.04131743 +-93.66425323;34.54315186;0 +-93.29360199;33.69874954;0 +-93.23204803;35.92494965;0 +-92.84230042;33.58475113;0 +-92.92479706;34.94335175;0 +-93.65505219;34.14757538;0 +-94.19985199;34.45940018;0 +-93.05415344;35.42575073;0 +-92.39234924;34.7519989;0 +-90.77572632;35.00814819;0 +-92.65950012;34.6358757;0 +-94.07945251;34.88264847;0 +-92.68115234;35.91685104;0 +-94.23795319;35.19205093;0 +-94.21765137;33.97154999;0 +-92.12684631;35.91889954;0 +-92.52884674;33.19630051;0 +-92.54585266;35.57720184;0 +-94.220047;35.99377441;0 +-91.73325348;35.27890015;0 +-121.9217987;37.67757416;0.000847377 +-119.8101501;38.62889862;0 +-120.5492477;38.46559906;0 +-120.5059509;38.17164993;0 +-121.9980469;37.90987396;0.005642753 +-123.890152;41.69127655;0 +-120.5111771;38.78570175;0 +-123.8980484;40.73345184;0 +-115.285347;33.02610016;0.000224565 +-117.2005463;36.62549973;0 +-122.7126007;39.12480164;0 +-120.6631012;40.44647598;0 +-118.298172;34.26347351;0 +-122.6778488;38.06425095;0 +-119.8471985;37.5271492;0 +-123.4216537;39.38570023;0 +-120.7268982;41.59080124;0 +-118.7369995;38.0882988;0 +-121.0956497;36.34690094;0 +-122.3560028;38.50984955;0 +-121.0003357;39.2710495;0 +-117.7664032;33.66650009;0 +-120.7421494;39.01385117;0 +-120.7967987;40.02339935;0 +-116.055397;33.75289917;8.67168E-05 +-121.1194534;36.59429932;0 +-116.8377991;33.0196991;0 +-122.3984985;37.81909943;0 +-120.4091721;35.34640121;0 +-122.2912521;37.40790176;0 +-120.0454712;34.73550034;0 +-121.7052536;37.19210052;0 +-121.9485016;37.06840134;0 +-122.1912003;40.73529816;0 +-120.5155258;39.58217621;0 +-122.5621262;41.50479889;0 +-122.9457016;38.46125031;0 +-122.2154236;40.12167358;0.000370638 +-123.0228271;40.66040039;0 +-119.9507523;38.03504944;0 +-119.0536728;34.47320175;0 +-105.747673;37.55410004;0 +-104.3806992;39.65227509;0.000972744 +-106.9785004;37.20755005;0 +-106.264473;38.74005127;0 +-105.6604004;39.70849991;0 +-106.1977768;37.19817352;0 +-105.4532471;37.31534958;0 +-103.7794037;38.31790161;0.00099019 +-105.4223022;38.07842636;0 +-104.9216537;39.71142578;0 +-108.4516525;37.68935013;0 +-104.9943466;39.34794998;0 +-106.6443253;39.64144897;0 +-104.1882477;39.21657562;0 +-105.4753036;38.4776001;0 +-107.5255051;39.72862625;0 +-105.5480499;39.84069824;0 +-106.1399994;40.08560181;0 +-106.9406509;38.70185089;0 +-107.2917023;37.78504944;0 +-104.9253464;37.64664841;0.000196433 +-106.3559723;40.6711998;0 +-105.2227478;39.52190018;0 +-107.9298477;37.31980133;0 +-106.3563538;39.21784973;0 +-104.1144714;37.40520096;0 +-106.9188004;37.67615128;0 +-108.1817017;40.61159897;0 +-108.508049;37.31887436;0 +-107.8392029;38.1147995;0 +-105.7592468;39.13105011;0 +-106.9298019;39.18225098;0 +-108.0437012;39.93757629;0 +-106.3744278;37.61555099;0 +-107.0333481;40.46115112;0 +-106.2238998;38.09704971;0 +-107.7265472;37.80224991;0 +-108.3899231;37.96450043;0 +-106.1078033;39.64839935;0 +-105.1373749;38.88880157;0 +-73.40844727;41.32450104;0 +-72.71914673;41.7975502;0 +-73.20310211;41.75954819;0 +-72.52964783;41.44984818;0 +-72.92737579;41.40394974;0 +-72.12760162;41.49459839;0 +-72.30975342;41.81135178;0 +-72.01877594;41.83140182;0 +-82.35579681;29.6827507;0.001608522 +-82.25440216;30.36014938;0 +-85.69020081;30.24580002;0 +-82.23262787;29.93052483;0 +-80.717453;28.30644989;0 +-80.47814941;26.14539909;0 +-85.16120148;30.40360069;0.002478803 +-81.96884918;26.90184975;0 +-82.48885345;28.85510063;0 +-81.8151474;29.95439911;0 +-81.35962677;26.15979958;0 +-81.81030273;27.18585014;0 +-83.17375183;29.55597496;0 +-81.71524811;30.34474945;0 +-81.3131485;29.46559906;0 +-84.78199768;29.82142448;0 +-82.81447601;29.74710083;0.003582791 +-81.21869659;26.98914909;0 +-85.21295166;29.93959999;0 +-81.80964661;27.49135017;0 +-81.22309875;26.60560036;0 +-82.36804962;28.56389999;0 +-81.25244904;27.33909988;0 +-82.35295105;27.90912437;0 +-80.60154724;27.70885086;0 +-83.84030151;30.37585068;0.008887643 +-83.12352753;30.04125023;0.00287838 +-81.654953;28.81122589;0 +-81.9177475;26.55285072;0 +-84.34625244;30.47929955;0 +-82.78434753;29.28790092;0.000838266 +-84.84470367;30.28874969;0 +-82.40035248;27.42675018;0 +-82.08760071;29.2378006;0 +-80.48314667;27.10980034;0 +-81.16349792;25.46039963;0 +-81.74434662;30.55055046;0 +-80.93887329;27.30080032;0 +-81.26129913;28.56615067;0 +-81.2594986;27.99477577;0 +-80.45862579;26.64559937;0 +-82.4306488;28.3247509;0 +-82.72290039;27.8960495;0 +-81.62439728;28.00225067;0 +-81.74524689;29.58180046;0 +-80.43975067;27.38184929;0 +-87.04820251;30.67485046;0 +-82.13269806;28.63607407;0 +-83.65585327;29.98344994;0 +-82.35534668;30.03289986;0 +-81.20704651;29.01989937;0 +-84.3946991;30.13647461;0 +-82.42774963;31.56380081;0.007676785 +-83.24324799;33.05754852;0 +-83.50174713;34.34484863;0 +-83.70314789;34.01192474;0 +-83.69114685;32.8083992;0 +-83.31855011;32.42810059;0.011500069 +-83.97064972;33.31470108;0 +-81.6664505;30.93995094;0 +-85.07389832;33.61925125;0 +-85.12245178;34.87779999;0.002889416 +-82.15955353;30.71139908;0 +-81.1174469;31.98114967;0 +-84.81860352;32.38272476;0 +-84.45835114;34.24295044;0 +-83.39084625;33.94544983;0 +-84.35150146;33.50059891;0 +-82.69494629;30.88297462;0.000965352 +-84.55719757;33.91025162;0 +-82.23239899;33.52157593;0 +-84.75624847;33.35105133;0 +-83.95645142;32.69485092;0 +-85.48445129;34.80479813;0 +-84.15184784;34.47582626;0 +-84.62057495;30.8845253;0 +-84.74220276;33.69004822;0 +-82.82089996;34.12265015;0 +-84.23242188;34.80939865;0 +-84.50644684;33.4041481;0 +-84.10489655;34.19279861;0 +-83.22515106;34.38010025;0 +-84.40937042;33.84429932;0 +-84.42434692;34.7016983;0 +-82.59400177;33.2242012;0 +-81.51605225;31.23349953;0 +-83.18977356;33.55830002;0 +-84.03775024;33.96025085;0 +-83.51010132;34.62712479;0 +-83.83914948;34.30609894;0.001190748 +-83.01129913;33.27434921;0 +-85.21179962;33.7791481;0 +-84.93855286;32.72757339;0 +-85.11634827;33.27875137;0 +-84.13899994;33.47254944;0 +-83.67745209;32.48735046;0.0066357 +-83.58769989;34.13314819;0 +-83.6984024;33.32894897;0 +-83.58737183;33.01229858;0 +-84.15579987;33.06710052;0 +-84.13044739;31.76869965;0.018445906 +-81.47805023;31.82209969;0 +-82.43240356;33.8138504;0 +-84.01454926;34.5719986;0 +-83.18945313;34.13605118;0 +-84.68084717;33.0340004;0 +-83.91777802;33.02529907;0 +-83.48120117;33.62604904;0 +-84.76574707;34.78572464;0 +-84.88764954;32.49150085;0 +-83.86270142;33.55619812;0 +-83.46130371;33.83200073;0 +-83.02999878;33.870224;0 +-84.88752747;33.92850113;0 +-84.45574951;34.46844864;0 +-84.38764954;33.08535004;0 +-83.3513031;33.3274231;0 +-85.02404785;31.88125038;0 +-83.38147736;34.85829926;0 +-82.103302;33.38802338;0 +-84.0496521;33.65594864;0 +-84.30594635;32.29282379;0.003728894 +-84.3035965;33.26554871;0 +-83.28205109;34.57320023;0 +-84.85557556;32.07649994;0.003747002 +-84.49479675;32.70069885;0 +-82.84719849;33.58824921;0 +-84.22754669;32.55960083;0.002483838 +-83.74199677;34.89260101;0 +-85.04904938;33.04639816;0 +-83.96894836;34.81779861;0.002179895 +-84.32814789;32.84154892;0 +-85.29270172;34.7859993;0.002099288 +-83.74424744;33.76340103;0 +-82.61795044;33.43090057;0 +-84.98329926;34.80187607;0 +-82.71882629;33.79199982;0 +-83.17667389;32.79354858;0 +-116.5115509;44.85634995;0 +-112.3122025;42.65399933;0 +-111.3354263;42.29932404;0 +-116.6811523;47.21855164;0 +-112.2967758;43.24494934;0 +-113.9973526;43.30445099;0 +-115.631073;43.96549988;0 +-116.5436478;48.36882401;0 +-111.7811966;43.32324982;0 +-116.5395966;48.75045013;0 +-113.2460785;43.7594986;0 +-114.7296982;43.52845001;0 +-111.5887527;42.71924973;0 +-113.6428223;42.33137512;0.000121764 +-112.3051758;44.27024841;0 +-115.524353;46.60914993;0 +-114.3069;44.22200012;0 +-111.8162537;42.21414948;0.000469621 +-111.6010971;44.31740189;0 +-115.5579529;45.88764954;0 +-112.1607513;43.84090042;0 +-116.6823502;47.67882538;0 +-116.683548;46.83604813;0 +-113.8152771;44.96360016;0 +-116.3767471;46.23300171;0 +-111.6871262;43.77560043;0 +-116.7057724;46.24380112;0 +-112.534874;42.25019836;0 +-112.8106232;42.71947479;0 +-116.0068588;47.49879837;0 +-111.2220001;43.7425499;0 +-115.4530258;44.67570114;0 +-116.7665253;44.4958992;0 +-86.2302475;39.19625092;0 +-86.46495056;38.27125168;0 +-85.90899658;38.2995491;0 +-84.99445343;38.96592331;0 +-84.99389648;38.81029892;0 +-95.62460327;43.08375168;0 +-98.67932129;37.23460007;0.002342145 +-96.24465179;37.15069962;0.002191479 +-96.24302673;37.45322418;0.010138423 +-98.20494843;38.69647598;0.002182537 +-96.67514801;39.30485153;0.00307604 +-98.76274872;38.91507339;0.001589558 +-94.75497437;39.09635162;0.025802563 +-84.98335266;38.00844955;0 +-83.69972229;36.76910019;0 +-82.69945526;38.3716011;0 +-83.26405334;37.51699829;0 +-84.36910248;38.95740128;0.006186077 +-83.05590057;38.3356514;0 +-83.0842514;38.12950134;0 +-82.75444794;37.51815033;0 +-84.63105011;38.63814926;0 +-82.91605377;38.5619545;0.005417353 +-83.1901474;36.8430481;0 +-83.99437714;37.41590118;0 +-82.80860138;37.85820007;0 +-84.52079773;38.94240189;0 +-82.91854858;37.34930038;0 +-82.75585175;38.07389832;0 +-83.71430206;37.60537338;0 +-83.36765289;37.10015106;0 +-82.84580231;37.11657333;0 +-84.50274658;36.77724838;0 +-83.07565308;37.69049835;0 +-83.60140228;37.93745041;0 +-83.24340057;37.91395187;0 +-84.8275528;38.53454971;0 +-83.70694733;37.3898735;0 +-83.27500153;37.22169876;0 +-82.35050201;37.47040176;0 +-83.81655121;37.82350159;0 +-84.05715179;38.50839996;0 +-83.47904968;37.74335098;0.003506823 +-70.23999786;44.19710159;0 +-68.30476379;46.51744843;0 +-70.36630249;43.85200119;0 +-70.3812027;45.06335068;0 +-68.37825012;44.68455124;0 +-69.75160217;44.41450119;0 +-69.24015045;44.11790085;0 +-69.53555298;44.07429886;0 +-70.65885162;44.56790161;0 +-68.64790344;45.51974869;0 +-69.28512573;45.79195023;0 +-69.87112427;43.91749954;0 +-69.91004944;45.58877563;0 +-69.15135193;44.50154877;0 +-67.52940369;45.04085159;0 +-70.65910339;43.43640137;0 +-70.51000214;41.77000046;0 +-73.22989655;42.39274979;0 +-71.10990143;41.78844833;0 +-70.64524841;41.39184952;0 +-70.92590332;42.64659882;0 +-72.62809753;42.52774811;0 +-72.6013031;42.17060089;0 +-72.63639832;42.3695488;0 +-71.46009827;42.44660187;0 +-70.02153778;41.31867599;0 +-71.20200348;42.1542511;0 +-70.80687714;41.96364975;0 +-71.05549622;42.33945084;0 +-71.89679718;42.36465073;0 +-83.59294891;44.68405151;0 +-86.49079895;46.4245491;0 +-88.33525085;46.67910004;0 +-84.4693985;45.49354935;0 +-84.37090302;46.35115051;0 +-84.61109924;44.68304825;0 +-87.87014771;45.98479843;0 +-89.70404816;46.43149948;0 +-88.60997772;46.85285187;0 +-88.55419922;46.17105103;0 +-85.09044647;44.68489838;0.001671063 +-88.11855316;47.3390007;0 +-85.80259705;43.99100113;0 +-85.55149841;46.49990082;0 +-84.98999786;46.04007339;0 +-86.27400208;43.99729919;0 +-83.3864975;42.65980148;0.005909674 +-86.26554871;43.64350128;0.029488769 +-89.37519836;46.68190002;0 +-84.12934875;44.68185043;0 +-84.61074829;44.33584976;0 +-85.5271759;41.91524887;0 +-83.21105194;42.23984909;0.005851583 +-93.43254852;46.5923996;0 +-95.00789642;47.97489929;0.000561317 +-92.67780304;46.59329987;0.000908082 +-94.28079987;46.87822342;0.002633079 +-90.27330017;47.85549927;0 +-94.92085266;47.10834885;0.01930978 +-93.73735046;47.46205139;0 +-93.75554657;48.27885056;0 +-91.4102478;47.57300186;0 +-94.88539886;48.87535095;0 +-93.10585022;45.00650024;0 +-91.40457153;31.47050095;0 +-90.8214035;31.17402458;0 +-89.27092743;33.32089996;0 +-88.67275238;32.02705002;0 +-90.42919922;31.8675251;0 +-89.5773468;31.61535072;0 +-89.29502869;31.17180061;0 +-90.89505005;31.46637535;0 +-88.63369751;31.21665001;0 +-89.50630188;30.41394997;0 +-89.09137726;30.49440002;0 +-88.63925171;30.52734947;0 +-89.11669922;32.01309967;0 +-91.02687836;31.74169922;0.00571584 +-89.78130341;31.58209991;0 +-89.17234802;31.62895012;0 +-88.63142395;32.75284958;0 +-89.50125122;31.20804977;0 +-88.65190125;32.40110016;0 +-90.10185242;31.54490089;0.006168288 +-89.52410126;32.75394821;0 +-90.49060059;31.52490044;0 +-89.84694672;31.21655083;0 +-89.11689758;32.7541008;0 +-89.11820221;32.40019989;0.00405472 +-88.87924957;33.42557526;0 +-89.59155273;30.73460007;0 +-88.98995209;31.1718502;0 +-90.40382385;31.17480087;0 +-89.03530121;34.22702408;0 +-89.52384949;32.00284958;0 +-89.11319733;30.77894974;0 +-88.2315979;34.72940063;0 +-90.04820251;31.17539978;0 +-88.69644928;31.66360092;0 +-91.34770203;31.18090057;0 +-89.06620026;33.10264969;0 +-92.7440033;38.04862595;0 +-90.94360352;36.95589828;0 +-93.2562027;36.95349884;0 +-91.31465149;37.95434952;0 +-93.01860046;37.65950012;0 +-91.48100281;37.60345078;0 +-92.49884796;36.93235016;0 +-93.31945038;37.93785095;0.005487545 +-91.88782501;36.77724838;0 +-90.65872192;37.5054512;0 +-92.57250214;39.82184982;0 +-91.39714813;36.69260025;0 +-92.44204712;36.65174866;0 +-91.77700043;37.87614822;0 +-93.40264893;37.62324905;0.004168628 +-92.21720123;37.8118515;0 +-91.02390289;37.32699966;0 +-91.33719635;37.15325165;0 +-93.11109924;40.20954895;0 +-91.95040131;37.32695007;0 +-94.33724976;37.84989929;0 +-90.44490051;37.11935043;0 +-90.23964691;38.65219879;0 +-112.7058029;45.14937592;0 +-107.481102;45.51874924;0 +-108.8939972;48.36724854;0 +-111.4241028;46.29795074;0 +-104.5386505;45.56755066;0 +-111.3413239;47.26119995;0 +-110.470047;47.86149979;0 +-105.5618515;48.78144836;0 +-113.0370026;46.01335144;0 +-104.4747467;46.2826004;0 +-109.2624283;47.24762344;0 +-113.9546967;48.29869843;0 +-111.2937469;45.3396492;0 +-107.0269012;47.40224838;0 +-113.1252975;48.65439987;0 +-109.2164993;46.39837646;0 +-113.4565735;46.38555145;0 +-110.1094742;48.56634903;0 +-112.1176529;46.15890121;0 +-110.2654037;47.0522995;0 +-112.3191528;47.18754959;0 +-111.0752487;48.56534958;0 +-115.3313522;48.45257568;0 +-105.8076248;47.59537506;0 +-110.9649277;46.63619995;0 +-115.0840988;47.09685135;0 +-114.0493011;47.11702347;0 +-108.3947983;46.44437408;0 +-110.4215469;45.59757614;0 +-108.2809982;47.17174911;0 +-108.0382996;48.22529984;0 +-112.3781509;48.23410034;0 +-105.6296997;45.39112473;0 +-112.8842239;46.93304825;0 +-114.0395966;46.08055115;0 +-115.1170502;47.69347382;0 +-104.5515976;48.69464874;0 +-112.6369476;45.9021492;0 +-109.8569489;45.69857407;0 +-112.2117996;47.81774902;0 +-111.7310028;48.60924911;0 +-106.6090469;48.33100128;0 +-109.8346481;46.48582458;0 +-104.3249512;47.01900101;0 +-101.746048;41.91927338;0 +-101.1335983;41.91630173;0 +-100.5554276;41.91415024;0 +-114.9714966;35.92777634;0 +-119.6593475;38.82424927;0 +-115.5318985;41.05950165;0 +-117.7955475;37.7228508;0 +-116.2046967;40.08095169;0 +-118.1675262;41.26435089;0 +-117.191597;40.04764938;0 +-114.9716492;37.75970078;0 +-119.2242966;39.06900024;0 +-118.4232483;38.48384857;0 +-116.5979233;37.58380127;0 +-118.3183517;40.48004913;0 +-119.4953003;39.43915176;0 +-119.5870514;40.58157349;0 +-114.9759521;39.40295029;0 +-119.7768021;39.16794968;0 +-71.44470215;43.52289963;0 +-71.26374817;43.88399887;0 +-72.23245239;42.94684982;0 +-71.38804626;44.69580078;0 +-71.83995056;43.96749878;0 +-71.6536026;42.95195007;0 +-71.6480484;43.31024933;0 +-71.07785034;43.00579834;0 +-71.03430176;43.32672501;0 +-72.19429779;43.36460114;0 +-74.64785004;39.51102448;0.001392429 +-74.08364868;40.95287323;0 +-74.93859863;39.80039978;0 +-74.75279999;39.12770081;0 +-74.24375153;40.79185104;0 +-74.07552338;40.72880173;0 +-74.28109741;40.27539825;0.008608086 +-74.29260254;39.83647537;0.000982272 +-74.30960083;41.01110077;0 +-74.29985046;40.66579819;0 +-106.672699;35.0442009;0 +-108.3785019;33.89094925;0 +-108.0557404;34.96300125;0 +-104.6909485;36.60699844;0 +-104.4189529;34.38734818;0 +-108.3271484;32.53644943;0 +-104.7184525;34.78177643;0 +-103.8998489;35.80390167;0 +-108.6283035;32.05485153;0.001133682 +-105.6285477;33.73934937;0 +-106.2854996;35.86100006;0 +-107.7626266;32.1943512;0 +-105.028923;36.01707458;0 +-105.6120224;32.69540024;0 +-103.5849991;35.17235184;0 +-106.5782013;36.46559906;0 +-104.6791534;35.45624924;0.000168494 +-106.9350967;35.71730042;0.003627118 +-108.2326736;36.50025177;0 +-105.9814987;35.52114868;0 +-107.1699982;33.04285049;0 +-106.8250504;34.02774811;0 +-105.6388016;36.50445175;0 +-106.8076477;34.69778061;0 +-73.84854889;40.85172653;0 +-73.8221283;44.14559937;0 +-74.15924835;42.28039932;0.002412196 +-74.45729828;43.66744995;0 +-73.94519806;40.65179825;0 +-73.60234833;40.75260162;0 +-73.97149658;40.78070068;0 +-73.75704956;41.42414856;0 +-73.83159637;40.67869949;0 +-74.15164948;40.57379913;0 +-74.06137848;41.16104889;0 +-73.82679749;43.52109909;0 +-73.73255157;41.12120056;0 +-81.1276474;36.46794891;0 +-81.48797607;36.41405106;0 +-81.91535187;36.09894943;0 +-82.5298996;35.62105179;0 +-83.74434662;35.07377625;0 +-75.74755096;35.71232605;0.000814693 +-78.85949707;36.05110168;0 +-81.20539856;35.28435135;0 +-83.81204987;35.34825134;0 +-83.00045013;35.54124832;0 +-83.13504791;35.25844955;0 +-82.05055237;35.74354935;0 +-83.42340088;35.16220093;0 +-80.80020142;35.25975037;0.00059379 +-82.12619019;35.99034882;0 +-79.89865112;35.32704926;0 +-77.97650146;35.96649933;0.007519388 +-77.87284851;34.1515007;0.003927261 +-77.88760376;34.5081749;0.019561085 +-82.16300201;35.29359818;0 +-79.76750183;34.99425125;0.001813104 +-81.97910309;35.39545059;0 +-83.56559753;35.48730087;0 +-78.40100098;36.3593483;0 +-81.68689728;36.25114822;0 +-82.31954956;35.88845062;0 +-102.4799728;48.77280045;0.000991138 +-101.5606537;46.37060165;0 +-102.377449;48.14899826;0.000771 +-100.8879623;46.18597412;0 +-80.96994781;40.01134872;0.002907788 +-81.67279816;41.4531517;0 +-80.77490234;40.37685013;0.005721906 +-81.24569702;41.71155167;0 +-82.55180359;38.63124847;0.004121241 +-81.07044983;39.7045517;0.003429995 +-81.46595001;39.76839828;0.005090852 +-94.64015198;35.89994812;0 +-98.32415009;36.73099899;0.000721168 +-96.03939819;34.4184761;0.00063887 +-99.68015289;35.26914978;0 +-98.42142487;35.85829926;0.000673179 +-97.9917984;35.52555084;0 +-97.24759674;34.28889847;0 +-95.03072357;35.89994812;0 +-96.30255127;34.59289932;0 +-98.39994812;34.28889847;0.000736287 +-96.32595062;35.90117645;0 +-94.78714752;36.41590118;0 +-99.00630188;35.98849869;0.000468689 +-97.78244781;36.37919998;0 +-99.56729889;34.92077637;0 +-99.46424866;34.57804871;0 +-97.83760071;34.06980133;0 +-96.66999817;34.31019974;0 +-99.01010132;34.85587311;0 +-95.22112274;34.86944962;0 +-94.74639893;34.94704819;0 +-97.24770355;33.89424896;0 +-94.80870056;34.06147385;0.001684137 +-97.08944702;34.48467636;0 +-96.30284882;35.46440125;0.003001152 +-96.52845001;36.58005142;0 +-96.6371994;36.35957336;0 +-95.72045135;34.94704819;0.000837519 +-96.66934967;34.73474884;0.00086849 +-95.35800171;34.41880035;0 +-99.68190002;35.71720123;0 +-96.6086731;35.1711998;0 +-97.85189819;34.48540115;0 +-96.02950287;36.13970184;0 +-98.99445343;36.69649887;0 +-99.2805481;36.4836998;0 +-117.6473007;44.66842651;0 +-123.4700012;44.49855042;0 +-122.2734756;45.17369843;0 +-123.6898727;46.02154922;0 +-123.0618515;45.94512558;0 +-124.0898514;43.13909912;0 +-120.3807983;44.13087463;0 +-124.1501465;42.47510147;0 +-121.4049606;44.0019989;0 +-123.1011734;43.32374954;0 +-120.2055511;45.43894958;0.000908377 +-118.947525;44.47832489;0 +-119.0666275;43.01990128;0 +-121.6801529;45.49224854;0 +-122.7553024;42.49987411;0 +-121.105751;44.60797501;0 +-123.6135254;42.38954926;0 +-121.5835037;42.80512619;0 +-120.3543472;42.80550003;0 +-122.9735031;43.86312485;0 +-122.5245972;44.49914932;0.000340833 +-122.8246231;44.97919846;0 +-122.0274048;45.58084869;0 +-123.3960037;44.89825058;0 +-120.6918716;45.41025162;0 +-123.6558533;45.41415024;0 +-117.9807968;45.40882492;0 +-117.22435;45.5401001;0 +-121.0860977;45.26345062;0 +-123.1134033;45.54875183;0 +-120.0722504;44.68785095;0 +-123.324501;45.25462341;0 +-75.4041214;39.93379974;0 +-75.35624695;40.21194839;0.020051954 +-75.12809753;40.00175095;0 +-71.28504944;41.71282578;0 +-71.5582962;41.68199921;0 +-71.26744843;41.56900024;0 +-71.55899811;41.87155151;0 +-71.63842773;41.47809982;0 +-82.49060059;34.24890137;0 +-80.71829987;32.39260101;0 +-79.85972595;32.85414886;0 +-81.62072754;35.01129913;0 +-81.17700195;34.68239975;0.001345776 +-81.09752655;34.38774872;0 +-82.45475006;34.84095001;0 +-82.10234833;34.18164825;0 +-81.05370331;32.39944839;0.002143282 +-80.65799713;34.76725006;0.001565375 +-81.9789505;34.49404907;0 +-83.07460022;34.76404953;0.001124262 +-82.71105194;34.86605072;0 +-81.63582611;34.69507599;0 +-103.4024734;43.66695023;0.000200618 +-103.5274734;43.23947525;0.000898061 +-103.4925766;45.57907486;0 +-101.6011276;43.69165039;0.00016719 +-103.7537537;44.37267685;0 +-100.5313721;44.47392654;0 +-84.1914978;36.09024811;0 +-84.13929749;36.38650131;0 +-82.13665009;36.30554962;0 +-85.54489899;36.5149498;0 +-83.10250092;35.94794846;0 +-86.7854538;36.1868515;0 +-85.21019745;35.22100067;0 +-83.15135193;36.49634933;0 +-87.74729919;36.27285004;0 +-85.66275024;36.36687469;0 +-83.96837616;35.99570084;0 +-87.49620056;35.52880096;0 +-84.3585968;35.75379944;0 +-84.625;36.14165115;0 +-85.2901001;36.33819962;0 +-85.152771;36.51677322;0 +-84.52424622;35.84654999;0 +-84.51734924;36.38335037;0 +-83.52435303;35.80052567;0 +-82.41862488;36.11505127;0 +-83.83644867;36.2969017;0 +-85.42814636;35.67504883;0 +-95.66259766;31.79409981;0 +-102.6375504;32.30502319;0 +-94.56614685;31.27625084;0 +-97.02494812;28.07519913;0 +-98.68710327;33.61560059;0 +-101.3571243;34.9654007;0 +-102.8309021;34.0686264;0.007182257 +-99.19094849;29.7622757;0 +-99.21405029;33.61572647;0 +-98.35829926;30.21980095;0 +-101.4319;32.74485016;0 +-97.64064789;31.89565086;0.002053469 +-95.46459961;29.20622444;0.002817549 +-103.0609512;29.81615067;0 +-101.2077026;34.53020096;0 +-98.2539978;27.02280045;0 +-98.93454742;31.76989937;0 +-98.14147186;30.73040009;0 +-99.37227631;32.29724884;0 +-94.93579865;32.98905182;0.00949319 +-94.34822845;33.08644867;0 +-94.66664886;29.70770073;0.002346448 +-95.16365051;31.78204918;0 +-100.207222;34.53002167;0 +-98.20024872;33.80965042;0 +-102.8248978;33.60649872;0 +-100.5299988;31.88949966;0 +-99.45880127;31.76062584;0 +-100.2699509;34.96500015;0 +-98.32219696;29.81595039;0.003617864 +-98.5401535;31.97444916;0.000833164 +-99.85814667;31.33349991;0 +-100.2575989;34.07462311;0 +-102.5338287;31.36879921;0 +-101.6750488;30.68704987;0 +-101.3006516;33.61437607;0.000868528 +-104.4718018;31.33200073;0 +-96.7772522;32.76689911;0.007638415 +-101.9468994;32.74212646;0 +-97.11599731;33.20745087;0.031015411 +-97.36564636;29.09869957;0.007385711 +-100.7787781;33.61634827;0 +-99.7559967;28.42194939;0.001408811 +-98.51730347;27.65992546;0.000697268 +-98.79644775;32.29650116;0 +-102.5426483;31.86902428;0 +-100.2270966;29.95605087;0 +-96.73482513;32.30049896;0.003855702 +-98.2039032;32.21495056;0.000287666 +-100.4012985;32.74185181;0 +-99.76119995;33.98775101;0 +-95.21682739;33.17570114;0 +-96.14179993;31.71199989;0 +-102.6334;32.74095154;0 +-94.97724915;29.31710052;0 +-101.2976227;33.17910004;0 +-98.94550323;30.31679916;0.001914359 +-101.5197983;31.86919975;0 +-94.78237152;32.51554871;0 +-95.98842621;30.54439926;0.003150202 +-100.680603;34.53020096;0 +-98.11454773;31.71705055;0 +-99.7364502;34.32125092;0 +-94.40094757;30.31979942;0 +-94.37245178;32.5617981;0 +-99.73075104;33.17699814;0 +-98.00279999;30.05425072;0.007837048 +-100.2701721;35.83750153;0 +-95.9408493;32.18235016;0 +-102.3449478;33.60672379;0 +-97.84182739;32.39775085;0 +-95.58544922;33.16884995;0 +-101.4341965;32.30590057;0 +-105.4522018;31.31575012;0 +-100.9813004;31.30380058;0 +-98.17230225;33.23455048;0 +-93.99437714;30.69985008;0 +-104.2106018;30.75855064;0 +-94.13500214;29.8641243;0.001210503 +-98.68585205;27.07027435;0 +-99.87879944;32.73675156;0 +-98.6671524;29.92939949;0 +-97.63665009;26.94034958;0 +-100.7776718;33.17869949;0 +-99.33719635;30.03569984;0 +-99.70887756;30.4986496;0 +-100.2545013;33.6162262;0 +-100.4529037;29.35384941;0 +-97.64074707;27.42205048;0.007534703 +-99.73404694;33.61642456;0 +-99.10427856;28.33855057;0.00087776 +-95.58294678;33.66025162;0 +-96.00875092;31.31147575;0 +-98.65679932;30.70375061;0 +-103.6527023;31.82589912;0 +-101.8208466;33.60960007;0 +-101.8158264;33.17734909;0 +-99.34725189;31.21725082;0 +-97.20290375;31.55355072;0 +-98.56867218;28.35415077;0 +-95.92925262;30.95800018;0 +-101.9496002;32.30585098;0 +-99.79969788;30.89900017;0 +-102.03125;31.86910057;0 +-98.6269989;31.47775078;0 +-100.922699;32.30657578;0 +-97.73169708;33.70199966;0 +-95.46330261;30.33665085;0 +-94.73574829;33.1216011;0 +-100.7790985;34.07344818;0 +-94.63574982;31.53520012;0 +-93.73200226;30.7139492;0 +-100.4054031;32.30334854;0 +-102.6020966;35.40530014;0 +-93.90879822;30.05464935;0 +-98.31572723;32.75960159;0 +-94.30705261;32.18294907;0 +-97.80535126;32.7792511;0 +-102.6782532;30.71190071;0 +-94.8690033;30.81780052;0 +-101.8946991;35.401474;0 +-104.3859482;29.94574928;0 +-95.79035187;32.85012436;0 +-101.8955536;34.96530151;0.001537843 +-101.5211029;31.36517525;0 +-99.81414795;29.85284996;0 +-95.02262878;33.63047409;0.012795426 +-103.5560989;31.38145065;0 +-97.1651001;28.30694962;0.030227146 +-100.8125;35.83847427;0.003719876 +-96.40775299;32.89749908;0.031543598 +-99.97470093;31.82970047;0 +-94.7193985;32.12009811;0 +-93.82225037;31.36425018;0 +-94.19139862;31.37594986;0 +-95.10334778;30.62540054;0 +-98.76584625;31.2010994;0 +-100.5386963;30.89690018;0 +-100.9145966;32.7458992;0 +-99.35377502;32.73574829;0 +-94.17275238;31.77395058;0 +-95.28959656;32.41109848;0 +-97.78005219;32.20289993;0 +-98.74485016;26.5177002;0.000383273 +-98.83550262;32.73592377;0 +-101.0443497;31.82169914;0 +-100.2555237;33.17805099;0 +-100.538002;30.49880028;0 +-97.29105377;32.77119827;0 +-99.89035034;32.30149841;0 +-102.1132736;30.21805;0 +-102.3345718;33.17317581;0 +-99.21140289;33.17774963;0 +-94.96704865;33.18842316;0 +-95.1385498;31.10564995;0 +-94.36170197;30.79224968;0 +-94.92735291;32.71295166;0 +-102.0460968;31.36560059;0 +-101.2298508;29.76515007;0 +-95.76255035;32.59659958;0 +-95.59480286;30.78100014;0.003174805 +-103.1884003;31.47260094;0 +-96.44002533;30.21184921;0.004103608 +-99.50489807;27.7322998;0 +-100.2690506;35.40119934;0 +-97.61370087;26.46224976;0.002356791 +-103.0624008;31.86907578;0 +-97.65180206;33.21250153;0 +-95.40850067;32.7806015;0 +-102.8289261;33.17347336;0 +-98.68710327;33.17532349;0 +-99.20379639;26.94470024;0 +-99.76074982;28.86569977;0.005294652 +-109.5239716;40.8273735;0 +-111.3074036;37.84489822;0 +-109.6030502;38.99879837;0 +-111.7737503;37.27214813;0 +-111.5625;41.08477402;0 +-112.1426468;38.32884979;0 +-111.2778015;41.56964874;0 +-111.9059525;40.67369843;0 +-110.2266464;37.74909973;0 +-110.8199997;40.91557312;0 +-113.1057739;40.49005127;0 +-111.2419968;40.28915024;0 +-113.4753494;37.30905151;0 +-110.8664474;38.3301506;0 +-73.08959961;44.02835083;0 +-73.05139923;43.02674866;0 +-72.13600159;44.4632988;0 +-73.0828476;44.44200134;0 +-71.73535156;44.68090057;0 +-72.89167786;44.82440186;0 +-73.2901535;44.78850174;0 +-72.64649963;44.5998497;0 +-72.41480255;43.99610138;0 +-72.22814941;44.77590179;0 +-73.06140137;43.57500076;0 +-72.58535004;44.25944901;0 +-72.72045135;42.99470139;0 +-72.59455109;43.59189987;0 +-79.96520233;37.78377533;0.001757079 +-79.15254974;37.59894943;0 +-77.10894775;38.88755035;0 +-79.7492981;38.0522995;0 +-82.02719879;37.28860092;0 +-80.23674774;37.48815155;0 +-82.35150146;37.12590027;0 +-77.20651245;38.83769989;0 +-80.71555328;37.30687332;0 +-78.47525024;38.3301506;0 +-79.75827026;36.69889832;0 +-79.55404663;38.38589859;0 +-78.90809631;37.79470062;0 +-79.87618256;37.26774979;0 +-82.09384918;36.92584991;0 +-78.19975281;38.89759827;0.005118156 +-82.58850098;37.00125122;0 +-76.56755066;37.23744965;0 +-77.20651245;38.83769989;0 +-79.87618256;37.26774979;0 +-117.1966019;46.22919846;0 +-119.4205017;46.28205109;0 +-120.5125732;47.89955139;0 +-123.8416519;48.13022614;0 +-117.9219513;46.31209946;0 +-122.7273026;46.11940002;0 +-119.6288757;47.66785049;0 +-118.4929276;48.43717575;0 +-117.5451508;46.34407425;0 +-123.7559509;47.16247559;0 +-122.6033173;48.16009903;0 +-123.6296005;47.82139969;0 +-122.7356262;47.63914871;0 +-120.682724;47.16682434;0 +-120.7379532;45.82595062;0 +-122.3979034;46.58914948;0 +-118.3983536;47.60914993;0 +-123.1514282;47.3451004;0 +-119.8595505;48.47202682;0 +-123.7262497;46.5177002;0 +-117.3296967;48.52299881;0 +-122.1117477;47.06850052;0 +-122.9624481;48.56072617;0 +-121.8820038;45.96865082;0 +-117.4302521;47.65370178;0 +-117.9016724;48.41287613;0 +-122.7014008;46.97845078;0 +-123.4692001;46.26615143;0 +-118.5158005;46.30067444;0 +-117.6288528;46.84270096;0 +-80.01875305;39.12319946;0 +-81.71309662;37.99900055;0 +-80.74144745;38.70555115;0 +-80.5997467;40.27980042;0 +-82.27365112;38.41384888;0 +-81.13179779;38.82585144;0 +-81.05110168;38.46554947;0 +-80.72109985;39.2730484;0 +-81.06500244;38.04114914;0 +-80.81994629;38.91350174;0 +-79.23370361;39.07049942;0 +-80.4213028;37.97660065;0 +-78.6638031;39.31389999;0 +-80.59349823;40.51670074;0 +-80.38150024;39.28509903;0 +-81.70680237;38.82645035;0 +-81.55484772;38.29778671;0 +-80.51464844;38.94725037;0 +-82.05470276;38.15930176;0 +-81.89914703;37.83480072;0 +-80.21679688;39.51160049;0 +-80.69047546;39.8769989;0 +-81.10590363;37.41360092;0 +-78.96514893;39.43315125;0 +-82.10825348;37.74335098;0 +-80.09324646;39.57844925;0 +-80.53994751;37.55189896;0.002640641 +-78.24555206;39.53824997;0 +-80.83409882;38.30270004;0 +-80.62550354;40.10039902;0 +-79.35340118;38.68460083;0.002462851 +-81.18979645;39.36285019;0 +-79.99420166;38.3818512;0 +-81.88064575;38.47592545;0 +-81.24150085;37.74874878;0 +-79.81510162;38.75194931;0 +-81.0556488;39.1922493;0 +-81.31529999;38.72014999;0 +-80.87750244;37.64844894;0 +-80.04850006;39.34524918;0 +-79.57324982;39.11780167;0 +-80.87020111;39.45055008;0 +-80.22667694;38.90465164;0 +-82.43460083;38.13857651;0 +-80.43090057;38.48244858;0 +-80.66999817;39.57524872;0 +-81.37197876;39.04090118;0 +-81.49810028;39.20055008;0 +-81.53919983;37.60660172;0 +-90.61440277;46.34104919;0 +-91.16145325;46.55722427;0 +-91.92195129;46.4565506;0 +-88.37110138;45.86370087;0 +-88.73664856;45.72534943;0 +-90.23970032;46.28457642;0 +-89.5454483;45.68199921;0 +-89.48815155;46.07839966;0 +-105.7952728;41.74229813;0 +-105.5462265;44.24772644;0 +-106.9998779;41.71694946;0 +-105.4845505;42.89442444;0 +-104.5715027;44.58840179;0 +-108.7768478;43.13479996;0 +-108.4534988;43.76967621;0 +-106.6884537;44.02774811;0 +-110.5459976;42.44677353;0 +-106.8064499;42.96617508;0 +-104.4781494;43.0461998;0.000297527 +-106.9599991;44.77902603;0 +-109.8338776;42.8641243;0 +-108.77845;41.63399887;0 +-110.5525513;43.95159912;0 +-110.5467758;41.2873497;0 +-104.5677032;43.83959961;0 +-79.51689911;37.32115173;0.136066763 +-79.91410065;37.00320053;0.355538922 +-96.67050171;44.02225113;0.306985683 +-97.75624847;43.33387375;0.272584951 +-98.14459991;43.67520142;0.183004547 +-97.490448;44.36967468;0.159535766 +-76.29844666;40.01839828;0.117221251 +-76.41539764;40.37530136;0.111835378 +-77.7766037;40.00479889;0.042305226 +-78.22190094;42.69475174;0.027407195 +-94.05590057;45.6916008;0.201684506 +-100.0701523;45.42024994;0.046137028 +-100.8709259;37.56217575;0.296603564 +-74.42404938;42.91009903;0.037439479 +-75.62664795;38.39447403;0.047747413 +-78.18515015;42.9978981;0.071783105 +-94.20845032;46.06135178;0.104351934 +-87.57039642;44.5019989;0.091659614 +-94.59339905;45.52904892;0.1900074 +-97.71869659;47.4557991;0.066241086 +-114.839447;42.9239006;0.008980131 +-81.88874817;40.82934952;0.12829516 +-81.93495178;40.55614853;0.090132154 +-88.0075531;44.46030045;0.091915242 +-120.9292984;37.60620117;0.001882795 +-73.44345093;43.37464905;0.01402704 +-103.8069992;40.26269913;0.079014829 +-97.78714752;43.67490005;0.247981645 +-77.08255005;40.97790146;0.057225397 +-76.50295258;43.01860046;0.082049396 +-75.88829803;39.54389954;0.107499499 +-88.22290039;44.06784821;0.141183873 +-77.23970032;40.13660049;0.054244597 +-89.42377472;43.07009888;0.21658596 +-120.6414032;37.18704987;0.006157029 +-87.78025055;44.10962677;0.100375298 +-79.20079803;39.46244812;0.052092762 +-78.85545349;38.52819824;0.016521765 +-114.2736969;42.67364883;0.002865347 +-77.07855225;40.76377487;0.064160628 +-75.61250305;40.60295105;0.144684273 +-86.02729797;42.98675156;0.115188279 +-97.4878006;27.77702522;0.046361657 +-77.87619781;39.31275177;0.078300243 +-102.3577499;40.59415054;0.149973823 +-91.68160248;44.01990128;0.17788001 +-121.2508011;37.89110184;0.052482348 +-94.89949799;46.0713501;0.095871107 +-98.09037781;44.02327347;0.169428042 +-78.76550293;43.19697571;0.076190362 +-97.60175323;30.65670013;0.153359548 +-78.36840057;40.49295044;0.01812714 +-75.33239746;40.75249863;0.120385233 +-76.19155121;43.01224899;0.051062746 +-119.8937988;36.13874817;0.003931552 +-75.74894714;39.98160172;0.0607663 +-87.92772675;43.36762619;0.08218366 +-88.52249908;43.74034882;0.168589451 +-88.22924805;43.36782455;0.095384964 +-92.47022247;44.7011261;0.183220871 +-90.6187973;44.72795105;0.070693215 +-83.03820038;43.87039948;0.184917874 +-118.7862473;36.26660156;0.00097171 +-77.58384705;40.61085129;0.043628296 +-91.1140976;45.89749908;0.072828316 +-88.7045517;43.41410065;0.218007605 +-76.52144623;41.77199936;0.013443255 +-80.90254974;35.78229904;0.008144101 +-98.56402588;43.71910095;0.061116171 +-96.56945038;33.19329834;0.065076514 +-82.501297;36.26679993;0.010055622 +-75.98529816;40.40707397;0.070404189 +-116.7490997;43.58549881;0.038418656 +-94.94725037;46.5870018;0.051399948 +-80.66020203;36.16659927;0.037252272 +-88.58339691;39.06354904;0.308778985 +-75.61994934;42.95365143;0.025729576 +-98.01625061;27.65914917;0.039581213 +-76.06954956;42.59925079;0.013446913 +-99.34745026;39.7846489;0.016044086 +-88.05265045;38.41289902;0.274763886 +-77.13542175;42.61175156;0.04665696 +-75.91560364;44.03570175;0.013387243 +-100.4766464;40.17657471;0.084376962 +-77.92549896;38.5033989;0.025426509 +-79.41112518;42.28424835;0.013535144 +-93.59417725;44.67630005;0.168656078 +-75.48214722;43.23970032;0.024093964 +-93.28585052;45.94509888;0.043160517 +-93.43184662;39.41094971;0.170482353 +-76.32620239;42.20422363;0.006928512 +-88.6446991;44.06855011;0.146067992 +-77.50330353;40.47864914;0.035113256 +-77.96724701;40.40240097;0.015195069 +-73.64150238;42.2439003;0.015484168 +-96.96547699;30.78384972;0.087906089 +-79.91410065;37.00320053;0.004289605 +-78.12239838;39.9435997;0.013926343 +-88.73345184;44.80720139;0.104951522 +-85.09377289;44.33705139;0.01681738 +-90.13334656;43.00954819;0.115957754 +-76.47154999;42.44490051;0.027898855 +-97.10614777;31.9877491;0.059424228 +-79.03829956;40.0033493;0.02079146 +-75.47944641;43.81880188;0.004900347 +-88.05419922;42.58107376;0.184409706 +-74.98329926;40.84315109;0.077716769 +-92.8830719;45.02140045;0.088556293 +-97.52014923;28.0115242;0.038630449 +-97.6101532;44.02199936;0.150677666 +-92.52014923;45.46887589;0.08977046 +-77.35749817;42.28955078;0.019073486 +-75.02324677;42.61135101;0.008569755 +-101.7837524;37.5632515;0.133501838 +-78.03645325;38.25094986;0.026979075 +-101.0414734;40.17652512;0.053694914 +-75.59320068;42.4701004;0.006638826 +-103.3908005;34.62827682;0.007223763 +-94.80390167;30.19034958;0.019671375 +-92.08083344;44.54589844;0.152698252 +-90.78974915;43.57695007;0.095731063 +-104.3121033;40.50102615;0.027786823 +-90.38230133;39.31999969;0.381497542 +-97.73245239;28.40997505;0.040654044 +-73.68309784;44.72014999;0.00676765 +-91.84752655;45.4233017;0.109014997 +-78.79982758;42.76480103;0.009875887 +-98.62909698;44.0658493;0.050421298 +-116.7169037;43.97634888;0.018787421 +-81.06005096;36.91790009;0.001686491 +-95.7568512;29.52535057;0.031798797 +-86.09747314;35.17549896;0.064808303 +-76.86329651;42.39025116;0.107722138 +-74.0583725;42.83314896;0.011555095 +-81.36775208;40.81159973;0.067800361 +-77.99024963;39.12194824;0.024770694 +-91.47405243;43.6739769;0.139066725 +-90.64234924;43.94329834;0.077537829 +-106.3127975;31.69482422;0.057851284 +-77.06900024;37.70434952;0.059565951 +-102.6047974;39.30607605;0.061824057 +-90.43157196;43.35945129;0.072234306 +-82.53215027;40.77280045;0.088992815 +-78.68492126;42.26824951;0.006799443 +-99.89189911;37.69052505;0.062016386 +-89.76987457;44.90084839;0.063209565 +-78.27410126;38.43054962;0.022847831 +-85.55059814;43.03134918;0.065882068 +-81.88569641;31.57670021;0.015512053 +-76.77980042;42.78255081;0.001803601 +-74.35434723;41.38750076;0.004593674 +-76.77915192;40.38904953;0.043428392 +-92.45302582;45.03414917;0.12703252 +-82.50330353;35.32265091;0.007937834 +-93.62020111;45.90295029;0.051678068 +-76.00225067;41.51464844;0.010227845 +-93.88839722;45.20019913;0.16340333 +-119.78125;37.27455139;0.00409671 +-99.10482788;43.71709824;0.014690888 +-85.80059814;43.5542984;0.035057338 +-88.46472168;44.41635132;0.134694069 +-91.16725159;43.90245056;0.100775279 +-81.2023468;36.21879959;0.002476261 +-97.49185181;31.03610039;0.104538227 +-77.759552;40.97219849;0.023842469 +-93.82922363;45.40304947;0.106721293 +-75.74472809;42.20664978;0.004863332 +-75.19329834;44.52740097;0.007313633 +-76.00350189;39.23550034;0.044635772 +-87.97914886;42.3240509;0.045384976 +-121.9621506;38.61965179;0.028836095 +-85.28504944;37.11790085;0.011137671 +-77.28659821;42.80547333;0.062802616 +-77.24289703;41.7717514;0.008544803 +-81.17359924;35.9123497;0.002402183 +-84.86039734;37.82440186;0.013699586 +-76.32319641;39.51015091;0.010851364 +-77.8309021;38.71165085;0.016834693 +-101.3326721;47.13880157;0.006262785 +-77.21365356;39.89474869;0.062799273 +-100.4394989;37.73867416;0.138824951 +-93.98370361;39.65705109;0.07424186 +-80.72145081;37.06190109;0 +-80.06504822;42.05924988;0.038772679 +-73.52604675;42.70397568;0.015291455 +-78.47174835;40.02460098;0.017557737 +-99.01667786;43.24895096;0.030298593 +-89.53410339;44.46474838;0.070924954 +-78.01644897;42.25987625;0.006218332 +-97.9720993;29.61750031;0.050535056 +-83.71700287;38.35639954;0.011133993 +-81.54940033;36.82624817;0 +-75.85945129;38.88904953;0.03848701 +-84.21035004;38.21929932;0.018229077 +-83.00710297;30.15925026;0.005226208 +-121.4436493;38.38034821;0.038833946 +-79.13085175;38.18212509;0.01304284 +-92.71677399;46.07474899;0.016720823 +-121.7280273;48.82182312;0 +-92.45829773;45.8987999;0.024725509 +-75.80505371;41.82020187;0.002658411 +-81.19722748;41.53134918;0.015870411 +-77.45944977;37.77330017;0.039667428 +-79.40384674;36.83570099;0.004506778 +-78.02719879;39.44200134;0.014596438 +-85.95294952;36.94615173;0.026732794 +-80.20490265;33.69269943;0.090063569 +-96.15104675;38.45500183;0.03195259 +-87.89369965;41.46519852;0.219736481 +-81.48870087;40.4334259;0.039641989 +-101.8924484;35.8376503;0.112021486 +-93.76634979;44.80807495;0.251634971 +-101.3077469;37.56207657;0.095403664 +-77.03929901;43.17802429;0.063616127 +-94.83174896;38.89780045;0.029822455 +-75.10424805;40.32844925;0.043990171 +-88.04454803;42.72717667;0.184347793 +-97.7710495;30.3240509;0.023217174 +-74.959198;43.46089935;0.004982285 +-88.30259705;43.0190506;0.084933763 +-83.27459717;36.1875;0.014554741 +-84.6006012;42.9442482;0.155577381 +-95.28250122;38.56432343;0.068343208 +-79.45165253;40.85025024;0.025088695 +-77.9457016;37.34529877;0.008758408 +-78.70259857;40.484375;0.020439086 +-116.2430496;43.46039963;0.002517796 +-84.23077393;30.87730026;0.050478041 +-90.79149628;42.85800171;0.170291197 +-73.97104645;42.60485077;0.004477505 +-89.73590088;45.33752441;0.006368165 +-97.65032196;39.82785034;0.076338675 +-84.12680054;44.33485031;0.021597554 +-85.70760345;36.72847366;0.00425093 +-97.08719635;39.78412628;0.019303864 +-79.78990173;36.07835007;0.006496181 +-85.05535126;37.00659943;0.019105234 +-85.32575226;43.98934937;0.010490653 +-100.7012024;43.94284821;0.003863908 +-99.8376236;48.77217484;0.010734667 +-84.84604645;37.62459946;0.006871908 +-77.29364777;40.40274811;0.02371188 +-85.44764709;35.93529892;0.004979022 +-91.29419708;45.07412338;0.096184298 +-80.80290222;41.0170517;0.044024684 +-101.5264969;44.37115097;0.001551322 +-89.38339996;43.81245041;0.091590924 +-82.29940033;31.71859932;0.008603567 +-86.1256485;43.2947998;0.03345233 +-77.76415253;42.72977448;0.066244285 +-81.23925018;35.48495102;0.00418354 +-81.96920013;36.76150131;0 +-102.3358994;41.11222458;0.049017936 +-83.08580017;39.07284927;0.02406623 +-79.45414734;41.20249939;0.019452145 +-84.87174988;34.50704956;0.021094673 +-79.92774963;40.9211998;0.028335954 +-91.7963028;45.89787674;0.018139882 +-91.80619812;44.31100082;0.116193916 +-97.6053772;29.85680008;0.023764041 +-83.45400238;36.03620148;0.006847814 +-97.88240051;28.94459915;0.024781093 +-80.39254761;37.17675018;0 +-101.0554962;39.35065079;0.104381827 +-76.65670013;41.02730179;0.064531549 +-111.7887268;41.68495178;0.000805053 +-74.92559814;42.18299866;0.002052552 +-77.61904907;41.22055054;0.010523421 +-75.73629761;40.93024826;0.008201239 +-102.4787521;45.49147415;0.000108824 +-80.54154968;35.34590149;0.012863964 +-76.97104645;38.48952484;0.084379399 +-76.75154877;42.14704895;0.00803893 +-97.36837769;43.67440033;0.13709249 +-73.74365234;41.75970078;0.01150058 +-81.19824982;41.16799927;0.046964804 +-76.88005066;38.83375168;0.035316435 +-78.96320343;41.14044952;0.013348996 +-80.1921463;40.21744919;0.009844357 +-79.8042984;35.71310043;0.008533263 +-76.59004974;34.92375183;0.061617517 +-87.33294678;30.63694954;0.007784781 +-94.9815979;39.19150162;0.053289658 +-96.52449799;29.60485077;0.023852485 +-84.86125183;35.17264938;0.002376643 +-111.9562531;41.24985123;0.001628905 +-103.4955215;34.08747482;0.001276292 +-92.85385132;38.43907547;0.016215832 +-103.115799;40.71889877;0.03654434 +-77.02394867;41.33272552;0.024674049 +-84.54634857;37.6440239;0.006757926 +-80.37850189;36.91579819;0 +-91.28562164;44.72674942;0.093116806 +-74.42894745;43.13579941;0.002519298 +-96.83875275;37.23762512;0.006936799 +-80.33242798;40.66564941;0.012594712 +-112.1154022;40.96110153;0.00564487 +-74.31819916;44.55255127;0.001532342 +-95.63300323;41.03082657;0.3633055 +-92.20449829;44.28115082;0.2202339 +-99.89794922;38.08760071;0.013627721 +-75.27570343;41.61669922;0.00107135 +-85.83920288;31.09630013;0.016539269 +-91.10929871;45.46545029;0.0162559 +-98.07185364;28.42144966;0.010102046 +-79.26439667;41.81162643;0.003006678 +-98.90977478;34.37832642;0.008422021 +-79.41192627;37.80934906;0.00390866 +-95.40799713;35.55997467;0.016319039 +-83.73744965;38.14697647;0.014536964 +-96.58370209;31.51754951;0.022005105 +-74.43840027;42.59230042;0.01426029 +-84.24344635;35.43344879;0.002214881 +-79.78645325;37.55405045;0.004031069 +-77.8927536;37.53839874;0.004186122 +-75.8877182;36.28860092;0.068064578 +-121.7008514;48.47665024;0 +-81.90615082;40.30405045;0.060387009 +-77.90274811;41.73757553;0.00216779 +-83.55075073;30.85754967;0.020575037 +-78.26820374;37.52827454;0 +-76.62065125;39.28445053;0.127115734 +-97.88175201;35.02954865;0.00269651 +-80.74984741;36.74480057;0 +-102.6016235;36.27795029;0.135929161 +-82.96057129;38.79315186;0.020160632 +-82.87174988;36.18180084;0.003769966 +-78.47669983;38.62215042;0.010044033 +-102.4962006;46.11362457;0.006484649 +-88.77455139;43.02017593;0.213191864 +-94.85085297;37.85540009;0.019375206 +-88.35057831;34.27595139;0.013208938 +-76.46295166;37.73007584;0.061025988 +-79.39622498;36.04650116;0.005450708 +-96.90019989;29.3478508;0.009665945 +-98.46344757;29.43320084;0.01478762 +-91.35504913;44.29072571;0.134522123 +-79.51689911;37.32115173;0.001242545 +-78.23104858;43.25234985;0.094612289 +-107.33815;46.17634964;0.003032865 +-79.13379669;37.22645187;0.003097249 +-84.02124786;38.32574844;0.007947205 +-77.46252441;38.7228508;0.004625792 +-81.09140015;40.57690048;0.025733524 +-90.02112579;44.46644974;0.047696082 +-83.74930573;41.57374954;0.119782794 +-94.07710266;46.48080063;0.009093691 +-94.845047;38.2132988;0.027926487 +-79.10759735;36.0522995;0.002735469 +-76.74880219;37.31034851;0.017492303 +-118.9042511;35.30452347;0.000307089 +-85.10835266;38.46620178;0.014581605 +-85.60929871;37.00025177;0.00698264 +-81.25797272;36.68259811;0 +-86.04634857;35.80860138;0.030586508 +-80.80325317;40.75624847;0.044017391 +-81.15865326;37.12704849;0 +-108.1947479;45.9782486;0.000830124 +-76.51820374;41.43284988;0.003472685 +-85.27565002;37.57115173;0.018793289 +-106.8195038;32.42020035;0 +-81.44544983;39.43090057;0.022386444 +-85.23204803;30.78317451;0.01143326 +-82.33920288;38.80889893;0.006999552 +-81.4776001;40.03079987;0.010179153 +-76.18695068;43.43109894;0.00491713 +-83.92584991;35.67375183;0.006154218 +-119.6395035;36.74649811;0.001310222 +-88.70809937;37.08420181;0.004609354 +-84.74559784;38.02125168;0.012291536 +-96.11552429;33.60689926;0.025415054 +-78.45664978;37.23849869;0 +-77.64550018;39.08599854;0.018034048 +-93.26560211;45.22517395;0.029508274 +-87.55525208;45.54024887;0.006588278 +-79.6522522;39.93080139;0.01680873 +-84.84179688;38.75960159;0.014231859 +-81.54599762;35.37155151;0.001008867 +-114.5181503;42.45629883;0.00267853 +-81.6309967;34.30407333;0.001486279 +-89.03199768;45.24959946;0.0100267 +-82.93714905;32.43135071;0.009425745 +-84.06710052;38.69055176;0.006919841 +-98.20890045;39.04510117;0.00304297 +-78.28874969;39.23690033;0.00753684 +-85.9076004;37.29914856;0.008640276 +-104.4311523;33.3036499;0 +-75.6413269;41.40190125;0.003406886 +-84.57119751;38.3049469;0.010975695 +-85.85765076;41.59764862;0.150250102 +-90.93560028;43.20624924;0.066844628 +-84.57804871;39.44094849;0.095969675 +-81.63259888;37.375;0.013387171 +-79.68840027;39.45800018;0.004579143 +-84.53420258;35.13657379;0.005746466 +-82.98605347;30.47687531;0.014870547 +-87.35189819;44.98690033;0.0362529 +-84.65350342;37.43629837;0.021839499 +-82.25379944;36.50624847;0.003026488 +-81.83350372;39.6100502;0.011597352 +-121.692749;48.03739929;0 +-84.32845306;38.43035126;0.012109274 +-85.81694794;44.96327591;0.013003271 +-100.4411469;39.35010147;0.105112622 +-78.38079834;36.71620178;0.002754716 +-82.03414917;39.03950119;0.009096572 +-102.621727;47.40205002;0.002254802 +-85.13549805;36.74520111;0.007121696 +-95.9924469;29.9864502;0.026466036 +-83.91815186;38.04902649;0.008654766 +-81.23177338;35.68164825;0.006250469 +-114.1533966;42.9822998;0.000388833 +-84.04640198;32.34645081;0.013560677 +-81.10269928;40.29645157;0.008905781 +-84.73789978;38.96234894;0.013324332 +-80.43499756;33.07540131;0.020118142 +-85.71240234;30.60980034;0.003233149 +-111.5237274;40.1894989;0.002111138 +-88.91470337;44.4620018;0.080507995 +-101.6870728;40.17655182;0.098180701 +-95.71320343;46.41239929;0.091711665 +-84.15644836;37.97404861;0.011059337 +-86.66419983;32.5245018;0.005243553 +-75.38645172;38.70584869;0.171485367 +-85.60350037;31.40769958;0.008633058 +-81.56324768;37.1480484;0 +-85.14565277;45.00999832;0.008845452 +-75.30805206;41.03369904;0.009244043 +-84.11979675;37.1406517;0.002869144 +-96.80934906;48.77197647;0.002990894 +-80.29049683;35.32320023;0.039155445 +-76.94000244;39.23384857;0.065288975 +-104.378952;39.86967468;0.005899046 +-77.18630219;37.15679932;0.028235826 +-85.41649628;35.35720062;0.005877374 +-94.83724976;38.56370163;0.047683451 +-96.68297577;43.29190063;0.381104153 +-77.66139984;38.18370056;0.010914035 +-86.8302002;34.4952507;0.015565689 +-87.89395142;41.8118515;0.011235302 +-78.52404785;38.00540161;0.00194607 +-98.21800232;39.7846489;0.01117078 +-96.59715271;38.30405045;0.012888425 +-82.01035309;39.36819839;0.005241633 +-101.8928757;36.27774811;0.120188401 +-76.62754822;38.97434998;0.022539309 +-100.7129517;43.1936264;0.004164806 +-85.23355103;34.33324814;0.009744119 +-82.61599731;39.02614975;0.010409727 +-120.6921234;46.56380081;0.004618875 +-83.43990326;38.20759964;0.006120469 +-89.63282776;35.73509979;0.066425763 +-98.07542419;37.19210052;0.002924424 +-85.46044922;38.40465164;0.029732015 +-96.94329834;29.89590073;0.016940255 +-102.60495;34.96654892;0.014779802 +-102.3503494;34.06869888;0.03885756 +-78.63079834;36.28224945;0.002353517 +-87.94799805;43.01732635;0.008408884 +-83.75;34.65200043;0.004527486 +-112.4899979;33.34999847;3.39558E-05 +-86.91165161;35.87590027;0.009921827 +-79.23135376;35.69549942;0.002517024 +-104.9652252;42.12182617;0.001573794 +-77.99720001;37.93412399;0.006914551 +-104.6652527;41.32749939;0.002268666 +-78.42254639;40.98905182;0.004630356 +-93.79290009;38.38502502;0.048266499 +-82.79190063;35.22520065;0.003097003 +-97.40715027;35.55134964;0.00462741 +-74.60632324;40.56565094;0.00974351 +-105.5664978;40.62805176;0.004504959 +-96.9368515;31.25395012;0.155215905 +-85.81580353;34.52985001;0.026111632 +-87.84677124;35.63195038;0.008661934 +-85.95850372;36.25479889;0.011430139 +-78.01672363;37.14110184;0.002482918 +-84.32044983;37.34540176;0.003936636 +-83.4957962;30.45549965;0.01016385 +-78.13700104;38.69139862;0 +-80.33370209;36.7071991;0.001940431 +-83.69045258;36.47187424;0.002158746 +-101.5401993;48.72917557;0.006251643 +-100.4660034;38.48125076;0.008496347 +-99.32440186;39.35029984;0.008266824 +-105.880249;34.6508255;0.000233571 +-85.45355225;36.13899994;0.001948011 +-78.27774811;37.84820175;0.006524689 +-101.1687775;45.70807648;0.001705972 +-80.2154007;39.86500168;0.001899333 +-84.83830261;42.59614944;0.175656217 +-84.57994843;37.87609863;0.009927511 +-84.53974915;39.16675186;0.013808425 +-81.95794678;32.78025055;0.011166776 +-78.8214035;38.99945068;0.006963609 +-96.97485352;28.80164909;0.063208215 +-86.45257568;35.52477646;0.021441615 +-100.7207718;43.61985016;0.002033119 +-95.79975128;39.43462372;0.056962049 +-99.31725311;38.91479874;0.003472338 +-96.07794952;33.12310028;0.009102014 +-82.45400238;39.5115509;0.009240095 +-80.2661972;33.90570068;0.061052584 +-108.2266006;38.93310165;0.001408609 +-97.13535309;38.87120056;0.004791136 +-74.75759888;41.72069931;0.000644523 +-103.4849472;46.45552444;0.001411194 +-87.79872131;35.24755096;0.003831953 +-93.34349823;37.25794983;0.001388951 +-100.8209;48.77209854;0.004682097 +-90.3967514;32.31105042;0.022111358 +-83.49410248;36.24689865;0.002786894 +-85.31755066;34.43772507;0.006482782 +-100.1956482;44.72272491;0.013810954 +-84.84780121;43.98889923;0.006064749 +-104.6388779;47.75189972;0.001574428 +-111.3255234;32.98355103;0.000261892 +-87.37284851;41.43740082;0.183600748 +-98.7677002;39.35042572;0.004202199 +-78.59185028;38.84844971;0.015252831 +-78.53275299;37.56487656;0.001613986 +-103.7264481;42.50114822;0.00899731 +-91.50505066;38.43109894;0.014404517 +-74.67925262;41.13439941;0.004796071 +-100.8794022;45.10105133;0.001560709 +-111.9076233;38.77389908;0.000245387 +-94.42375183;40.47809982;0.001053394 +-96.25824738;44.0231514;0.314586061 +-78.80844879;37.37889862;0.002809494 +-103.4329987;32.78530121;0 +-85.62404633;35.15100098;0.015297131 +-84.97679901;35.95119858;0.001146183 +-104.5515747;38.1283493;0.001308248 +-87.26834869;37.53490067;0.029320275 +-85.30852509;42.59529877;0.093838782 +-93.47290039;45.01672363;0.042668248 +-84.54354858;32.0761261;0.012676577 +-84.37169647;42.59930038;0.13691679 +-84.87445068;38.23479843;0.007424214 +-79.77320099;36.39187622;0.003861839 +-77.88905334;37.73460007;0.019227051 +-96.30255127;32.59960175;0.006957343 +-95.66374969;35.35169983;0.003810162 +-80.0270462;40.43574905;0.002353904 +-74.72489929;39.84500122;0.01495364 +-84.61180115;45.0284996;0.002733024 +-86.78502655;34.08580017;0.007194318 +-96.63130188;30.51350021;0.030520329 +-97.33634949;30.10289955;0.002638318 +-82.00830078;31.19174957;0.003867574 +-100.4589996;39.7845993;0.01503833 +-81.84909821;36.43915176;0.003141019 +-74.34625244;41.87879944;0.002219303 +-85.20807648;37.77474976;0.014034898 +-74.71309662;40.28089905;0.02351392 +-99.24825287;38.17515182;0.053452802 +-83.85929871;32.58010101;0.013446644 +-74.41435242;40.4300499;0.020684005 +-103.5206985;46.11309814;0.003227056 +-84.82189941;35.51985168;0.004009289 +-84.91402435;45.53020096;0.004007952 +-109.753952;31.87989998;0.005470504 +-121.3218536;39.31169891;0.000990978 +-78.67414856;36.97375107;0.003618497 +-98.74700165;38.04270172;0.089364623 +-81.33634949;32.34859848;0.015644229 +-99.64890289;36.79745102;0.002556545 +-116.293045;44.15359879;0.005277027 +-85.16449738;35.56404877;0.004999139 +-90.36100006;45.67955017;0.002120657 +-93.30290222;35.03034973;0.006735712 +-83.92375183;37.69784927;0.004922616 +-87.04789734;31.4720993;0.003305751 +-78.9747467;36.38935089;0.007169075 +-94.39467621;33.47237396;0.013726672 +-99.29154968;37.91059875;0.140168323 +-84.93579865;37.33295059;0.011220574 +-89.24165344;44.11384964;0.057904573 +-96.98719788;30.29459953;0.002983294 +-103.5065994;44.89274979;0.000764395 +-85.32865143;37.33772659;0.035323166 +-103.2431259;40.00215149;0.012086074 +-77.46085358;38.41225052;0.007513409 +-95.3756485;47.58610153;0.002513295 +-95.43830109;29.8335495;0.004518952 +-105.3452988;40.08834839;0.005261226 +-103.1395493;42.71977615;0.001790523 +-111.6306992;39.42274857;9.83874E-05 +-74.58035278;40.86740112;0.002998465 +-95.51364899;35.97112656;0.013601197 +-75.34355164;38.22299957;0.101910279 +-84.10012817;36.77887344;0.002839941 +-81.46824646;31.98069954;0.002122433 +-79.33517456;36.39179993;0.00202363 +-85.68344879;37.96504974;0.012016016 +-88.00655365;45.38050079;0.019729661 +-97.30004883;34.68099976;0.004837521 +-80.06604767;41.66595078;0.034868967 +-81.34654999;32.95135117;0.042871142 +-85.17205048;33.99862671;0.006528412 +-89.95574951;43.40137482;0.0038552 +-97.64785004;38.39172363;0.002319454 +-87.76805115;36.02930069;0.019082804 +-106.5646362;46.01857376;0.000405247 +-86.0372467;44.64599991;0.003890324 +-86.07479858;35.49639893;0.058296801 +-82.63962555;36.73834991;0 +-112.9580994;41.50037384;0.001255823 +-75.02760315;41.34534836;0.003428979 +-102.5408478;43.35015106;0.000895467 +-99.69192505;36.2181015;0.002288185 +-80.25900269;41.28094864;0.058842026 +-109.4506989;45.65309906;0.000348172 +-81.56974792;35.9416008;0.002153573 +-81.74034882;33.99644852;0.003107872 +-101.339447;46.63344955;0.001054499 +-108.9611511;45.31235123;0.00106812 +-98.99047852;41.91514969;0.022202779 +-82.21060181;31.37512589;0.025036417 +-102.5637741;37.31869888;0.014061595 +-83.72454834;37.14355087;0.002985605 +-97.49434662;29.4470005;0.007024116 +-82.65329742;34.51390076;0.000979255 +-98.20097351;38.34754944;0.029676705 +-79.9055481;33.18830109;0.003558512 +-87.40194702;35.22848892;0.030380608 +-86.42475128;35.8628006;0.011613159 +-86.00875092;36.52600098;0.006584096 +-122.5142517;45.80170059;0 +-87.33679962;31.52842522;0.003503192 +-102.7869034;44.58980179;0.000270124 +-83.84079742;36.87099838;0.003627534 +-88.13710022;32.65155029;0.002762614 +-89.80995178;43.94509888;0.048727113 +-81.01035309;33.23289871;0.022647807 +-91.09055328;39.36830139;0.120295919 +-107.8827515;44.58340073;0.001045987 +-82.24859619;39.74140167;0.067871785 +-95.7696991;39.04302597;0.033533702 +-81.5401001;41.12820053;0.006057375 +-77.8513031;36.78357315;0.001379959 +-78.70075226;39.58987427;0.001469136 +-83.91165161;42.60359955;0.05387931 +-99.97660065;41.91284943;0.002198413 +-84.0122757;35.13964844;0.00205958 +-97.46929932;28.64642525;0.00878714 +-85.52484894;37.29014969;0.01786271 +-105.3423233;46.86125183;0.000539864 +-105.4614487;46.32397461;0.000289112 +-93.2664032;45.57305145;0.112809461 +-75.96070099;41.16464996;0.012804358 +-101.1475525;38.91717529;0.009028509 +-86.14414978;32.58969879;0.005784615 +-99.76184845;29.35695076;0.016964601 +-99.27275085;37.19174957;0.002180297 +-113.212326;38.3614769;0 +-81.96604919;34.89412689;0.0009634 +-107.9388733;38.94307327;0.004104246 +-103.3494034;46.97949982;0.002035548 +-84.5951004;37.11130142;0.011808495 +-78.66539764;41.41579819;0.000942799 +-84.90617371;36.35974884;0.002193466 +-95.44457245;45.58607483;0.209844212 +-96.47519684;32.06259918;0.019847965 +-83.06122589;39.34185028;0.111216426 +-107.8302536;43.83435059;0.000906784 +-98.45709991;34.63069916;0.00043835 +-78.11109924;36.37355042;0.001093422 +-99.30889893;38.52265167;0.006091533 +-82.41649628;31.01874924;0.005021944 +-101.7622223;38.91630173;0.057440453 +-80.26260376;35.76509857;0.00905551 +-96.66220093;33.67625046;0.028454435 +-108.2795029;38.41042328;0.004881483 +-88.84100342;35.61244965;0.007909074 +-113.0311508;39.0632019;0.000474266 +-98.046875;40.17625046;0.10239223 +-90.13297272;38.30414963;0.201202709 +-86.13232422;39.77975082;0.028391167 +-98.45155334;28.93124962;0.002789907 +-81.92945099;41.13259888;0.048555925 +-82.99821472;36.74319839;0.001787226 +-98.95970154;46.97814941;0.001477109 +-85.57505035;44.74634933;0.017134517 +-80.76117706;41.7384491;0.037147301 +-97.52611542;35.09519958;0.004246101 +-104.2866516;32.48265076;0 +-98.99970245;35.63850021;0.001267093 +-79.33989716;33.4530983;0.004026914 +-95.60847473;36.79835129;0.002765731 +-84.4733963;38.02752686;0.013180093 +-97.21472168;33.68249893;0.002861591 +-110.5647964;39.6406517;0.000211369 +-72.72325134;40.8852005;0.00205547 +-99.21327209;34.11349869;0.002091786 +-75.15969849;39.70154953;0.010586043 +-87.61634827;46.448349;0 +-92.21929932;38.53075027;0.018760857 +-82.62709808;30.21375084;0.001960354 +-96.23574829;35.02842331;0.001162099 +-110.4394531;40.31990051;0.000434289 +-82.51165009;39.21165085;0.004528111 +-121.7530975;47.43375015;0 +-77.56152344;37.39260101;0 +-79.23940277;41.4796257;0.001459871 +-103.0274963;44.09930038;0.000619113 +-93.84220123;37.73675156;0.005909482 +-103.5048523;36.36975098;0.009587141 +-96.8804245;35.7024765;0.001142011 +-82.947052;36.41815186;0.001926396 +-90.34394836;37.47764969;0.002072556 +-112.8153992;39.66270065;0.00041461 +-86.30439758;36.15359879;0.001369245 +-83.582901;45.03179932;0.011702081 +-96.34459686;30.65200043;0.013336918 +-104.9433975;48.27985001;0 +-92.43845367;38.22539902;0.003693479 +-94.35624695;39.03414917;0.046499901 +-90.2934494;35.13547516;0.02944876 +-95.36579895;31.25964928;0.003173517 +-110.6508026;39.10189819;0.000245684 +-109.5364532;40.16025162;0.000314101 +-98.06784821;29.1602993;0.016264142 +-97.61795044;46.10889816;0.014919942 +-98.78530121;39.78495026;0.003315446 +-86.5943985;30.68770027;0.000667999 +-81.44660187;29.9375;0.000460946 +-88.19029999;31.40670013;0.001735051 +-117.5645981;43.2207489;0.002007036 +-109.8025284;44.40952682;0.000180053 +-115.9697037;34.83615112;0 +-113.2589264;37.8117485;0 +-116.0283966;42.83850098;0.001282075 +-86.31984711;34.34954834;0.012123938 +-85.92455292;31.8335495;0.008615487 +-84.84604645;34.24409866;0.007482097 +-82.9450531;34.35174942;0.003364412 +-81.73090363;31.77599907;0.001948643 +-96.24165344;37.88835144;0.010830986 +-85.40574646;36.77830124;0.008174743 +-84.59349823;41.88470078;0.168815961 +-84.12615204;45.02845001;0.004279806 +-88.64880371;30.86650085;0.002940162 +-93.81099701;38.74694824;0.061337965 +-83.00975037;39.96900177;0.047175709 +-100.4778519;36.75094986;0.004736519 +-96.19035339;33.94105148;0.00257895 +-95.57324982;34.01327515;0.004441616 +-102.5147476;36.7499733;0.016178121 +-95.5700531;36.33634949;0.000925995 +-78.20549774;41.41749954;0.000393418 +-76.22989655;40.72280121;0.035129953 +-81.91149902;33.75870132;0.001245293 +-81.17987823;34.99264908;0.000686864 +-96.79100037;43.67457581;0.001929799 +-87.36064911;36.14680099;0.002232735 +-84.93477631;35.61709976;0.003461891 +-77.35624695;38.0135498;0.029341621 +-76.43609619;37.85689926;0.117817213 +-76.7227478;37.95835114;0.085690223 +-76.7227478;37.95835114;0.273118861 +-95.74810028;48.76934814;0.001315782 +-99.10890198;29.38960075;0.02553643 +-122.3963013;39.59180069;0.01307243 +-81.60555267;33.53829956;0.003495982 +-97.24105072;46.9347496;0.005222407 +-103.604248;39.04150009;0.002114679 +-104.8727036;47.32452393;0.001053457 +-97.40795135;35.94445038;0.001259319 +-93.43689728;35.54804993;0.003539499 +-90.51439667;38.25260162;0.007374772 +-86.91265106;45.85485077;0.002804415 +-85.44315338;33.30350113;0.001344549 +-95.30662537;37.55882645;0.050557499 +-99.83327484;34.76862335;0.001743148 +-97.79904938;31.3899498;0.009804994 +-79.16049957;35.46640015;0.004251535 +-81.65370178;35.7820015;0.002466846 +-75.13047791;39.37284851;0.015966687 +-90.48432159;45.20650101;0.027568061 +-85.76795197;30.84964943;0.002267074 +-80.23970032;36.40375137;0.00293937 +-97.94184875;35.94509888;0.000692137 +-103.4670029;48.8166008;0.001240542 +-82.8601532;30.7303009;0.002706431 +-99.88265228;43.37395096;0.001162056 +-87.70210266;30.75984955;0.003719422 +-92.87770081;37.27719879;0.002258452 +-84.52570343;32.34794998;0.003618869 +-82.33999634;32.13169861;0.008523115 +-83.41449738;32.67509842;0.002168392 +-101.0759277;39.78525162;0.009495195 +-86.18049622;36.78105164;0.011285826 +-96.98735046;36.09400177;0.001365941 +-84.22170258;31.5428009;0.011377427 +-91.33474731;43.29045105;0.15391441 +-97.73692322;44.84859848;0.005382756 +-100.8143463;34.9653511;0.001512471 +-85.83795166;33.76205063;0.002824754 +-114.1143036;47.59505081;0.000313803 +-88.59684753;35.41775131;0.021662276 +-95.13439941;35.2580986;0.001083133 +-99.00614929;44.54610062;0.001087653 +-97.40619659;35.15337372;0.002331611 +-80.54139709;35.89680099;0.020622785 +-84.60755157;30.54990005;0.003027338 +-80.47724915;35.6772995;0.012529092 +-103.8270035;46.93595123;0.004210371 +-88.21980286;45.02539825;0.053076495 +-100.1050034;45.76845169;0.002335925 +-99.91259766;40.17644882;0.065712515 +-93.29105377;38.29885101;0.017053266 +-86.12114716;30.63165092;0.000443237 +-90.8499527;36.66090012;0.002730472 +-76.99230194;37.5021019;0.03429969 +-95.89360046;36.7112999;0.001499448 +-88.99089813;34.48165131;0.012411838 +-73.87265015;43.0882988;0 +-99.83260345;43.85309982;0.004859112 +-87.46704865;35.80120087;0.006377343 +-82.56439972;32.40499878;0.003114721 +-80.17637634;34.59130096;0.007630541 +-76.60559845;39.46392441;0.050900867 +-99.82424927;37.23699951;0.001122163 +-93.82634735;36.71524811;0.001604498 +-85.67712402;38.18930054;0.004463242 +-96.73190308;39.04489899;0.00690491 +-100.6890488;31.39579964;0.001437159 +-96.13735199;40.92464828;0.00558854 +-97.03977966;45.61640167;0.001844445 +-98.35700226;35.20325089;0.002444596 +-80.30815125;40.98970032;0.060252871 +-98.27907562;44.41455078;0.011917057 +-99.88269806;39.34975052;0.012349854 +-101.6485977;40.85074997;0.206108891 +-89.48487854;34.74520111;0.012830405 +-96.64125061;38.69594955;0.016355861 +-82.79239655;32.99885178;0.004593292 +-119.5749283;45.45999908;0.002844571 +-87.45310211;33.3070488;0.003421438 +-90.73905182;44.33377457;0.048110035 +-121.5606995;39.72399902;0.000190608 +-78.92195129;36.80142593;0.002860674 +-96.88269806;35.18190002;0.003173797 +-98.35162354;45.58955002;0.003374789 +-115.6177979;43.38389969;0.000964641 +-84.82125092;36.80039978;0.01530529 +-101.5686722;44.99217606;0.000477748 +-98.75595093;38.47887421;0.037402125 +-101.4885788;36.74879837;0.059058935 +-84.36385345;38.70869827;0.005569616 +-79.43240356;35.28044891;0.001903467 +-92.7594986;35.26755142;0.008428482 +-103.4373016;48.29595184;0.000377332 +-86.10285187;44.34125137;0.004311455 +-89.18460083;34.78882599;0.012291354 +-86.03530121;34.02109909;0.003798007 +-83.82015228;45.41267395;0.009231975 +-89.94242859;31.90105057;0.00291942 +-86.38484955;35.26869965;0.008466868 +-79.44020081;40.35549927;0.022855151 +-98.99069977;35.28347397;0.000622913 +-99.90335083;39.78414917;0.010679623 +-82.53234863;32.15137482;0.008279664 +-80.58197784;34.3417511;0.00258172 +-92.50395203;39.42834854;0.043733932 +-78.62529755;35.79745102;0.001502548 +-103.0033493;41.7215004;0.057396839 +-86.44409943;31.26097488;0.002569354 +-85.0746994;42.94499969;0.176909422 +-97.09537506;38.34754944;0.003289364 +-102.6636505;46.82012558;0.001517943 +-101.1258011;42.54264832;0.00233306 +-76.54370117;37.42259979;0.044723235 +-85.77735138;35.68804932;0.010833391 +-98.13547516;37.55884933;0.012306712 +-98.46365356;48.77185059;0.001574614 +-98.68740082;34.01205063;0.002489405 +-97.64987183;39.48007584;0.021833909 +-88.76715088;33.65890121;0.010325901 +-93.70980072;35.22489929;0.005502768 +-95.95220184;35.61695099;0.002017662 +-94.40865326;39.28300095;0.03114356 +-100.3815994;40.52484894;0.057879278 +-76.42475128;41.042099;0.059854804 +-83.33664703;38.52249908;0.005160065 +-91.92694855;38.81482315;0.045449669 +-85.70627594;35.37334824;0.006933659 +-77.16484833;38.26309967;0.030381944 +-87.68765259;33.71995163;0.006721394 +-80.09564972;35.00450134;0.018518257 +-80.71595001;32.82770157;0.007691507 +-91.9178009;38.4966507;0.019592153 +-87.15209961;31.12789917;0.004617999 +-100.2729034;36.27772522;0.005196543 +-94.78164673;35.46794891;0.013913211 +-80.70709991;36.39920044;0.013396518 +-83.11634827;32.17625046;0.006246377 +-97.35160065;32.34434891;0.005997916 +-99.10700226;28.86580086;0.003723456 +-85.40209961;31.88222504;0.003531472 +-118.7025986;45.49862671;0.001020524 +-118.6631241;46.99879837;0.002678627 +-96.0134964;39.78337479;0.138189599 +-92.55425262;37.68429947;0.000816087 +-80.7766037;33.67274857;0.028355294 +-85.90834808;42.59382629;0.118963454 +-82.74185181;32.11389923;0.002886521 +-85.30410004;38.61449814;0.00944743 +-101.3542023;35.83977509;0.018488624 +-82.92590332;31.96685028;0.003188125 +-82.28105164;40.81007385;0.099043877 +-85.44535065;37.76620102;0.036231455 +-81.12039948;32.79199982;0.024842349 +-85.06069946;45.24084854;0.005997505 +-81.99755096;32.05379868;0.013244537 +-82.91555023;42.67229843;0.031546603 +-80.76145172;41.31655121;0.043087367 +-98.22389984;26.4225502;0.013835802 +-89.5530014;32.42810059;0.006413361 +-89.93624878;34.66297531;0.027040892 +-93.51107788;39.78939819;0.076879724 +-97.47727203;37.23789978;0.001189912 +-97.64992523;38.78352356;0.002171315 +-100.9061508;38.4823494;0.040504097 +-100.3707504;37.23770142;0.114023119 +-87.61560059;32.74354935;0.003398111 +-79.12729645;40.63985062;0.023929179 +-99.45310211;41.9151001;0.013438855 +-102.3310471;41.61539841;0.012284382 +-95.73310089;38.23625183;0.056818182 +-83.08387756;31.01772499;0.012545498 +-84.63339996;35.44279861;0.007631133 +-98.68589783;43.16957474;0.008968676 +-104.3534775;42.07842636;0.016500436 +-96.09729767;41.09550095;0.025985365 +-97.65035248;39.13227463;0.002600158 +-98.78079987;42.87995148;0.008679752 +-89.28170013;37.46519852;0.059322136 +-88.53269958;34.61009979;0.010543947 +-97.48004913;37.69345093;0.026266285 +-101.3024979;34.07144928;0.007086596 +-89.48400116;34.35812378;0.002970956 +-94.33860016;36.90230179;0.006235832 +-89.64134979;33.08262634;0.002763117 +-99.78032684;46.97904968;0.00532096 +-97.39454651;47.9334259;0.003477511 +-101.3460236;38.48225021;0.044144561 +-122.2919998;39.16930008;0.004345257 +-74.94934845;40.56420135;0.023622482 +-82.70465088;42.84334946;0.126290465 +-95.15057373;40.73752594;0.300919001 +-91.90309906;44.94660187;0.123418483 +-85.35494995;31.15579987;0.011038407 +-80.79740143;33.44169998;0.04943905 +-91.41615295;40.59627533;0.229517385 +-89.98999786;32.31715012;0.007060825 +-96.92147827;46.28279877;0.001522576 +-93.83380127;37.10634995;0.01095901 +-87.09742737;36.24869919;0.004646303 +-83.25307465;30.82805061;0.008676768 +-84.06330109;41.89450073;0.19091439 +-81.25767517;33.92499924;0.011843046 +-78.88234711;35.38755035;0.009716223 +-86.39442444;36.98217392;0.073652813 +-102.608551;38.82979965;0.014472872 +-82.11184692;32.4144516;0.018986957 +-102.3992233;42.50215149;0.012417863 +-93.60669708;33.24985123;0.028193257 +-98.53522491;46.45645142;0.002860275 +-96.08674622;40.65352631;0.006344508 +-95.80854797;47.32519913;0.004760924 +-100.8117523;35.40169907;0.007742815 +-83.60250092;44.33667374;0.011382055 +-95.74005127;37.88650131;0.044320928 +-95.21514893;36.75469971;0.006775357 +-96.68840027;40.78454971;0.013410738 +-84.13954926;41.01345062;0.235729638 +-96.31977844;29.84320068;0.009816654 +-86.14755249;33.39120102;0.013310819 +-82.6957016;32.66735077;0.006676034 +-85.13379669;38.67354965;0.010809824 +-76.34484863;37.45287323;0.012765523 +-91.52574921;35.73452377;0.015343096 +-121.998703;38.28677368;0.011306214 +-99.48770142;44.54515076;0.012159251 +-88.98104858;39.85390091;0.194404641 +-101.6691513;43.19255066;0.003295607 +-92.77905273;38.87234879;0.107018654 +-102.4262009;40.00389862;0.116570469 +-95.72509766;38.65192413;0.055525942 +-83.22219849;43.10374832;0.075712703 +-91.05049896;38.45154953;0.027768446 +-98.08529663;37.9531517;0.022794399 +-96.79049683;44.36965179;0.016520656 +-104.5615768;38.82474899;0 +-77.28649902;36.90930176;0.015283353 +-76.6888504;39.97299957;0.128358395 +-101.694725;41.56914902;0.002184184 +-118.8313522;46.46620178;0.012576465 +-75.31105042;39.57931519;0.070291228 +-102.9953537;41.22069931;0.018937969 +-103.7093964;41.54629898;0.008375546 +-96.68965149;40.26229858;0.059921196 +-85.30715179;38.04417419;0.025215169 +-78.29350281;34.10504913;0.008956878 +-98.73940277;37.64775085;0.10863243 +-86.34745026;37.47045135;0.018613008 +-83.23795319;31.74975014;0.035371738 +-98.52839661;41.91464996;0.076879444 +-103.7289276;37.95429993;0.005567719 +-81.96264648;39.96110153;0.028446278 +-76.89955139;37.09609985;0.033591315 +-88.54167175;42.66754913;0.26027129 +-102.3520508;40.8757515;0.074100759 +-92.53434753;40.47029877;0.045169227 +-99.93122864;42.46519852;0.044139141 +-85.19864655;43.29370117;0.112987967 +-86.25910187;37.19440079;0.007744862 +-85.84459686;35.98160172;0.00935909 +-96.23670197;40.13159943;0.009775355 +-90.86985016;37.97109985;0.063396977 +-97.89277649;42.96749878;0.008043544 +-88.90325165;34.79579926;0.012966522 +-83.83679962;42.25325012;0.085174451 +-79.73925018;41.3980484;0.010647517 +-98.49992371;40.17639923;0.088603907 +-94.37527466;32.78580093;0.01444085 +-98.20894623;39.3932991;0.003572347 +-102.6018524;35.83879852;0.10952727 +-76.91674805;37.7011261;0.056816385 +-89.7193985;34.02954865;0.004348454 +-101.0608521;40.52489853;0.074497623 +-86.6989212;41.04097366;0.401361603 +-81.87210083;32.16320038;0.00929456 +-87.83119965;36.50497437;0.005112001 +-90.97699738;32.00550079;0.01283974 +-113.6716003;42.86455154;0.00041139 +-84.38684845;43.98770142;0.0175735 +-77.41600037;37.54040146;0.011157901 +-94.69962311;40.73559952;0.203378125 +-96.22364807;38.9681015;0.004115039 +-95.6590271;40.13090134;0.001412085 +-76.02140045;38.47634888;0.017285792 +-101.6016998;48.32740021;0.003648379 +-86.11470032;38.21009827;0.08050472 +-86.57700348;35.17795181;0.024661102 +-96.83882141;45.15174866;0.003434016 +-97.42700195;38.04339981;0.044905253 +-98.34697723;44.93759918;0.012883427 +-92.6006012;40.19179916;0.032242063 +-87.14409637;32.3879509;0.00286782 +-86.47989655;36.44874954;0.01476006 +-82.32015228;33.83182526;0.017571975 +-93.03992462;36.65729904;0.022323143 +-89.32375336;33.94200134;0.014651601 +-80.55899811;35.01110077;0.061287008 +-77.64745331;37.07089996;0.007755574 +-93.28115082;38.72449875;0.114309235 +-77.39935303;34.71440125;0.021191414 +-76.19519806;35.38359833;0.068843832 +-97.18804932;44.67385101;0.01140601 +-79.33644867;33.99895096;0.010097246 +-84.16815186;40.520401;0.226635514 +-100.4829254;38.91537476;0.008312877 +-80.2717514;36.11645126;0.005340576 +-94.81259918;36.83345032;0.012266082 +-77.53224945;36.71955109;0.005288722 +-78.27680206;36.04175186;0.0030169 +-98.50579834;46.1093483;0.010914014 +-92.61820221;38.67504883;0.043515317 +-77.73718262;39.52159882;0.186357472 +-87.0305481;35.22344971;0.017391518 +-94.50924683;45.10932541;0.277816577 +-101.7305222;39.78564835;0.03493058 +-85.99275208;31.40055084;0.011275939 +-88.18164825;35.20930099;0.018927595 +-85.62110138;34.23194885;0.006779722 +-89.0652771;43.80754852;0.192732443 +-101.7989731;37.19110107;0.032537812 +-81.73184967;32.40074921;0.022908206 +-99.45559692;42.44530106;0.032537235 +-103.713974;41.19805145;0.013954997 +-83.69264984;43.00194931;0.068886405 +-96.53514862;31.02599907;0.012798984 +-85.96672821;34.73329926;0.028968982 +-83.48664856;38.82649994;0.023280557 +-88.98960114;35.21305084;0.014043892 +-102.4621277;46.41815186;0.002069993 +-102.3944016;37.95610046;0.007620186 +-100.8501968;37.19287491;0.144395406 +-95.58390045;33.35615158;0.016070164 +-86.50164795;39.16934967;0.015056422 +-100.5215759;46.98065186;0.008610977 +-78.80660248;35.0489006;0.021065694 +-91.04472351;32.71120071;0.128613277 +-76.63180542;37.64199829;0.063555257 +-89.26294708;33.59965134;0.004807806 +-80.98077393;34.00569916;0.008469283 +-99.2507019;44.06475067;0.00531248 +-101.7309494;47.2778511;0.000896709 +-95.15579987;41.03034973;0.331756629 +-95.30135345;37.88542557;0.052807101 +-85.57737732;44.33874893;0.003868327 +-97.18800354;44.97777557;0.003181032 +-96.17285156;41.29175186;0.028324974 +-91.45345306;38.92145157;0.129660812 +-97.6568222;46.45657349;0.022638366 +-87.39689636;40.97755051;0.393881423 +-85.23179626;31.5352993;0.008343717 +-76.10088348;36.29735184;0.107767575 +-103.0710526;42.22055054;0.060884738 +-88.23815155;37.5006485;0.026285538 +-93.57119751;40.42205048;0.055044256 +-88.07272339;36.08684921;0.011871897 +-99.24765015;48.68524933;0.006557757 +-83.65070343;40.99297333;0.240837019 +-103.0719757;37.95555115;0.001032103 +-83.83384705;31.58325005;0.009598845 +-88.56140137;37.33435059;0.020644583 +-76.1223526;35.84647369;0.10298851 +-93.1059494;39.86864853;0.045841057 +-89.97104645;35.19955063;0.016566604 +-99.72595215;42.86157608;0.008486467 +-88.59335327;34.8756485;0.030086399 +-83.39489746;31.98857498;0.009449776 +-103.7067032;41.85100174;0.162110934 +-97.16490173;39.34944916;0.0203855 +-99.88445282;48.1962738;0.012127636 +-96.36602783;39.34629822;0.009253885 +-96.04109955;48.05445099;0.004054887 +-85.32375336;43.64085007;0.038521928 +-89.58399963;33.48187637;0.009217156 +-78.94110107;33.93564224;0.020949474 +-81.4769516;33.29272461;0.013390985 +-88.87670135;37.45175171;0.026749703 +-85.97789764;37.72254944;0.053494936 +-96.20694733;43.08449936;0.438504389 +-82.88500214;31.30154991;0.013404479 +-75.60054779;39.5679512;0.067812067 +-89.6019516;42.67874908;0.209763866 +-95.62640381;40.74060059;0.354641152 +-83.43489838;31.18860054;0.010916077 +-78.38565063;35.53575134;0.015391173 +-82.45535278;33.03715134;0.024282195 +-85.67935181;37.57695007;0.086673632 +-90.04879761;43.94499969;0.058419966 +-86.63839722;38.0542984;0.03277485 +-76.91425323;37.94520187;0.100016488 +-78.57714844;34.61512375;0.043217249 +-92.33719635;38.94599915;0.049011205 +-87.19300079;36.85684967;0.181845357 +-94.33840179;38.64720154;0.061919698 +-100.3529282;47.58739853;0.002894263 +-91.18849945;38.77019882;0.069885785 +-101.6524963;41.19919968;0.084804855 +-92.92445374;45.51234818;0.08380624 +-82.8707962;31.58989906;0.013827434 +-97.19300079;36.37662506;0.002988796 +-76.68464661;36.89899826;0.043530566 +-86.13110352;36.39260101;0.006838673 +-83.78555298;31.91690063;0.006276934 +-84.1114502;41.3266983;0.277611044 +-89.07250214;42.6697998;0.324657862 +-91.25215149;42.86285019;0.255798911 +-96.83785248;37.78167343;0.032500788 +-88.39624786;35.62049866;0.027042229 +-96.19080353;41.53839874;0.037612999 +-77.2059021;39.1440239;0.224228787 +-83.76170349;31.18020058;0.00707308 +-100.6643753;38.0006485;0.077778717 +-81.92915344;33.04909897;0.011288788 +-121.6795273;39.02019882;0.01400322 +-83.9278717;30.86795044;0.021657852 +-94.15509796;33.7424736;0.014692607 +-96.54814911;42.14649963;0.027773546 +-85.49160004;39.29195023;0.029180728 +-90.0897522;32.64229965;0.050985902 +-99.91607666;38.47992325;0.001163061 +-82.32465363;32.57294846;0.005695986 +-79.69869995;34.04192352;0.029107384 +-89.35890198;38.08464813;0.174335541 +-81.61579895;32.76720047;0.017349331 +-77.07209778;37.35720062;0.043602265 +-98.95227814;40.17634964;0.142977757 +-79.24710083;35.020401;0.007588635 +-88.46025085;33.51615143;0.036706152 +-84.97975159;31.63299942;0.010405461 +-79.71320343;33.59474945;0.022754042 +-86.06334686;42.24489975;0.082107602 +-95.29170227;38.21390152;0.078001698 +-84.75274658;31.77510071;0.01456028 +-95.74102783;37.19284821;0.052067188 +-94.34279633;36.63249969;0.040823245 +-89.8219986;33.78704834;0.008520235 +-89.41404724;35.19924927;0.028832505 +-90.04174805;37.31959915;0.025675001 +-102.7751999;38.44020081;0.000441137 +-86.52367401;42.00149918;0.114109019 +-93.98545074;39.33520126;0.096855409 +-94.69965363;41.02780151;0.234260637 +-95.45375061;45.93349838;0.111339702 +-89.71859741;40.74399948;0.284746659 +-101.7853775;37.99969864;0.013641632 +-82.82299805;40.52854919;0.151934297 +-97.39849854;42.98485184;0.011084747 +-100.4820786;41.56667328;0.030117579 +-76.62840271;35.15995026;0.045445777 +-88.95510101;33.90674973;0.009034313 +-88.9694519;36.86270142;0.178580705 +-84.39109802;31.2635498;0.037330284 +-91.3085022;35.62210083;0.030829729 +-91.57362366;34.78549957;0.007256978 +-91.54434967;40.98740005;0.302113065 +-85.21250153;38.19760132;0.039043182 +-92.98345184;39.46640015;0.135398614 +-75.53614807;39.09764862;0.12082005 +-86.67030334;37.19749832;0.046720239 +-94.24310303;40.734375;0.12205442 +-85.52940369;42.24509811;0.129869985 +-91.91394806;38.15174866;0.179765791 +-92.686203;39.15724945;0.106349573 +-76.24334717;36.28839874;0.151511812 +-84.87979889;30.8933506;0.062358217 +-86.2335968;37.99549866;0.049127257 +-101.3541489;36.27747345;0.056568004 +-75.75017548;38.10010147;0.063032915 +-84.42479706;42.24767685;0.103930795 +-83.81739807;32.15980148;0.00477245 +-83.5026474;31.46190071;0.007663648 +-86.41474915;37.80244827;0.031391398 +-96.26462555;40.3926239;0.023263233 +-96.61227417;41.23419952;0.111710771 +-119.4890976;47.29534912;0.013054538 +-82.63400269;31.82010078;0.009842372 +-87.90840149;36.81755066;0.075811857 +-87.13647461;37.22822571;0.041800956 +-76.94934845;36.3906517;0.046000113 +-86.47917175;38.83955002;0.044560301 +-99.2852478;37.55825043;0.049531763 +-86.83615112;36.85820007;0.154652858 +-86.26055145;41.32532501;0.302110766 +-87.37905121;36.48149872;0.043460725 +-82.46510315;40.40570068;0.125090112 +-96.22962189;43.37889862;0.384308511 +-79.51319885;34.83660126;0.005385567 +-93.02749634;40.46649933;0.026851226 +-90.13212585;42.66025162;0.246371751 +-84.15654755;39.02097321;0.041137525 +-93.98860168;39.96127319;0.087361324 +-86.80055237;37.82714844;0.048828125 +-91.72650146;33.5910759;0.005660001 +-97.78665161;36.79607391;0.002030113 +-86.70950317;41.4986496;0.184552195 +-91.07845306;36.3118515;0.011985671 +-98.8980484;47.71747589;0.009670777 +-76.68782806;36.42409897;0.027982884 +-85.16809845;39.6569519;0.249319641 +-100.7462006;41.04642487;0.093726355 +-86.55049896;34.7371254;0.024264834 +-90.66300201;35.57339859;0.008248212 +-92.01065063;39.50030136;0.112716338 +-97.50585175;26.14155006;0.008452846 +-77.04425049;39.53525162;0.146420316 +-84.57164764;40.83732605;0.263661635 +-76.96724701;36.03039932;0.039107753 +-88.44229889;35.96984863;0.071731745 +-82.81195068;43.42214966;0.145016984 +-77.39700317;39.46989822;0.068660993 +-97.16212463;43.29167557;0.024571593 +-84.18695068;33.78609848;0.019897788 +-95.76304626;44.02225113;0.379074216 +-90.96324921;39.04914856;0.091694555 +-86.82830048;39.66830063;0.213403843 +-93.32685089;40.739151;0.130802892 +-101.3540497;35.40380096;0.019463762 +-84.18334961;32.05120087;0.022538534 +-93.0305481;44.69544983;0.236193774 +-84.38745117;43.64725113;0.055162225 +-96.62460327;42.78319931;0.066521135 +-89.89937592;38.01305008;0.143710453 +-92.06964874;39.77935028;0.126327531 +-92.86930084;41.02944946;0.090128285 +-97.8973465;42.65987396;0.037789229 +-93.55827332;41.3362999;0.179035319 +-87.7723999;39.32220078;0.3277667 +-95.67845154;46.9341507;0.016931594 +-93.28230286;44.37009811;0.246510541 +-92.46977234;37.27054977;0.065074854 +-91.94785309;40.7492981;0.13858236 +-96.98274994;42.89419937;0.02163812 +-95.80970001;40.41192627;0.006107989 +-100.8154984;36.27799988;0.018220879 +-96.09559631;47.86222458;0.012285225 +-84.8715744;31.29475021;0.019866303 +-95.00614929;45.1521759;0.273416891 +-94.40384674;39.60129929;0.119773259 +-86.1013031;39.49007416;0.242531778 +-99.72740173;41.39392471;0.087111903 +-97.19355011;41.91699982;0.058162529 +-93.57185364;41.67762375;0.248364363 +-84.302948;37.71220016;0.053810391 +-82.60904694;39.74795151;0.165805468 +-88.55905151;33.10710144;0.063868219 +-95.57927704;41.33300018;0.362682864 +-88.05854797;37.02334976;0.036219286 +-77.16492462;36.76974869;0.027363817 +-93.99770355;40.35485077;0.088992663 +-76.66525269;40.89434814;0.115851471 +-86.49539948;38.54119873;0.075481202 +-83.62807465;31.71080017;0.009832885 +-86.80314636;38.70230103;0.05578033 +-88.68364716;34.29180145;0.020157254 +-93.32767487;41.02965164;0.07983974 +-89.4240036;38.36045074;0.243841204 +-90.83934784;32.35977554;0.025570652 +-83.23594666;31.25065041;0.015887138 +-90.16145325;40.44940186;0.257763222 +-89.3947525;43.46215057;0.214447718 +-86.87615204;38.36460114;0.196543165 +-85.42355347;41.39509964;0.218919511 +-101.8056488;38.48099899;0.012853305 +-94.47112274;41.33050156;0.288182856 +-86.8477478;36.51100159;0.098379751 +-99.47619629;46.45615005;0.008657469 +-83.86299896;38.94689941;0.06259405 +-99.87190247;38.91410065;0.006156492 +-84.25305176;31.26029968;0.032045961 +-78.61662292;34.21369934;0.052872812 +-94.33184814;38.25859833;0.072741228 +-94.7385025;44.30265045;0.368077383 +-93.84980011;37.43175125;0.021349982 +-84.13884735;40.78170013;0.22562619 +-101.7189484;39.35079956;0.084207473 +-91.87969971;34.77429962;0.010199619 +-96.32409668;41.86505127;0.095125515 +-97.07945251;41.56060028;0.154306046 +-88.46219635;33.86980057;0.025763246 +-91.2542038;35.17910004;0.031966039 +-91.22395325;34.67029953;0.073405176 +-91.69415283;40.10115051;0.157484505 +-85.00060272;41.6432991;0.167526399 +-88.57465363;35.18999863;0.036467684 +-94.85134888;39.34452438;0.140886327 +-77.49269867;36.35725021;0.005533757 +-88.26934814;36.62385178;0.147653722 +-89.82332611;37.72537613;0.075381318 +-84.17115021;39.4225502;0.069595626 +-84.13845062;41.60285187;0.316894267 +-94.01525116;41.33430099;0.174031882 +-85.71035004;38.43724823;0.059160356 +-95.15055084;43.37810135;0.354684958 +-88.73789978;36.28274918;0.166971165 +-96.61722565;41.56800079;0.178649956 +-86.51057434;39.76234818;0.22382098 +-101.6966019;40.52474976;0.215902739 +-97.1059494;36.79632568;0.013095951 +-90.3927536;34.64220047;0.019925903 +-93.7866745;40.73609924;0.077860568 +-87.88790131;37.16952515;0.090062828 +-84.72975159;31.16365051;0.060722487 +-94.92735291;41.33119965;0.332252406 +-88.37954712;37.20294952;0.029660213 +-88.26014709;36.31095123;0.089018658 +-98.9809494;41.56740189;0.157319885 +-88.07975006;37.34669876;0.032359723 +-92.08950043;43.67442322;0.266146589 +-89.60575104;39.74985123;0.24081887 +-91.17882538;40.88550186;0.274089871 +-84.2684021;39.75045013;0.101531364 +-89.42250061;38.58074951;0.259631402 +-95.23014832;44.44680023;0.403710513 +-84.44519806;31.79364967;0.038204073 +-89.41564941;37.76264954;0.069607393 +-84.57254791;41.12134933;0.19068611 +-94.82315063;39.97505188;0.176291994 +-95.56404877;39.82654953;0.277360383 +-87.70700073;34.87467575;0.016804589 +-94.85247803;37.50650024;0.077214501 +-94.33499908;37.20600128;0.030286214 +-76.84925079;35.47840118;0.080958549 +-76.59700012;35.85354996;0.114342392 +-83.45355225;32.25360107;0.004420621 +-84.84735107;43.6405983;0.081353283 +-97.60362244;45.36989975;0.085829112 +-99.82042694;40.85932541;0.279976454 +-83.4016037;43.47494888;0.151743843 +-97.14234924;40.17589951;0.102256069 +-92.14775085;40.12680054;0.101033695 +-92.14714813;40.45330048;0.120447237 +-89.96179962;34.3567009;0.017584405 +-87.24272156;38.06195068;0.155001041 +-91.68489838;40.43114853;0.149378585 +-88.65164948;36.72394943;0.163114887 +-86.46882629;39.48600006;0.167217138 +-101.3112717;37.19184875;0.196512515 +-95.62374878;42.73542404;0.362773976 +-92.4083786;40.74580002;0.100908612 +-95.15422821;43.67419815;0.402822115 +-86.84279633;39.3193512;0.055169012 +-94.41020203;40.21120071;0.088691711 +-84.61795044;40.53995132;0.239466186 +-79.35241699;34.42515182;0.028560666 +-89.90975189;33.44549942;0.010454335 +-86.07804871;38.59534836;0.103575386 +-89.96690369;35.71875;0.020004175 +-79.97360229;34.30884933;0.032021866 +-82.48280334;40.09505081;0.094683176 +-77.62840271;36.25709915;0.009047232 +-94.4041214;39.89279938;0.088033357 +-89.82522583;35.50654984;0.054422361 +-88.94104767;36.00695038;0.14777988 +-96.78717804;41.91630173;0.06364729 +-91.84294891;43.29127502;0.274158582 +-94.90065002;40.35165024;0.192144259 +-94.85179901;39.67634964;0.165886681 +-94.06945038;44.05599976;0.379431057 +-95.8430481;44.4129982;0.373029181 +-87.26767731;38.38835144;0.143617259 +-101.7342987;34.53030014;0.010932277 +-86.89495087;40.38869858;0.312943936 +-88.41995239;41.93690109;0.253991815 +-92.38424683;44.01435089;0.242387176 +-84.92514801;39.62599945;0.305632931 +-90.18789673;33.92950058;0.072068347 +-91.4477005;39.50239944;0.141321656 +-92.64199829;44.45449829;0.25819464 +-92.74890137;43.67427444;0.399683767 +-75.5904007;37.74407578;0.082374132 +-96.38999939;47.77999878;0.010784776 +-85.79122162;39.5231514;0.21437395 +-90.96714783;39.62034988;0.295638625 +-83.88069916;39.70064926;0.205631749 +-85.05709839;39.3974762;0.12386658 +-87.07610321;41.46474838;0.227212934 +-93.79570007;39.10070038;0.226686768 +-96.62609863;28.3953495;0.032939041 +-91.86139679;39.20360184;0.165244914 +-91.83820343;34.27754974;0.016776011 +-91.71534729;41.33667374;0.320077456 +-94.24214935;41.0271492;0.187769504 +-87.78314972;34.72940063;0.017608447 +-86.85904694;37.47510147;0.052628033 +-77.05014801;35.11729813;0.024040837 +-82.68599701;35.86902618;0.011519613 +-84.93800354;40.74554825;0.276255304 +-75.84447479;37.3199501;0.043702078 +-90.80394745;42.48474884;0.273622484 +-88.93005371;37.72975159;0.038009849 +-92.86834717;40.74140167;0.080289673 +-85.42684937;41.64157486;0.281816244 +-97.53710175;48.77145004;0.013407821 +-89.28559875;35.60985184;0.035165041 +-89.98355103;38.43984985;0.185473246 +-79.14320374;34.62639236;0.069822099 +-96.24375153;29.29829979;0.067939145 +-90.69380188;39.1344986;0.11388484 +-95.81839752;41.68597412;0.354338902 +-96.5414505;42.40052414;0.058638173 +-92.53314972;42.07989883;0.344430195 +-90.52674866;42.20740128;0.208795139 +-83.86605072;44.03694916;0.046852851 +-96.82430267;42.51259995;0.035750645 +-98.77799988;42.48875046;0.080629368 +-95.29772186;37.19172668;0.035159983 +-87.49649811;36.89704895;0.158130233 +-97.14035034;40.52405167;0.149911357 +-98.20337677;47.93365097;0.015121687 +-97.71777344;48.36875153;0.011702841 +-86.58194733;36.7582016;0.219632499 +-96.01345062;42.03889847;0.367450802 +-78.38549805;34.9341011;0.041316304 +-97.0951004;42.22117615;0.058140518 +-89.12580109;36.57474899;0.19292893 +-82.91850281;40.84989929;0.2922086 +-101.8264999;34.06869888;0.025972976 +-88.23957825;39.27510071;0.309772556 +-88.86374664;40.51959991;0.208830446 +-89.32109833;37.15855026;0.044948811 +-85.00430298;42.24622345;0.144847401 +-91.20445251;40.41614914;0.308323475 +-78.05549622;35.38574982;0.073520097 +-94.34545135;37.50147629;0.067574674 +-83.60852814;39.19865036;0.085004428 +-99.18624878;48.10874939;0.022408735 +-93.76824951;44.36980057;0.305183946 +-88.92430115;38.30075073;0.11220138 +-82.99755096;40.28490067;0.114783233 +-90.66134644;35.8478508;0.010990518 +-84.05554962;43.7378006;0.162492966 +-77.58429718;35.91450119;0.034032632 +-84.57379913;41.5658493;0.177834685 +-86.05225372;40.07249832;0.206925039 +-85.0707016;41.09360123;0.175684671 +-84.61955261;40.1352005;0.276915222 +-96.25232697;43.67487335;0.388836224 +-87.45075226;39.87779999;0.203759391 +-89.1704483;42.32524872;0.253958715 +-99.40450287;40.17655182;0.121544738 +-84.60690308;43.29185104;0.200338081 +-83.47975159;41.90895081;0.170114317 +-88.94145203;37.99407578;0.140673534 +-86.01629639;40.78094864;0.278705873 +-93.17254639;39.16965103;0.25788333 +-91.59962463;41.64260101;0.255551442 +-83.64904785;41.39242554;0.230330299 +-84.03279877;43.34859848;0.169977625 +-84.22989655;40.03984833;0.24222128 +-77.92545319;35.71974945;0.023579186 +-83.24990082;31.62075043;0.044666629 +-96.24424744;42.73550034;0.37182564 +-95.75257874;43.6745491;0.399694572 +-90.52365112;36.11629868;0.018939066 +-86.11889648;40.46955109;0.366273416 +-92.64009857;41.33544922;0.32434048 +-90.28859711;39.9992485;0.353734371 +-87.55449677;37.33784866;0.062436428 +-85.72924805;38.68904877;0.36313433 +-90.05535126;33.1167984;0.031001984 +-93.09784698;41.33459854;0.208630303 +-83.31369781;40.84009933;0.238452578 +-98.23474884;31.2466507;0.033211639 +-102.7835464;34.52997589;0.048913413 +-84.51560211;41.29655075;0.175190315 +-77.68460083;43.15254974;0.036971591 +-85.79244995;40.84885025;0.261696154 +-94.49295044;41.68315125;0.275143921 +-91.21109772;39.97859955;0.231831421 +-96.66805267;44.76050186;0.131304627 +-89.64520264;37.36349869;0.065619491 +-87.73654938;39.01404953;0.285352738 +-97.13670349;41.25040054;0.179920835 +-93.78569794;41.02939987;0.097855585 +-87.10320282;41.01134872;0.389599371 +-87.11274719;37.74544907;0.217619326 +-84.21859741;40.33359909;0.202341975 +-85.01584625;39.86045074;0.219137944 +-98.50174713;40.87250137;0.491855783 +-95.74357605;37.55924988;0.036760862 +-97.99725342;41.39580154;0.132061203 +-87.57517242;37.9986496;0.220483225 +-85.44280243;38.74995041;0.059668761 +-90.71495056;39.97309875;0.237710617 +-90.4108963;36.71234894;0.028448189 +-83.76554871;40.14175034;0.276361536 +-89.00205231;36.6487999;0.249294305 +-83.36090088;40.30697632;0.152081186 +-88.53845215;38.08145142;0.210411113 +-80.24034882;34.17739868;0.057122837 +-93.34889984;43.6739502;0.388174072 +-83.12560272;41.1242981;0.205461414 +-76.78939819;38.12115097;0.078404529 +-95.2989502;41.68444824;0.404621465 +-90.3180542;41.55495071;0.242750439 +-90.55609894;40.13135147;0.334394429 +-95.15067291;43.08264923;0.399626905 +-77.65180206;35.50970078;0.064760739 +-89.1005249;37.20119858;0.129955426 +-82.11289978;41.28879929;0.042195431 +-94.03520203;41.68315125;0.331698552 +-85.05905151;41.91595078;0.238034824 +-95.16030121;44.02194977;0.414313114 +-86.89354706;40.03984833;0.365763315 +-84.62915039;31.53560066;0.02398012 +-90.29250336;34.29167557;0.025087071 +-88.9986496;37.07355118;0.147440874 +-92.86180115;44.02265167;0.36511661 +-88.91950226;38.65000153;0.132686676 +-98.51734924;41.22014999;0.20550907 +-76.54055023;36.16175079;0.030772127 +-92.40930176;41.03044891;0.199016929 +-89.44390106;36.0469017;0.082639569 +-89.1359024;35.84472656;0.032396238 +-88.9756012;38.97719955;0.204774316 +-95.27400208;40.06892395;0.316309243 +-94.89679718;41.6834259;0.356131384 +-93.56285095;40.11307526;0.079949865 +-82.64790344;41.38705063;0.149580195 +-100.0930786;44.32595062;0.007381026 +-97.13909912;40.87239838;0.188397129 +-88.20159912;37.74515152;0.349925092 +-82.99915314;39.64025116;0.25527485 +-84.97605133;39.12760162;0.170510913 +-94.67890167;42.73424911;0.458445127 +-93.94795227;43.67385101;0.434617729 +-77.38970184;35.01205063;0.020529584 +-88.15354919;39.01094818;0.293916869 +-77.43544769;35.58670044;0.035250852 +-88.21600342;39.53004837;0.355665565 +-98.74584961;48.22949982;0.046155632 +-87.02204895;34.77484894;0.015403574 +-85.86437225;41.23929977;0.261918605 +-95.53177643;42.38619995;0.396316393 +-83.12999725;41.3769989;0.210787429 +-91.21697235;41.24805069;0.297400602 +-88.42784882;41.59080124;0.385531693 +-90.60790253;41.61054993;0.361681916 +-95.68195343;45.2820015;0.320683093 +-77.61160278;35.21720123;0.064868076 +-91.94670105;41.03165054;0.226116369 +-92.06430054;41.68629837;0.28508398 +-94.61380005;43.97857666;0.431160963 +-89.78652191;40.01884842;0.397772474 +-82.58924866;41.14139938;0.176010778 +-92.53250122;41.68610001;0.317825582 +-90.28277588;42.35172653;0.190282492 +-90.75524902;34.76824951;0.029865289 +-89.7492981;36.20262527;0.069081082 +-98.06244659;41.69844818;0.225720485 +-77.92355347;34.9524498;0.069743375 +-89.44454956;38.88444901;0.218634929 +-86.05175018;40.31129837;0.426643688 +-85.39880371;39.93199921;0.280743389 +-86.69824982;41.30205154;0.295011477 +-93.05169678;41.68555069;0.336049124 +-99.07364655;40.85022354;0.242286852 +-96.09449768;45.03689957;0.310918849 +-87.31809998;34.55182648;0.023210577 +-92.99877167;42.03637314;0.375390953 +-87.82109833;38.41009903;0.345404305 +-97.60077667;42.26435089;0.162258464 +-87.40155029;39.43360138;0.175122121 +-83.78309631;39.90380096;0.220780398 +-76.35307312;36.19950104;0.082812437 +-94.35514832;44.31824875;0.379665148 +-98.97564697;41.22060013;0.126471264 +-85.49700165;41.14844894;0.219808059 +-85.71974945;40.16159821;0.274395638 +-92.17839813;41.33634949;0.27516877 +-99.14450073;45.07125092;0.063584848 +-87.70619965;38.71025085;0.281853191 +-87.65405273;37.49914932;0.15403286 +-90.21324921;40.93235016;0.336155205 +-98.5210495;41.56757355;0.132436167 +-91.59889984;42.07939911;0.288569427 +-83.13947296;40.56930161;0.199645899 +-99.66717529;47.58717346;0.020771388 +-90.45894623;39.65515137;0.347309198 +-88.42504883;38.43119812;0.173781342 +-95.1223526;42.38555145;0.40974176 +-85.25869751;40.74219894;0.31295613 +-94.84619904;37.16897583;0.06333884 +-90.98504639;40.85345078;0.365866133 +-89.15480042;36.35449982;0.189251436 +-94.6556778;42.38465118;0.445332369 +-86.20690155;41.04085159;0.362948088 +-93.22582245;44.02244949;0.363025259 +-93.45809937;36.74710083;0.174929273 +-83.76734924;40.38394928;0.188478863 +-95.90564728;44.73889923;0.35601318 +-91.07630157;41.46595001;0.30632366 +-101.0587463;41.56845093;0.135354629 +-91.84382629;42.86259842;0.350585563 +-87.21504974;39.77890015;0.188650876 +-90.81085205;32.87982559;0.111421875 +-84.64689636;39.74300003;0.228784134 +-86.96154785;39.03699875;0.09633489 +-97.54142761;41.53817368;0.264307151 +-92.06472778;42.08039856;0.372963749 +-88.47399902;38.7574501;0.226425147 +-85.76415253;39.82154846;0.350148635 +-93.5869751;44.02214813;0.424173066 +-95.47419739;40.42335129;0.346508298 +-98.22985077;47.45642471;0.013452717 +-85.99255371;41.91559982;0.186352046 +-83.43045044;39.90345001;0.258156261 +-85.88504791;39.19297409;0.21891284 +-87.31159973;40.30217361;0.369984652 +-89.66110229;42.3512001;0.409056064 +-99.86830139;40.52560043;0.160280894 +-92.78015137;42.38292313;0.471072715 +-94.67822266;43.37779999;0.45208028 +-85.4654007;39.61999893;0.372370065 +-90.27745056;33.5304985;0.069951261 +-85.48919678;40.82904816;0.244631982 +-96.62359619;28.96854973;0.10605161 +-92.31777191;42.77460098;0.403202786 +-90.02518463;36.31344986;0.033505462 +-100.2697525;46.28807449;0.001241821 +-85.61994934;39.00154877;0.115980065 +-89.6453476;36.60385132;0.175375363 +-88.58305359;40.86539841;0.439816145 +-96.26529694;44.41397476;0.242072137 +-85.01129913;40.15707397;0.282942826 +-94.20709991;43.20404816;0.47211745 +-90.94364929;33.82437515;0.023893618 +-98.95137787;40.51995087;0.384243649 +-96.4693985;45.38150024;0.222607908 +-91.25454712;33.81832504;0.006127531 +-87.26167297;40.15950012;0.381453916 +-93.26055145;43.37742615;0.416015625 +-97.59490204;40.17627335;0.215385954 +-85.65450287;40.51599884;0.26379429 +-92.31772614;43.06029892;0.373100131 +-95.15070343;42.73529816;0.42679373 +-88.19625092;40.13975143;0.425889839 +-98.05400085;40.93600082;0.507249209 +-95.62445068;43.3777504;0.427094566 +-92.30919647;42.46974945;0.366441255 +-85.01129913;40.43930054;0.244369722 +-90.43624878;36.34980011;0.041549351 +-97.25032806;42.60874939;0.07978939 +-86.57279968;40.5842514;0.412178316 +-97.59680176;40.87279892;0.460612601 +-88.19560242;40.69805145;0.449552377 +-93.93090057;42.0366745;0.386892783 +-92.31707764;43.35675049;0.344911808 +-83.05825043;41.54014969;0.097445578 +-89.93524933;38.82717514;0.338917598 +-98.0508728;40.5244751;0.299940666 +-94.19860077;44.58684921;0.347458167 +-86.05670166;38.89854813;0.151859869 +-97.94225311;41.13367462;0.372514849 +-90.51979828;41.88140106;0.379956977 +-91.13140106;42.12142563;0.351712555 +-90.77375031;41.19862366;0.357329061 +-89.42150116;39.26160049;0.348776286 +-94.20722198;42.77685165;0.446007228 +-86.37335205;40.73600006;0.342495822 +-95.64129639;44.95280075;0.361133751 +-91.25557709;33.28395081;0.027900486 +-88.82309723;42.32365036;0.387194688 +-89.52185059;36.34429932;0.105173785 +-88.17402649;38.07529831;0.246908279 +-90.14689636;41.36687469;0.400869766 +-96.5059967;46.89082336;0.051872058 +-96.37825012;48.35832596;0.46896587 +-94.86074829;42.03635025;0.42403304 +-90.26055145;39.69842529;0.355213535 +-102.2617493;34.53065109;0.095839688 +-90.61444855;40.84859848;0.404362973 +-90.36295319;32.75875092;0.080378312 +-83.46130371;39.54712677;0.274392493 +-94.25565338;44.80490112;0.02472399 +-92.78865051;43.35639954;0.414849548 +-89.59384918;40.53390121;0.337370542 +-88.20545197;39.76599884;0.483150818 +-86.29299927;41.59659958;0.071399496 +-97.59784698;41.22079849;0.32685603 +-89.30979919;36.81890106;0.228800586 +-93.73435211;42.73292542;0.449334447 +-94.18710327;42.42702484;0.403060903 +-85.3967514;40.22785187;0.23797643 +-96.00012207;45.58620071;0.356945878 +-98.88269806;47.45712662;0.022630668 +-83.64785004;40.66189957;0.224263221 +-93.46487427;42.03620148;0.429587945 +-94.98954773;44.67425156;0.385172496 +-90.37457275;39.09270096;0.249299084 +-87.09030151;39.38764954;0.228070298 +-96.08394623;42.38647461;0.31873503 +-87.94407654;37.6908493;0.34546146 +-95.38134766;42.03715134;0.372929965 +-95.27850342;38.89844894;0.006498019 +-89.53884888;37.05530167;0.218967483 +-91.36945343;42.4708519;0.413710305 +-94.39659882;42.03654861;0.426078887 +-90.00295258;42.06399918;0.445313028 +-87.75019836;39.67969894;0.400428995 +-87.73445129;40.17969894;0.351748454 +-90.60714722;33.62872314;0.064861125 +-87.6520462;38.34925079;0.270443758 +-97.59619904;40.52457428;0.336645214 +-96.52729797;46.32619858;0.084630305 +-88.09764862;38.70949936;0.304135197 +-88.54119873;37.75355148;0.399002799 +-88.8615036;40.16579819;0.385142613 +-86.84020233;40.73667526;0.414716273 +-85.25499725;39.11159897;0.102915808 +-94.67835236;43.08212662;0.414298193 +-86.46900177;40.30455017;0.41540669 +-92.78929901;43.06000137;0.393309493 +-85.32382202;40.47269821;0.179815263 +-88.87625122;41.2784996;0.384939027 +-87.88880157;41.14630127;0.406815663 +-90.67449951;34.25445175;0.052445186 +-96.46205139;47.32529831;0.031917959 +-90.96665192;33.26884842;0.101434412 +-87.31015015;40.60630035;0.453010017 +-93.73374939;43.37752533;0.4642982 +-98.50095367;40.52460098;0.378276921 +-91.13152313;41.77225113;0.373423264 +-87.89015198;38.01462555;0.294905141 +-88.41835022;41.28544998;0.386624792 +-87.44117737;39.08039856;0.211384727 +-87.42769623;38.6609993;0.33471541 +-88.45290375;42.32415009;0.311389059 +-86.46829987;40.05170059;0.401294785 +-99.41124725;40.51815033;0.42594959 +-83.79194641;39.40819931;0.206873053 +-99.21514893;45.41854858;0.095884836 +-89.93804932;41.75759888;0.469363801 +-98.06659698;42.17625046;0.2001756 +-96.55410004;45.80369949;0.278701946 +-87.82859802;40.7478981;0.430081823 +-89.31324768;42.04505157;0.389374102 +-84.99884796;41.39709854;0.269223027 +-87.59944916;37.78969955;0.205909752 +-87.07777405;38.70209885;0.279367715 +-95.38839722;39.23210144;0.003205965 +-89.92569733;39.26065063;0.380446045 +-89.51270294;41.36677551;0.445780948 +-90.52909851;33.12594986;0.061290631 +-89.24485016;40.76100159;0.398356852 +-91.83944702;42.47019958;0.421218009 +-98.07407379;46.93562317;0.034776925 +-89.31414795;41.21319962;0.339291302 +-93.71707153;42.38375092;0.451127575 +-90.84234619;34.38195038;0.045115279 +-90.67727661;40.45764923;0.396196513 +-92.78967285;42.7317009;0.378254333 +-88.64040375;39.61975098;0.401799017 +-93.73435211;43.08169937;0.435008142 +-93.25002289;42.3830986;0.441911429 +-93.26065063;43.08160019;0.427513504 +-88.60269928;40.03654861;0.4396107 +-89.37345123;40.12097549;0.455246789 +-93.26174927;42.73244858;0.439959481 +-89.27935028;39.58472443;0.413840155 +-96.01200104;45.93434906;0.215325946 +-89.81130219;41.10385132;0.489468292 +-99.99237823;45.07220078;0.002704878 +-89.28507233;41.74874878;0.452146176 +-97.15119934;47.45484924;0.113304772 +-95.26959991;39.53597641;0.003252648 +-98.40887451;43.34777451;0.005766614 +-97.12895203;44.02209854;0.003606431 +-95.09937286;39.81169891;0.011554006 diff --git a/InteractiveDataDisplay_OneDll/samples/BingMapsSample/packages.config b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/packages.config new file mode 100644 index 000000000..10664af48 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/BingMapsSample/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.config b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.config new file mode 100644 index 000000000..88fa4027b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.xaml b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.xaml new file mode 100644 index 000000000..158681e54 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.xaml.cs b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.xaml.cs new file mode 100644 index 000000000..84802cb85 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace HeatmapSample +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/HeatmapSample.csproj b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/HeatmapSample.csproj new file mode 100644 index 000000000..efd14c70b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/HeatmapSample.csproj @@ -0,0 +1,120 @@ + + + + + Debug + AnyCPU + {A59C204A-01D9-41A4-BE67-64ECDB991560} + WinExe + HeatmapSample + HeatmapSample + v4.5.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\packages\System.Reactive.Core.3.1.1\lib\net45\System.Reactive.Core.dll + + + ..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll + + + ..\packages\System.Reactive.Linq.3.1.1\lib\net45\System.Reactive.Linq.dll + + + ..\packages\System.Reactive.PlatformServices.3.1.1\lib\net45\System.Reactive.PlatformServices.dll + + + ..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {2d34544c-2df2-4b20-a43a-6c8d2df3dd82} + InteractiveDataDisplay.WPF + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/MainWindow.xaml b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/MainWindow.xaml new file mode 100644 index 000000000..8539554c4 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/MainWindow.xaml @@ -0,0 +1,14 @@ + + + + + Heatmap sample + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/MainWindow.xaml.cs b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/MainWindow.xaml.cs new file mode 100644 index 000000000..89144ae67 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/MainWindow.xaml.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Reactive.Linq; + +namespace HeatmapSample +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private volatile bool isUnloaded = false; + private AutoResetEvent renderComplete = new AutoResetEvent(false); + private Thread thread; + private double phase = 0; + + const int N = 1000; + const int M = 500; + + double[] x = new double[N + 1]; + double[] y = new double[M + 1]; + double[,] f = new double[N, M]; + + public MainWindow() + { + InitializeComponent(); + + // Start computation thread when Page appears on the screen + Loaded += (s, e) => + { + thread = new Thread(ModelRun); + isUnloaded = false; + thread.Start(); + }; + + // Stop computation thread when Page is removed from the screen + Closing += (s, e) => + { + isUnloaded = true; + renderComplete.Set(); + thread.Join(); + }; + } + + private void ModelRun() + { + // Coordinate grid is constant + for (int i = 0; i <= N; i++) + x[i] = -Math.PI + 2 * i * Math.PI / N; + + for (int j = 0; j <= M; j++) + y[j] = -Math.PI / 2 + j * Math.PI / M; + + while (!isUnloaded) // Run until page is on the screen + { + // Data array is updated + for (int i = 0; i < N; i++) + for (int j = 0; j < M; j++) + f[i, j] = Math.Sqrt(x[i] * x[i] + y[j] * y[j]) * Math.Abs(Math.Cos(x[i] * x[i] + y[j] * y[j] + phase)); + phase += 0.1; + + // Uncomment next line to simulate delay in computations + // Thread.Sleep(1000); + + // Plot the computed array. + // The Plot method must be called from the UI dispatcher thread. + // Unlike Tutoral3 sample here we wait for each frame to be rendered. + if (!isUnloaded) + { + renderComplete.Reset(); + PlotDataDel handler = PlotData; + Dispatcher.BeginInvoke(handler); + renderComplete.WaitOne(); + } + } + } + private delegate void PlotDataDel(); + private void PlotData() + { + // HeatmapGraph objects prepare images to be drawn in a background thread. + // The Plot method cancels current incomplete images before starting a new one. + // This increases responsiveness of the UI but may result in loss of certain + // or even all of the frames. + // The following code shows how to wait until a certain data is actually drawn. + + long id = heatmap.Plot(f, x, y); // receive a unique operation identifier + heatmap.RenderCompletion // an observable of completed and cancelled operations + .Where(rc => rc.TaskId == id) // filter out an operation with the known id + .Subscribe(dummy => { renderComplete.Set(); }); // signal when the id is observed + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/AssemblyInfo.cs b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..13a33f81a --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("HeatmapSample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("HeatmapSample")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Resources.Designer.cs b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Resources.Designer.cs new file mode 100644 index 000000000..d154f716c --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace HeatmapSample.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HeatmapSample.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Resources.resx b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Settings.Designer.cs b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Settings.Designer.cs new file mode 100644 index 000000000..bcbf719d2 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace HeatmapSample.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Settings.settings b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/HeatmapSample/packages.config b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/packages.config new file mode 100644 index 000000000..ae4ab0a2d --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/HeatmapSample/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.config b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.config new file mode 100644 index 000000000..88fa4027b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.xaml b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.xaml new file mode 100644 index 000000000..ac43cbb64 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.xaml.cs b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.xaml.cs new file mode 100644 index 000000000..43804b1d4 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace LineGraphSample +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/LineGraphSample.csproj b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/LineGraphSample.csproj new file mode 100644 index 000000000..f4d1ea65c --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/LineGraphSample.csproj @@ -0,0 +1,103 @@ + + + + + Debug + AnyCPU + {284A7617-B70F-4A4A-B73A-1A8088B75926} + WinExe + LineGraphSample + LineGraphSample + v4.5.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {2d34544c-2df2-4b20-a43a-6c8d2df3dd82} + InteractiveDataDisplay.WPF + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/MainWindow.xaml b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/MainWindow.xaml new file mode 100644 index 000000000..b3082e40d --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/MainWindow.xaml @@ -0,0 +1,35 @@ + + + + + + + + + Line graph legend sample + + + + + + + + + + + + + + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/MainWindow.xaml.cs b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/MainWindow.xaml.cs new file mode 100644 index 000000000..9e05c7bef --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/MainWindow.xaml.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media; +using InteractiveDataDisplay.WPF; + +namespace LineGraphSample +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + + double[] x = new double[200]; + for (int i = 0; i < x.Length; i++) + x[i] = 3.1415 * i / (x.Length - 1); + + for (int i = 0; i < 25; i++) + { + var lg = new LineGraph(); + lines.Children.Add(lg); + lg.Stroke = new SolidColorBrush(Color.FromArgb(255, 0, (byte)(i * 10), 0)); + lg.Description = String.Format("Data series {0}", i + 1); + lg.StrokeThickness = 2; + lg.Plot(x, x.Select(v => Math.Sin(v + i / 10.0)).ToArray()); + } + } + } + + public class VisibilityToCheckedConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return ((Visibility)value) == Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return ((bool)value) ? Visibility.Visible : Visibility.Collapsed; + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/AssemblyInfo.cs b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c9d23ca9c --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LineGraphSample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LineGraphSample")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Resources.Designer.cs b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Resources.Designer.cs new file mode 100644 index 000000000..9b9dc79ba --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LineGraphSample.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LineGraphSample.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Resources.resx b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Settings.Designer.cs b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Settings.Designer.cs new file mode 100644 index 000000000..d0381d518 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LineGraphSample.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Settings.settings b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/LineGraphSample/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.config b/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.config new file mode 100644 index 000000000..88fa4027b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.xaml b/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.xaml new file mode 100644 index 000000000..65a1fe8e6 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.xaml.cs b/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.xaml.cs new file mode 100644 index 000000000..34d1954b0 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace MarkersSample +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/MainWindow.xaml b/InteractiveDataDisplay_OneDll/samples/MarkersSample/MainWindow.xaml new file mode 100644 index 000000000..011b8a519 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/MainWindow.xaml @@ -0,0 +1,18 @@ + + + + + Markers sample + + + + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/MainWindow.xaml.cs b/InteractiveDataDisplay_OneDll/samples/MarkersSample/MainWindow.xaml.cs new file mode 100644 index 000000000..700505fcc --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/MainWindow.xaml.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; + +namespace MarkersSample +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + + const int N =100; + const int M = 1000; + double[] x = new double[N]; + double[] y = new double[N]; + double[] c = new double[N]; + double[] d = new double[N]; + + Random rand = new Random(); + //circles + for (int i = 0; i < N; i++) + { + x[i] = rand.Next(M); + y[i] = rand.Next(M); + c[i] = rand.NextDouble(); + d[i] = 20 * rand.NextDouble(); + } + + circles.PlotColorSize(x, y, c, d); + //diamonds + x = new double[N]; + y = new double[N]; + c = new double[N]; + d = new double[N]; + + for (int i = 0; i < N; i++) + { + x[i] = rand.Next(M); + y[i] = rand.Next(M); + c[i] = rand.NextDouble(); + d[i] = 20 * rand.NextDouble(); + } + diamonds.PlotColorSize(x, y, c, d); + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/MarkersSample.csproj b/InteractiveDataDisplay_OneDll/samples/MarkersSample/MarkersSample.csproj new file mode 100644 index 000000000..9c5f8bc73 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/MarkersSample.csproj @@ -0,0 +1,103 @@ + + + + + Debug + AnyCPU + {4BEA4B4C-5B83-4FD7-B9BF-3AE4F1CC571F} + WinExe + MarkersSample + MarkersSample + v4.5.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {2d34544c-2df2-4b20-a43a-6c8d2df3dd82} + InteractiveDataDisplay.WPF + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/AssemblyInfo.cs b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b7bcd4ffa --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MarkersSample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MarkersSample")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Resources.Designer.cs b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Resources.Designer.cs new file mode 100644 index 000000000..8b84e16b0 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MarkersSample.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MarkersSample.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Resources.resx b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Settings.Designer.cs b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Settings.Designer.cs new file mode 100644 index 000000000..b602d2628 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MarkersSample.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Settings.settings b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/MarkersSample/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.config b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.config new file mode 100644 index 000000000..8227adb98 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.xaml b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.xaml new file mode 100644 index 000000000..cf49a1a7c --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.xaml.cs b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.xaml.cs new file mode 100644 index 000000000..baf17bc01 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace SyncGraphsSample +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/MainWindow.xaml b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/MainWindow.xaml new file mode 100644 index 000000000..e74aa46d9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/MainWindow.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + These three Charts have shared axes. Two of the share + vertical axis, two - horizontal. Navigate in one plot and see how other is changed. This is done purely in + XAML. + + + diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/MainWindow.xaml.cs b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/MainWindow.xaml.cs new file mode 100644 index 000000000..e023808ac --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/MainWindow.xaml.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Media; + +namespace SyncGraphsSample +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + + barGraph.PlotBars(Enumerable.Range(1, 10)); + + int N = 25; + Random r = new Random(); + double[] x = new double[N]; + double[] y = new double[N]; + for (int i = 0; i < N; i++) + { + x[i] = r.NextDouble() * 12 - 1; + y[i] = r.NextDouble() * 12 - 1; + } + circles.PlotXY(x, y); + + double[] x1 = Enumerable.Range(0, 90).Select(i => i / 10.0).ToArray(); + double[] y1 = x1.Select(v => 7 * (Math.Abs(v) < 1e-10 ? 1 : Math.Sin(v) / v) + 1).ToArray(); + lineGraph1.Stroke = new SolidColorBrush(Colors.Green); + lineGraph1.StrokeThickness = 2.0; + lineGraph1.Plot(x1, y1); + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/AssemblyInfo.cs b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e1d9baa18 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SyncGraphsSample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SyncGraphsSample")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Resources.Designer.cs b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Resources.Designer.cs new file mode 100644 index 000000000..30ad0cd71 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// Этот код создан программой. +// Исполняемая версия:4.0.30319.42000 +// +// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае +// повторной генерации кода. +// +//------------------------------------------------------------------------------ + +namespace SyncGraphsSample.Properties { + using System; + + + /// + /// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д. + /// + // Этот класс создан автоматически классом StronglyTypedResourceBuilder + // с помощью такого средства, как ResGen или Visual Studio. + // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen + // с параметром /str или перестройте свой проект VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SyncGraphsSample.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Перезаписывает свойство CurrentUICulture текущего потока для всех + /// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Resources.resx b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Settings.Designer.cs b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Settings.Designer.cs new file mode 100644 index 000000000..c51c31c4b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// Этот код создан программой. +// Исполняемая версия:4.0.30319.42000 +// +// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае +// повторной генерации кода. +// +//------------------------------------------------------------------------------ + +namespace SyncGraphsSample.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Settings.settings b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/SyncGraphsSample.csproj b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/SyncGraphsSample.csproj new file mode 100644 index 000000000..a0886badb --- /dev/null +++ b/InteractiveDataDisplay_OneDll/samples/SyncGraphsSample/SyncGraphsSample.csproj @@ -0,0 +1,113 @@ + + + + + Debug + AnyCPU + {0C4C6C54-F253-4FD2-8B38-75E476882A4B} + WinExe + Properties + SyncGraphsSample + SyncGraphsSample + v4.5.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + {2d34544c-2df2-4b20-a43a-6c8d2df3dd82} + InteractiveDataDisplay.WPF + + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/sln/InteractiveDataDisplay.WPF.sln b/InteractiveDataDisplay_OneDll/sln/InteractiveDataDisplay.WPF.sln new file mode 100644 index 000000000..ba1327086 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/sln/InteractiveDataDisplay.WPF.sln @@ -0,0 +1,57 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31313.79 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteractiveDataDisplay.WPF", "..\src\InteractiveDataDisplay.WPF.csproj", "{2D34544C-2DF2-4B20-A43A-6C8D2DF3DD82}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{6E637A98-DA68-40CF-B194-56CEB99A57A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkersSample", "..\samples\MarkersSample\MarkersSample.csproj", "{4BEA4B4C-5B83-4FD7-B9BF-3AE4F1CC571F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LineGraphSample", "..\samples\LineGraphSample\LineGraphSample.csproj", "{284A7617-B70F-4A4A-B73A-1A8088B75926}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BarChartSample", "..\samples\BarChartSample\BarChartSample.csproj", "{F36872BF-8818-4019-A207-2B381C33C370}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyncGraphsSample", "..\samples\SyncGraphsSample\SyncGraphsSample.csproj", "{0C4C6C54-F253-4FD2-8B38-75E476882A4B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2D34544C-2DF2-4B20-A43A-6C8D2DF3DD82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D34544C-2DF2-4B20-A43A-6C8D2DF3DD82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D34544C-2DF2-4B20-A43A-6C8D2DF3DD82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D34544C-2DF2-4B20-A43A-6C8D2DF3DD82}.Release|Any CPU.Build.0 = Release|Any CPU + {4BEA4B4C-5B83-4FD7-B9BF-3AE4F1CC571F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BEA4B4C-5B83-4FD7-B9BF-3AE4F1CC571F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BEA4B4C-5B83-4FD7-B9BF-3AE4F1CC571F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BEA4B4C-5B83-4FD7-B9BF-3AE4F1CC571F}.Release|Any CPU.Build.0 = Release|Any CPU + {284A7617-B70F-4A4A-B73A-1A8088B75926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {284A7617-B70F-4A4A-B73A-1A8088B75926}.Debug|Any CPU.Build.0 = Debug|Any CPU + {284A7617-B70F-4A4A-B73A-1A8088B75926}.Release|Any CPU.ActiveCfg = Release|Any CPU + {284A7617-B70F-4A4A-B73A-1A8088B75926}.Release|Any CPU.Build.0 = Release|Any CPU + {F36872BF-8818-4019-A207-2B381C33C370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F36872BF-8818-4019-A207-2B381C33C370}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F36872BF-8818-4019-A207-2B381C33C370}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F36872BF-8818-4019-A207-2B381C33C370}.Release|Any CPU.Build.0 = Release|Any CPU + {0C4C6C54-F253-4FD2-8B38-75E476882A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C4C6C54-F253-4FD2-8B38-75E476882A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C4C6C54-F253-4FD2-8B38-75E476882A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C4C6C54-F253-4FD2-8B38-75E476882A4B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4BEA4B4C-5B83-4FD7-B9BF-3AE4F1CC571F} = {6E637A98-DA68-40CF-B194-56CEB99A57A0} + {284A7617-B70F-4A4A-B73A-1A8088B75926} = {6E637A98-DA68-40CF-B194-56CEB99A57A0} + {F36872BF-8818-4019-A207-2B381C33C370} = {6E637A98-DA68-40CF-B194-56CEB99A57A0} + {0C4C6C54-F253-4FD2-8B38-75E476882A4B} = {6E637A98-DA68-40CF-B194-56CEB99A57A0} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {206EA3D1-9D79-4D18-BE4A-C941D2C4BDEF} + EndGlobalSection +EndGlobal diff --git a/InteractiveDataDisplay_OneDll/src/Axes/Axis.cs b/InteractiveDataDisplay_OneDll/src/Axes/Axis.cs new file mode 100644 index 000000000..a0cfd2dcd --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/Axis.cs @@ -0,0 +1,656 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Data; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// Facilitates drawing of vertical or horizontal coordinate axis + [Description("Vertical or horizontal coordinate axis")] + public class Axis : Panel + { + private ILabelProvider labelProvider; + private TicksProvider ticksProvider; + private Path majorTicksPath; + private Path minorTicksPath; + + private FrameworkElement[] labels; + + private const int maxTickArrangeIterations = 12; + private int maxTicks = 20; + private const double increaseRatio = 8.0; + private const double decreaseRatio = 8.0; + private const double tickLength = 10; + + private bool drawTicks = true; + private bool drawMinorTicks = true; + + /// + /// Initializes a new instance of class with double + /// and . + /// + public Axis() + { + DataTransform = new IdentityDataTransform(); + Ticks = new double[0]; + + majorTicksPath = new Path(); + minorTicksPath = new Path(); + Children.Add(majorTicksPath); + Children.Add(minorTicksPath); + + BindingOperations.SetBinding(majorTicksPath, Path.StrokeProperty, new Binding("Foreground") { Source = this, Mode = BindingMode.TwoWay }); + BindingOperations.SetBinding(minorTicksPath, Path.StrokeProperty, new Binding("Foreground") { Source = this, Mode = BindingMode.TwoWay }); + + if (labelProvider == null) + this.labelProvider = new LabelProvider(); + if (ticksProvider == null) + this.ticksProvider = new TicksProvider(); + } + + /// + /// Initializes a new instance of class. + /// + /// A to create labels. + /// A to create ticks. + public Axis(ILabelProvider labelProvider, TicksProvider ticksProvider) + : this() + { + this.labelProvider = labelProvider; + this.ticksProvider = ticksProvider; + } + + /// + /// Gets or sets a set of double values displayed as axis ticks in data values. + /// + /// + /// Ticks are calculated from current Range of axis in plot coordinates and current DataTransform of axis. + /// Example: for range [-1,1] int plot coordinates and DataTransform as y = 0.001 * x + 0.5 values of Ticks will be in [-1000,1000] range + /// It is not recommended to set this property manually. + /// + [Browsable(false)] + public IEnumerable Ticks + { + get { return (IEnumerable)GetValue(TicksProperty); } + set { SetValue(TicksProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TicksProperty = + DependencyProperty.Register("Ticks", typeof(IEnumerable), typeof(Axis), new PropertyMetadata(new double[0])); + + /// + /// Gets or sets the range of values on axis in plot coordinates. + /// + /// + /// should be inside Domain of , otherwise exception will be thrown. + /// + [Category("InteractiveDataDisplay")] + [Description("Range of values on axis")] + public Range Range + { + get { return (Range)GetValue(RangeProperty); } + set { SetValue(RangeProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RangeProperty = + DependencyProperty.Register("Range", typeof(Range), typeof(Axis), new PropertyMetadata(new Range(0, 1), + (o, e) => + { + Axis axis = (Axis)o; + if (axis != null) + { + axis.InvalidateMeasure(); + } + })); + + + /// + /// Gets or sets orientation of the axis and location of labels + /// + /// The default AxisOrientation is AxisOrientation.Bottom + [Category("InteractiveDataDisplay")] + [Description("Defines orientation of axis and location of labels")] + public AxisOrientation AxisOrientation + { + get { return (AxisOrientation)GetValue(AxisOrientationProperty); } + set { SetValue(AxisOrientationProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AxisOrientationProperty = + DependencyProperty.Register("AxisOrientation", typeof(AxisOrientation), typeof(Axis), new PropertyMetadata(AxisOrientation.Bottom, + (o, e) => + { + Axis axis = (Axis)o; + if (axis != null) + { + axis.InvalidateMeasure(); + } + })); + + /// + /// Gets or sets a flag indicating whether the axis is reversed or not. + /// + /// Axis is not reversed by default. + [Category("InteractiveDataDisplay")] + [Description("Defines orientation of axis and location of labels")] + public bool IsReversed + { + get { return (bool)GetValue(IsReversedProperty); } + set { SetValue(IsReversedProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsReversedProperty = + DependencyProperty.Register("IsReversed", typeof(bool), typeof(Axis), new PropertyMetadata(false, + (o, e) => + { + Axis axis = (Axis)o; + if (axis != null) + { + axis.InvalidateMeasure(); + } + })); + + /// + /// Gets or sets for an axis. + /// + /// + /// The default transform is + /// DataTransform is used for transform plot coordinates from Range property to data values, which will be displayed on ticks + /// + [Description("Transform from data to plot coordinates")] + [Category("InteractiveDataDisplay")] + public DataTransform DataTransform + { + get { return (DataTransform)GetValue(DataTransformProperty); } + set { SetValue(DataTransformProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DataTransformProperty = + DependencyProperty.Register("DataTransform", typeof(DataTransform), typeof(Axis), new PropertyMetadata(null, + (o, e) => + { + Axis axis = (Axis)o; + if (axis != null) + { + axis.InvalidateMeasure(); + } + })); + + /// + /// Gets or sets the brush for labels and ticks of axis + /// + /// The default foreground is black + [Category("Appearance")] + [Description("Brush for labels and ticks")] + public SolidColorBrush Foreground + { + get { return (SolidColorBrush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ForegroundProperty = + DependencyProperty.Register("Foreground", typeof(SolidColorBrush), typeof(Axis), new PropertyMetadata(new SolidColorBrush(Colors.Black))); + + /// + /// Gets or sets the maximum possible count of ticks. + /// + /// The defalut count is 20 + [Category("InteractiveDataDisplay")] + [Description("Maximum number of major ticks")] + public int MaxTicks + { + set + { + maxTicks = value; + InvalidateMeasure(); + } + get + { + return maxTicks; + } + } + + /// + /// Gets or sets a value indicating whether the ticks should be rendered or not. + /// + /// The default value is true. + [Category("InteractiveDataDisplay")] + public bool AreTicksVisible + { + set + { + drawTicks = value; + drawMinorTicks = value; + if (drawTicks) + InvalidateMeasure(); + } + get { return drawTicks; } + } + + /// + /// Gets or sets a value indicating whether the minor ticks should be rendered or not. + /// + /// The default value is true. + [Category("InteractiveDataDisplay")] + public bool AreMinorTicksVisible + { + set + { + drawMinorTicks = value; + if (drawMinorTicks) + InvalidateMeasure(); + } + get { return drawMinorTicks; } + } + + private void CreateTicks(Size axisSize) + { + Range range = new Range( + DataTransform.PlotToData(Double.IsInfinity(Range.Min) || Double.IsNaN(Range.Min) ? 0 : Range.Min), + DataTransform.PlotToData(Double.IsInfinity(Range.Max) || Double.IsNaN(Range.Max) ? 1 : Range.Max)); + + // One tick if range is point + if (range.IsPoint) + { + var t = new double[] { range.Min }; + Ticks = t; + labels = labelProvider.GetLabels(t); + return; + } + + // Do first pass of ticks arrangement + ticksProvider.Range = range; + double[] ticks = ticksProvider.GetTicks(); + labels = labelProvider.GetLabels(ticks); + + TickChange result; + if (ticks.Length > MaxTicks) + result = TickChange.Decrease; + else if (ticks.Length < 2) + result = TickChange.Increase; + else + result = CheckLabelsArrangement(axisSize, labels, ticks); + + int iterations = 0; + int prevLength = ticks.Length; + while (result != TickChange.OK && iterations++ < maxTickArrangeIterations) + { + if (result == TickChange.Increase) + ticksProvider.IncreaseTickCount(); + else + ticksProvider.DecreaseTickCount(); + double[] newTicks = ticksProvider.GetTicks(); + if (newTicks.Length > MaxTicks && result == TickChange.Increase) + { + ticksProvider.DecreaseTickCount(); // Step back and stop to not get more than MaxTicks + break; + } + else if (newTicks.Length < 2 && result == TickChange.Decrease) + { + ticksProvider.IncreaseTickCount(); // Step back and stop to not get less than 2 + break; + } + var prevTicks = ticks; + ticks = newTicks; + var prevLabels = labels; + labels = labelProvider.GetLabels(newTicks); + var newResult = CheckLabelsArrangement(axisSize, labels, ticks); + if (newResult == result) // Continue in the same direction + { + prevLength = newTicks.Length; + } + else // Direction changed or layout OK - stop the loop + { + if (newResult != TickChange.OK) // Direction changed - time to stop + { + if (result == TickChange.Decrease) + { + if (prevLength < MaxTicks) + { + ticks = prevTicks; + labels = prevLabels; + ticksProvider.IncreaseTickCount(); + } + } + else + { + if (prevLength >= 2) + { + ticks = prevTicks; + labels = prevLabels; + ticksProvider.DecreaseTickCount(); + } + } + break; + } + break; + } + } + + Ticks = ticks; + } + + private void DrawCanvas(Size axisSize, double[] cTicks) + { + double length = IsHorizontal ? axisSize.Width : axisSize.Height; + + GeometryGroup majorTicksGeometry = new GeometryGroup(); + GeometryGroup minorTicksGeometry = new GeometryGroup(); + + if (!Double.IsNaN(length) && length != 0) + { + int start = 0; + if (cTicks.Length > 0 && cTicks[0] < Range.Min) start++; + + if (Range.IsPoint) + drawMinorTicks = false; + + for (int i = start; i < cTicks.Length; i++) + { + LineGeometry line = new LineGeometry(); + majorTicksGeometry.Children.Add(line); + + if (IsHorizontal) + { + line.StartPoint = new Point(GetCoordinateFromTick(cTicks[i], axisSize), 0); + line.EndPoint = new Point(GetCoordinateFromTick(cTicks[i], axisSize), tickLength); + } + else + { + line.StartPoint = new Point(0, GetCoordinateFromTick(cTicks[i], axisSize)); + line.EndPoint = new Point(tickLength, GetCoordinateFromTick(cTicks[i], axisSize)); + } + + if (labels[i] is TextBlock) + { + (labels[i] as TextBlock).Foreground = Foreground; + } + else if (labels[i] is Control) + { + (labels[i] as Control).Foreground = Foreground; + } + + Children.Add(labels[i]); + } + + if (drawMinorTicks) + { + double[] minorTicks = ticksProvider.GetMinorTicks(Range); + if (minorTicks != null) + { + for (int j = 0; j < minorTicks.Length; j++) + { + LineGeometry line = new LineGeometry(); + minorTicksGeometry.Children.Add(line); + + if (IsHorizontal) + { + line.StartPoint = new Point(GetCoordinateFromTick(minorTicks[j], axisSize), 0); + line.EndPoint = new Point(GetCoordinateFromTick(minorTicks[j], axisSize), tickLength / 2.0); + } + else + { + line.StartPoint = new Point(0, GetCoordinateFromTick(minorTicks[j], axisSize)); + line.EndPoint = new Point(tickLength / 2.0, GetCoordinateFromTick(minorTicks[j], axisSize)); + } + } + } + } + + majorTicksPath.Data = majorTicksGeometry; + minorTicksPath.Data = minorTicksGeometry; + + if (!drawMinorTicks) + drawMinorTicks = true; + } + } + + /// + /// Positions child elements and determines a size for a Figure. + /// + /// The final area within the parent that Figure should use to arrange itself and its children. + /// The actual size used. + protected override Size ArrangeOverride(Size finalSize) + { + finalSize.Width = Math.Min(DesiredSize.Width, finalSize.Width); + finalSize.Height = Math.Min(DesiredSize.Height, finalSize.Height); + + double labelArrangeOriginX = 0; + double labelArrangeOriginY = 0; + + + switch (AxisOrientation) + { + case AxisOrientation.Top: + majorTicksPath.Arrange(new Rect(0, finalSize.Height - tickLength, finalSize.Width, tickLength)); + minorTicksPath.Arrange(new Rect(0, finalSize.Height - tickLength / 2.0, finalSize.Width, tickLength / 2.0)); + break; + case AxisOrientation.Bottom: + majorTicksPath.Arrange(new Rect(0, 0, finalSize.Width, tickLength)); + minorTicksPath.Arrange(new Rect(0, 0, finalSize.Width, tickLength / 2.0)); + break; + case AxisOrientation.Right: + majorTicksPath.Arrange(new Rect(0, 0, tickLength, finalSize.Height)); + minorTicksPath.Arrange(new Rect(0, 0, tickLength / 2.0, finalSize.Height)); + break; + case AxisOrientation.Left: + majorTicksPath.Arrange(new Rect(Math.Max(0, finalSize.Width - tickLength), 0, tickLength, finalSize.Height)); + minorTicksPath.Arrange(new Rect(Math.Max(0, finalSize.Width - tickLength / 2.0), 0, tickLength / 2.0, finalSize.Height)); + labelArrangeOriginX = finalSize.Width - tickLength - CalculateMaxLabelWidth(); + break; + } + + foreach (var label in labels) + { + label.Arrange(new Rect(labelArrangeOriginX, labelArrangeOriginY, label.DesiredSize.Width, label.DesiredSize.Height)); + } + + return finalSize; + } + + /// + /// Measures the size in layout required for child elements and determines a size for the Figure. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + if (Double.IsInfinity(availableSize.Width)) + availableSize.Width = 128; + if (Double.IsInfinity(availableSize.Height)) + availableSize.Height = 128; + + + Size effectiveSize = availableSize; + + + ClearLabels(); + CreateTicks(effectiveSize); + + double[] cTicks = Ticks.ToArray(); + DrawCanvas(effectiveSize, cTicks); + + foreach (UIElement child in Children) + { + child.Measure(availableSize); + } + + double maxLabelWidth = CalculateMaxLabelWidth(); + double maxLabelHeight = CalculateMaxLabelHeight(); + + switch (AxisOrientation) + { + case AxisOrientation.Top: + + for (int i = 0; i < labels.Length; i++) + { + labels[i].RenderTransform = new TranslateTransform { X = GetCoordinateFromTick(cTicks[i], effectiveSize) - labels[i].DesiredSize.Width / 2, Y = 0 }; + } + break; + case AxisOrientation.Bottom: + for (int i = 0; i < labels.Length; i++) + { + labels[i].RenderTransform = new TranslateTransform { X = GetCoordinateFromTick(cTicks[i], effectiveSize) - labels[i].DesiredSize.Width / 2, Y = majorTicksPath.DesiredSize.Height }; + } + break; + case AxisOrientation.Right: + for (int i = 0; i < labels.Length; i++) + { + labels[i].RenderTransform = new TranslateTransform { X = majorTicksPath.DesiredSize.Width, Y = GetCoordinateFromTick(cTicks[i], effectiveSize) - labels[i].DesiredSize.Height / 2 }; + } + break; + case AxisOrientation.Left: + for (int i = 0; i < labels.Length; i++) + { + labels[i].RenderTransform = new TranslateTransform { X = maxLabelWidth - labels[i].DesiredSize.Width, Y = GetCoordinateFromTick(cTicks[i], effectiveSize) - labels[i].DesiredSize.Height / 2 }; + } + break; + } + + if (IsHorizontal) + { + availableSize.Height = majorTicksPath.DesiredSize.Height + maxLabelHeight; + } + else + { + availableSize.Width = majorTicksPath.DesiredSize.Width + maxLabelWidth; + } + + return availableSize; + } + + private void ClearLabels() + { + if (labels != null) + { + foreach (var elt in labels) + { + if (Children.Contains(elt)) + Children.Remove(elt); + } + } + } + + private double CalculateMaxLabelHeight() + { + if (labels != null) + { + double max = 0; + for (int i = 0; i < labels.Length; i++) + if (Children.Contains(labels[i]) && labels[i].DesiredSize.Height > max) + max = labels[i].DesiredSize.Height; + return max; + } + return 0; + } + + private double CalculateMaxLabelWidth() + { + if (labels != null) + { + double max = 0; + for (int i = 0; i < labels.Length; i++) + if (Children.Contains(labels[i]) && labels[i].DesiredSize.Width > max) + max = labels[i].DesiredSize.Width; + return max; + } + return 0; + } + + private TickChange CheckLabelsArrangement(Size axisSize, IEnumerable inputLabels, double[] inputTicks) + { + var actualLabels1 = inputLabels.Select((label, i) => new { Label = label, Index = i }); + var actualLabels2 = actualLabels1.Where(el => el.Label != null); + var actualLabels3 = actualLabels2.Select(el => new { Label = el.Label, Tick = inputTicks[el.Index] }); + var actualLabels = actualLabels3.ToList(); + + actualLabels.ForEach(item => item.Label.Measure(axisSize)); + + var sizeInfos = actualLabels.Select(item => + new { Offset = GetCoordinateFromTick(item.Tick, axisSize), Size = IsHorizontal ? item.Label.DesiredSize.Width : item.Label.DesiredSize.Height }) + .OrderBy(item => item.Offset).ToArray(); + + // If distance between labels if smaller than threshold for any of the labels - decrease + for (int i = 0; i < sizeInfos.Length - 1; i++) + if ((sizeInfos[i].Offset + sizeInfos[i].Size * decreaseRatio / 2) > sizeInfos[i + 1].Offset) + return TickChange.Decrease; + + // If distance between labels if larger than threshold for all of the labels - increase + TickChange res = TickChange.Increase; + for (int i = 0; i < sizeInfos.Length - 1; i++) + { + if ((sizeInfos[i].Offset + sizeInfos[i].Size * increaseRatio / 2) > sizeInfos[i + 1].Offset) + { + res = TickChange.OK; + break; + } + } + + return res; + } + + private double GetCoordinateFromTick(double tick, Size screenSize) + { + return ValueToScreen(DataTransform.DataToPlot(tick), screenSize, Range); + } + + private double ValueToScreen(double value, Size screenSize, Range range) + { + double leftBound, rightBound, topBound, bottomBound; + leftBound = bottomBound = IsReversed ? range.Max : range.Min; + rightBound = topBound = IsReversed ? range.Min : range.Max; + + if (IsHorizontal) + { + return range.IsPoint ? + (screenSize.Width / 2) : + ((value - leftBound) * screenSize.Width / (rightBound - leftBound)); + } + else + { + return range.IsPoint ? + (screenSize.Height / 2) : + (screenSize.Height - (value - leftBound) * screenSize.Height / (rightBound - leftBound)); + } + } + + /// + /// Gets the value indcating whether the axis is horizontal or not. + /// + private bool IsHorizontal + { + get + { + return (AxisOrientation == AxisOrientation.Bottom || AxisOrientation == AxisOrientation.Top); + } + } + } + + internal enum TickChange + { + Increase = -1, + OK = 0, + Decrease = 1 + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/AxisGrid.cs b/InteractiveDataDisplay_OneDll/src/Axes/AxisGrid.cs new file mode 100644 index 000000000..3b83d13a0 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/AxisGrid.cs @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Windows.Data; +using System.Collections.Generic; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Facilitates drawing of vertical and/or horizontal grid lines + /// + [Description("Grid of vertical and horizontal lines")] + public class AxisGrid : PlotBase + { + private Path path; + + /// + /// Identifies the dependency property + /// + public static readonly DependencyProperty VerticalTicksProperty = + DependencyProperty.Register("VerticalTicks", typeof(IEnumerable), typeof(AxisGrid), new PropertyMetadata(new double[0], + (o, e) => + { + AxisGrid axisGrid = (AxisGrid)o; + if (axisGrid != null) + { + axisGrid.InvalidateMeasure(); + } + })); + + /// + /// Identifies the dependency property + /// + public static readonly DependencyProperty HorizontalTicksProperty = + DependencyProperty.Register("HorizontalTicks", typeof(IEnumerable), typeof(AxisGrid), new PropertyMetadata(new double[0], + (o, e) => + { + AxisGrid axisGrid = (AxisGrid)o; + if (axisGrid != null) + { + axisGrid.InvalidateMeasure(); + } + })); + + /// + /// Identifies the dependency property + /// + public static readonly DependencyProperty StrokeProperty = + DependencyProperty.Register("Stroke", typeof(SolidColorBrush), typeof(AxisGrid), new PropertyMetadata(new SolidColorBrush(Colors.LightGray), + (o, e) => + { + AxisGrid axisGrid = (AxisGrid)o; + if (axisGrid != null) + { + axisGrid.InvalidateMeasure(); + } + })); + + /// + /// Gets or sets collection for horizontal lines coordinates + /// + [Category("InteractiveDataDisplay")] + [Description("Horizontal lines coordinates")] + public IEnumerable VerticalTicks + { + get { return (IEnumerable)GetValue(VerticalTicksProperty); } + set { SetValue(VerticalTicksProperty, value); } + } + + /// + /// Gets or sets collection for vertical lines coordinates + /// + [Category("InteractiveDataDisplay")] + [Description("Vertical lines coordinates")] + public IEnumerable HorizontalTicks + { + get { return (IEnumerable)GetValue(HorizontalTicksProperty); } + set { SetValue(HorizontalTicksProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsXAxisReversedProperty = + DependencyProperty.Register("IsXAxisReversed", typeof(bool), typeof(AxisGrid), new PropertyMetadata(false)); + + /// + /// Gets or sets a flag indicating whether the x-axis is reversed or not. + /// + [Category("InteractiveDataDisplay")] + public bool IsXAxisReversed + { + get { return (bool)GetValue(IsXAxisReversedProperty); } + set { SetValue(IsXAxisReversedProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsYAxisReversedProperty = + DependencyProperty.Register("IsYAxisReversed", typeof(bool), typeof(AxisGrid), new PropertyMetadata(false)); + + /// + /// Gets or sets a flag indicating whether the y-axis is reversed or not. + /// + [Category("InteractiveDataDisplay")] + public bool IsYAxisReversed + { + get { return (bool)GetValue(IsYAxisReversedProperty); } + set { SetValue(IsYAxisReversedProperty, value); } + } + + /// + /// Gets or sets the Brush that specifies how the horizontal and vertical lines is painted + /// + [Category("Appearance")] + public SolidColorBrush Stroke + { + get { return (SolidColorBrush)GetValue(StrokeProperty); } + set { SetValue(StrokeProperty, value); } + } + + /// + /// Initializes new instance of class + /// + public AxisGrid() + { + HorizontalTicks = new double[0]; + VerticalTicks = new double[0]; + + path = new Path(); + BindingOperations.SetBinding(path, Path.StrokeProperty, new Binding("Stroke") { Source = this, Mode = BindingMode.TwoWay }); + path.StrokeThickness = 1.0; + Children.Add(path); + } + + /// + /// Measures the size in layout required for child elements and determines a size for the AxisGrid. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + if (Double.IsInfinity(availableSize.Width)) + availableSize.Width = 1024; + if (Double.IsInfinity(availableSize.Height)) + availableSize.Height = 1024; + + + GeometryGroup group = new GeometryGroup(); + + double[] hTicks = HorizontalTicks.ToArray(); + double[] vTicks = VerticalTicks.ToArray(); + + if (hTicks != null && hTicks.Length > 0) + { + double minY = 0; + double maxY = availableSize.Height; + + int i = 0; + if (hTicks[0] < ActualPlotRect.X.Min) i++; + for (; i < hTicks.Length; i++) + { + double screenX = GetHorizontalCoordinateFromTick(hTicks[i], availableSize.Width, ActualPlotRect.X); + LineGeometry line = new LineGeometry(); + line.StartPoint = new Point(screenX, minY); + line.EndPoint = new Point(screenX, maxY); + group.Children.Add(line); + } + } + + if (vTicks != null && vTicks.Length > 0) + { + double minX = 0; + double maxX = availableSize.Width; + + int i = 0; + if (vTicks[0] < ActualPlotRect.Y.Min) i++; + for (; i < vTicks.Length; i++) + { + double screenY = GetVerticalCoordinateFromTick(vTicks[i], availableSize.Height, ActualPlotRect.Y); + LineGeometry line = new LineGeometry(); + line.StartPoint = new Point(minX, screenY); + line.EndPoint = new Point(maxX, screenY); + group.Children.Add(line); + } + } + + path.Data = group; + + foreach (UIElement child in Children) + { + child.Measure(availableSize); + } + + return availableSize; + } + + /// + /// Positions child elements and determines a size for a AxisGrid + /// + /// The final area within the parent that AxisGrid should use to arrange itself and its children + /// The actual size used + protected override Size ArrangeOverride(Size finalSize) + { + foreach (UIElement child in Children) + { + child.Arrange(new Rect(new Point(0, 0), finalSize)); + } + + return finalSize; + } + + private double GetHorizontalCoordinateFromTick(double tick, double screenSize, Range range) + { + return ValueToScreen(XDataTransform.DataToPlot(tick), screenSize, range, IsXAxisReversed); + } + + private double GetVerticalCoordinateFromTick(double tick, double screenSize, Range range) + { + return screenSize - ValueToScreen(YDataTransform.DataToPlot(tick), screenSize, range, IsYAxisReversed); + } + + + private static double ValueToScreen(double value, double dimensionSize, Range range, bool isReversed) + { + var bound1 = isReversed ? range.Max : range.Min; + var bound2 = isReversed ? range.Min : range.Max; + return (value - bound1) * dimensionSize / (bound2 - bound1); + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/AxisOrientation.cs b/InteractiveDataDisplay_OneDll/src/Axes/AxisOrientation.cs new file mode 100644 index 000000000..78122ab58 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/AxisOrientation.cs @@ -0,0 +1,29 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Specifies axes orientation. + /// + public enum AxisOrientation + { + /// + /// Axis will be vertical and ticks will be aligned to right. + /// + Left, + /// + /// Axis will be vertical and ticks will be aligned to left. + /// + Right, + /// + /// Axis will be horizontal and ticks will be aligned to bottom. + /// + Top, + /// + /// Axis will be horizontal and ticks will be aligned to top. + /// + Bottom + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/ILabelProvider.cs b/InteractiveDataDisplay_OneDll/src/Axes/ILabelProvider.cs new file mode 100644 index 000000000..319a43b83 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/ILabelProvider.cs @@ -0,0 +1,21 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System.Windows; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Defines a method to create labels as an array of from an array of double values. + /// + public interface ILabelProvider + { + /// + /// Generates an array of labels from an array of double. + /// + /// An array of double ticks. + /// + FrameworkElement[] GetLabels(double[] ticks); + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/LabelProvider.cs b/InteractiveDataDisplay_OneDll/src/Axes/LabelProvider.cs new file mode 100644 index 000000000..2ac3880f6 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/LabelProvider.cs @@ -0,0 +1,38 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Collections.Generic; +using System.Globalization; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Provides mechanisms to generate labels displayed on an axis by double ticks. + /// + public class LabelProvider : ILabelProvider + { + /// + /// Generates an array of labels from an array of double. + /// + /// An array of double ticks. + /// An array of . + public FrameworkElement[] GetLabels(double[] ticks) + { + if (ticks == null) + throw new ArgumentNullException("ticks"); + + List Labels = new List(); + foreach (double tick in ticks) + { + TextBlock text = new TextBlock(); + text.Text = tick.ToString(CultureInfo.InvariantCulture); + Labels.Add(text); + } + return Labels.ToArray(); + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/MinorTicksProvider.cs b/InteractiveDataDisplay_OneDll/src/Axes/MinorTicksProvider.cs new file mode 100644 index 000000000..158983d02 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/MinorTicksProvider.cs @@ -0,0 +1,92 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Collections.Generic; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Provides mechanisms to generate minor ticks displayed on an axis. + /// + public class MinorTicksProvider + { + /// + /// Default ticks count for provider. + /// + public static readonly int DefaultTicksCount = 3; + private int ticksCount; + + /// + /// Initializes a new instance of class with default ticks count. + /// + public MinorTicksProvider() + { + ticksCount = DefaultTicksCount; + } + + /// + /// Gets or sets the count of generated ticks. + /// + public int TicksCount + { + get { return ticksCount; } + set { ticksCount = value; } + } + + /// + /// Generates minor ticks for given array of major ticks and range. + /// + /// The range. + /// An array of major ticks. + /// + public double[] CreateTicks(Range range, double[] ticks) + { + if (ticks == null) + throw new ArgumentNullException("ticks"); + + if (ticks.Count() < 2) + return null; + + List res = new List(); + double step = (ticks[1] - ticks[0]) / (ticksCount + 1); + int i = 1; + if (range.Min > ticks[0]) + { + double x0 = ticks[1] - i * step; + res.Add(x0); + while (res[res.Count - 1] >= range.Min) + { + res.Add(x0 - i * step); + i++; + } + i++; + } + + int fin = ticks.Length - 1; + if (ticks[fin] > range.Max) + fin--; + for (i = 0; i < fin; i++) + { + double x0 = ticks[i] + step; + for (int j = 0; j < ticksCount; j++) + res.Add(x0 + j * step); + } + + if (fin != ticks.Length - 1) + { + double x0 = ticks[fin] + step; + res.Add(x0); + i = 1; + while (res[res.Count - 1] <= range.Max) + { + res.Add(x0 + i * step); + i++; + } + } + return res.ToArray(); + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/PlotAxis.cs b/InteractiveDataDisplay_OneDll/src/Axes/PlotAxis.cs new file mode 100644 index 000000000..690f4f980 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/PlotAxis.cs @@ -0,0 +1,193 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Data; + +namespace InteractiveDataDisplay.WPF +{ + /// Facilitates drawing of vertical or horizontal coordinate axis connected to parent element + [TemplatePart(Name = "PART_Axis", Type = typeof(Axis))] + [Description("Figure's axis")] + public sealed class PlotAxis : ContentControl + { + private PlotBase masterPlot = null; + private Axis axis; + + /// + /// Initializes new instance of class + /// + public PlotAxis() + { + XDataTransform = new IdentityDataTransform(); + YDataTransform = new IdentityDataTransform(); + Ticks = new double[0]; + DefaultStyleKey = typeof(PlotAxis); + Loaded += PlotAxisLoaded; + Unloaded += PlotAxisUnloaded; + } + + private void PlotAxisUnloaded(object sender, RoutedEventArgs e) + { + masterPlot = null; + } + + private void PlotAxisLoaded(object sender, RoutedEventArgs e) + { + masterPlot = PlotBase.FindMaster(this); + if (masterPlot != null) + InvalidateAxis(); + } + + /// + /// Measures the size in layout required for child elements and determines a size for parent. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + InvalidateAxis(); + return base.MeasureOverride(availableSize); + } + + private void InvalidateAxis() + { + if (axis != null) + { + if (axis.AxisOrientation == AxisOrientation.Left || + axis.AxisOrientation == AxisOrientation.Right) + { + if (masterPlot != null) + { + axis.Range = new Range(masterPlot.ActualPlotRect.YMin, masterPlot.ActualPlotRect.YMax); + } + axis.DataTransform = YDataTransform; + } + else + { + if (masterPlot != null) + { + axis.Range = new Range(masterPlot.ActualPlotRect.XMin, masterPlot.ActualPlotRect.XMax); + } + axis.DataTransform = XDataTransform; + } + } + } + + /// + /// Invoked whenever application code or internal processes call ApplyTemplate + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + axis = GetTemplateChild("PART_Axis") as Axis; + if (axis == null) + { + throw new InvalidOperationException("Invalid template!"); + } + BindingOperations.SetBinding(this.axis, Axis.TicksProperty, new Binding("Ticks") + { + Source = this, + Mode = BindingMode.TwoWay + }); + InvalidateAxis(); + } + + /// + /// Gets or sets a set of double values displayed as axis ticks. + /// + [Browsable(false)] + public IEnumerable Ticks + { + get { return (IEnumerable)GetValue(TicksProperty); } + set { SetValue(TicksProperty, value); } + } + + /// Identify property + public static readonly DependencyProperty TicksProperty = + DependencyProperty.Register("Ticks", typeof(IEnumerable), typeof(PlotAxis), new PropertyMetadata(new double[0])); + + /// + /// Gets or Sets orientation of axis and location of labels + /// + [Category("InteractiveDataDisplay")] + [Description("Defines orientation of axis and location of labels")] + public AxisOrientation AxisOrientation + { + get { return (AxisOrientation)GetValue(AxisOrientationProperty); } + set { SetValue(AxisOrientationProperty, value); } + } + + /// Identify property + public static readonly DependencyProperty AxisOrientationProperty = + DependencyProperty.Register("AxisOrientation", typeof(AxisOrientation), typeof(PlotAxis), new PropertyMetadata(AxisOrientation.Bottom)); + + /// + /// Gets or sets a flag indicating whether the axis is reversed or not. + /// + /// Axis is not reversed by default. + [Category("InteractiveDataDisplay")] + [Description("Defines orientation of axis and location of labels")] + public bool IsReversed + { + get { return (bool)GetValue(IsReversedProperty); } + set { SetValue(IsReversedProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsReversedProperty = + DependencyProperty.Register("IsReversed", typeof(bool), typeof(PlotAxis), new PropertyMetadata(false)); + + /// Gets or sets transform from user data to horizontal plot coordinate. + /// By default transform is + /// + [Category("InteractiveDataDisplay")] + public DataTransform XDataTransform + { + get { return (DataTransform)GetValue(XDataTransformProperty); } + set { SetValue(XDataTransformProperty, value); } + } + + /// Gets or sets transform from user data to vertical plot coordinate. + /// By default transform is + /// + [Category("InteractiveDataDisplay")] + public DataTransform YDataTransform + { + get { return (DataTransform)GetValue(YDataTransformProperty); } + set { SetValue(YDataTransformProperty, value); } + } + + /// Identify property + public static readonly DependencyProperty XDataTransformProperty = + DependencyProperty.Register("XDataTransform", typeof(DataTransform), typeof(PlotAxis), new PropertyMetadata(null, + (o, e) => + { + PlotAxis plot = o as PlotAxis; + if (plot != null) + { + plot.InvalidateAxis(); + } + })); + + /// Identify property + public static readonly DependencyProperty YDataTransformProperty = + DependencyProperty.Register("YDataTransform", typeof(DataTransform), typeof(PlotAxis), new PropertyMetadata(null, + (o, e) => + { + PlotAxis plot = o as PlotAxis; + if (plot != null) + { + plot.InvalidateAxis(); + } + })); + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/RoundHelper.cs b/InteractiveDataDisplay_OneDll/src/Axes/RoundHelper.cs new file mode 100644 index 000000000..710446a55 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/RoundHelper.cs @@ -0,0 +1,71 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +namespace InteractiveDataDisplay.WPF +{ + internal static class RoundHelper + { + //internal static int GetDifferenceLog(double min, double max) + //{ + // return (int)Math.Log(Math.Abs(max - min)); + //} + + internal static double Floor(double number, int rem) + { + if (rem <= 0) + { + rem = MathHelper.Clamp(-rem, 0, 15); + } + double pow = Math.Pow(10, rem - 1); + double val = pow * Math.Floor((double)(number / Math.Pow(10, rem - 1))); + return val; + + } + + internal static double Round(double number, int rem) + { + if (rem <= 0) + { + rem = MathHelper.Clamp(-rem, 0, 15); + return Math.Round(number, rem); + } + else + { + double pow = Math.Pow(10, rem - 1); + double val = pow * Math.Round(number / Math.Pow(10, rem - 1)); + return val; + } + } + + //internal static RoundingInfo CreateRoundedRange(double min, double max) + //{ + // double delta = max - min; + + // if (delta == 0) + // return new RoundingInfo { Min = min, Max = max, Log = 0 }; + + // int log = (int)Math.Round(Math.Log10(Math.Abs(delta))) + 1; + + // double newMin = Round(min, log); + // double newMax = Round(max, log); + // if (newMin == newMax) + // { + // log--; + // newMin = Round(min, log); + // newMax = Round(max, log); + // } + + // return new RoundingInfo { Min = newMin, Max = newMax, Log = log }; + //} + } + + //[DebuggerDisplay("{Min} - {Max}, Log = {Log}")] + //internal sealed class RoundingInfo + //{ + // public double Min { get; set; } + // public double Max { get; set; } + // public int Log { get; set; } + //} +} + diff --git a/InteractiveDataDisplay_OneDll/src/Axes/TicksProvider.cs b/InteractiveDataDisplay_OneDll/src/Axes/TicksProvider.cs new file mode 100644 index 000000000..e03f0f39d --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Axes/TicksProvider.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Collections.Generic; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Provides mechanisms to generate ticks displayed on an axis. + /// + public class TicksProvider + { + private int delta = 1; + private int beta = 0; + + /// + /// Initializes a new instance of class with default . + /// + public TicksProvider() + { + minorProvider = new MinorTicksProvider(); + } + + private readonly MinorTicksProvider minorProvider; + /// + /// Gets the . + /// + public MinorTicksProvider MinorProvider + { + get { return minorProvider; } + } + + private Range range = new Range(0, 1); + /// + /// Gets or sets the range of axis. + /// + public Range Range + { + get { return range; } + set + { + range = value; + delta = 1; + beta = (int)Math.Round(Math.Log10(range.Max - range.Min)) - 1; + } + } + + /// + /// Gets an array of ticks for specified axis range. + /// + public double[] GetTicks() + { + double start = Range.Min; + double finish = Range.Max; + double d = finish - start; + + if (d == 0) + return new double[] { start, finish }; + + double temp = delta * Math.Pow(10, beta); + double min = Math.Floor(start / temp); + double max = Math.Floor(finish / temp); + int count = (int)(max - min + 1); + List res = new List(); + double x0 = min * temp; + for (int i = 0; i < count + 1; i++) + { + double v = RoundHelper.Round(x0 + i * temp, beta); + if(v >= start && v <= finish) + res.Add(v); + } + return res.ToArray(); + } + + /// + /// Decreases the tick count. + /// + public void DecreaseTickCount() + { + if (delta == 1) + { + delta = 2; + } + else if (delta == 2) + { + delta = 5; + } + else if (delta == 5) + { + delta = 1; + beta++; + } + } + + /// + /// Increases the tick count. + /// + public void IncreaseTickCount() + { + if (delta == 1) + { + delta = 5; + beta--; + } + else if (delta == 2) + { + delta = 1; + } + else if (delta == 5) + { + delta = 2; + } + } + + /// + /// Generates minor ticks in specified range. + /// + /// The range. + /// An array of minor ticks. + public double[] GetMinorTicks(Range range) + { + var ticks = new List(GetTicks()); + double temp = delta * Math.Pow(10, beta); + ticks.Insert(0, RoundHelper.Round(ticks[0] - temp, beta)); + ticks.Add(RoundHelper.Round(ticks[ticks.Count - 1] + temp, beta)); + return minorProvider.CreateTicks(range, ticks.ToArray()); + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Chart.cs b/InteractiveDataDisplay_OneDll/src/Chart.cs new file mode 100644 index 000000000..014121c60 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Chart.cs @@ -0,0 +1,332 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Markup; +using System.ComponentModel; +using System.Windows.Data; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Ready to use figure with left and bottom axes, grid, legend, mouse and keyboard navigation + /// + [ContentProperty("Content")] + [Description("Ready to use figure")] + public class Chart : ContentControl + { + /// + /// Initializes a new instance of class. + /// + public Chart() + { + DefaultStyleKey = typeof(Chart); + Background = new SolidColorBrush(Colors.White); + Foreground = new SolidColorBrush(Colors.Black); + LegendContent = new LegendItemsPanel(); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + PlotAxis plotAxis = base.GetTemplateChild("PART_horizontalAxis") as PlotAxis; + PlotAxis plotAxis2 = base.GetTemplateChild("PART_verticalAxis") as PlotAxis; + AxisGrid axisGrid = base.GetTemplateChild("PART_axisGrid") as AxisGrid; + if (plotAxis == null || plotAxis2 == null || axisGrid == null) + { + return; + } + BindingOperations.SetBinding(axisGrid, AxisGrid.HorizontalTicksProperty, new Binding("Ticks") + { + Source = plotAxis + }); + BindingOperations.SetBinding(axisGrid, AxisGrid.VerticalTicksProperty, new Binding("Ticks") + { + Source = plotAxis2 + }); + } + /// + /// Raises the GotFocus event. + /// + /// An EventArgs that contains the event data. + protected override void OnGotFocus(RoutedEventArgs e) + { + VisualStateManager.GoToState(this, "Focused", false); + base.OnGotFocus(e); + } + + /// + /// Raises the LostFocus event. + /// + /// An EventArgs that contains the event data. + protected override void OnLostFocus(RoutedEventArgs e) + { + VisualStateManager.GoToState(this, "Unfocused", false); + base.OnLostFocus(e); + } + + /// + /// Gets or sets legend content + /// + [Category("InteractiveDataDisplay")] + public object LegendContent + { + get { return (object)GetValue(LegendContentProperty); } + set { SetValue(LegendContentProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty LegendContentProperty = + DependencyProperty.Register("LegendContent", typeof(object), typeof(Chart), new PropertyMetadata(null)); + + /// + /// Gets or sets visibility of LegendControl + /// + [Category("InteractiveDataDisplay")] + public Visibility LegendVisibility + { + get { return (Visibility)GetValue(LegendVisibilityProperty); } + set { SetValue(LegendVisibilityProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty LegendVisibilityProperty = + DependencyProperty.Register("LegendVisibility", typeof(Visibility), typeof(Chart), new PropertyMetadata(Visibility.Visible)); + + /// + /// Gets or sets auto fit mode. property of all + /// plots in compositions are updated instantly to have same value. + /// + [Category("InteractiveDataDisplay")] + public bool IsAutoFitEnabled + { + get { return (bool)GetValue(IsAutoFitEnabledProperty); } + set { SetValue(IsAutoFitEnabledProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsAutoFitEnabledProperty = + DependencyProperty.Register("IsAutoFitEnabled", typeof(bool), typeof(Chart), new PropertyMetadata(true)); + + /// + /// Gets or sets desired plot width in plot coordinates. Settings this property + /// turns off auto fit mode. + /// + [Category("InteractiveDataDisplay")] + public double PlotWidth + { + get { return (double)GetValue(PlotWidthProperty); } + set { SetValue(PlotWidthProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotWidthProperty = + DependencyProperty.Register("PlotWidth", typeof(double), typeof(Chart), new PropertyMetadata(1.0)); + + /// + /// Gets or sets desired plot height in plot coordinates. Settings this property + /// turns off auto fit mode. + /// + [Category("InteractiveDataDisplay")] + public double PlotHeight + { + get { return (double)GetValue(PlotHeightProperty); } + set { SetValue(PlotHeightProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotHeightProperty = + DependencyProperty.Register("PlotHeight", typeof(double), typeof(Chart), new PropertyMetadata(1.0)); + + /// + /// Gets or sets desired minimal visible horizontal coordinate in plot coordinates. Settings this property + /// turns off auto fit mode. + /// + [Category("InteractiveDataDisplay")] + public double PlotOriginX + { + get { return (double)GetValue(PlotOriginXProperty); } + set { SetValue(PlotOriginXProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotOriginXProperty = + DependencyProperty.Register("PlotOriginX", typeof(double), typeof(Chart), new PropertyMetadata(0.0)); + + /// + /// Gets or sets desired minimal visible vertical coordinate in plot coordinates. Settings this property + /// turns off auto fit mode. + /// + [Category("InteractiveDataDisplay")] + public double PlotOriginY + { + get { return (double)GetValue(PlotOriginYProperty); } + set { SetValue(PlotOriginYProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotOriginYProperty = + DependencyProperty.Register("PlotOriginY", typeof(double), typeof(Chart), new PropertyMetadata(0.0)); + + /// + /// Desired ratio of horizontal scale to vertical scale. Values less than or equal to zero + /// represent unspecified aspect ratio. + /// + [Category("InteractiveDataDisplay")] + public double AspectRatio + { + get { return (double)GetValue(AspectRatioProperty); } + set { SetValue(AspectRatioProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty AspectRatioProperty = + DependencyProperty.Register("AspectRatio", typeof(double), typeof(Chart), new PropertyMetadata(0.0)); + + /// Gets or sets chart title. Chart title is an object that is shown centered above plot area. + /// Chart title is used inside and can be a + /// . + [Category("InteractiveDataDisplay")] + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// Identifies dependency property + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(object), typeof(Chart), new PropertyMetadata(null)); + + /// Gets or sets bottom axis title. Bottom axis title is an object that is centered under plot area. + /// Bottom axis title is used inside and can be a + /// . + [Category("InteractiveDataDisplay")] + public object BottomTitle + { + get { return (object)GetValue(BottomTitleProperty); } + set { SetValue(BottomTitleProperty, value); } + } + + /// Identifies dependency property + public static readonly DependencyProperty BottomTitleProperty = + DependencyProperty.Register("BottomTitle", typeof(object), typeof(Chart), new PropertyMetadata(null)); + + /// Gets or sets right axis title. Right axis title is an object that is vertically centered + /// and located to the right from plot area. + /// Right axis title is used inside and can be a + /// . + [Category("InteractiveDataDisplay")] + public object RightTitle + { + get { return (object)GetValue(RightTitleProperty); } + set { SetValue(RightTitleProperty, value); } + } + + /// Identifies dependency property + public static readonly DependencyProperty RightTitleProperty = + DependencyProperty.Register("RightTitle", typeof(object), typeof(Chart), new PropertyMetadata(null)); + + /// Gets or sets left axis title. Left axis title is an object that is vertically centered + /// and located to the left from plot area. + /// Left axis title is used inside and can be a + /// . + [Category("InteractiveDataDisplay")] + public object LeftTitle + { + get { return (object)GetValue(LeftTitleProperty); } + set { SetValue(LeftTitleProperty, value); } + } + + /// Identifies dependency property + public static readonly DependencyProperty LeftTitleProperty = + DependencyProperty.Register("LeftTitle", typeof(object), typeof(Chart), new PropertyMetadata(null)); + + /// Gets or sets thickness of border surrounding Chart center. This value also is taken into + /// account when computing screen padding + [Category("InteractiveDataDisplay")] + public new Thickness BorderThickness + { + get { return (Thickness)GetValue(BorderThicknessProperty); } + set { SetValue(BorderThicknessProperty, value); } + } + + /// Identifies property + public static new readonly DependencyProperty BorderThicknessProperty = + DependencyProperty.Register("BorderThickness", typeof(Thickness), typeof(Chart), new PropertyMetadata(new Thickness(1))); + + /// Gets or sets vertical navigation status. True means that user can navigate along Y axis + [Category("InteractiveDataDisplay")] + public bool IsVerticalNavigationEnabled + { + get { return (bool)GetValue(IsVerticalNavigationEnabledProperty); } + set { SetValue(IsVerticalNavigationEnabledProperty, value); } + } + + /// Identifies property + public static readonly DependencyProperty IsVerticalNavigationEnabledProperty = + DependencyProperty.Register("IsVerticalNavigationEnabled", typeof(bool), typeof(Chart), new PropertyMetadata(true)); + + /// Gets or sets horizontal navigation status. True means that user can navigate along X axis + [Category("InteractiveDataDisplay")] + public bool IsHorizontalNavigationEnabled + { + get { return (bool)GetValue(IsHorizontalNavigationEnabledProperty); } + set { SetValue(IsHorizontalNavigationEnabledProperty, value); } + } + + /// Identifies property + public static readonly DependencyProperty IsHorizontalNavigationEnabledProperty = + DependencyProperty.Register("IsHorizontalNavigationEnabled", typeof(bool), typeof(Chart), new PropertyMetadata(true)); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsXAxisReversedProperty = + DependencyProperty.Register("IsXAxisReversed", typeof(bool), typeof(Chart), new PropertyMetadata(false)); + + /// + /// Gets or sets a flag indicating whether the x-axis is reversed or not. + /// + [Category("InteractiveDataDisplay")] + public bool IsXAxisReversed + { + get { return (bool)GetValue(IsXAxisReversedProperty); } + set { SetValue(IsXAxisReversedProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsYAxisReversedProperty = + DependencyProperty.Register("IsYAxisReversed", typeof(bool), typeof(Chart), new PropertyMetadata(false)); + + /// + /// Gets or sets a flag indicating whether the y-axis is reversed or not. + /// + [Category("InteractiveDataDisplay")] + public bool IsYAxisReversed + { + get { return (bool)GetValue(IsYAxisReversedProperty); } + set { SetValue(IsYAxisReversedProperty, value); } + } + + } +} diff --git a/InteractiveDataDisplay_OneDll/src/Common/ArrayHelper.cs b/InteractiveDataDisplay_OneDll/src/Common/ArrayHelper.cs new file mode 100644 index 000000000..7f0c873a2 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Common/ArrayHelper.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Helper class for performing manipulations with arrays + /// + public static class ArrayExtensions + { + /// Gets index of item that is nearest to . + /// Array in ascending order. + /// Value to look for. + /// Index of closest element or -1 if + /// is less than first element or greater than last. + public static int GetNearestIndex(double[] array, double value) + { + if (array == null) + throw new ArgumentNullException("array"); + int i = Array.BinarySearch(array, value); + if (i >= 0) + return i; + i = ~i; + if (i == array.Length) + return -1; // greater than last value + if (i == 0) + return -1; // less than first value + return (value - array[i - 1] > array[i] - value) ? (i) : (i - 1); + } + + #region 1D array conversion utils + + /// Converts array of floats to array of doubles + /// Array of float + /// Array of same length but with double elements + public static double[] ToDoubleArray(this float[] array) + { + if (array == null) + throw new ArgumentNullException("array"); + double[] result = new double[array.Length]; + for (int i = 0; i < array.Length; i++) + result[i] = array[i]; + return result; + } + + /// Converts array of 32-bit integers to array of doubles + /// Array of 32-bit integers + /// Array of same length but with double elements + public static double[] ToDoubleArray(this int[] array) + { + if (array == null) + throw new ArgumentNullException("array"); + double[] result = new double[array.Length]; + for (int i = 0; i < array.Length; i++) + result[i] = array[i]; + return result; + } + + /// Converts array of booleans to array of doubles where true maps to 1, false maps to 0 + /// Array of booleans + /// Array of same dimensions but with double elements + public static double[] ToDoubleArray(this bool[] array) + { + if (array == null) + throw new ArgumentNullException("array"); + double[] result = new double[array.Length]; + for (int i = 0; i < array.Length; i++) + result[i] = array[i] ? 1 : 0 ; + return result; + } + + /// Converts array of doubles, floats, integers or booleans to array of doubles where true maps to 1, false maps to 0 + /// Source array + /// Array of same length but with double elements + public static double[] ToDoubleArray(Array array) + { + if (array == null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new InvalidOperationException("Source array is not 1D"); + var elemType = array.GetType().GetElementType(); + if (elemType == typeof(double)) + return (double[])array; + if (elemType == typeof(float)) + return ArrayExtensions.ToDoubleArray((float[])array); + else if (elemType == typeof(int)) + return ArrayExtensions.ToDoubleArray((int[])array); + else if (elemType == typeof(bool)) + return ArrayExtensions.ToDoubleArray((bool[])array); + else + throw new NotSupportedException("Conversions of 1D arrays of " + elemType.Name + " to double[] is not supported"); + } + + #endregion + + #region 2D array conversion utils + + /// Converts 2D array of floats to 2D array of doubles + /// 2D array of float + /// 2D array of same dimensions but with double elements + [CLSCompliantAttribute(false)] + public static double[,] ToDoubleArray(this float[,] array) + { + if (array == null) + throw new ArgumentNullException("array"); + int n = array.GetLength(0); + int m = array.GetLength(1); + double[,] result = new double[n, m]; + for (int i = 0; i < n; i++) + for (int j = 0; j < m; j++) + result[i, j] = array[i, j]; + return result; + } + + /// Converts 2D array of 32-bit integers to 2D array of doubles + /// 2D array of 32-bit integers + /// 2D array of same dimensions but with double elements + [CLSCompliantAttribute(false)] + public static double[,] ToDoubleArray(this int[,] array) + { + if (array == null) + throw new ArgumentNullException("array"); + int n = array.GetLength(0); + int m = array.GetLength(1); + double[,] result = new double[n, m]; + for (int i = 0; i < n; i++) + for (int j = 0; j < m; j++) + result[i, j] = array[i, j]; + return result; + } + + /// Converts 2D array of booleans to 2D array of doubles where true maps to 1, false maps to 0 + /// 2D array of booleans + /// 2D array of same dimensions but with double elements + [CLSCompliantAttribute(false)] + public static double[,] ToDoubleArray(this bool[,] array) + { + if (array == null) + throw new ArgumentNullException("array"); + int n = array.GetLength(0); + int m = array.GetLength(1); + double[,] result = new double[n, m]; + for (int i = 0; i < n; i++) + for (int j = 0; j < m; j++) + result[i, j] = array[i, j] ? 1 : 0; + return result; + } + + /// Converts 2D array of doubles, floats, integers or booleans to 2D array of doubles where true maps to 1, false maps to 0 + /// 2D array + /// 2D array of same dimensions but with double elements + public static double[,] ToDoubleArray2D(Array array) + { + if (array == null) + throw new ArgumentNullException("array"); + if (array.Rank != 2) + throw new InvalidOperationException("Source array is not 2D"); + var elemType = array.GetType().GetElementType(); + if (elemType == typeof(double)) + return (double[,])array; + if (elemType == typeof(float)) + return ArrayExtensions.ToDoubleArray((float[,])array); + else if (elemType == typeof(int)) + return ArrayExtensions.ToDoubleArray((int[,])array); + else if (elemType == typeof(bool)) + return ArrayExtensions.ToDoubleArray((bool[,])array); + else + throw new NotSupportedException("Conversions of 2D arrays of " + elemType.Name + " to double[,] is not supported"); + } + + #endregion + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Common/DataRect.cs b/InteractiveDataDisplay_OneDll/src/Common/DataRect.cs new file mode 100644 index 000000000..d29b90cf5 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Common/DataRect.cs @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System.Windows; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Describes a width, a height and location of a rectangle. + /// This type is very similar to , but has one important difference: + /// while describes a rectangle in screen coordinates, where y axis + /// points to bottom (that's why 's Bottom property is greater than Top). + /// This type describes rectange in usual coordinates, and y axis point to top. + /// + public struct DataRect + { + private Range x; + private Range y; + + /// + /// Gets the empty . + /// + public static readonly DataRect Empty = + new DataRect(Range.Empty, Range.Empty); + + /// + /// Initializes a new instance of the struct. + /// + /// Left value of DataRect. + /// Bottom value of DataRect. + /// Right value of DataRect. + /// Top value of DataRect. + public DataRect(double minX, double minY, double maxX, double maxY) + : this() + { + X = new Range(minX, maxX); + Y = new Range(minY, maxY); + } + + /// + /// Initializes a new instance of the struct. + /// + /// Horizontal range + /// Vertical range + public DataRect(Range horizontal, Range vertical) + : this() + { + X = horizontal; Y = vertical; + } + + /// + /// Gets of Sets horizontal range + /// + public Range X + { + get { return x; } + set { x = value; } + } + + /// + /// Gets or sets vertical range + /// + public Range Y + { + get { return y; } + set { y = value; } + } + + /// + /// Gets the width of . + /// + public double Width + { + get + { + return X.Max - X.Min; + } + } + + /// + /// Gets the height of . + /// + public double Height + { + get + { + return Y.Max - Y.Min; + } + } + + /// + /// Gets the left. + /// + /// The left. + public double XMin + { + get + { + return X.Min; + } + } + + /// + /// Gets the right. + /// + /// The right. + public double XMax + { + get + { + return X.Max; + } + } + + /// + /// Gets the bottom. + /// + /// The bottom. + public double YMin + { + get + { + return Y.Min; + } + } + + /// + /// Gets the top. + /// + /// The top. + public double YMax + { + get + { + return Y.Max; + } + } + + /// + /// Gets a center point of a rectangle. + /// + public Point Center + { + get + { + return new Point((X.Max + X.Min) / 2.0, (Y.Max + Y.Min) / 2.0); + } + } + + /// + /// Gets a value indicating whether this instance is empty. + /// + /// true if this instance is empty; otherwise, false. + public bool IsEmpty + { + get + { + return X.IsEmpty || Y.IsEmpty; + } + } + + + /// + /// Updates current instance of with minimal DataRect + /// which vertical range will contain current vertical range and x value and + /// horizontal range will contain current horizontal range and y value + /// + /// Value, which will be used for surrond of horizontal range + /// Value, which will be used for surrond of vertical range + public void Surround(double x, double y) + { + this.x.Surround(x); + this.y.Surround(y); + } + + /// + /// Updates current instance of with minimal DataRect which will contain current DataRect and specified DataRect + /// + /// DataRect, which will be used for surrond of current instance of + public void Surround(DataRect rect) + { + this.x.Surround(rect.X); + this.y.Surround(rect.Y); + } + + /// + /// Updates horizontal range of current instance of with minimal range which will contain both horizontal range of current DataRect and specified value + /// + /// Value, which will be used for surrond of horizontal range of current instance of + public void XSurround(double x) + { + this.x.Surround(x); + } + + /// + /// Updates horizontal range of current instance of with minimal range which will contain both horizontal range of current DataRect and specified range + /// + /// Range, which will be used for surrond of horizontal range of current instance of + public void XSurround(Range x) + { + this.x.Surround(x); + } + + /// + /// Updates vertical range of current instance of with minimal range which will contain both vertical range of current DataRect and specified value + /// + /// Value, which will be used for surrond of vertical range of current instance of + public void YSurround(double y) + { + this.y.Surround(y); + } + + /// + /// Updates vertical range of current instance of with minimal range which will contain both vertical range of current DataRect and specified range + /// + /// Range, which will be used for surrond of vertical range of current instance of + public void YSurround(Range y) + { + this.y.Surround(y); + } + + /// + /// Returns a string that represents the current instance of . + /// + /// String that represents the current instance of + public override string ToString() + { + return "{" + X.ToString() + " " + Y.ToString() + "}"; + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Common/IPalette.cs b/InteractiveDataDisplay_OneDll/src/Common/IPalette.cs new file mode 100644 index 000000000..ae73246b9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Common/IPalette.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +// Copyright © 2010 Microsoft Corporation, All Rights Reserved. +// This code released under the terms of the Microsoft Research License Agreement (MSR-LA, http://sds.codeplex.com/License) +using System.Windows.Media; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Represents a color palette, which can convert double values to colors. + /// + public interface IPalette + { + /// + /// Gets a color for the specified value. + /// + /// A value from the . + /// A color. + Color GetColor(double value); + + /// + /// Gets the value indicating whether the contains absolute values + /// or it is relative ([0...1]). + /// + bool IsNormalized { get; } + + /// + /// Gets the range on which palette is defined. + /// + Range Range { get; } + } +} + + diff --git a/InteractiveDataDisplay_OneDll/src/Common/MathHelper.cs b/InteractiveDataDisplay_OneDll/src/Common/MathHelper.cs new file mode 100644 index 000000000..bb7ed7621 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Common/MathHelper.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Helper class for mathematical calculations. + /// + public static class MathHelper + { + /// + /// Verifies whether the value is NaN of Infinity. + /// + /// The value to varify. + /// True if the value is not NaN and is Infinity, false otherwise. + public static bool IsFinite(this double value) + { + return !Double.IsNaN(value) && !Double.IsInfinity(value); + } + + /// + /// Clamps specified long value to specified interval. + /// + /// Value to clamp. + /// Minimum of the interval. + /// Maximum of the interval. + /// Long value in range [min, max]. + public static long Clamp(long value, long min, long max) + { + return Math.Max(min, Math.Min(value, max)); + } + + /// + /// Clamps specified double value to specified interval. + /// + /// Value to clamp. + /// Minimum of the interval. + /// Maximum of the interval. + /// Double value in range [min, max]. + public static double Clamp(double value, double min, double max) + { + return Math.Max(min, Math.Min(value, max)); + } + + /// Clamps specified double value to [0,1]. + /// Value to clamp. + /// Double value in range [0, 1]. + public static double Clamp(double value) + { + return Math.Max(0, Math.Min(value, 1)); + } + + /// + /// Clamps specified integer value to specified interval. + /// + /// Value to clamp. + /// Minimum of the interval. + /// Maximum of the interval. + /// Integer value in range [min, max]. + public static int Clamp(int value, int min, int max) + { + return Math.Max(min, Math.Min(value, max)); + } + + /// + /// Returns by four given coordinates of corner points. + /// + /// X coordinate on a left-top corner. + /// Y coordinate on a left-top corner. + /// X coordinate on a right-bottom corner. + /// Y coordinate on a right-bottom corner. + /// + public static Rect CreateRectByPoints(double minX, double minY, double maxX, double maxY) + { + return new Rect(new Point(minX, minY), new Point(maxX, maxY)); + } + + /// + /// Returns by given point of a center and . + /// + /// Point of a center of a rectangle. + /// Size of a rectangle. + /// Rect. + public static Rect CreateRectFromCenterSize(Point center, Size size) + { + return new Rect(center.X - size.Width / 2, center.Y - size.Height / 2, size.Width, size.Height); + } + + /// + /// Converts an angle in radians to the angle in degrees. + /// + /// Angle in radians. + /// Angle in degrees. + public static double ToDegrees(this double angleInRadians) + { + return angleInRadians * 180 / Math.PI; + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Common/Palette.cs b/InteractiveDataDisplay_OneDll/src/Common/Palette.cs new file mode 100644 index 000000000..9be780330 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Common/Palette.cs @@ -0,0 +1,897 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +// Copyright © 2010 Microsoft Corporation, All Rights Reserved. +// This code released under the terms of the Microsoft Research License Agreement (MSR-LA, http://sds.codeplex.com/License) +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Media; +using System.Globalization; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Represents a point within the range of a palette, containing colors on the left and rigth sides. + /// + public class PalettePoint + { + private Color rightColor, leftColor; + private double x; + + internal PalettePoint() + {} + + /// + /// Initializes a new instance of class. + /// + /// Coordinate of a point. + /// Color on a right side. + /// Color on a left side. + public PalettePoint(double x, Color right, Color left) + { + this.rightColor = right; + this.leftColor = left; + this.x = x; + } + + /// + /// Gets the color on a right side of a point. + /// + public Color RightColor + { + internal set { rightColor = value; } + get { return rightColor; } + } + /// + /// Gets the color on a left side of a point. + /// + public Color LeftColor + { + internal set { leftColor = value; } + get { return leftColor; } + } + /// + /// Gets the coordinate of a point. + /// + public double X + { + internal set { x = value; } + get { return x; } + } + + /// + /// Returns a string that represents the current palette point with information of coordinate and colors. + /// + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture, "{0} - left: {1}, right: {2}", x, leftColor, rightColor); + } + } + + /// + /// Defines mapping between color and double value. + /// + public class Palette : IPalette + { + private bool isNormalized; + private Range range; + private PalettePoint[] points; + + /// + /// Predefined palette "Blue, Green, Red". + /// + public static Palette Heat = new Palette(true, new Range(0, 1), + new PalettePoint[3]{ + new PalettePoint(0.0, Colors.Blue, Colors.Blue), + new PalettePoint(0.5, Color.FromArgb(255, 0, 255, 0), Color.FromArgb(255, 0, 255, 0)), + new PalettePoint(1.0, Colors.Red, Colors.Red) + }); + + // See http://www.geos.ed.ac.uk/it/howto/GMT/CPT/palettes.html + // public readonly static Palette Topo = Palette.Parse("#C977D9,#A18AE6,#8AA2E6,#8BD1E7,#8AF3CF,#85F38E,#EDE485,#F0B086,#DE9F8B,#74A3B3=0.5,#99CC70,#DCD68E,#BDF385,#EDDFAD,#F7E8CA,#FFF9F3,#FFF9F6,#FFFBF9,#FFFCFA,White"); + + private Palette() + { + } + + /// + /// Initializes an instance of class. + /// + /// Indicated whether palette is absolute or normalized. + /// If true, the actual range of palette is always [0...1]. + /// Range of palette. + /// An array of color points. + /// + /// If is true, value of the + /// is ignored and actual range is always [0...1]. + /// The array is cloned before use and therefore can be modified + /// after the constructor is completed without any effect on the palette instance. + /// + /// + public Palette(bool isNormalized, Range range, PalettePoint[] points) + { + this.isNormalized = isNormalized; + if (isNormalized) + this.range = new Range(0, 1); + else + this.range = new Range(range.Min, range.Max); + + if (points == null) throw new ArgumentNullException("points"); + if (points.Length < 2) throw new ArgumentException("Palette should have at least two points"); + this.points = (PalettePoint[])points.Clone(); + } + + /// + /// Initializes new instance of class on a basis of existing palette's colors. + /// + /// Indicated whether palette is absolute or normalized. + /// If true, the actual range of palette is always [0...1]. + /// Range of palette. + /// A palette to construct a new one. + public Palette(bool isNormalized, Range range, Palette palette) + { + if (palette == null) throw new ArgumentNullException("palette"); + this.isNormalized = isNormalized; + if (isNormalized) + this.range = new Range(0, 1); + else + this.range = range; + + if (palette.IsNormalized == isNormalized) + { + points = (PalettePoint[])palette.points.Clone(); + } + else + { + var srcRange = palette.Range; + double alpha, beta; + if (isNormalized) // from absolute to normalized + { + alpha = 1.0 / (srcRange.Max - srcRange.Min); + beta = -srcRange.Min * alpha; + } + else // from normalized to absolute + { + alpha = range.Max - range.Min; + beta = range.Min; + } + points = palette.points.Select(pp => new PalettePoint(pp.X * alpha + beta, pp.RightColor, pp.LeftColor)).ToArray(); + } + } + + /// + /// Gets the points with colors describing the palette. + /// + public PalettePoint[] Points + { + get { return points.Clone() as PalettePoint[]; } + } + + /// + /// Parses the palette from the string. See remarks for details. + /// + /// A string to parse from. + /// + /// String can contain: names of colors ( or hexadecimal representation #AARRGGBB), + /// double values and separators (equal symbol or comma) to separate colors and values from each other. + /// Comma is used for gradient colors, equal symbol - for initializing a color of specified value. + /// Palette can be normalized or absolute. + /// Absolute palette maps entire numbers to colors exactly as numbers are specified. For examples, + /// palette '-10=Blue,Green,Yellow,Red=10' maps values -10 and below to Blue, 10 and above to red. + /// Normalized palette maps minimum value to left palette color, maximum value to right palette color. In normalized palette + /// no numbers on left and right sides are specified. + /// Palettes can contain regions of constant color. For example, palette 'Blue=0.25=Yellow=0.5=#00FF00=0.75=Orange' maps first quarter of + /// values to blue, second quarter to yellow and so on. + /// + /// A palette that specified string describes. + public static Palette Parse(string value) + { + bool isNormalized = true; + Range range; + List points = new List(); + + if (value == null) value = String.Empty; + if (String.IsNullOrEmpty(value)) + return new Palette(true, new Range(0, 1), new PalettePoint[2]{ + new PalettePoint(0.0, Colors.White, Colors.White), + new PalettePoint(1.0, Colors.Black, Colors.Black)}); + Lexer lexer = new Lexer(value); + int state = -1; + double lastNumber; + if (lexer.ReadNext()) + { + points.Add(new PalettePoint(0.0, Colors.White, Colors.White)); + if (lexer.CurrentLexeme == Lexer.LexemeType.Number) + { + points[points.Count - 1].X = lexer.ValueNumber; + isNormalized = false; + if (lexer.ReadNext() && lexer.CurrentLexeme != Lexer.LexemeType.Separator) + throw new PaletteFormatException(lexer.Position, "separator expected"); + if (lexer.ReadNext() && lexer.CurrentLexeme != Lexer.LexemeType.Color) + throw new PaletteFormatException(lexer.Position, "color expected"); + } + if (lexer.CurrentLexeme == Lexer.LexemeType.Color) + { + points[points.Count - 1].RightColor = lexer.ValueColor; + points[points.Count - 1].LeftColor = lexer.ValueColor; + points.Add(new PalettePoint(points[0].X, lexer.ValueColor, lexer.ValueColor)); + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + lastNumber = points[0].X; + while (lexer.ReadNext()) + { + if (lexer.CurrentLexeme == Lexer.LexemeType.Separator) + { + if (lexer.ValueSeparator == Lexer.Separator.Equal) + { + if (lexer.ReadNext()) + { + if (lexer.CurrentLexeme == Lexer.LexemeType.Number) + { + if (lexer.ValueNumber < lastNumber) + throw new PaletteFormatException(lexer.Position, "number is less than previous"); + lastNumber = lexer.ValueNumber; + if (state == -1) + { + //x1 = color = x2 + points[points.Count - 1].X = lexer.ValueNumber; + state = 1; + } + else if (state == 0) + { + //color = x + points[points.Count - 1].X = lexer.ValueNumber; + state = 2; + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + else if (lexer.CurrentLexeme == Lexer.LexemeType.Color) + { + if (state == 1 || state == 2) + { + //x = color (,x=color || color1=x=color2) + points[points.Count - 1].RightColor = lexer.ValueColor; + state = -1; + } + else if (state == 0 || state == -1) + { + //color1 = color2 + points[points.Count - 1].X = points[0].X - 1; + points[points.Count - 1].RightColor = lexer.ValueColor; + state = -1; + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + } + else if (lexer.ValueSeparator == Lexer.Separator.Comma) + { + if (lexer.ReadNext()) + { + if (state == 1 || state == -1 || state == 2) + { + if (lexer.CurrentLexeme == Lexer.LexemeType.Number) + { + if (lexer.ValueNumber <= lastNumber) + throw new PaletteFormatException(lexer.Position, "number is less than previous"); + lastNumber = lexer.ValueNumber; + //x1 = color, x2 + if (lexer.ReadNext() && lexer.CurrentLexeme == Lexer.LexemeType.Separator && lexer.ValueSeparator == Lexer.Separator.Equal) + { + if (lexer.ReadNext() && lexer.CurrentLexeme == Lexer.LexemeType.Color) + { + if (state != -1) + points.Add(new PalettePoint(lexer.ValueNumber, lexer.ValueColor, lexer.ValueColor)); + else + { + points[points.Count - 1].X = lexer.ValueNumber; + points[points.Count - 1].RightColor = lexer.ValueColor; + points[points.Count - 1].LeftColor = lexer.ValueColor; + } + state = -1; + } + else + throw new PaletteFormatException(lexer.Position, "color expected"); + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + else if (lexer.CurrentLexeme == Lexer.LexemeType.Color) + { + // x = color1, color2 + if (state == -1) + points.RemoveAt(points.Count - 1); + state = 0; + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + + else if (state == 0) + { + if (lexer.CurrentLexeme == Lexer.LexemeType.Number) + { + if (lexer.ValueNumber <= lastNumber) + throw new PaletteFormatException(lexer.Position, "number is less than previous"); + lastNumber = lexer.ValueNumber; + //color, x + points[points.Count - 1].X = points[0].X - 1; + if (lexer.ReadNext() && lexer.CurrentLexeme == Lexer.LexemeType.Separator && lexer.ValueSeparator == Lexer.Separator.Equal) + { + if (lexer.ReadNext() && lexer.CurrentLexeme == Lexer.LexemeType.Color) + { + points.Add(new PalettePoint(lexer.ValueNumber, lexer.ValueColor, lexer.ValueColor)); + state = -1; + } + else + throw new PaletteFormatException(lexer.Position, "color expected"); + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + else if (lexer.CurrentLexeme == Lexer.LexemeType.Color) + { + //color1, color2 + points[points.Count - 1].X = points[0].X - 1; + state = 0; + } + else + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + } + } + } + if (state == -1) + points.Add(new PalettePoint(points[0].X, lexer.ValueColor, lexer.ValueColor)); + else if (state == 0) + points.Add(new PalettePoint(points[0].X, lexer.ValueColor, lexer.ValueColor)); + } + else + throw new PaletteFormatException(lexer.Position, "separator expected"); + } + + if (lexer.CurrentLexeme == Lexer.LexemeType.Separator) + throw new PaletteFormatException(lexer.Position, "wrong lexeme"); + if ((lexer.CurrentLexeme == Lexer.LexemeType.Number && isNormalized) || + (lexer.CurrentLexeme == Lexer.LexemeType.Color && !isNormalized)) + throw new PaletteFormatException(lexer.Position, "wrong ending"); + if (isNormalized) + { + points[points.Count - 1].X = 1.0; + if (points[points.Count - 1].X < points[points.Count - 2].X) + throw new PaletteFormatException(lexer.Position, "number is less than previous"); + } + points[points.Count - 1].RightColor = points[points.Count - 1].LeftColor; + if (points[0].X >= points[points.Count - 1].X) + throw new PaletteFormatException(lexer.Position, "wrong range of palette"); + range = new Range(points[0].X, points[points.Count - 1].X); + + int start = 1; + int count = 0; + for (int i = 1; i < points.Count; i++) + { + if (points[i].X == points[0].X - 1) + { + if (count == 0) start = i; + count++; + } + else if (count != 0) + { + double res_x = (points[start + count].X - points[start - 1].X) / (count + 1); + for (int j = 0; j < count; j++) + points[start + j].X = points[start - 1].X + res_x * (j + 1); + count = 0; + start = 1; + } + } + + return new Palette(isNormalized, range, points.ToArray()); + } + + private static string ColorToString(Color color) + { + Type colors = typeof(Colors); + foreach (var property in colors.GetProperties()) + { + if (((Color)property.GetValue(null, null)) == color) + return property.Name; + } + if (color.A == 0xff) + return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B); + return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2}{3:X2}", color.A, color.R, color.G, color.B); + } + + private static string PositionToString(double x) + { + return x.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Gets a string that describes current palette. + /// + /// String that describes palette. + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + int k = 0; + + if (!IsNormalized) + sb.Append(PositionToString(points[k].X)).Append('='); + sb.Append(ColorToString(points[k].RightColor)); + k++; + for (; k < points.Length - 1; k++) + { + if (points[k].LeftColor == points[k - 1].RightColor) + { + sb.Append('=').Append(PositionToString(points[k].X)); + if (points[k].RightColor != points[k].LeftColor || + (points[k].RightColor == points[k].LeftColor && points[k + 1].LeftColor == points[k].RightColor)) + { + sb.Append('='); + sb.Append(ColorToString(points[k].RightColor)); + } + } + else + { + sb.Append(','); + if (points[k].RightColor == points[k].LeftColor && points[k].LeftColor == points[k + 1].LeftColor) + { + sb.Append(PositionToString(points[k].X)).Append('='); + sb.Append(ColorToString(points[k].LeftColor)); + } + else + { + sb.Append(ColorToString(points[k].LeftColor)).Append('=').Append(PositionToString(points[k].X)); + if (points[k].RightColor == points[k + 1].LeftColor || + points[k].RightColor != points[k].LeftColor) + { + sb.Append('='); + sb.Append(ColorToString(points[k].RightColor)); + } + } + } + } + k = points.Length - 1; + if (points[k].LeftColor != points[k - 1].RightColor) + { + sb.Append(','); + sb.Append(ColorToString(points[k].LeftColor)); + } + if (!IsNormalized) + sb.Append('=').Append(PositionToString(points[k].X)); + return sb.ToString(); + } + + #region IPalette Members + + /// + /// Gets a color for the specified value. Note that palette uses HSL interpolation of colors. + /// + /// A double value from . + /// A color for specific double value. + public Color GetColor(double value) + { + Color color = new Color(); + if (IsNormalized && (value > 1 || value < 0)) + throw new ArgumentException("Wrong value for normalized palette"); + else + { + if (value <= points[0].X) + { + color.A = points[0].LeftColor.A; + color.R = points[0].LeftColor.R; + color.G = points[0].LeftColor.G; + color.B = points[0].LeftColor.B; + } + else if (value >= points[points.Length - 1].X) + { + color.A = points[points.Length - 1].RightColor.A; + color.R = points[points.Length - 1].RightColor.R; + color.G = points[points.Length - 1].RightColor.G; + color.B = points[points.Length - 1].RightColor.B; + } + else + for (int i = 0; i < points.Length - 1; i++) + { + var p1 = points[i]; + var p2 = points[i + 1]; + if (value >= p1.X && value <= p2.X) + { + var c1 = p1.RightColor; + var c2 = p2.LeftColor; + double alpha = (value - points[i].X) / (p2.X - p1.X); + + HSLColor cHSL1 = new HSLColor(c1); + HSLColor cHSL2 = new HSLColor(c2); + + if (Math.Abs(cHSL2.H - cHSL1.H) > 3) + { + if (cHSL1.H < cHSL2.H) + cHSL1 = new HSLColor(cHSL1.A, cHSL1.H + 6, cHSL1.S, cHSL1.L); + else if (cHSL1.H > cHSL2.H) + cHSL2 = new HSLColor(cHSL2.A, cHSL2.H + 6, cHSL2.S, cHSL2.L); + } + + HSLColor cHSL = new HSLColor(cHSL1.A + ((cHSL2.A - cHSL1.A) * alpha), + cHSL1.H + ((cHSL2.H - cHSL1.H) * alpha), + cHSL1.S + ((cHSL2.S - cHSL1.S) * alpha), + cHSL1.L + ((cHSL2.L - cHSL1.L) * alpha)); + + if (cHSL.H >= 6) + cHSL = new HSLColor(cHSL.A, cHSL.H - 6, cHSL.S, cHSL.L); + color = cHSL.ConvertToRGB(); + break; + } + } + } + return color; + } + + /// + /// Gets the value indicating whether the is absolute or relative ([0, 1]). + /// + public bool IsNormalized + { + get { return isNormalized; } + } + + /// + /// Gets the range on which palette is defined. + /// + public Range Range + { + get { return range; } + } + #endregion + + /// + /// Determines whether the specified palette is equal to the current instance. + /// + /// The palette to compare with current. + /// True if the objects are equal, false otherwise. + public override bool Equals(object obj) + { + if (obj == null) return false; + Palette p = obj as Palette; + if (p == null) return false; + if (isNormalized != p.isNormalized) return false; + if (range.Min != p.range.Min || range.Max != p.range.Max) return false; + int n = this.points.Length; + if (n != p.points.Length) return false; + for (int i = 0; i < n; i++) + { + var l = points[i]; + var r = p.points[i]; + if (l.X != r.X || l.LeftColor != r.LeftColor || l.RightColor != r.RightColor) + return false; + } + return true; + } + + /// + /// Serves as a hash function for a palette type. + /// + /// + public override int GetHashCode() + { + int hash = isNormalized.GetHashCode() ^ range.GetHashCode(); + for (int i = 0; i < points.Length; i++) + { + var l = points[i]; + hash ^= l.X.GetHashCode() ^ l.LeftColor.GetHashCode() ^ l.RightColor.GetHashCode(); + } + return hash; + } + } + + internal class Lexer + { + public enum Separator { Equal, Comma }; + public enum LexemeType { Color, Separator, Number }; + + LexemeType currentLexem; + Color valueColor; + double valueNumber; + Separator valueSeparator; + + string paletteString; + int position; + + public LexemeType CurrentLexeme + { + get { return currentLexem; } + } + public Color ValueColor + { + get { return valueColor; } + } + public double ValueNumber + { + get { return valueNumber; } + } + public Separator ValueSeparator + { + get { return valueSeparator; } + } + public int Position + { + get { return position; } + } + + public Lexer(string value) + { + paletteString = value; + position = 0; + } + + public bool ReadNext() + { + if (position >= paletteString.Length) + return false; + while (paletteString[position] == ' ') position++; + + if (paletteString[position] == '#' || Char.IsLetter(paletteString[position])) + { + currentLexem = LexemeType.Color; + int start = position; + while (position < paletteString.Length && paletteString[position] != ' ' && + paletteString[position] != '=' && paletteString[position] != ',') + { + position++; + } + string color = paletteString.Substring(start, position - start); + valueColor = GetColorFromString(color); + } + else if (paletteString[position] == '=' || paletteString[position] == ',') + { + currentLexem = LexemeType.Separator; + if (paletteString[position] == '=') valueSeparator = Separator.Equal; + else valueSeparator = Separator.Comma; + position++; + } + else + { + currentLexem = LexemeType.Number; + int start = position; + while (position < paletteString.Length && paletteString[position] != ' ' && + paletteString[position] != '=' && paletteString[position] != ',') + { + position++; + } + string number = paletteString.Substring(start, position - start); + valueNumber = Double.Parse(number, CultureInfo.InvariantCulture); + } + return true; + } + + private Color GetColorFromString(string str) + { + Color color = new Color(); + + if (Char.IsLetter(str[0])) + { + bool isNamed = false; + Type colors = typeof(Colors); + foreach (var property in colors.GetProperties()) + { + if (property.Name == str) + { + color = (Color)property.GetValue(null, null); + isNamed = true; + } + } + if (!isNamed) + throw new PaletteFormatException(Position, "wrong name of color"); + } + else if (str[0] == '#') + { + if (str.Length == 7) + { + color.A = 255; + color.R = Convert.ToByte(str.Substring(1, 2), 16); + color.G = Convert.ToByte(str.Substring(3, 2), 16); + color.B = Convert.ToByte(str.Substring(5, 2), 16); + } + else if (str.Length == 9) + { + color.A = Convert.ToByte(str.Substring(1, 2), 16); + color.R = Convert.ToByte(str.Substring(3, 2), 16); + color.G = Convert.ToByte(str.Substring(5, 2), 16); + color.B = Convert.ToByte(str.Substring(7, 2), 16); + } + else throw new PaletteFormatException(Position, "wrong name of color"); + } + else throw new PaletteFormatException(Position, "wrong name of color"); + + return color; + } + } + + /// + /// An exception that is thrown when an argument does not meet the parameter specifications + /// of class methods. + /// + public class PaletteFormatException : FormatException + { + /// + /// Initializes a new instance of class. + /// + /// A position in a string in which an exception occured. + /// A message to show. + public PaletteFormatException(int position, string message) : + base(String.Format(CultureInfo.InvariantCulture, "Incorrect palette string at char {0}: {1}", position, message)) { } + + /// + /// Initializes a new instance of class with default message. + /// + public PaletteFormatException() { } + + /// + /// Initializes a new instance of class. + /// + /// A message to show. + public PaletteFormatException(string message) + : base(message) { } + + /// + /// Initializes a new instance of class. + /// + /// A message to show. + /// An occured exception. + public PaletteFormatException(string message, Exception innerException) + : base(message, innerException) { } + } + + internal class HSLColor + { + private double h; + private double s; + private double l; + private double a; + + public HSLColor(double a, double h, double s, double l) + { + this.a = a; + this.h = h; + this.s = s; + this.l = l; + } + + public HSLColor(Color color) + { + this.a = color.A / 255.0; + + double r = color.R / 255.0; + double g = color.G / 255.0; + double b = color.B / 255.0; + + double maxcolor = Math.Max(r, g); + maxcolor = Math.Max(maxcolor, b); + double mincolor = Math.Min(r, g); + mincolor = Math.Min(mincolor, b); + + this.l = (maxcolor + mincolor) / 2.0; + + if (maxcolor == mincolor) + this.s = 0.0; + else + { + if (this.l < 0.5) + s = (maxcolor - mincolor) / (maxcolor + mincolor); + else + s = (maxcolor - mincolor) / (2.0 - maxcolor - mincolor); + } + if (maxcolor == mincolor) + this.h = 0; + else if (maxcolor == r) + { + if (g >= b) + this.h = (g - b) / (maxcolor - mincolor); + else + this.h = (g - b) / (maxcolor - mincolor) + 6.0; + } + else if (maxcolor == g) + this.h = 2.0 + (b - r) / (maxcolor - mincolor); + else if (maxcolor == b) + this.h = 4.0 + (r - g) / (maxcolor - mincolor); + } + + public Color ConvertToRGB() + { + Color color = new Color(); + color.A = (byte)(this.a * 255.0); + + double c = (1.0 - Math.Abs(2.0 * this.l - 1.0)) * this.s; + double x = c * (1.0 - Math.Abs(this.h % 2.0 - 1.0)); + + if (this.h < 1 && this.h >= 0) + { + color.R = Convert.ToByte(c * 255.0); + color.G = Convert.ToByte(x * 255.0); + color.B = 0; + } + if (this.h < 2 && this.h >= 1) + { + color.R = Convert.ToByte(x * 255.0); + color.G = Convert.ToByte(c * 255.0); + color.B = 0; + } + if (this.h < 3 && this.h >= 2) + { + color.R = 0; + color.G = Convert.ToByte(c * 255.0); + color.B = Convert.ToByte(x * 255.0); + } + if (this.h < 4 && this.h >= 3) + { + color.R = 0; + color.G = Convert.ToByte(x * 255.0); + color.B = Convert.ToByte(c * 255.0); + } + if (this.h < 5 && this.h >= 4) + { + color.R = Convert.ToByte(x * 255.0); + color.G = 0; + color.B = Convert.ToByte(c * 255.0); + } + if (this.h < 6 && this.h >= 5) + { + color.R = Convert.ToByte(c * 255.0); + color.G = 0; + color.B = Convert.ToByte(x * 255.0); + } + + double m = (this.l - c / 2.0) * 255.0; + double temp = color.R + m; + if (temp > 255) + color.R = 255; + else if (temp < 0) + color.R = 0; + else + color.R = Convert.ToByte(temp); + + temp = color.G + m; + if (temp > 255) + color.G = 255; + else if (temp < 0) + color.G = 0; + else + color.G = Convert.ToByte(color.G + m); + + temp = color.B + m; + if (temp > 255) + color.B = 255; + else if (temp < 0) + color.B = 0; + else + color.B = Convert.ToByte(color.B + m); + + return color; + } + + public double A + { + get { return a; } + } + public double H + { + get { return h; } + } + public double S + { + get { return s; } + } + public double L + { + get { return l; } + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Common/Range.cs b/InteractiveDataDisplay_OneDll/src/Common/Range.cs new file mode 100644 index 000000000..c8c07af34 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Common/Range.cs @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.Globalization; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Represents ranges for double type. + /// + public struct Range + { + double minimum; + double maximum; + + /// + /// Gets the minimum value of current range. + /// + public double Min + { + get { return minimum; } + set { minimum = value; } + } + + /// + /// Gets the maximum value of current range. + /// + public double Max + { + get { return maximum; } + set { maximum = value; } + } + + /// + /// Initializes new instance of range struct from given minimum and maximum values. + /// + /// Minimum value of the range. + /// Maximum value of the range. + public Range(double minimum, double maximum) + { + Debug.Assert(!double.IsNaN(minimum)); + Debug.Assert(!double.IsNaN(maximum)); + + if (minimum < maximum) + { + this.minimum = minimum; + this.maximum = maximum; + } + else + { + this.minimum = maximum; + this.maximum = minimum; + } + } + + private Range(bool isEmpty) + { + if (isEmpty) + { + this.minimum = double.PositiveInfinity; + this.maximum = double.NegativeInfinity; + } + else + { + minimum = 0; + maximum = 0; + } + } + + /// + /// Readonly instance of empty range + /// + public static readonly Range Empty = new Range(true); + + /// Returns true of this range contains no points (e.g. Min > Max). + public bool IsEmpty { get { return Min > Max; } } + + /// Returns true of this range is a point (e.g. Min == Max). + public bool IsPoint { get { return Max == Min; } } + + /// + /// Updates current instance of with minimal range which contains current range and specified value + /// + /// Value, which will be used for for current instance of range surrond + public void Surround(double value) + { + if (value < minimum) minimum = value; + if (value > maximum) maximum = value; + } + + /// + /// Updates current instance of with minimal range which contains current range and specified range + /// + /// Range, which will be used for current instance of range surrond + public void Surround(Range range) + { + if (range.IsEmpty) + return; + + Surround(range.minimum); + Surround(range.maximum); + } + + /// + /// Returns a string that represents the current range. + /// + /// String that represents the current range + public override string ToString() + { + return "[" + minimum.ToString(CultureInfo.InvariantCulture) + "," + maximum.ToString(CultureInfo.InvariantCulture) + "]"; + } + + /// + /// Calculates range from current which will have the same center and which size will be larger in factor times + /// + /// Zoom factor + /// Zoomed with specified factor range + public Range Zoom(double factor) + { + if (IsEmpty) + return new Range(true); + + double delta = (Max - Min) / 2; + double center = (Max + Min) / 2; + + return new Range(center - delta * factor, center + delta * factor); + } + + /// + /// Determines whether the specified is equal to the current range. + /// + /// The range to compare with the current . + /// True if the specified range is equal to the current range, false otherwise. + public override bool Equals(object obj) + { + Range r = (Range)obj; + return r.minimum == minimum && r.maximum == maximum; + } + + /// + /// Returns the hash code for this instance. + /// + /// The hash code for current instance + public override int GetHashCode() + { + return minimum.GetHashCode() ^ maximum.GetHashCode(); + } + + /// + /// Returns a value that indicates whether two specified range values are equal. + /// + /// The first value to compare. + /// The second value to compare. + /// True if values are equal, false otherwise. + public static bool operator ==(Range first, Range second) + { + return first.Equals(second); + } + + /// + /// Returns a value that indicates whether two specified ranges values are not equal. + /// + /// The first value to compare. + /// The second value to compare. + /// True if values are not equal, false otherwise. + public static bool operator !=(Range first, Range second) + { + return !first.Equals(second); + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Figure.cs b/InteractiveDataDisplay_OneDll/src/Figure.cs new file mode 100644 index 000000000..2923e2d3c --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Figure.cs @@ -0,0 +1,444 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Media; +using System.Linq; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Specifies the dock position of a child element that is inside a + /// + public enum Placement + { + /// + /// A child element that is positioned on the left side of the + /// + Left, + + /// + /// A child element that is positioned at the top of the + /// + Top, + + /// + /// A child element that is positioned on the right side of the + /// + Right, + + /// + /// A child element that is positioned at the bottom of the + /// + Bottom, + + /// + /// A child element that is positioned at the center of the + /// + Center + } + + /// + /// Figure class provides special layout options that are often found in charts. + /// It provides attached property Placement that allows to place child elements in center, left, top, right and bottom slots. + /// Figure override plot-to-screen transform, so that plot coordinates are mapped to the cental part of the Figure. + /// + /// + /// Figure class provides two-pass algorithm that prevents well-known loop occurring on resize of figure with fixed aspect ratio: figure resize forces update of plot-to-screen transform which adjusts labels on the axes. + /// Change of label size may result in change of central part size which again updates plot-to-screen transform which in turn leads to axes label updates and so on. + /// + [Description("Plot with center, left, top, right and bottom slots")] + public class Figure : PlotBase + { + private double topHeight = 0; + private double topHeight2 = 0; + private double bottomHeight = 0; + private double bottomHeight2 = 0; + private double leftWidth = 0; + private double leftWidth2 = 0; + private double rightWidth = 0; + private double rightWidth2 = 0; + + private Size centerSize = Size.Empty; + + /// + /// Identify attached property + /// + public static readonly DependencyProperty PlacementProperty = DependencyProperty.RegisterAttached( + "Placement", + typeof(Placement), + typeof(Figure), + new PropertyMetadata(Placement.Center, OnPlacementPropertyChanged)); + + private static void OnPlacementPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + FrameworkElement elt = sender as FrameworkElement; + if (elt != null) // TODO: What if sender is not a FrameworkElement + { + UIElement parent = elt.Parent as UIElement; + if (parent != null) + parent.InvalidateMeasure(); + } + } + + /// Gets or sets extra padding added to effective padding that is computed from all plots in composition + [Category("InteractiveDataDisplay")] + public Thickness ExtraPadding + { + get { return (Thickness)GetValue(ExtraPaddingProperty); } + set { SetValue(ExtraPaddingProperty, value); } + } + + /// Identifies dependency property + public static readonly DependencyProperty ExtraPaddingProperty = + DependencyProperty.Register("ExtraPadding", typeof(Thickness), typeof(Figure), new PropertyMetadata(new Thickness())); + + /// + /// Computes padding with maximum values for each side from padding of all children + /// + /// Padding with maximum values for each side from padding of all children + protected override Thickness AggregatePadding() + { + var ep = base.AggregatePadding(); + return new Thickness( + ep.Left + ExtraPadding.Left, + ep.Top + ExtraPadding.Top, + ep.Right + ExtraPadding.Right, + ep.Bottom + ExtraPadding.Bottom); + } + + /// + /// Measures the size in layout required for child elements and determines a size for the Figure. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + // Assuming we are master panel + var topElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Top); + var bottomElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Bottom); + var centerElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Center); + var rightElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Right); + var leftElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Left); + + DataRect desiredRect; + if (IsAutoFitEnabled) + { + desiredRect = AggregateBounds(); + if (desiredRect.IsEmpty) + desiredRect = new DataRect(0, 0, 1, 1); + SetPlotRect(desiredRect, true); + } + else // resize + desiredRect = PlotRect; + + //First Iteration: Measuring top and bottom slots, + //then meassuring left and right with top and bottom output values + + // Create transform for first iteration + if (double.IsNaN(availableSize.Width) || double.IsNaN(availableSize.Height) || + double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) + availableSize = new Size(100, 100); + + Fit(desiredRect, availableSize); + + // Measure top and bottom slots + double topBottomWidth = 0; + double topBottomHeight = 0; + topHeight = 0; + bottomHeight = 0; + + foreach (var elt in topElts) + { + elt.Measure(availableSize); + var ds = elt.DesiredSize; + topBottomWidth = Math.Max(topBottomWidth, ds.Width); + topHeight += ds.Height; + } + topBottomHeight += topHeight; + + foreach (var elt in bottomElts) + { + elt.Measure(availableSize); + var ds = elt.DesiredSize; + topBottomWidth = Math.Max(topBottomWidth, ds.Width); + bottomHeight += ds.Height; + } + topBottomHeight += bottomHeight; + + // Measure left and right slots + double leftRightWidth = 0; + double leftRightHeight = 0; + leftWidth = 0; + rightWidth = 0; + + foreach (var elt in leftElts) + { + + elt.Measure(availableSize); + var ds = elt.DesiredSize; + leftRightHeight = Math.Max(leftRightHeight, ds.Height); + leftWidth += ds.Width; + } + leftRightWidth += leftWidth; + + foreach (var elt in rightElts) + { + elt.Measure(availableSize); + var ds = elt.DesiredSize; + leftRightHeight = Math.Max(leftRightHeight, ds.Height); + rightWidth += ds.Width; + } + leftRightWidth += rightWidth; + + //Measure center elements + Size availCenterSize = new Size(Math.Max(0, availableSize.Width - leftRightWidth), Math.Max(0, availableSize.Height - topBottomHeight)); + Fit(desiredRect, availCenterSize); + + foreach (var elt in centerElts) + { + elt.Measure(availCenterSize); + } + + // Remeasure top and bottom slots + double topBottomWidth2 = 0; + double topBottomHeight2 = 0; + topHeight2 = 0; + bottomHeight2 = 0; + + foreach (var elt in topElts) + { + elt.Measure(new Size(availCenterSize.Width, elt.DesiredSize.Height)); + var ds = elt.DesiredSize; + topBottomWidth2 = Math.Max(topBottomWidth2, ds.Width); + topHeight2 += ds.Height; + } + topBottomHeight2 += topHeight2; + + foreach (var elt in bottomElts) + { + elt.Measure(new Size(availCenterSize.Width, elt.DesiredSize.Height)); + var ds = elt.DesiredSize; + topBottomWidth2 = Math.Max(topBottomWidth2, ds.Width); + bottomHeight2 += ds.Height; + } + topBottomHeight2 += bottomHeight2; + + + //Scaling elements of their new meassured height it not equal to first + if (bottomHeight2 > bottomHeight) + { + ScaleTransform transform = new ScaleTransform { ScaleY = bottomHeight / bottomHeight2 }; + foreach (var elt in bottomElts) + { + elt.RenderTransform = transform; + } + } + + if (topHeight2 > topHeight) + { + ScaleTransform transform = new ScaleTransform { ScaleY = topHeight / topHeight2 }; + foreach (var elt in topElts) + { + elt.RenderTransform = transform; + } + } + + // ReMeasure left and right slots + double leftRightWidth2 = 0; + double leftRightHeight2 = 0; + leftWidth2 = 0; + rightWidth2 = 0; + + foreach (var elt in leftElts) + { + elt.Measure(new Size(elt.DesiredSize.Width, availCenterSize.Height)); + var ds = elt.DesiredSize; + leftRightHeight2 = Math.Max(leftRightHeight2, ds.Height); + leftWidth2 += ds.Width; + } + leftRightWidth2 += leftWidth2; + + foreach (var elt in rightElts) + { + elt.Measure(new Size(elt.DesiredSize.Width, availCenterSize.Height)); + var ds = elt.DesiredSize; + leftRightHeight2 = Math.Max(leftRightHeight2, ds.Height); + rightWidth2 += ds.Width; + } + leftRightWidth2 += rightWidth2; + + //Scaling elements of their new meassured height it not equal to first + if (leftWidth2 > leftWidth) + { + ScaleTransform transform = new ScaleTransform { ScaleX = leftWidth / leftWidth2 }; + foreach (var elt in leftElts) + { + elt.RenderTransform = transform; + } + } + + if (rightWidth2 > rightWidth) + { + ScaleTransform transform = new ScaleTransform { ScaleX = rightWidth / rightWidth2 }; + foreach (var elt in rightElts) + { + elt.RenderTransform = transform; + } + } + + centerSize = availCenterSize; + return new Size(availCenterSize.Width + leftRightWidth, availCenterSize.Height + topBottomHeight); + } + + /// + /// Positions child elements and determines a size for a Figure + /// + /// The final area within the parent that Figure should use to arrange itself and its children + /// The actual size used + protected override Size ArrangeOverride(Size finalSize) + { + var topElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Top).ToArray(); + var bottomElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Bottom).ToArray(); + var centerElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Center); + var rightElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Right).ToArray(); + var leftElts = Children.Cast().Where(elt => GetPlacement(elt) == Placement.Left).ToArray(); + + double x = 0, y = 0; + + //Arranging top elements and setting clip bounds + if (topHeight < topHeight2) + { + foreach (var elt in topElts) + { + double finalHeight = elt.DesiredSize.Height * topHeight / topHeight2; + elt.Arrange(new Rect(leftWidth, y, centerSize.Width, finalHeight)); + elt.Clip = new RectangleGeometry { Rect = new Rect(-leftWidth, 0, finalSize.Width, finalHeight) }; + y += finalHeight; + } + } + else + { + double iy = topHeight; + for (int i = topElts.Length - 1; i > -1; i--) + { + UIElement elt = topElts[i]; + elt.Arrange(new Rect(leftWidth, iy - elt.DesiredSize.Height, centerSize.Width, elt.DesiredSize.Height)); + elt.Clip = new RectangleGeometry { Rect = new Rect(-leftWidth, 0, finalSize.Width, elt.DesiredSize.Height) }; + iy -= elt.DesiredSize.Height; + } + y = topHeight; + } + + // Arranging left elements and setting clip bounds + if (leftWidth < leftWidth2) + { + foreach (var elt in leftElts) + { + double finalWidth = elt.DesiredSize.Width * leftWidth / leftWidth2; + elt.Arrange(new Rect(x, topHeight, finalWidth, centerSize.Height)); + elt.Clip = new RectangleGeometry { Rect = new Rect(0, -topHeight, finalWidth, finalSize.Height) }; + x += finalWidth; + } + } + else + { + double ix = leftWidth; + for (int i = leftElts.Length - 1; i > -1; i--) + { + UIElement elt = leftElts[i]; + elt.Arrange(new Rect(ix - elt.DesiredSize.Width, topHeight, elt.DesiredSize.Width, centerSize.Height)); + elt.Clip = new RectangleGeometry { Rect = new Rect(0, -topHeight, elt.DesiredSize.Width, finalSize.Height) }; + ix -= elt.DesiredSize.Width; + } + x = leftWidth; + } + + // Arranging center elements + foreach (var elt in centerElts) + { + elt.Arrange(new Rect(leftWidth, topHeight, centerSize.Width, centerSize.Height)); + } + + x += centerSize.Width; + y += centerSize.Height; + + // Arranging bottom elements and setting clip bounds + if (bottomHeight < bottomHeight2) + { + foreach (var elt in bottomElts) + { + double finalHeight = elt.DesiredSize.Height * bottomHeight / bottomHeight2; + elt.Arrange(new Rect(leftWidth, y, centerSize.Width, finalHeight)); + elt.Clip = new RectangleGeometry { Rect = new Rect(-leftWidth, 0, finalSize.Width, finalHeight) }; + y += finalHeight; + } + } + else + { + for (int i = bottomElts.Length - 1; i > -1; i--) + { + UIElement elt = bottomElts[i]; + elt.Arrange(new Rect(leftWidth, y, centerSize.Width, elt.DesiredSize.Height)); + elt.Clip = new RectangleGeometry { Rect = new Rect(-leftWidth, 0, finalSize.Width, elt.DesiredSize.Height) }; + y += elt.DesiredSize.Height; + } + } + + // Arranging right elements and setting clip bounds + if (rightWidth < rightWidth2) + { + foreach (var elt in rightElts) + { + double finalWidth = elt.DesiredSize.Width * rightWidth / rightWidth2; + elt.Arrange(new Rect(x, topHeight, finalWidth, centerSize.Height)); + elt.Clip = new RectangleGeometry { Rect = new Rect(0, -topHeight, finalWidth, finalSize.Height) }; + x += finalWidth; + } + } + else + { + for (int i = rightElts.Length - 1; i > -1; i--) + { + UIElement elt = rightElts[i]; + elt.Arrange(new Rect(x, topHeight, elt.DesiredSize.Width, centerSize.Height)); + elt.Clip = new RectangleGeometry { Rect = new Rect(0, -topHeight, elt.DesiredSize.Width, finalSize.Height) }; + x += elt.DesiredSize.Width; + } + } + + return new Size(x, y); + } + + /// + /// Sets the value of the attached property to a specified element. + /// + /// The element to which the attached property is written. + /// The needed value. + public static void SetPlacement(DependencyObject element, Placement placement) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetValue(PlacementProperty, placement); + } + + /// + /// Gets the value of the attached property to a specified element. + /// + /// The element from which the property value is read. + /// The property value for the element. + /// The default value of FigurePlacement property is Placement.Center + public static Placement GetPlacement(DependencyObject element) + { + if (element == null) + throw new ArgumentNullException("element"); + + return (Placement)element.GetValue(PlacementProperty); + } + } + +} + diff --git a/InteractiveDataDisplay_OneDll/src/InteractiveDataDisplay.WPF.csproj b/InteractiveDataDisplay_OneDll/src/InteractiveDataDisplay.WPF.csproj new file mode 100644 index 000000000..43e9c6c9c --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/InteractiveDataDisplay.WPF.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {2D34544C-2DF2-4B20-A43A-6C8D2DF3DD82} + Library + Properties + InteractiveDataDisplay.WPF + InteractiveDataDisplay.WPF + net452;net46 + 512 + + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NETFRAMEWORK;NETFRAMEWORK; + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE;RELEASE;NETFRAMEWORK;;RELEASE;NETFRAMEWORK;RELEASE;NETFRAMEWORK; + prompt + 4 + true + + + $(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets + 1.1.0 + Microsoft; MSU ITIS Lab + Sergey Berezin, Vassily Lyutsarev, Nikita Skoblov, Natalia Stepanova + Interactive Data Display for WPF is a set of controls for adding interactive visualization of dynamic data to your application. + Copyright 2017 Microsoft Corporation + idd wpf visualization plot plots plotting chart charting data interactive datavisualization + https://github.com/Microsoft/InteractiveDataDisplay.WPF + https://github.com/Microsoft/InteractiveDataDisplay.WPF/blob/master/LICENSE + true + false + sn.key.snk + + + + + + + + + + + + + + + + + + + + Designer + MSBuild:Compile + + + + \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/src/Legend/CountToVisibilityConverter.cs b/InteractiveDataDisplay_OneDll/src/Legend/CountToVisibilityConverter.cs new file mode 100644 index 000000000..5763b2342 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Legend/CountToVisibilityConverter.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System.Windows.Data; +using System; +using System.Windows; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Converts the count of collection to Visibility. + /// + public class CountToVisibilityConverter : IValueConverter + { + #region IValueConverter Members + + /// + /// Converts any numeric value to Visibility. + /// + /// A value of any numeric type. + /// + /// + /// + /// Visible if the value is positive. Collapsed if the value is negative or 0. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return (int)value > 0 ? Visibility.Visible : Visibility.Collapsed; + } + + /// + /// Converts Visibility to integer value. + /// + /// A value of Visibility enum. + /// + /// + /// + /// 1 if the value is Visible. 0 if the value is Collapsed. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return (Visibility)value == Visibility.Visible ? 1 : 0; + } + + #endregion + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Legend/DefaultTemplates.xaml b/InteractiveDataDisplay_OneDll/src/Legend/DefaultTemplates.xaml new file mode 100644 index 000000000..decc97796 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Legend/DefaultTemplates.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/InteractiveDataDisplay_OneDll/src/Legend/Legend.cs b/InteractiveDataDisplay_OneDll/src/Legend/Legend.cs new file mode 100644 index 000000000..5a6c8498e --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Legend/Legend.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Control for placing legend contents. + /// + [Description("Control for legend contents")] + public class Legend : ContentControl + { + /// + /// Gets or sets the number of items in legend. Value of this property + /// is automatically updated set to item count if content is . + /// It is recommended to use this property as read only. + /// + [Browsable(false)] + public int ItemsCount + { + get { return (int)GetValue(ItemsCountProperty); } + private set { SetValue(ItemsCountProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ItemsCountProperty = + DependencyProperty.Register("ItemsCount", typeof(int), typeof(Legend), new PropertyMetadata(0)); + + /// + /// This method is called when the content property changes. + /// + /// Old value of property. + /// New value of property. + protected override void OnContentChanged(object oldContent, object newContent) + { + var panel = oldContent as Panel; + if (panel != null) + panel.LayoutUpdated -= OnContentLayoutUpdated; + panel = newContent as Panel; + if (panel != null) + panel.LayoutUpdated += OnContentLayoutUpdated; + UpdateItemsCount(); + base.OnContentChanged(oldContent, newContent); + } + + private void OnContentLayoutUpdated(object sender, EventArgs e) + { + UpdateItemsCount(); + } + + /// + /// Initializes a new instance of class. + /// + public Legend() + { + DefaultStyleKey = typeof(Legend); + IsTabStop = false; + } + + /// + /// This method is invoked whenever application code or internal processes call ApplyTemplate. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + UpdateItemsCount(); + } + + private void UpdateItemsCount() + { + var panel = Content as Panel; + if (panel != null) + ItemsCount = panel.Children.Count; + else + ItemsCount = Content != null ? 1 : 0; + } + + /// + /// Returns value of attached property that controls + /// if object is visible in legend. Default value is true. + /// + /// An object with a legend. + /// Visibility of specified object in legend. + public static bool GetIsVisible(DependencyObject obj) + { + if (obj != null) + return (bool)obj.GetValue(IsVisibleProperty); + else + return true; + } + + /// + /// Sets attached property. Objects with false value + /// of this property are not shown in legend. + /// An object to be presented in a legend. + /// Legend visibility flag + public static void SetIsVisible(DependencyObject obj, bool isVisible) + { + if (obj != null) + obj.SetValue(IsVisibleProperty, isVisible); + } + + /// + /// Identifies attached property to get or set visibility of a legend. + /// + public static readonly DependencyProperty IsVisibleProperty = + DependencyProperty.RegisterAttached("IsVisible", typeof(bool), typeof(Legend), new PropertyMetadata(true)); + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Legend/LegendItemsPanel.cs b/InteractiveDataDisplay_OneDll/src/Legend/LegendItemsPanel.cs new file mode 100644 index 000000000..e5057fb4d --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Legend/LegendItemsPanel.cs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows.Controls; +using System.Windows; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// This panel automatically builds legend for given + [Description("Presents legend items for a plot")] + public class LegendItemsPanel : Panel + { + private IDisposable unsubsrciber; + private PlotBase masterPlot = null; + + /// + /// Gets of sets plot to build legend for + /// + [Category("InteractiveDataDisplay")] + [Description("Plot to build legend for")] + public PlotBase MasterPlot + { + get { return (PlotBase)GetValue(MasterPlotProperty); } + set { SetValue(MasterPlotProperty, value); } + } + + /// + /// Identifies property + /// + public static readonly DependencyProperty MasterPlotProperty = + DependencyProperty.Register("MasterPlot", typeof(PlotBase), typeof(LegendItemsPanel), new PropertyMetadata(null, + (o, e) => + { + LegendItemsPanel panel = (LegendItemsPanel)o; + if (panel != null) + { + panel.OnMasterPlotChanged(e); + } + })); + + private void LegendItemsPanelUnloaded(object sender, RoutedEventArgs e) + { + if (MasterPlot == null) + masterPlot = null; + } + + private void LegendItemsPanelLoaded(object sender, RoutedEventArgs e) + { + if (MasterPlot == null) + masterPlot = PlotBase.FindMaster(this); + else + masterPlot = MasterPlot; + + Resubscribe(); + + } + + private void OnMasterPlotChanged(DependencyPropertyChangedEventArgs e) + { + PlotBase newPlot = (PlotBase)e.NewValue; + + if (newPlot != null) + masterPlot = newPlot; + + Resubscribe(); + } + + private void Resubscribe() + { + if (unsubsrciber != null) + { + unsubsrciber.Dispose(); + unsubsrciber = null; + } + + if (masterPlot != null) + { + //unsubsrciber = masterPlot.CompositionChange.Subscribe(change => RefreshContents()); + RefreshContents(); + } + } + + /// + /// Measures the size in layout required for child elements and determines a size for parent. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + Size result = new Size(); + foreach (UIElement c in Children) + { + c.Measure(availableSize); + result.Width = Math.Max(result.Width, c.DesiredSize.Width); + if (!Double.IsInfinity(availableSize.Height)) + availableSize.Height = Math.Max(0, availableSize.Height - c.DesiredSize.Height); + result.Height += c.DesiredSize.Height; + } + return result; + } + + /// + /// Positions child elements and determines a size for parent. + /// + /// The final area within the parent that Figure should use to arrange itself and its children. + /// The actual size used. + protected override Size ArrangeOverride(Size finalSize) + { + double y = 0; + foreach (UIElement c in Children) + { + c.Arrange(new Rect(new Point(0, y), c.DesiredSize)); + y += c.DesiredSize.Height; + } + return finalSize; + } + + /// + /// Initializes a new instance of class. + /// + public LegendItemsPanel() + { + var rd = new ResourceDictionary(); + rd = (ResourceDictionary)Application.LoadComponent(new System.Uri("/InteractiveDataDisplay.WPF;component/Legend/DefaultTemplates.xaml", System.UriKind.Relative)); + Resources.MergedDictionaries.Add(rd); + + Loaded += LegendItemsPanelLoaded; + Unloaded += LegendItemsPanelUnloaded; + } + + /// + /// Constructs legend UI for current plot + /// + protected virtual void RefreshContents() + { + Children.Clear(); + + if (masterPlot != null) + { + foreach (var elt in masterPlot.RelatedPlots) + { + var c = CreateLegendContent(elt); + if (c != null) + Children.Add(c); + } + } + + InvalidateMeasure(); + } + + /// + /// Creates legend content from given UIElement + /// + /// UIElement for legend content construction + /// UIElement which reperesents input UIElement in legend control + protected virtual UIElement CreateLegendContent(UIElement element) + { + if (element == null) + throw new ArgumentNullException("element"); + + if (!Legend.GetIsVisible(element)) + return null; + + Type eltype = element.GetType(); + while (eltype != null) + { + var template = Resources[eltype.FullName] as DataTemplate; + if (template != null) + { + var newElt = template.LoadContent() as FrameworkElement; + if (newElt != null) + newElt.DataContext = element; + + return newElt; + } + eltype = eltype.BaseType; + } + return null; + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Legend/SizeLegendControl.cs b/InteractiveDataDisplay_OneDll/src/Legend/SizeLegendControl.cs new file mode 100644 index 000000000..f1ee4c74f --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Legend/SizeLegendControl.cs @@ -0,0 +1,300 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// A control to show information about size of drawing markers in a legend. + /// + [Description("Visually maps value to screen units")] + public class SizeLegendControl : ContentControl + { + #region Fields + + private Canvas image; + private Axis axis; + + #endregion + + #region Properties + + /// + /// Gets or sets the range of axis values. + /// Default value is [0, 1]. + /// + [Category("InteractiveDataDisplay")] + [Description("Range of mapped values")] + public Range Range + { + get { return (Range)GetValue(RangeProperty); } + set { SetValue(RangeProperty, value); } + } + + /// + /// Gets or sets the filling color. + /// Default value is black color. + /// + [Category("Appearance")] + public Color Color + { + get { return (Color)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + /// + /// Gets or sets the maximum value of displaying height. + /// Default value is 20. + /// + [Category("InteractiveDataDisplay")] + [Description("Maximum screen height of palette")] + public new double MaxHeight + { + get { return (double)GetValue(MaxHeightProperty); } + set { SetValue(MaxHeightProperty, value); } + } + + /// + /// Gets or sets the value indicating whether an axis should be rendered or not. + /// Default value is true. + /// + [Category("InteractiveDataDisplay")] + public bool IsAxisVisible + { + get { return (bool)GetValue(IsAxisVisibleProperty); } + set { SetValue(IsAxisVisibleProperty, value); } + } + + /// + /// Gets or sets the minimum of size to display in a legend. This is screen size that corresponds to minimum value of data. + /// Default value is Double.NaN. + /// + [Category("InteractiveDataDisplay")] + [Description("Screen size that corresponds to minimum value")] + public double Min + { + get { return (double)GetValue(MinProperty); } + set { SetValue(MinProperty, value); } + } + + /// + /// Gets or sets the maximum of size to display in a legend. This is screen size that corresponds to maximum value of data. + /// Default value is Double.NaN. + /// + [Category("InteractiveDataDisplay")] + [Description("Screen size that corresponds to maximum value")] + public double Max + { + get { return (double)GetValue(MaxProperty); } + set { SetValue(MaxProperty, value); } + } + + #endregion + + #region Dependency properties + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RangeProperty = DependencyProperty.Register( + "Range", + typeof(Range), + typeof(SizeLegendControl), + new PropertyMetadata(new Range(0, 1), OnRangeChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register( + "Color", + typeof(Color), + typeof(SizeLegendControl), + new PropertyMetadata(Colors.Black, OnColorChanged)); + + /// + /// Identifies the dependency property. + /// + public static new readonly DependencyProperty MaxHeightProperty = DependencyProperty.Register( + "MaxHeight", + typeof(double), + typeof(SizeLegendControl), + new PropertyMetadata(20.0, OnMinMaxChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsAxisVisibleProperty = DependencyProperty.Register( + "IsAxisVisible", + typeof(bool), + typeof(SizeLegendControl), + new PropertyMetadata(true)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MinProperty = DependencyProperty.Register( + "Min", + typeof(double), + typeof(SizeLegendControl), + new PropertyMetadata(Double.NaN, OnMinMaxChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MaxProperty = DependencyProperty.Register( + "Max", + typeof(double), + typeof(SizeLegendControl), + new PropertyMetadata(Double.NaN, OnMinMaxChanged)); + + #endregion + + #region ctor + + /// + /// Initializes a new instance of the class. + /// + public SizeLegendControl() + { + StackPanel stackPanel = new StackPanel(); + + image = new Canvas { HorizontalAlignment = HorizontalAlignment.Stretch }; + axis = new Axis { AxisOrientation = AxisOrientation.Bottom, HorizontalAlignment = HorizontalAlignment.Stretch }; + + stackPanel.Children.Add(image); + stackPanel.Children.Add(axis); + + Content = stackPanel; + + SizeChanged += (o, e) => + { + if (e.PreviousSize.Width == 0 || e.PreviousSize.Height == 0 || Double.IsNaN(e.PreviousSize.Width) || Double.IsNaN(e.PreviousSize.Height)) + UpdateCanvas(); + }; + + IsTabStop = false; + } + + #endregion + + #region Private methods + + private void UpdateCanvas() + { + if (Width == 0 || Double.IsNaN(Width)) + return; + + image.Children.Clear(); + + double maxHeight = Double.IsNaN(Max) ? Range.Max : Max; + double minHeight = Double.IsNaN(Min) ? Range.Min : Min; + + double visHeight = Math.Min(maxHeight, MaxHeight); + + image.Width = Width; + axis.Width = Width; + image.Height = visHeight; + + PathFigure pathFigure = new PathFigure(); + pathFigure.StartPoint = new Point(0, maxHeight); + + LineSegment lineSegment1 = new LineSegment(); + lineSegment1.Point = new Point(Width, maxHeight); + LineSegment lineSegment2 = new LineSegment(); + lineSegment2.Point = new Point(Width, 0); + LineSegment lineSegment3 = new LineSegment(); + lineSegment3.Point = new Point(0, maxHeight - minHeight); + LineSegment lineSegment4 = new LineSegment(); + lineSegment4.Point = new Point(0, maxHeight); + + PathSegmentCollection pathSegmentCollection = new PathSegmentCollection(); + pathSegmentCollection.Add(lineSegment1); + pathSegmentCollection.Add(lineSegment2); + pathSegmentCollection.Add(lineSegment3); + pathSegmentCollection.Add(lineSegment4); + + pathFigure.Segments = pathSegmentCollection; + PathFigureCollection pathFigureCollection = new PathFigureCollection(); + pathFigureCollection.Add(pathFigure); + PathGeometry pathGeometry = new PathGeometry(); + pathGeometry.Figures = pathFigureCollection; + + Path path = new Path(); + // TODO: Make it dependency property of SizeControl + path.Fill = new SolidColorBrush(Colors.LightGray); + + path.Data = pathGeometry; + path.Stroke = new SolidColorBrush(Color); + + if (!Double.IsNaN(MaxHeight)) + { + RectangleGeometry clip = new RectangleGeometry(); + clip.Rect = new Rect(0, maxHeight - this.MaxHeight, Width, this.MaxHeight); + path.Clip = clip; + } + + Canvas.SetTop(path, image.Height - maxHeight); + image.Children.Add(path); + + if (visHeight < maxHeight) + { + Line top; + if (minHeight >= visHeight) + top = new Line { X1 = 0, Y1 = 0, X2 = Width, Y2 = 0 }; + else + top = new Line + { + X1 = Width * (visHeight - minHeight) / (maxHeight - minHeight), + Y1 = 0, + X2 = Width, + Y2 = 0 + }; + top.Stroke = new SolidColorBrush(Colors.Black); + top.StrokeDashArray = new DoubleCollection(); + top.StrokeDashArray.Add(5); + top.StrokeDashArray.Add(5); + image.Children.Add(top); + } + } + + private static void OnRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SizeLegendControl control = (SizeLegendControl)d; + control.OnRangeChanged(); + } + + private void OnRangeChanged() + { + axis.Range = Range; + UpdateCanvas(); + } + + private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SizeLegendControl control = (SizeLegendControl)d; + control.OnColorChanged(); + } + + private void OnColorChanged() + { + if (image.Children.Count > 0) + ((Path)image.Children[0]).Fill = new SolidColorBrush(Color); + } + + private static void OnMinMaxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SizeLegendControl control = (SizeLegendControl)d; + control.UpdateCanvas(); + } + + #endregion + + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Navigation/KeyboardNavigation.cs b/InteractiveDataDisplay_OneDll/src/Navigation/KeyboardNavigation.cs new file mode 100644 index 000000000..f48e05423 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Navigation/KeyboardNavigation.cs @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Provides keyboard navigation around viewport of parent . + /// + /// + /// Place instance of inside your element to enable keyboard navigation over it. + /// + [Description("Navigates parent plot by keyboard commands")] + public class KeyboardNavigation : Control + { + private PlotBase masterPlot = null; + + /// + /// Initializes a new instance of class. + /// + public KeyboardNavigation() + { + Loaded += KeyboardNavigationLoaded; + Unloaded += KeyboardNavigationUnloaded; + + KeyDown += KeyboardNavigationKeyDown; + KeyUp += KeyboardNavigationKeyUp; + } + + /// Gets or sets vertical navigation status. True means that user can navigate along Y axis + /// The default value is true + [Category("InteractiveDataDisplay")] + public bool IsVerticalNavigationEnabled + { + get { return (bool)GetValue(IsVerticalNavigationEnabledProperty); } + set { SetValue(IsVerticalNavigationEnabledProperty, value); } + } + + /// Identifies property + public static readonly DependencyProperty IsVerticalNavigationEnabledProperty = + DependencyProperty.Register("IsVerticalNavigationEnabled", typeof(bool), typeof(KeyboardNavigation), new PropertyMetadata(true)); + + /// Gets or sets horizontal navigation status. True means that user can navigate along X axis + /// The default value is true + [Category("InteractiveDataDisplay")] + public bool IsHorizontalNavigationEnabled + { + get { return (bool)GetValue(IsHorizontalNavigationEnabledProperty); } + set { SetValue(IsHorizontalNavigationEnabledProperty, value); } + } + + /// Identifies property + public static readonly DependencyProperty IsHorizontalNavigationEnabledProperty = + DependencyProperty.Register("IsHorizontalNavigationEnabled", typeof(bool), typeof(KeyboardNavigation), new PropertyMetadata(true)); + + void KeyboardNavigationUnloaded(object sender, RoutedEventArgs e) + { + masterPlot = null; + } + + void KeyboardNavigationLoaded(object sender, RoutedEventArgs e) + { + masterPlot = PlotBase.FindMaster(this); + } + + private void KeyboardNavigationKeyDown(object sender, KeyEventArgs e) + { + if (masterPlot != null) + { + + if (e.Key == Key.Up && IsVerticalNavigationEnabled) + { + var rect = masterPlot.PlotRect; + double dy = rect.Height / 200; + + masterPlot.SetPlotRect(new DataRect( + rect.XMin, + rect.YMin - dy, + rect.XMin + rect.Width, + rect.YMin - dy + rect.Height)); + + masterPlot.IsAutoFitEnabled = false; + e.Handled = true; + } + if (e.Key == Key.Down && IsVerticalNavigationEnabled) + { + var rect = masterPlot.PlotRect; + double dy = - rect.Height / 200; + + masterPlot.SetPlotRect(new DataRect( + rect.XMin, + rect.YMin - dy, + rect.XMin + rect.Width, + rect.YMin - dy + rect.Height)); + + masterPlot.IsAutoFitEnabled = false; + e.Handled = true; + } + if (e.Key == Key.Right && IsHorizontalNavigationEnabled) + { + var rect = masterPlot.PlotRect; + double dx = - rect.Width / 200; + + masterPlot.SetPlotRect(new DataRect( + rect.XMin + dx, + rect.YMin, + rect.XMin + dx + rect.Width, + rect.YMin + rect.Height)); + + masterPlot.IsAutoFitEnabled = false; + e.Handled = true; + } + if (e.Key == Key.Left && IsHorizontalNavigationEnabled) + { + var rect = masterPlot.PlotRect; + double dx = rect.Width / 200; + + masterPlot.SetPlotRect(new DataRect( + rect.XMin + dx, + rect.YMin, + rect.XMin + dx + rect.Width, + rect.YMin + rect.Height)); + + masterPlot.IsAutoFitEnabled = false; + e.Handled = true; + } + if (e.Key == Key.Subtract) + { + DoZoom(1.2); + masterPlot.IsAutoFitEnabled = false; + e.Handled = true; + } + if (e.Key == Key.Add) + { + DoZoom(1 / 1.2); + masterPlot.IsAutoFitEnabled = false; + e.Handled = true; + } + if (e.Key == Key.Home) + { + masterPlot.IsAutoFitEnabled = true; + e.Handled = true; + } + } + } + + private void DoZoom(double factor) + { + if (masterPlot != null) + { + var rect = masterPlot.PlotRect; + + if (IsHorizontalNavigationEnabled) + rect.X = rect.X.Zoom(factor); + if (IsVerticalNavigationEnabled) + rect.Y = rect.Y.Zoom(factor); + + if (IsZoomEnable(rect)) + { + masterPlot.SetPlotRect(rect); + masterPlot.IsAutoFitEnabled = false; + } + } + } + + private bool IsZoomEnable(DataRect rect) + { + bool res = true; + if (IsHorizontalNavigationEnabled) + { + double e_max = Math.Log(Math.Max(Math.Abs(rect.XMax), Math.Abs(rect.XMin)), 2); + double log = Math.Log(rect.Width, 2); + res = log > e_max - 40; + } + if (IsVerticalNavigationEnabled) + { + double e_max = Math.Log(Math.Max(Math.Abs(rect.YMax), Math.Abs(rect.YMin)), 2); + double log = Math.Log(rect.Height, 2); + res = res && log > e_max - 40; + } + return res; + } + + void KeyboardNavigationKeyUp(object sender, KeyEventArgs e) + { + } + } +} + + diff --git a/InteractiveDataDisplay_OneDll/src/Navigation/MouseNavigation.cs b/InteractiveDataDisplay_OneDll/src/Navigation/MouseNavigation.cs new file mode 100644 index 000000000..f2e284e59 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Navigation/MouseNavigation.cs @@ -0,0 +1,499 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Provides mouse navigation around viewport of parent . + /// + /// + /// Place instance of inside your element to enable mouse navigation over it. + /// + [Description("Navigates parent plot by mouse")] + public class MouseNavigation : Panel + { + private Canvas navigationCanvas = new Canvas + { + // This background brush allows Canvas to intercept mouse events while remaining transparent + Background = new SolidColorBrush(Color.FromArgb(0, 255, 255, 255)) + }; + + private PlotBase masterPlot = null; + + /// First parent Control in visual tree. Used to receive focus on mouse click + private Control parentControl = null; + + private bool isPanning = false; + private bool isSelecting = false; + private bool wasInside = false; + private bool isLeftClicked = false; + + private bool selectingStarted = false; + private Point panningStart; + private Point panningEnd; + private Point selectStart; + private Point selectEnd; + private DateTime lastClick; + + private bool selectingMode = true; + + private Rectangle selectArea; + private Plot plot = new NavigationPlot(); + + private bool transformChangeRequested = false; + + /// + /// Initializes a new instance of class. + /// + public MouseNavigation() + { + Children.Add(navigationCanvas); + + + Loaded += MouseNavigationLoaded; + Unloaded += MouseNavigationUnloaded; + + MouseLeave += new MouseEventHandler(MouseNavigationLayer_MouseLeave); + MouseMove += new MouseEventHandler(MouseNavigationLayer_MouseMove); + MouseLeftButtonUp += new MouseButtonEventHandler(MouseNavigationLayer_MouseLeftButtonUp); + MouseLeftButtonDown += new MouseButtonEventHandler(MouseNavigationLayer_MouseLeftButtonDown); + MouseWheel += new MouseWheelEventHandler(MouseNavigationLayer_MouseWheel); + + LayoutUpdated += (s, a) => transformChangeRequested = false; + } + + /// Gets or sets vertical navigation status. True means that user can navigate along Y axis + /// The default value is true + [Category("InteractiveDataDisplay")] + public bool IsVerticalNavigationEnabled + { + get { return (bool)GetValue(IsVerticalNavigationEnabledProperty); } + set { SetValue(IsVerticalNavigationEnabledProperty, value); } + } + + /// Identifies property + public static readonly DependencyProperty IsVerticalNavigationEnabledProperty = + DependencyProperty.Register("IsVerticalNavigationEnabled", typeof(bool), typeof(MouseNavigation), new PropertyMetadata(true)); + + /// Gets or sets horizontal navigation status. True means that user can navigate along X axis + /// The default value is true + [Category("InteractiveDataDisplay")] + public bool IsHorizontalNavigationEnabled + { + get { return (bool)GetValue(IsHorizontalNavigationEnabledProperty); } + set { SetValue(IsHorizontalNavigationEnabledProperty, value); } + } + + /// Identifies property + public static readonly DependencyProperty IsHorizontalNavigationEnabledProperty = + DependencyProperty.Register("IsHorizontalNavigationEnabled", typeof(bool), typeof(MouseNavigation), new PropertyMetadata(true)); + + void MouseNavigationUnloaded(object sender, RoutedEventArgs e) + { + if (masterPlot != null) + { + masterPlot.MouseLeave -= MouseNavigationLayer_MouseLeave; + masterPlot.MouseMove -= MouseNavigationLayer_MouseMove; + masterPlot.MouseLeftButtonUp -= MouseNavigationLayer_MouseLeftButtonUp; + masterPlot.MouseLeftButtonDown -= MouseNavigationLayer_MouseLeftButtonDown; + masterPlot.MouseWheel -= MouseNavigationLayer_MouseWheel; + } + + masterPlot = null; + parentControl = null; + } + + void MouseNavigationLoaded(object sender, RoutedEventArgs e) + { + masterPlot = PlotBase.FindMaster(this); + + if (masterPlot != null) + { + masterPlot.MouseLeave += MouseNavigationLayer_MouseLeave; + masterPlot.MouseMove += MouseNavigationLayer_MouseMove; + masterPlot.MouseLeftButtonUp += MouseNavigationLayer_MouseLeftButtonUp; + masterPlot.MouseLeftButtonDown += MouseNavigationLayer_MouseLeftButtonDown; + masterPlot.MouseWheel += MouseNavigationLayer_MouseWheel; + } + + var parent = VisualTreeHelper.GetParent(this); + var controlParent = parent as Control; + while (parent != null && controlParent == null) + { + parent = VisualTreeHelper.GetParent(parent); + controlParent = parent as Control; + } + parentControl = controlParent; + } + + /// + /// Measures the size in layout required for child elements and determines a size for the Figure. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + navigationCanvas.Measure(availableSize); + return navigationCanvas.DesiredSize; + } + + /// + /// Positions child elements and determines a size for a Figure + /// + /// The final area within the parent that Figure should use to arrange itself and its children + /// The actual size used + protected override Size ArrangeOverride(Size finalSize) + { + navigationCanvas.Arrange(new Rect(new Point(0, 0), finalSize)); + transformChangeRequested = false; + return finalSize; + } + + private void DoZoom(double factor) + { + if (masterPlot != null) + { + var rect = masterPlot.PlotRect; + + if (IsHorizontalNavigationEnabled) + rect.X = rect.X.Zoom(factor); + if (IsVerticalNavigationEnabled) + rect.Y = rect.Y.Zoom(factor); + + if (IsZoomEnable(rect)) + { + masterPlot.SetPlotRect(rect); + masterPlot.IsAutoFitEnabled = false; + } + } + } + + private bool IsZoomEnable(DataRect rect) + { + bool res = true; + if (IsHorizontalNavigationEnabled) + { + double e_max = Math.Log(Math.Max(Math.Abs(rect.XMax), Math.Abs(rect.XMin)), 2); + double log = Math.Log(rect.Width, 2); + res = log > e_max - 40; + } + if (IsVerticalNavigationEnabled) + { + double e_max = Math.Log(Math.Max(Math.Abs(rect.YMax), Math.Abs(rect.YMin)), 2); + double log = Math.Log(rect.Height, 2); + res = res && log > e_max - 40; + } + return res; + } + + private void MouseNavigationLayer_MouseWheel(object sender, MouseWheelEventArgs e) + { + Point cursorPosition = e.GetPosition(this); + if (!CheckCursor(cursorPosition)) + return; + + // TODO: Zoom relative to mouse position + double factor = e.Delta < 0 ? 1.2 : 1 / 1.2; + DoZoom(factor); + e.Handled = true; + } + + private void DoPan(Point screenStart, Point screenEnd) + { + if (masterPlot != null) + { + double dx = IsHorizontalNavigationEnabled ? + masterPlot.XFromLeft(screenEnd.X) - masterPlot.XFromLeft(screenStart.X) : 0; + double dy = IsVerticalNavigationEnabled ? + masterPlot.YFromTop(screenEnd.Y) - masterPlot.YFromTop(screenStart.Y) : 0; + var rect = masterPlot.PlotRect; + + double width = rect.Width; + double height = rect.Height; + + masterPlot.SetPlotRect(new DataRect( + rect.XMin - dx, + rect.YMin - dy, + rect.XMin - dx + width, + rect.YMin - dy + height)); + + masterPlot.IsAutoFitEnabled = false; + } + } + + private void MouseNavigationLayer_MouseLeave(object sender, MouseEventArgs e) + { + if (isPanning && (e.OriginalSource == navigationCanvas || e.OriginalSource == masterPlot)) + { + //panningEnd = e.GetPosition(this); + DoPan(panningStart, panningEnd); + isPanning = false; + } + if (isSelecting && (e.OriginalSource == navigationCanvas || e.OriginalSource == masterPlot)) + { + if (selectArea != null) + { + if (navigationCanvas.Children.Contains(selectArea)) + { + navigationCanvas.Children.Remove(selectArea); + } + + if (masterPlot != null && masterPlot.Children.Contains(plot)) + { + masterPlot.Children.Remove(plot); + } + } + isSelecting = false; + selectingStarted = false; + } + } + + + + private void MouseNavigationLayer_MouseMove(object sender, MouseEventArgs e) + { + Point cursorPosition = e.GetPosition(this); + if (!CheckCursor(cursorPosition)) + { + wasInside = true; + MouseNavigationLayer_MouseLeave(sender, e); + return; + } + else + { + if (wasInside && isLeftClicked) + { + HandleMouseUp(); + HandleMouseDown(e); + } + } + + if (isPanning) + { + if (!transformChangeRequested) + { + transformChangeRequested = true; + panningEnd = e.GetPosition(this); + DoPan(panningStart, panningEnd); + panningStart = panningEnd; + InvalidateArrange(); + } + } + + if (isSelecting) + { + selectEnd = e.GetPosition(this); + if (!selectingStarted) + { + if ((Math.Abs(selectStart.X - selectEnd.X) > 4) || (Math.Abs(selectStart.Y - selectEnd.Y) > 4)) + { + selectingStarted = true; + selectArea = new Rectangle(); + selectArea.Fill = new SolidColorBrush(Colors.LightGray); + selectArea.StrokeThickness = 2; + selectArea.Stroke = new SolidColorBrush(Color.FromArgb(255, 40, 0, 120)); + selectArea.Opacity = 0.6; + + if (masterPlot != null) + { + plot.IsAutoFitEnabled = false; + + plot.Children.Clear(); + plot.Children.Add(selectArea); + + if (!masterPlot.Children.Contains(plot)) + { + masterPlot.Children.Add(plot); + Canvas.SetZIndex(plot, 40000); + } + + double width = Math.Abs(masterPlot.XFromLeft(selectStart.X) - masterPlot.XFromLeft(selectEnd.X)); + double height = Math.Abs(masterPlot.YFromTop(selectStart.Y) - masterPlot.YFromTop(selectEnd.Y)); + double xmin = masterPlot.XFromLeft(Math.Min(selectStart.X, selectEnd.X)); + double ymin = masterPlot.YFromTop(Math.Max(selectStart.Y, selectEnd.Y)); + + Plot.SetX1(selectArea, xmin); + Plot.SetY1(selectArea, ymin); + Plot.SetX2(selectArea, xmin + width); + Plot.SetY2(selectArea, ymin + height); + + } + else + { + navigationCanvas.Children.Add(selectArea); + Canvas.SetLeft(selectArea, selectStart.X); + Canvas.SetTop(selectArea, selectStart.Y); + } + } + } + else + { + if (masterPlot == null) + { + double width = Math.Abs(selectStart.X - selectEnd.X); + double height = Math.Abs(selectStart.Y - selectEnd.Y); + selectArea.Width = width; + selectArea.Height = height; + + if (selectEnd.X < selectStart.X) + { + Canvas.SetLeft(selectArea, selectStart.X - width); + } + + if (selectEnd.Y < selectStart.Y) + { + Canvas.SetTop(selectArea, selectStart.Y - height); + } + } + else + { + double width = Math.Abs(masterPlot.XFromLeft(selectStart.X) - masterPlot.XFromLeft(selectEnd.X)); + double height = Math.Abs(masterPlot.YFromTop(selectStart.Y) - masterPlot.YFromTop(selectEnd.Y)); + double xmin = masterPlot.XFromLeft(Math.Min(selectStart.X, selectEnd.X)); + double ymin = masterPlot.YFromTop(Math.Max(selectStart.Y, selectEnd.Y)); + + Plot.SetX1(selectArea, xmin); + Plot.SetY1(selectArea, ymin); + Plot.SetX2(selectArea, xmin + width); + Plot.SetY2(selectArea, ymin + height); + } + } + } + + wasInside = false; + } + + private void MouseNavigationLayer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + e.Handled = HandleMouseDown(e); + } + + private void MouseNavigationLayer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + e.Handled = HandleMouseUp(); + } + + private bool CheckCursor(Point cursorPosition) + { + return !(cursorPosition.X < 0 || cursorPosition.Y < 0 || cursorPosition.X > this.ActualWidth || cursorPosition.Y > this.ActualHeight); + } + + private bool HandleMouseDown(MouseEventArgs e) + { + Point cursorPosition = e.GetPosition(this); + if (!CheckCursor(cursorPosition)) + return false; + else + { + isLeftClicked = true; + + if (masterPlot != null) + { + if (Keyboard.Modifiers == ModifierKeys.Shift) + { + DoZoom(1.2); + } + else + { + if (Keyboard.Modifiers == ModifierKeys.Control) + { + if (selectingMode && IsVerticalNavigationEnabled && IsHorizontalNavigationEnabled) + { + isSelecting = true; + selectStart = cursorPosition; + this.CaptureMouse(); + } + lastClick = DateTime.Now; + } + else + { + DateTime d = DateTime.Now; + if ((d - lastClick).TotalMilliseconds < 200) + { + masterPlot.IsAutoFitEnabled = true; + return true; + } + else + { + + isPanning = true; + panningStart = cursorPosition; + this.CaptureMouse(); + lastClick = DateTime.Now; + + } + } + } + } + } + + if (parentControl != null) + parentControl.Focus(); + + return true; + } + + private bool HandleMouseUp() + { + isLeftClicked = false; + isPanning = false; + if ((!isSelecting || !selectingStarted) && (Keyboard.Modifiers == ModifierKeys.Control)) + { + DateTime d = DateTime.Now; + if ((d - lastClick).TotalMilliseconds < 300) + { + isSelecting = false; + DoZoom(1 / 1.2); + } + } + if (isSelecting) + { + if (selectingStarted) + { + if (!transformChangeRequested) + { + transformChangeRequested = true; + isSelecting = false; + selectingStarted = false; + navigationCanvas.Children.Remove(selectArea); + + if (masterPlot != null) + { + masterPlot.Children.Remove(plot); + + double width = Math.Abs(masterPlot.XFromLeft(selectStart.X) - masterPlot.XFromLeft(selectEnd.X)); + double height = Math.Abs(masterPlot.YFromTop(selectStart.Y) - masterPlot.YFromTop(selectEnd.Y)); + double xmin = masterPlot.XFromLeft(Math.Min(selectStart.X, selectEnd.X)); + double ymin = masterPlot.YFromTop(Math.Max(selectStart.Y, selectEnd.Y)); + + masterPlot.SetPlotRect(new DataRect(xmin, ymin, xmin + width, ymin + height)); + masterPlot.IsAutoFitEnabled = false; + } + } + } + } + this.ReleaseMouseCapture(); + + return true; + } + + } + + + internal class NavigationPlot : Plot + { + protected override DataRect ComputeBounds() + { + return DataRect.Empty; + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Palette/Palette.Converters.cs b/InteractiveDataDisplay_OneDll/src/Palette/Palette.Converters.cs new file mode 100644 index 000000000..3013c37bd --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Palette/Palette.Converters.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows.Data; +using System.Diagnostics; +using System.ComponentModel; +using System.Globalization; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Converts a string to a new instance of class. + /// + public class StringToPaletteConverter : IValueConverter + { + /// + /// Parses an string to . For details see method. + /// + /// A string to parse. + /// + /// + /// + /// A palette that this string describes. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + string str = (string)value; + return Palette.Parse(str); + } + catch (Exception exc) + { + Debug.WriteLine("StringToPaletteConverter: " + exc.Message); + return Palette.Parse("Black"); + } + } + + /// + /// Returns a string describing an instance of class. + /// + /// An instance of class. + /// + /// + /// + /// A string describing specified palette. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + Palette palette = (Palette)value; + return palette.ToString(); + } + } + + /// + /// Provides a way to convert strings defined in xaml to a new instance of class. + /// + public class StringToPaletteTypeConverter : TypeConverter + { + /// + /// Gets whether a value can be converted to . + /// + /// A format context. + /// A type to convert from. + /// True if a value can be converted, false otherwise. + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + } + + /// + /// Parses an string to . For details see method. + /// + /// + /// + /// A string to parse. + /// A palette that this string describes. + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string) + { + try + { + if (value == null) + throw new ArgumentNullException("value"); + + string str = value.ToString(); + return Palette.Parse(str); + } + catch (Exception exc) + { + Debug.WriteLine("StringToPaletteConverter: " + exc.Message); + return Palette.Parse("Black"); + } + } + else + return base.ConvertFrom(context, culture, value); + } + + /// + /// Returns whether a value can be converted to the specified type. + /// + /// A format context. + /// A type to convert to. + /// True if the value can be converted, false otherwise + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(string) || base.CanConvertTo(destinationType); + } + + /// + /// Returns a string describing an instance of class. + /// + /// + /// + /// An instance of class. + /// + /// A string describing specified palette. + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + var palette = value as Palette; + if (palette != null) + { + return palette.ToString(); + } + else + return base.ConvertTo(context, destinationType); + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Palette/PaletteControl.cs b/InteractiveDataDisplay_OneDll/src/Palette/PaletteControl.cs new file mode 100644 index 000000000..d91698ca7 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Palette/PaletteControl.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// A control to show all the information about . + /// + [Description("Visually maps value to color")] + public class PaletteControl : ContentControl + { + #region Fields + + private Image image; + private Axis axis; + + #endregion + + #region Properties + + /// + /// Gets or sets the palette. + /// Default value is black palette. + /// + [Category("InteractiveDataDisplay")] + [TypeConverter(typeof(StringToPaletteTypeConverter))] + public Palette Palette + { + get { return (Palette)GetValue(PaletteProperty); } + set { SetValue(PaletteProperty, value); } + } + + /// + /// Gets or sets the range of axis values. + /// Default value is [0, 1]. + /// + [Category("InteractiveDataDisplay")] + [Description("Range of values mapped to color")] + public Range Range + { + get { return (Range)GetValue(RangeProperty); } + set { SetValue(RangeProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PaletteProperty = DependencyProperty.Register( + "Palette", + typeof(Palette), + typeof(PaletteControl), + new PropertyMetadata(Palette.Parse("Black"), OnPaletteChanged)); + + private static void OnPaletteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PaletteControl control = (PaletteControl)d; + control.OnPaletteChanged(); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RangeProperty = DependencyProperty.Register( + "Range", + typeof(Range), + typeof(PaletteControl), + new PropertyMetadata(new Range(0, 1), OnRangeChanged)); + + /// + /// Gets or sets a value indicating whether an axis should be displayed. + /// Default value is true. + /// + [Category("InteractiveDataDisplay")] + public bool IsAxisVisible + { + get { return (bool)GetValue(IsAxisVisibleProperty); } + set { SetValue(IsAxisVisibleProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsAxisVisibleProperty = DependencyProperty.Register( + "IsAxisVisible", + typeof(bool), + typeof(PaletteControl), + new PropertyMetadata(true)); + + /// + /// Gets or sets the height of rendered palette. + /// Default value is 20. + /// + [Category("InteractiveDataDisplay")] + public double PaletteHeight + { + get { return (double)GetValue(PaletteHeightProperty); } + set { SetValue(PaletteHeightProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PaletteHeightProperty = DependencyProperty.Register( + "PaletteHeight", + typeof(double), + typeof(PaletteControl), + new PropertyMetadata(20.0, OnPaletteHeightChanged)); + + private static void OnPaletteHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PaletteControl control = (PaletteControl)d; + control.image.Height = (double)e.NewValue; + control.UpdateBitmap(); + } + + #endregion + + #region ctor + + /// + /// Initializes a new instance of the class. + /// + public PaletteControl() + { + StackPanel stackPanel = new StackPanel(); + + image = new Image { Height = 20, Stretch = Stretch.None, HorizontalAlignment = HorizontalAlignment.Stretch }; + axis = new Axis { AxisOrientation = AxisOrientation.Bottom, HorizontalAlignment = HorizontalAlignment.Stretch }; + + stackPanel.Children.Add(image); + stackPanel.Children.Add(axis); + + Content = stackPanel; + + SizeChanged += (o, e) => + { + if (e.PreviousSize.Width == 0 || e.PreviousSize.Height == 0 || Double.IsNaN(e.PreviousSize.Width) || Double.IsNaN(e.PreviousSize.Height)) + UpdateBitmap(); + }; + + IsTabStop = false; + } + + #endregion + + #region Private methods + + private void OnPaletteChanged() + { + UpdateBitmap(); + } + + private void UpdateBitmap() + { + if (Width == 0 || Double.IsNaN(Width)) + { + image.Source = null; + return; + } + if (Palette == null) + { + image.Source = null; + return; + } + + int width = (int)Width; + int height = (int)image.Height; + WriteableBitmap bmp2 = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null); + WriteableBitmap bmp = bmp2.Clone(); + bmp.Lock(); + unsafe + { + byte* pixels = (byte*)bmp.BackBuffer; + int stride = bmp.BackBufferStride; + int pixelWidth = bmp.PixelWidth; + double min = Palette.Range.Min; + double coeff = (Palette.Range.Max - min) / bmp.PixelWidth; + for (int i = 0; i < pixelWidth; i++) + { + double ratio = i * coeff + min; + Color color = Palette.GetColor(i * coeff + min); + for (int j = 0; j < height; j++) + { + pixels[(i << 2) + 3 + j * stride] = color.A; + pixels[(i << 2) + 2 + j * stride] = color.R; + pixels[(i << 2) + 1 + j * stride] = color.G; + pixels[(i << 2) + j * stride] = color.B; + } + } + } + bmp.Unlock(); + image.Source = bmp; + } + + private static void OnRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PaletteControl control = (PaletteControl)d; + control.OnRangeChanged(); + } + + private void OnRangeChanged() + { + axis.Range = Range; + } + + #endregion + + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/PlotBase.cs b/InteractiveDataDisplay_OneDll/src/PlotBase.cs new file mode 100644 index 000000000..e4180dd5b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/PlotBase.cs @@ -0,0 +1,947 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Collections.Generic; +using System.Linq; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// Represents a base for all elements that perform graphic plotting. Handles coordinate + /// transformations and manages composition of plots + public abstract class PlotBase : Panel + { + #region Fields + + private double scaleXField; + private double scaleYField; + private double offsetXField; + private double offsetYField; + + private bool isInternalChange = false; + + private DataRect actualPlotRect = new DataRect(0, 0, 1, 1); + + #endregion + + #region Properties + + /// Gets or sets padding - distance in screen units from each side of border to edges of plot bounding rectangle. + /// Effective padding for composition of plots is computed as maximum of all paddings. + [Category("InteractiveDataDisplay")] + public Thickness Padding + { + get { return (Thickness)GetValue(PaddingProperty); } + set { SetValue(PaddingProperty, value); } + } + + /// + /// Desired ratio of horizontal scale to vertical scale. Values less than or equal to zero + /// represent unspecified aspect ratio. + /// + [Category("InteractiveDataDisplay")] + public double AspectRatio + { + get { return (double)GetValue(AspectRatioProperty); } + set { SetValue(AspectRatioProperty, value); } + } + + /// + /// Gets or sets auto fit mode. property of all + /// plots in compositions are updated instantly to have same value. + /// + [Category("InteractiveDataDisplay")] + public bool IsAutoFitEnabled + { + get { return (bool)GetValue(IsAutoFitEnabledProperty); } + set { SetValue(IsAutoFitEnabledProperty, value); } + } + + /// Gets or sets desired plot width in plot coordinates. property of all + /// plots in compositions are updated instantly to have same value. Settings this property + /// turns off auto fit mode. + [Category("InteractiveDataDisplay")] + public double PlotWidth + { + get { return (double)GetValue(PlotWidthProperty); } + set { SetValue(PlotWidthProperty, value); } + } + + /// Gets or sets desired plot height in plot coordinates. property of all + /// plots in compositions are updated instantly to have same value. Settings this property + /// turns off auto fit mode. + [Category("InteractiveDataDisplay")] + public double PlotHeight + { + get { return (double)GetValue(PlotHeightProperty); } + set { SetValue(PlotHeightProperty, value); } + } + + /// Gets or sets desired minimal visible horizontal coordinate (in plot coordinate system). + /// property of all + /// plots in compositions are updated instantly to have same value. Settings this property + /// turns off auto fit mode. + [Category("InteractiveDataDisplay")] + public double PlotOriginX + { + get { return (double)GetValue(PlotOriginXProperty); } + set { SetValue(PlotOriginXProperty, value); } + } + + /// Gets or sets desired minimal visible vertical coordinate (in plot coordinate system). + /// property of all + /// plots in compositions are updated instantly to have same value. Settings this property + /// turns off auto fit mode. + [Category("InteractiveDataDisplay")] + public double PlotOriginY + { + get { return (double)GetValue(PlotOriginYProperty); } + set { SetValue(PlotOriginYProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsXAxisReversedProperty = + DependencyProperty.Register("IsXAxisReversed", typeof(bool), typeof(PlotBase), new PropertyMetadata(false)); + + /// + /// Gets or sets a flag indicating whether the x-axis is reversed or not. + /// + [Category("InteractiveDataDisplay")] + public bool IsXAxisReversed + { + get { return (bool)GetValue(IsXAxisReversedProperty); } + set { SetValue(IsXAxisReversedProperty, value); } + } + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsYAxisReversedProperty = + DependencyProperty.Register("IsYAxisReversed", typeof(bool), typeof(PlotBase), new PropertyMetadata(false)); + + /// + /// Gets or sets a flag indicating whether the y-axis is reversed or not. + /// + [Category("InteractiveDataDisplay")] + public bool IsYAxisReversed + { + get { return (bool)GetValue(IsYAxisReversedProperty); } + set { SetValue(IsYAxisReversedProperty, value); } + } + + /// Gets or sets transform from user data to horizontal plot coordinate. + /// By default transform is + /// + [Category("InteractiveDataDisplay")] + public DataTransform XDataTransform + { + get { return (DataTransform)GetValue(XDataTransformProperty); } + set { SetValue(XDataTransformProperty, value); } + } + + /// Gets or sets transform from user data to vertical plot coordinate. + /// By default transform is + /// + [Category("InteractiveDataDisplay")] + public DataTransform YDataTransform + { + get { return (DataTransform)GetValue(YDataTransformProperty); } + set { SetValue(YDataTransformProperty, value); } + } + + /// + /// Gets or Sets horizontal scale parameter for plot transform + /// + public double ScaleX + { + get + { + return IsMaster ? scaleXField : masterField.ScaleX; + } + protected set + { + scaleXField = value; + } + } + + /// + /// Gets or sets Horizontal offset for plot transform + /// + public double OffsetX + { + get + { + return IsMaster ? offsetXField : masterField.OffsetX; + } + protected set + { + offsetXField = value; + } + } + + /// + /// Gets or Sets horizontal scale parameter for plot transform + /// + public double ScaleY + { + get + { + return IsMaster ? scaleYField : masterField.ScaleY; + } + protected set + { + scaleYField = value; + } + } + + /// + /// Gets or sets Horizontal offset for plot transform + /// + public double OffsetY + { + get + { + return IsMaster ? offsetYField : masterField.OffsetY; + } + protected set + { + offsetYField = value; + } + } + + /// Gets actual plot rectangle which is exactly corresponds to visible part of plot + public DataRect ActualPlotRect + { + get { return IsMaster ? actualPlotRect : masterField.ActualPlotRect; } + private set { actualPlotRect = value; } + } + + /// Gets desired plot rectangle. Actual plot rectangle may be larger because of aspect ratio + /// constraint + public DataRect PlotRect + { + get { return new DataRect(PlotOriginX, PlotOriginY, PlotOriginX + PlotWidth, PlotOriginY + PlotHeight); } + } + + private bool IsInternalChange + { + get { return IsMaster ? isInternalChange : masterField.IsInternalChange; } + set + { + if (IsMaster) + isInternalChange = value; + else + masterField.IsInternalChange = value; + } + } + + + + #endregion + + /// + /// Initializes new instance of class + /// + protected PlotBase() + { + XDataTransform = new IdentityDataTransform(); + YDataTransform = new IdentityDataTransform(); + masterField = this; + Loaded += PlotBaseLoaded; + Unloaded += PlotBaseUnloaded; + } + + + #region dependants tree + + private List dependantsField = new List(); + /// + /// Master PlotBase of current PlotBase + /// + protected PlotBase masterField; + + private void AddDependant(PlotBase dependant) + { + if (!dependantsField.Contains(dependant)) + { + dependantsField.Add(dependant); + EnumAll(plot => plot.NotifyCompositionChange()); + } + } + + private void RemoveDependant(PlotBase dependant) + { + if (dependantsField.Contains(dependant)) + { + dependantsField.Remove(dependant); + EnumAll(plot => plot.NotifyCompositionChange()); + } + } + + private void connectMaster() + { + if (!forceMasterField) + { + var element = VisualTreeHelper.GetParent(this); + while (element != null && !(element is PlotBase)) + element = VisualTreeHelper.GetParent(element); + if (element != null) + { + var newMaster = (PlotBase)element; + + // Take plot-related properties from new master plot + IsInternalChange = true; + PlotOriginX = newMaster.PlotOriginX; + PlotOriginY = newMaster.PlotOriginY; + PlotWidth = newMaster.PlotWidth; + PlotHeight = newMaster.PlotHeight; + IsAutoFitEnabled = newMaster.IsAutoFitEnabled; + IsInternalChange = false; + + masterField = newMaster; + masterField.AddDependant(this); + InvalidateBounds(); + } + } + } + + private void disconnectMaster() + { + if (masterField != this) + { + masterField.RemoveDependant(this); + InvalidateBounds(); + masterField = this; + } + } + + private void EnumLeaves(Action action) + { + action(this); + foreach (var item in dependantsField) + { + item.EnumLeaves(action); + } + } + + private void EnumAll(Action action) + { + if (masterField != this) + { + if(masterField != null) + masterField.EnumAll(action); + } + else + EnumLeaves(action); + } + + bool forceMasterField = false; + + /// + /// Disables or enables the connection of the panel to its parent context. + /// + /// + /// The default value is false which allows automatically connecting to a parent master and switching to a dependant mode. + /// If set to true the connection is disabled and the pannel is always in a master mode. + /// + public bool ForceMaster + { + get { return forceMasterField; } + set + { + forceMasterField = value; + if (forceMasterField) + disconnectMaster(); + else + connectMaster(); + } + } + + /// + /// Gets a value indicating whether the panel is a master mode or in a dependant mode. + /// + public bool IsMaster { get { return masterField == this; } } + + private IEnumerable SelfAndAllDependantPlots + { + get + { + IEnumerable result = new PlotBase[] { this }; + foreach (var item in dependantsField) + { + result = result.Concat(item.SelfAndAllDependantPlots); + } + return result; + } + } + + /// + /// Gets collections of all related plots of current in composition + /// + public IEnumerable RelatedPlots + { + get + { + if (masterField != this) + return masterField.RelatedPlots; + else + { + IEnumerable result = new PlotBase[] { this }; + foreach (var item in dependantsField) + { + result = result.Concat(item.SelfAndAllDependantPlots); + } + return result; + } + } + } + + + /// + /// Raises plots composition changed event + /// + protected void NotifyCompositionChange() + { + //compositionChange.OnNext(new PlotCompositionChange()); + } + + /// + /// Gets event which occures when plots composition is changed + /// + public IObservable CompositionChange + { + get { return null; } + } + + #endregion + + #region Dependency Properties + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty XDataTransformProperty = + DependencyProperty.Register("XDataTransform", typeof(DataTransform), typeof(PlotBase), new PropertyMetadata(null, + (o, e) => + { + Plot plot = o as Plot; + if (plot != null) + { + plot.OnXDataTransformChanged(e); + } + })); + + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty YDataTransformProperty = + DependencyProperty.Register("YDataTransform", typeof(DataTransform), typeof(PlotBase), new PropertyMetadata(null, + (o, e) => + { + Plot plot = o as Plot; + if (plot != null) + { + plot.OnYDataTransformChanged(e); + } + })); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PaddingProperty = + DependencyProperty.Register("Padding", typeof(Thickness), typeof(PlotBase), new PropertyMetadata(new Thickness(0.0), + (o, e) => + { + PlotBase plotBase = (PlotBase)o; + if (plotBase != null) + { + if (!plotBase.IsMaster) + { + plotBase.masterField.InvalidateMeasure(); + } + else + plotBase.InvalidateMeasure(); + } + })); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty AspectRatioProperty = + DependencyProperty.Register("AspectRatio", typeof(double), typeof(PlotBase), new PropertyMetadata(0.0, + (o, e) => + { + PlotBase plotBase = (PlotBase)o; + if (plotBase != null) + { + plotBase.InvalidateMeasure(); + if (!plotBase.IsMaster) + { + plotBase.masterField.InvalidateMeasure(); + } + } + })); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty IsAutoFitEnabledProperty = + DependencyProperty.Register("IsAutoFitEnabled", typeof(bool), typeof(PlotBase), new PropertyMetadata(true, + (o, e) => + { + PlotBase plotBase = (PlotBase)o; + if (plotBase != null) + { + plotBase.OnIsAutoFitEnabledChanged(e); + } + })); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotWidthProperty = + DependencyProperty.Register("PlotWidth", typeof(double), typeof(PlotBase), new PropertyMetadata(1.0, + (o, e) => + { + PlotBase plotBase = (PlotBase)o; + if (plotBase != null) + { + plotBase.OnPlotWidthChanged(e); + } + })); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotHeightProperty = + DependencyProperty.Register("PlotHeight", typeof(double), typeof(PlotBase), new PropertyMetadata(1.0, + (o, e) => + { + PlotBase plotBase = (PlotBase)o; + if (plotBase != null) + { + plotBase.OnPlotHeightChanged(e); + } + })); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotOriginXProperty = + DependencyProperty.Register("PlotOriginX", typeof(double), typeof(PlotBase), new PropertyMetadata(0.0, + (o, e) => + { + PlotBase plotBase = (PlotBase)o; + if (plotBase != null) + { + plotBase.OnPlotOriginXChanged(e); + } + })); + + /// + /// Identifies dependency property + /// + public static readonly DependencyProperty PlotOriginYProperty = + DependencyProperty.Register("PlotOriginY", typeof(double), typeof(PlotBase), new PropertyMetadata(0.0, + (o, e) => + { + PlotBase plotBase = (PlotBase)o; + if (plotBase != null) + { + plotBase.OnPlotOriginYChanged(e); + } + })); + + /// Enables or disables clipping of graphic elements that are outside plotting area + public bool ClipToBounds + { + get { return (bool)GetValue(ClipToBoundsProperty); } + set { SetValue(ClipToBoundsProperty, value); } + } + + /// Identifies dependency property + public static readonly DependencyProperty ClipToBoundsProperty = + DependencyProperty.Register("ClipToBounds", typeof(bool), typeof(PlotBase), new PropertyMetadata(true, + (s, a) => ((PlotBase)s).OnClipToBoundsChanged(a))); + + /// + /// Occurs when property changed + /// + /// PropertyChanged parameters + protected virtual void OnClipToBoundsChanged(DependencyPropertyChangedEventArgs args) + { + InvalidateMeasure(); + } + + #endregion + + #region Methods + + /// + /// Handler for property changes + /// + protected virtual void OnPlotOriginXChanged(DependencyPropertyChangedEventArgs e) + { + if (!IsInternalChange) + { + IsInternalChange = true; + EnumAll(p => + { + p.PlotOriginX = (double)e.NewValue; + p.IsAutoFitEnabled = false; + p.InvalidateMeasure(); + }); + IsInternalChange = false; + } + } + + /// + /// Handler for property changes + /// + protected virtual void OnPlotOriginYChanged(DependencyPropertyChangedEventArgs e) + { + if (!IsInternalChange) + { + IsInternalChange = true; + EnumAll(p => + { + p.PlotOriginY = (double)e.NewValue; + p.IsAutoFitEnabled = false; + p.InvalidateMeasure(); + }); + IsInternalChange = false; + } + } + + /// + /// Handler for property changes + /// + protected virtual void OnPlotWidthChanged(DependencyPropertyChangedEventArgs e) + { + if (!IsInternalChange) + { + IsInternalChange = true; + EnumAll(p => + { + p.PlotWidth = (double)e.NewValue; + p.IsAutoFitEnabled = false; + p.InvalidateMeasure(); + }); + IsInternalChange = false; + } + } + + /// + /// Handler for property changes + /// + protected virtual void OnPlotHeightChanged(DependencyPropertyChangedEventArgs e) + { + if (!IsInternalChange) + { + IsInternalChange = true; + EnumAll(p => + { + p.PlotHeight = (double)e.NewValue; + p.IsAutoFitEnabled = false; + p.InvalidateMeasure(); + }); + IsInternalChange = false; + } + } + + /// + /// Handler for property changes + /// + protected virtual void OnXDataTransformChanged(DependencyPropertyChangedEventArgs e) + { + InvalidateBounds(); + } + + /// + /// Handler for property changes + /// + protected virtual void OnYDataTransformChanged(DependencyPropertyChangedEventArgs e) + { + InvalidateBounds(); + } + + private void OnIsAutoFitEnabledChanged(DependencyPropertyChangedEventArgs e) + { + if (!IsInternalChange) + { + IsInternalChange = true; + EnumAll(p => + { + p.IsAutoFitEnabled = (bool)e.NewValue; + if (p.IsAutoFitEnabled) + p.InvalidateMeasure(); + }); + IsInternalChange = false; + } + } + + /// + /// Gets the range of {x,y} plot coordinates that correspond to all elements produced by the plot. + /// + /// The structure that holds the ranges of {x} and {y} plot coordinates or the value. + protected virtual DataRect ComputeBounds() + { + return DataRect.Empty; + } + + /// + /// Invalidates effective plot coordinate ranges. This usually schedules recalculation of plot layout. + /// + public virtual void InvalidateBounds() + { + EnumAll(p => p.InvalidateMeasure()); + } + + /// + /// A common padding used by all graphing elements in the dependant tree. + /// + /// This is computed by traversing the master-dependant tree and computing the maximum mapping. + public Thickness ActualPadding { get { return IsMaster ? AggregatePadding() : masterField.ActualPadding; } } + + private double GetEffectiveAspectRatio() + { + double result = AspectRatio; + for (int i = 0; i < dependantsField.Count && result <= 0; i++) + result = dependantsField[i].GetEffectiveAspectRatio(); + return result; + } + + /// + /// Computes padding of current instance + /// + /// Padding of current instance + protected virtual Thickness ComputePadding() + { + return Padding; + } + + /// + /// Computes padding with maximum values for each side from padding of current instance and of all child instances + /// + /// Padding with maximum values for each side from padding of current instance and of all child instances + protected virtual Thickness AggregatePadding() + { + Thickness result = ComputePadding(); + foreach (var item in dependantsField) + { + Thickness their = item.AggregatePadding(); + if (their.Left > result.Left) result.Left = their.Left; + if (their.Right > result.Right) result.Right = their.Right; + if (their.Top > result.Top) result.Top = their.Top; + if (their.Bottom > result.Bottom) result.Bottom = their.Bottom; + } + return result; + } + + /// + /// Computes minimal plot rectangle which contains plot rectangles of current instance and of all child instances + /// + /// Minimal plot rectangle which contains plot rectangles of current instance and of all child instances + protected DataRect AggregateBounds() + { + DataRect result = ComputeBounds(); + foreach (var item in dependantsField) + result.Surround(item.AggregateBounds()); + + return result; + } + + /// This event is fired when transform from plot to screen coordinates changes. + public event EventHandler PlotTransformChanged; + + /// + /// For a given range of {x,y} plot coordinates and a given screen size taking into account effective aspect ratio and effective padding computes navigation transform. + /// + /// The range of {x,y} plot coordinates that must fit into the screen. + /// The width and height of the screen that must fit plotBounds. + internal void Fit(DataRect plotBounds, Size screenSize) + { + var padding = AggregatePadding(); + var screenWidth = Math.Max(1.0, screenSize.Width - padding.Left - padding.Right); + var screenHeight = Math.Max(1.0, screenSize.Height - padding.Top - padding.Bottom); + var plotWidth = plotBounds.X.IsEmpty ? 0.0 : plotBounds.X.Max - plotBounds.X.Min; + if (plotWidth <= 0) plotWidth = 1.0; + var plotHeight = plotBounds.Y.IsEmpty ? 0.0 : plotBounds.Y.Max - plotBounds.Y.Min; + if (plotHeight <= 0) plotHeight = 1.0; + + ScaleX = screenWidth / plotWidth; + ScaleY = screenHeight / plotHeight; + + var aspect = GetEffectiveAspectRatio(); + if (aspect > 0) + { + if (aspect * ScaleY < ScaleX) + ScaleX = aspect * ScaleY; + else + ScaleY = ScaleX / aspect; + } + + OffsetX = plotBounds.X.IsEmpty ? 0.0 : ((plotBounds.X.Min + plotBounds.X.Max) * ScaleX - screenWidth) * 0.5 - padding.Left; + OffsetY = plotBounds.Y.IsEmpty ? screenSize.Height : ((plotBounds.Y.Min + plotBounds.Y.Max) * ScaleY + screenHeight) * 0.5 + padding.Top; + + ActualPlotRect = new DataRect( + XFromLeft(screenSize.Width > padding.Left + padding.Right ? 0 : screenSize.Width * 0.5 - 0.5), + YFromTop(screenSize.Height > padding.Top + padding.Bottom ? screenSize.Height : screenSize.Height * 0.5 + 0.5), + XFromLeft(screenSize.Width > padding.Left + padding.Right ? screenSize.Width : screenSize.Width * 0.5 + 0.5), + YFromTop(screenSize.Height > padding.Top + padding.Bottom ? 0 : screenSize.Height * 0.5 - 0.5)); + + EnumAll(p => + { + if (p.PlotTransformChanged != null) + p.PlotTransformChanged(this, EventArgs.Empty); + }); + } + + /// + /// Sets plot rectangle for current instance of + /// + /// plot rectangle value that would be set for current instance of + /// Identifies that it is internal call + protected void SetPlotRect(DataRect plotRect, bool fromAutoFit) + { + IsInternalChange = true; + + EnumAll(p => + { + p.PlotOriginX = plotRect.XMin; + p.PlotOriginY = plotRect.YMin; + p.PlotWidth = plotRect.Width; + p.PlotHeight = plotRect.Height; + if (!fromAutoFit) + { + p.IsAutoFitEnabled = false; + p.InvalidateMeasure(); + } + }); + + IsInternalChange = false; + } + + /// + /// Sets plot rectangle for current instance of + /// + /// plot rectangle value that would be set for current instance of + public void SetPlotRect(DataRect plotRect) + { + SetPlotRect(plotRect, false); + } + + /// + /// Performs horizontal transform from plot coordinates to screen coordinates + /// + /// Value in plot coordinates + /// Value in screen coordinates + public double LeftFromX(double x) + { + return ScaleX > 0 ? x * ScaleX - OffsetX : x; + } + + /// + /// Performs vertical transform from plot coordinates to screen coordinates + /// + /// Value in plot coordinates + /// Value in screen coordinates + public double TopFromY(double y) + { + return ScaleY > 0 ? OffsetY - y * ScaleY : OffsetY - y; + } + + /// + /// Horizontal transform from screen coordinates to plot coordinates + /// + /// Value in screen coordinates + /// Value in plot coordinates + public double XFromLeft(double left) + { + return ScaleX > 0 ? (left + OffsetX) / ScaleX : left; + } + + /// + /// Vertical transform from screen coordinates to plot coordinates + /// + /// Value in screen coordinates + /// Value in plot coordinates + public double YFromTop(double top) + { + return ScaleY > 0 ? (OffsetY - top) / ScaleY : OffsetY - top; + } + + /// + /// Finds master plot for specifyied element + /// + /// Element, which mater plot should be found + /// Master Plot for specified element + public static PlotBase FindMaster(DependencyObject element) + { + var result = VisualTreeHelper.GetParent(element); + + while (result != null && !(result is PlotBase)) + result = VisualTreeHelper.GetParent(result); + + return result as PlotBase; + } + + /// + /// Occurs when PlotBase is loaded + /// + protected virtual void PlotBaseLoaded(object sender, RoutedEventArgs e) + { + connectMaster(); + } + + /// + /// Occurs when PlotBase is unloaded + /// + protected virtual void PlotBaseUnloaded(object sender, RoutedEventArgs e) + { + disconnectMaster(); + } + + /// + /// Performs measure algorithm if current instance of is master + /// + /// Availible size for measure + /// Desired size for current plot + protected Size PerformAsMaster(Size availableSize) + { + if (double.IsNaN(availableSize.Width) + || double.IsNaN(availableSize.Height) + || double.IsInfinity(availableSize.Width) + || double.IsInfinity(availableSize.Height)) + availableSize = new Size(100, 100); + if (IsMaster) + { + DataRect desiredRect; + if (IsAutoFitEnabled) + { + desiredRect = AggregateBounds(); + if (desiredRect.IsEmpty) + desiredRect = new DataRect(0, 0, 1, 1); + + SetPlotRect(desiredRect, true); + } + else // resize + desiredRect = PlotRect; + Fit(desiredRect, availableSize); + } + return availableSize; + } + + #endregion + + + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/PlotCompositionChange.cs b/InteractiveDataDisplay_OneDll/src/PlotCompositionChange.cs new file mode 100644 index 000000000..d0be0dce2 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/PlotCompositionChange.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +namespace InteractiveDataDisplay.WPF +{ + /// Identifies event when collection of related plots in composition change + public struct PlotCompositionChange + { + private PlotBase master; + + /// + /// Initializes a new instance of + /// + /// Master of composition that change + public PlotCompositionChange(PlotBase master) + { + this.master = master; + } + + /// Gets master plot of composition that change + public PlotBase Master + { + get { return master; } + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// True if the specified is equal to the current , false otherwise. + public override bool Equals(object obj) + { + PlotCompositionChange operand = (PlotCompositionChange)obj; + return master == operand.master; + } + + /// + /// Returns a value that indicates whether two specified values are equal. + /// + /// The first value to compare. + /// The second value to compare. + /// True if values are equal, false otherwise. + public static bool operator ==(PlotCompositionChange plotCompositionChange1, PlotCompositionChange plotCompositionChange2) + { + return plotCompositionChange1.Equals(plotCompositionChange2); + } + + /// + /// Returns a value that indicates whether two specified values are not equal. + /// + /// The first value to compare. + /// The second value to compare. + /// True if values are not equal, false otherwise. + public static bool operator !=(PlotCompositionChange plotCompositionChange1, PlotCompositionChange plotCompositionChange2) + { + return !plotCompositionChange1.Equals(plotCompositionChange2); + } + + /// + /// Returns the hash code for this instance. + /// + /// The hash code for current instance + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Base/BackgroundBitmapRenderer.cs b/InteractiveDataDisplay_OneDll/src/Plots/Base/BackgroundBitmapRenderer.cs new file mode 100644 index 000000000..cb532b05e --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Base/BackgroundBitmapRenderer.cs @@ -0,0 +1,332 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Collections.Generic; +using System.Threading; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Base class for renderers, which can prepare its render results in a separate thread. + /// + public abstract class BackgroundBitmapRenderer : Plot + { + private Image outputImage; + + /// Cartesian coordinates of image currently on the screen. + private DataRect imageCartesianRect; + + /// Size of image currently on the screen. + private Size imageSize; + + private int maxTasks; + private Queue tasks = new Queue(); + private delegate void RenderFunc(RenderResult r, RenderTaskState state); + private List runningTasks; + + private long nextID = 0; + + /// + /// Initializes new instance of class, performing all basic preparings for inheriting classes. + /// + protected BackgroundBitmapRenderer() + { + maxTasks = Math.Max(1, System.Environment.ProcessorCount - 1); + runningTasks = new List(); + + outputImage = new Image(); + outputImage.Stretch = Stretch.None; + outputImage.VerticalAlignment = System.Windows.VerticalAlignment.Center; + outputImage.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; + + Children.Add(outputImage); + Unloaded += BackgroundBitmapRendererUnloaded; + } + + void BackgroundBitmapRendererUnloaded(object sender, RoutedEventArgs e) + { + CancelAll(); + } + + private Size prevSize = new Size(Double.NaN, Double.NaN); + private double prevScaleX = Double.NaN, prevScaleY = Double.NaN, + prevOffsetX = Double.NaN, prevOffsetY = Double.NaN; + + /// + /// Measures the size in layout required for child elements and determines a size for parent. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + availableSize = base.MeasureOverride(availableSize); + + outputImage.Measure(availableSize); + + // From resize or navigation? + if (prevSize != availableSize || + prevOffsetX != masterField.OffsetX || prevOffsetY != masterField.OffsetY || + prevScaleX != masterField.ScaleX || prevScaleY != masterField.ScaleY) + { + prevSize = availableSize; + prevOffsetX = masterField.OffsetX; + prevOffsetY = masterField.OffsetY; + prevScaleX = masterField.ScaleX; + prevScaleY = masterField.ScaleY; + + CancelAll(); + + if (imageSize.Width > 0 && imageSize.Height > 0) + { + var newLT = new Point(LeftFromX(imageCartesianRect.XMin), TopFromY(imageCartesianRect.YMax)); + var newRB = new Point(LeftFromX(imageCartesianRect.XMax), TopFromY(imageCartesianRect.YMin)); + Canvas.SetLeft(outputImage, newLT.X); + Canvas.SetTop(outputImage, newLT.Y); + outputImage.RenderTransform = new ScaleTransform + { + ScaleX = (newRB.X - newLT.X) / imageSize.Width, + ScaleY = (newRB.Y - newLT.Y) / imageSize.Height + }; + } + QueueRenderTask(); + } + + return availableSize; + } + + private void EnqueueTask(long id/*Func task*/) + { + if (runningTasks.Count < maxTasks) + { + Size screenSize = new Size(Math.Abs(LeftFromX(ActualPlotRect.XMax) - LeftFromX(ActualPlotRect.XMin)), Math.Abs(TopFromY(ActualPlotRect.YMax) - TopFromY(ActualPlotRect.YMin))); + RenderTaskState state = new RenderTaskState(ActualPlotRect, screenSize); + state.Id = id; + state.Bounds = ComputeBounds(); + runningTasks.Add(state); + + if (!DesignerProperties.GetIsInDesignMode(this)) + { + ThreadPool.QueueUserWorkItem(s => + { + var rr = RenderFrame((RenderTaskState)s); + Dispatcher.BeginInvoke(new RenderFunc(OnTaskCompleted), rr, s); + + }, state); + } + else + { + var rr = RenderFrame(state); + OnTaskCompleted(rr, state); + } + } + else + tasks.Enqueue(id); + } + + /// + /// Renders frame and returns it as a render result. + /// + /// Render task state for rendering frame. + /// Render result of rendered frame. + protected virtual RenderResult RenderFrame(RenderTaskState state) + { + //if (!state.IsCancelled) + // return new RenderResult(HeatMap.BuildHeatMap(state.Transform.ScreenRect, new DataRect(state.Transform.ViewportRect), DataSource.X, DataSource.Y, DataSource.Data, 0)); + //else + return null; + } + + /// Creates new render task and puts it to queue. + /// Async operation ID. + protected long QueueRenderTask() + { + long id = nextID++; + EnqueueTask(id); + return id; + } + + private void OnTaskCompleted(RenderResult r, RenderTaskState state) + { + if (r != null && !state.IsCanceled) + { + + WriteableBitmap wr = new WriteableBitmap((int)r.Output.Width, (int)r.Output.Height, 96, 96, PixelFormats.Bgra32, null); + // Calculate the number of bytes per pixel. + int bytesPerPixel = (wr.Format.BitsPerPixel + 7) / 8; + // Stride is bytes per pixel times the number of pixels. + // Stride is the byte width of a single rectangle row. + int stride = wr.PixelWidth * bytesPerPixel; + wr.WritePixels(new Int32Rect(0, 0, wr.PixelWidth, wr.PixelHeight), r.Image, stride, 0); + + + outputImage.Source = wr; + Canvas.SetLeft(outputImage, r.Output.Left); + Canvas.SetTop(outputImage, r.Output.Top); + imageCartesianRect = r.Visible; + imageSize = new Size(r.Output.Width, r.Output.Height); + outputImage.RenderTransform = null; + } + + RaiseTaskCompletion(state.Id); + + runningTasks.Remove(state); + + while (tasks.Count > 1) + { + long id = tasks.Dequeue(); + RaiseTaskCompletion(id); + } + if (tasks.Count > 0 && runningTasks.Count < maxTasks) + { + EnqueueTask(tasks.Dequeue()); + } + + InvalidateMeasure(); + } + + /// + /// Cancel all tasks, which are in quere. + /// + public void CancelAll() + { + foreach (var s in runningTasks) + s.Stop(); + } + + /// + /// Gets event which is occured when render task is finished + /// + public IObservable RenderCompletion + { + get { return null; } + } + + /// + /// Raises RenderCompletion event when task with the specified id is finished + /// + /// ID of finished task + protected void RaiseTaskCompletion(long id) + { + //renderCompletion.OnNext(new RenderCompletion { TaskId = id }); + } + } + + /// + /// Represents contents of render result. + /// + public class RenderResult + { + private DataRect visible; + private Rect output; + private int[] image; + + /// + /// Initializes new instance of RenderResult class form given parameters. + /// + /// Array of image pixels. + /// Visible rect for graph. + /// Image start offset. + /// Image width. + /// image height. + public RenderResult(int[] image, DataRect visible, Point offset, double width, double height) + { + this.visible = visible; + this.output = new Rect(offset, new Size(width, height)); + this.image = image; + } + + /// + /// Gets the current visible rect. + /// + public DataRect Visible + { + get { return visible; } + } + + /// + /// Gets an array of image pixels. + /// + public int[] Image + { + get { return image; } + } + + /// + /// Gets the image output rect. + /// + public Rect Output + { + get { return output; } + } + } + + /// This class holds all information about rendering request. + public class RenderTaskState + { + bool isCanceled = false; + + /// + /// Initializes new instance of RenderTaskState class from given coordinate tranform. + /// + public RenderTaskState(DataRect actualPlotRect, Size screenSize) + { + ScreenSize = screenSize; + ActualPlotRect = actualPlotRect; + } + + /// + /// Gets or sets the state Id. + /// + public long Id { get; set; } + + /// + /// Gets the screen size of the output image + /// + public Size ScreenSize { get; private set; } + + /// + /// Gets plot rectangle of the visible area + /// + public DataRect ActualPlotRect { get; private set; } + + /// + /// Gets or sets the current bounds. + /// + public DataRect Bounds { get; set; } + + /// + /// Gets a value indicating whether the task is cancelled or not. + /// + public bool IsCanceled + { + get { return isCanceled; } + } + + /// + /// Sets state as canceled. + /// + public void Stop() + { + isCanceled = true; + } + } + + /// + ///Contains reference to completed tasks. + /// + public class RenderCompletion + { + /// + ///Gets or sets the Id of render task. + /// + public long TaskId { get; set; } + } + +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Base/Plot.cs b/InteractiveDataDisplay_OneDll/src/Plots/Base/Plot.cs new file mode 100644 index 000000000..84c696851 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Base/Plot.cs @@ -0,0 +1,485 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF { + /// + /// Plot is a panel that supports positioning of child elements in plot coordinates by + /// providing attached properties X1,Y1,X2,Y2 and Points. + /// Plot panel automatically computes bounding rectangle for all child elements. + /// + [Description("Position child elements in plot coordinates")] + public class Plot : PlotBase + { + #region Attached Properties + + /// + /// Identifies the Plot.X1 attached property. + /// + public static readonly DependencyProperty X1Property = + DependencyProperty.RegisterAttached("X1", typeof(double), typeof(Plot), new PropertyMetadata(double.NaN, DataCoordinatePropertyChangedHandler)); + + /// + /// Sets the value of the Plot.X1 attached property for a given dependency object. + /// + /// The element to which the property value is written + /// Sets the Plot.X1 coordinate of the specified element + /// + /// Infinity values for Plot.X1 are handled is special way. + /// They do not participate in plot bounds computation, + /// “+Infinity” is translated to maximum visible value, “-Infinity” is translated to minimum visible coordinate. + /// + public static void SetX1(DependencyObject element, double value) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetValue(X1Property, value); + } + + /// + /// Returns the value of the Plot.X1 attached property for a given dependency object. + /// + /// The element from which the property value is read + /// The Plot.X1 coordinate of the specified element + /// + /// Default value of Plot.X1 property is Double.NaN. Element is horizontally arranged inside panel according + /// to values of X1 and X2 attached property. X1 and X2 doesn't have to be ordered. If X1 is not + /// specified (has NaN value) than Canvas.Left value is used. + /// + public static double GetX1(DependencyObject element) + { + if (element == null) + throw new ArgumentNullException("element"); + + return (double)element.GetValue(X1Property); + } + + /// + /// Identifies the Plot.X2 attached property. + /// + public static readonly DependencyProperty X2Property = + DependencyProperty.RegisterAttached("X2", typeof(double), typeof(Plot), new PropertyMetadata(double.NaN, DataCoordinatePropertyChangedHandler)); + + /// + /// Sets the value of the Plot.X2 attached property for a given dependency object. + /// + /// The element to which the property value is written + /// Sets the Plot.X2 coordinate of the specified element + /// + /// Infinity values for Plot.X2 are handled is special way. + /// They do not participate in plot bounds computation, + /// “+Infinity” is translated to maximum visible value, “-Infinity” is translated to minimum visible coordinate. + /// + public static void SetX2(DependencyObject element, double value) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetValue(X2Property, value); + } + + /// + /// Returns the value of the Plot.X2 attached property for a given dependency object. + /// + /// The element from which the property value is read + /// The Plot.X2 coordinate of the specified element + /// + /// Default value of Plot.X2 property is Double.NaN. + /// Element is horizontally arranged inside panel according + /// to values of X1 and X2 attached property. X1 and X2 doesn't have to be ordered. If X2 + /// is not specified (has NaN value) then Width property is used to define element arrangement. + /// + public static double GetX2(DependencyObject element) + { + if (element == null) + throw new ArgumentNullException("element"); + + return (double)element.GetValue(X2Property); + } + + /// + /// Identifies the Plot.Y1 attached property. + /// + public static readonly DependencyProperty Y1Property = + DependencyProperty.RegisterAttached("Y1", typeof(double), typeof(Plot), new PropertyMetadata(double.NaN, DataCoordinatePropertyChangedHandler)); + + /// + /// Sets the value of the Plot.Y1 attached property for a given dependency object. + /// + /// The element to which the property value is written + /// Sets the Plot.Y1 coordinate of the specified element + /// + /// Infinity values for Plot.Y1 are handled is special way. + /// They do not participate in plot bounds computation, + /// “+Infinity” is translated to maximum visible value, “-Infinity” is translated to minimum visible coordinate. + /// + public static void SetY1(DependencyObject element, double value) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetValue(Y1Property, value); + } + + /// + /// Returns the value of the Plot.Y1 attached property for a given dependency object. + /// + /// The element from which the property value is read + /// The Plot.Y1 coordinate of the specified element + /// + /// Default value of Plot.Y1 property is Double.NaN. Element is vertically arranged inside panel according + /// to values of Y1 and Y2 attached property. Y1 and Y2 doesn't have to be ordered. + /// If Y1 is not specifed (has NaN value) then Canvas.Top property is used to define element position. + /// + public static double GetY1(DependencyObject element) + { + if (element == null) + throw new ArgumentNullException("element"); + + return (double)element.GetValue(Y1Property); + } + + /// + /// Identifies the Plot.Y2 attached property. + /// + public static readonly DependencyProperty Y2Property = + DependencyProperty.RegisterAttached("Y2", typeof(double), typeof(Plot), new PropertyMetadata(double.NaN, DataCoordinatePropertyChangedHandler)); + + /// + /// Sets the value of the Plot.Y2 attached property for a given dependency object. + /// + /// The element to which the property value is written + /// Sets the Plot.Y2 coordinate of the specified element + /// + /// Infinity values for Plot.Y2 are handled is special way. + /// They do not participate in plot bounds computation, + /// “+Infinity” is translated to maximum visible value, “-Infinity” is translated to minimum visible coordinate. + /// + public static void SetY2(DependencyObject element, double value) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetValue(Y2Property, value); + } + + /// + /// Returns the value of the Plot.Y2 attached property for a given dependency object. + /// + /// The element from which the property value is read + /// The Plot.Y2 coordinate of the specified element + /// + /// Default value of Plot.Y2 property is Double.NaN. Element is vertically arranged inside panel according + /// to values of Y1 and Y2 attached property. Y1 and Y2 doesn't have to be ordered. + /// If Y2 is not specifed (has NaN value) then Height property is used to define element arrangement. + /// + public static double GetY2(DependencyObject element) + { + if (element == null) + throw new ArgumentNullException("element"); + + return (double)element.GetValue(Y2Property); + } + + /// + /// Identifies the Plot.Points attached property. + /// + public static readonly DependencyProperty PointsProperty = + DependencyProperty.RegisterAttached("Points", typeof(PointCollection), typeof(Plot), new PropertyMetadata(null, DataCoordinatePropertyChangedHandler)); + + /// + /// Sets the value of the Plot.Points attached property for a given dependency object. + /// + /// The element to which the property value is written + /// Sets the Plot.Points coollection of the specified element + public static void SetPoints(DependencyObject element, PointCollection value) + { + if (element == null) + throw new ArgumentNullException("element"); + + element.SetValue(PointsProperty, value); + } + + /// + /// Returns the value of the Plot.Points attached property for a given dependency object. + /// + /// The element from which the property value is read + /// The Plot.Points collection of the specified element + /// + /// Default value of Plot.Points property is null + /// + public static PointCollection GetPoints(DependencyObject element) + { + if (element == null) + throw new ArgumentNullException("element"); + + return (PointCollection)element.GetValue(PointsProperty); + } + + static void DataCoordinatePropertyChangedHandler(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + var parent = VisualTreeHelper.GetParent(obj); + if (parent is ContentPresenter) parent = VisualTreeHelper.GetParent(parent); + var plotBase = parent as PlotBase; + if (plotBase != null) + { + plotBase.InvalidateBounds(); + } + } + + #endregion + + #region Methods + + /// + /// Computes minimal plot rectangle, which contains all plot rectangles of child elements + /// + /// Minimal plot rectangle, which contains all plot rectangles of child elements + protected override DataRect ComputeBounds() + { + var localPlotRect = DataRect.Empty; + foreach (UIElement child in Children) + { + DependencyObject item = child ; + if (item is ContentPresenter && VisualTreeHelper.GetChildrenCount(item) == 1) + item = VisualTreeHelper.GetChild(item, 0); + double v; + v = GetX1(item); + if (!double.IsNaN(v) && !double.IsInfinity(v)) localPlotRect.XSurround(XDataTransform.DataToPlot(v)); + v = GetX2(item); + if (!double.IsNaN(v) && !double.IsInfinity(v)) localPlotRect.XSurround(XDataTransform.DataToPlot(v)); + v = GetY1(item); + if (!double.IsNaN(v) && !double.IsInfinity(v)) localPlotRect.YSurround(YDataTransform.DataToPlot(v)); + v = GetY2(item); + if (!double.IsNaN(v) && !double.IsInfinity(v)) localPlotRect.YSurround(YDataTransform.DataToPlot(v)); + var points = GetPoints(item); + if (points != null) + foreach (var point in points) + { + localPlotRect.XSurround(XDataTransform.DataToPlot(point.X)); + localPlotRect.YSurround(YDataTransform.DataToPlot(point.Y)); + } + } + return localPlotRect; + } + + /// + /// Positions child elements and determines a size for a Plot + /// + /// The final area within the parent that Plot should use to arrange itself and its children + /// The actual size used + protected override Size ArrangeOverride(Size finalSize) + { + ArrangeChildren(finalSize); + + if (ClipToBounds) + Clip = new RectangleGeometry { Rect = new Rect(new Point(0, 0), finalSize) }; + else + Clip = null; + + return finalSize; + } + + /// + /// Measures the size in layout required for child elements and determines a size for the Plot. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + availableSize = PerformAsMaster(availableSize); + MeasureChildren(availableSize); + return availableSize; + } + + internal void MeasureChildren(Size availableSize) + { + foreach (UIElement child in Children) + { + DependencyObject item = child; + if (item is ContentPresenter && VisualTreeHelper.GetChildrenCount(item) == 1) + item = VisualTreeHelper.GetChild(item, 0); + + var xy = GetPoints(item); + if (xy != null) + if (item is Polyline) + { + var line = (Polyline)item; + var points = new PointCollection(); + foreach (var point in xy) points.Add(new Point(LeftFromX(XDataTransform.DataToPlot(point.X)), TopFromY(YDataTransform.DataToPlot(point.Y)))); + line.Points = points; + } + else if (item is Polygon) + { + var p = (Polygon)item; + var points = new PointCollection(); + foreach (var point in xy) points.Add(new Point(LeftFromX(XDataTransform.DataToPlot(point.X)), TopFromY(YDataTransform.DataToPlot(point.Y)))); + p.Points = points; + } + if (item is Line) + { + var line = (Line)item; + double v; + v = GetX1(line); + if (!double.IsNaN(v)) + { + if (Double.IsNegativeInfinity(v)) + line.X1 = 0; + else if (Double.IsPositiveInfinity(v)) + line.X1 = availableSize.Width; + else + line.X1 = LeftFromX(XDataTransform.DataToPlot(v)); + } + v = GetX2(line); + if (!double.IsNaN(v)) + { + if (Double.IsNegativeInfinity(v)) + line.X2 = 0; + else if (Double.IsPositiveInfinity(v)) + line.X2 = availableSize.Width; + else + line.X2 = LeftFromX(XDataTransform.DataToPlot(v)); + } + v = GetY1(line); + if (!double.IsNaN(v)) + { + if (Double.IsNegativeInfinity(v)) + line.Y1 = availableSize.Height; + else if (Double.IsPositiveInfinity(v)) + line.Y1 = 0; + else + line.Y1 = TopFromY(YDataTransform.DataToPlot(v)); + } + v = GetY2(line); + if (!double.IsNaN(v)) + { + if (Double.IsNegativeInfinity(v)) + line.Y2 = availableSize.Height; + else if (Double.IsPositiveInfinity(v)) + line.Y2 = 0; + else + line.Y2 = TopFromY(YDataTransform.DataToPlot(v)); + } + } + child.Measure(availableSize); + } + } + + internal void ArrangeChildren(Size finalSize) + { + foreach (UIElement child in Children) + { + DependencyObject item = child; + if (item is ContentPresenter && VisualTreeHelper.GetChildrenCount(item) == 1) + item = VisualTreeHelper.GetChild(item, 0); + + if (item is Line || item is Polyline) + { + child.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + else + { + double x1 = GetX1(item); + double x2 = GetX2(item); + double y1 = GetY1(item); + double y2 = GetY2(item); + + Size desiredSize = Size.Empty; + if (double.IsNaN(x1) || double.IsNaN(x2) || double.IsNaN(y1) || double.IsNaN(y2)) + desiredSize = child.DesiredSize; + + double L = 0.0; + if (!double.IsNaN(x1)) + { + if (double.IsNegativeInfinity(x1)) + L = 0; + else if (double.IsPositiveInfinity(x1)) + L = finalSize.Width; + else + L = LeftFromX(XDataTransform.DataToPlot(x1)); // x1 is not Nan and Inf here + } + else + L = (double)item.GetValue(Canvas.LeftProperty); + + double W = 0.0; + var elem = item as FrameworkElement; + if (!double.IsNaN(x1) && !double.IsNaN(x2)) + { + double L2 = 0.0; + if (double.IsNegativeInfinity(x2)) + L2 = 0; + else if (double.IsPositiveInfinity(x2)) + L2 = desiredSize.Width; + else + L2 = LeftFromX(XDataTransform.DataToPlot(x2)); // x2 is not Nan and Inf here + + if (L2 >= L) W = L2 - L; + else + { + W = L - L2; + L = L2; + } + } + else if (elem != null || double.IsNaN(W = elem.Width) || double.IsInfinity(W)) + W = desiredSize.Width; + + double T = 0.0; + if (!double.IsNaN(y1)) + { + if (double.IsNegativeInfinity(y1)) + T = desiredSize.Height; + else if (double.IsPositiveInfinity(y1)) + T = 0; + else + T = TopFromY(YDataTransform.DataToPlot(y1)); // y1 is not Nan and Inf here + } + else + T = (double)item.GetValue(Canvas.TopProperty); + + double H = 0.0; + if (!double.IsNaN(y1) && !double.IsNaN(y2)) + { + double T2 = 0.0; + if (double.IsNegativeInfinity(y2)) + T2 = desiredSize.Height; + else if (double.IsPositiveInfinity(y2)) + T2 = desiredSize.Width; + else + T2 = TopFromY(YDataTransform.DataToPlot(y2)); // y2 is not Nan and Inf here + if (T2 >= T) H = T2 - T; + else + { + H = T - T2; + T = T2; + } + } + else if (elem != null || double.IsNaN(H = elem.Height) || double.IsInfinity(H)) + H = desiredSize.Height; + + if (Double.IsNaN(L) || Double.IsInfinity(L) || Double.IsNaN(W) || Double.IsInfinity(W)) // Horizontal data to screen transform fails + { + L = 0; + W = desiredSize.Width; + } + if (Double.IsNaN(T) || Double.IsInfinity(T) || Double.IsNaN(H) || Double.IsInfinity(H)) // Vertical data to screen transform fails + { + T = 0; + H = desiredSize.Height; + } + child.Arrange(new Rect(L, T, W, H)); + } + } + } + + #endregion + + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapBuilder.cs b/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapBuilder.cs new file mode 100644 index 000000000..eca47d58b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapBuilder.cs @@ -0,0 +1,480 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Media; +using System.Collections.Generic; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Helper class, which provides various methods for building heatmaps. + /// + public static class HeatmapBuilder + { + /// + /// Builds heatmap from specified data, grid, missing value, visibleRect and palette. + /// + /// 2D array with data to plot + /// Missing value. Heatmap will have transparent regions at missing value. + /// Palette to translate numeric values to colors + /// Min and max values in array + /// Heatmap rectangle screen coordinates + /// Plot rectaneg screen coordinates + static public int[] BuildHeatMap(Rect screen, DataRect rect, double[] x, double[] y, double[,] data, + double missingValue, IPalette palette, Range range) + { + if (data == null) + throw new ArgumentNullException("data"); + if (x == null) + throw new ArgumentNullException("x"); + if (y == null) + throw new ArgumentNullException("y"); + if (palette == null) + throw new ArgumentNullException("palette"); + + int pixelWidth = (int)screen.Width; + int pixelHeight = (int)screen.Height; + int[] pixels = new int[pixelWidth * pixelHeight]; + double doubleMissingValue = missingValue; + UInt32[] paletteColors = new uint[512]; + + if (!palette.IsNormalized) + range = palette.Range; + + for (int i = 0; i < 512; i++) + { + Color c; + if (palette.IsNormalized) + c = palette.GetColor((double)i / 511.0); + else + c = palette.GetColor(range.Min + i * (range.Max - range.Min) / 511.0); + + paletteColors[i] = (((uint)(c.A)) << 24) | (((uint)c.R) << 16) | (((uint)c.G) << 8) | c.B; + } + + int xdimRank = x.Length; + int ydimRank = y.Length; + + int xdataRank = data.GetLength(0); + int ydataRank = data.GetLength(1); + + double factor = (range.Max != range.Min) ? (1.0 / (range.Max - range.Min)) : 0.0; + + if (xdataRank == xdimRank && ydataRank == ydimRank) + { + double[,] v = null; + v = Filter(pixelWidth, pixelHeight, rect, x, y, data, doubleMissingValue); + if (palette.IsNormalized) + { + if (Double.IsNaN(doubleMissingValue)) + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = v[i, pixelHeight - j - 1]; + if (!Double.IsNaN(vv)) + { + double v01 = (vv - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + else + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = v[i, pixelHeight - j - 1]; + if (vv != doubleMissingValue) + { + double v01 = (vv - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + } + else // Palette is absolute + { + if (Double.IsNaN(doubleMissingValue)) + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = v[i, pixelHeight - j - 1]; + if (!Double.IsNaN(vv)) + { + double v01 = (Math.Max(range.Min, Math.Min(range.Max, vv)) - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + else + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = v[i, pixelHeight - j - 1]; + if (vv != doubleMissingValue) + { + double v01 = (Math.Max(range.Min, Math.Min(range.Max, vv)) - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + } + } + else if ((xdataRank + 1) == xdimRank && (ydataRank + 1) == ydimRank) + { + + // Prepare arrays + int[] xPixelDistrib; + int[] yPixelDistrib; + + xPixelDistrib = CreatePixelToDataMap(pixelWidth, Math.Max(x[0], rect.XMin), Math.Min(x[x.Length - 1], rect.XMax), x); + yPixelDistrib = CreatePixelToDataMap(pixelHeight, Math.Max(y[0], rect.YMin), Math.Min(y[y.Length - 1], rect.YMax), y); + + if (palette.IsNormalized) + { + if (Double.IsNaN(doubleMissingValue)) + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = data[xPixelDistrib[i], yPixelDistrib[pixelHeight - j - 1]]; + if (!Double.IsNaN(vv)) + { + double v01 = (vv - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + else + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = data[xPixelDistrib[i], yPixelDistrib[pixelHeight - j - 1]]; + if (vv != doubleMissingValue) + { + double v01 = (vv - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + } + else // Palette is absolute + { + if (Double.IsNaN(doubleMissingValue)) + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = data[xPixelDistrib[i], yPixelDistrib[pixelHeight - j - 1]]; + if (!Double.IsNaN(vv)) + { + double v01 = (Math.Max(range.Min, Math.Min(range.Max, vv)) - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + else + { + for (int j = 0, k = 0; j < pixelHeight; j++) + { + for (int i = 0; i < pixelWidth; i++, k++) + { + double vv = data[xPixelDistrib[i], yPixelDistrib[pixelHeight - j - 1]]; + if (vv != doubleMissingValue) + { + double v01 = (Math.Max(range.Min, Math.Min(range.Max, vv)) - range.Min) * factor; + pixels[k] = (int)paletteColors[((uint)(511 * v01)) & 0x1FF]; + } + } + } + } + } + } + else + { + throw new ArgumentException("Size of x,y and data arrays does not match conditions"); + } + + return pixels; + } + + + /// Prepares linear interpolation filter tables + /// Size of heatmap in pixels + /// Plot coordinate corresponding to left pixel + /// Plot coordinate corresponding to right pixel + /// Plot coordinates of data points + /// Indices in target array (pixels) + /// Indices in source array + /// Linear transform coefficient + /// Weights + /// + /// + /// Following code performs linear interpolation without taking missing values into account. + /// Names of variables correspond to parameter names + /// + /// + /// + /// + public static void PrepareFilterTables(int width, double minX, double maxX, double[] x, + out int[] pixels, out int[] indexes, out double[] alphas, out double[] weights) + { + if (x == null) + throw new ArgumentNullException("x"); + + int length = x.Length; + if (length <= 1) + throw new ArgumentException("Knots array should contain at least 2 knots"); + if (maxX > x[length - 1]) + maxX = x[length - 1]; + else if (minX < x[0]) + minX = x[0]; + + List p = new List(length); + List idx = new List(length); + List k = new List(length); + List w = new List(length); + + + int i = 0; + double delta = (maxX - minX) / width; + for (int pixel = 0; pixel < width; pixel++) + { + double pixmin = minX + pixel * delta; // Current pixel is segment [pixmin, pixmin + delta] + while (x[i + 1] <= pixmin) + i++; // No out of range here because of prerequisites + while (i < length - 1) + { + double min = Math.Max(pixmin, x[i]); + double max = Math.Min(pixmin + delta, x[i + 1]); + double center = (min + max) / 2; + idx.Add(i); + p.Add(pixel); + k.Add((x[i + 1] - center) / (x[i + 1] - x[i])); + w.Add((max - min) / delta); + if (x[i + 1] >= pixmin + delta) + break; + else + i++; + } + } + pixels = p.ToArray(); + indexes = idx.ToArray(); + alphas = k.ToArray(); + weights = w.ToArray(); + } + + /// Performs array resize and subset with linear interpolation + /// Result width + /// Result height + /// Range of coordinates for entire data + /// Coordinates of x points (length of array must be + /// Coordinates of y points (length of array must be + /// Source data array + /// Missing value or NaN if no missing value + /// Resulting array with size * + public static double[,] Filter(int width, int height, DataRect rect, + double[] x, double[] y, double[,] data, double missingValue) + { + if (data == null) + throw new ArgumentNullException("data"); + if (x == null) + throw new ArgumentNullException("x"); + if (y == null) + throw new ArgumentNullException("y"); + + // Check preconditions + if (x.Length != data.GetLength(0)) + throw new ArgumentException("Size of x and data arrays does not match"); + if (y.Length != data.GetLength(1)) + throw new ArgumentException("Size of y and data arrays does not match"); + + // Prepare filters + int[] hp, vp, hi, vi; + double[] ha, va, hw, vw; + PrepareFilterTables(width, rect.XMin, rect.XMax, x, out hp, out hi, out ha, out hw); + PrepareFilterTables(height, rect.YMin, rect.YMax, y, out vp, out vi, out va, out vw); + + // Prepare arrays + double[,] r = new double[width, height]; + bool hasMissingValue = !Double.IsNaN(missingValue); + double offset = 0; + if (hasMissingValue && missingValue == 0) + { + offset = -1; + for (int i = 0; i < width; i++) + for (int j = 0; j < height; j++) + r[i, j] = offset; + } + + // Do filtering + int hpLen = hp.Length; + int vpLen = vp.Length; + for (int i = 0; i < hpLen; i++) + { + int px = hp[i]; + int i0 = hi[i]; + + for (int j = 0; j < vpLen; j++) + { + int py = vp[j]; + if (hasMissingValue && r[px, py] == missingValue) + continue; + int j0 = vi[j]; + if (hasMissingValue && + (data[i0, j0] == missingValue || + data[i0 + 1, j0] == missingValue || + data[i0, j0 + 1] == missingValue || + data[i0 + 1, j0 + 1] == missingValue)) + r[px, py] = missingValue; + else + { + double v0 = ha[i] * data[i0, j0] + (1 - ha[i]) * data[i0 + 1, j0]; + double v1 = ha[i] * data[i0, j0 + 1] + (1 - ha[i]) * data[i0 + 1, j0 + 1]; + double v = va[j] * v0 + (1 - va[j]) * v1; + r[px, py] += v * hw[i] * vw[j]; + } + } + } + // Offset data if needed + if (offset != 0) + for (int i = 0; i < width; i++) + for (int j = 0; j < height; j++) + if (r[i, j] != missingValue) + r[i, j] -= offset; + + return r; + } + + /// Computes array of indices in data array for rendering heatmap with given width in colormap mode + /// Width of heatmap in pixels + /// Data value corresponding to left heatmap edge + /// Data value corresponding to right heatmap edge + /// Grid array. Note that and should be + /// inside grid range. Otherwise exception will occur. + /// Array of length where value of each element is index in data + /// array + private static int[] CreatePixelToDataMap(int width, double xmin, double xmax, double[] x) + { + int length = x.Length; + if (length <= 1) + throw new ArgumentException("Knots array should contain at least 2 knots"); + if (xmax > x[length - 1] || xmin < x[0]) + throw new ArgumentException("Cannot interpolate beyond bounds"); + + int[] pixels = new int[width]; + double delta = (xmax - xmin) / width; + int i = 0; + for (int pixel = 0; pixel < width; pixel++) + { + double pixCenter = xmin + (pixel + 0.5) * delta; // Pixel center + while (x[i+1] < pixCenter) + i++; // No out of range here because of prerequisites. Now x[i+1] > pixCenter + pixels[pixel] = i; + } + + return pixels; + } + + /// + /// Calculates data range from specified 2d array of data. + /// + public static Range GetMaxMin(double[,] data) + { + double Max, Min; + if (data != null) + { + int N = data.GetLength(0); + int M = data.GetLength(1); + Max = data[0, 0]; + Min = data[0, 0]; + + for (int i = 0; i < N; i++) + for (int j = 0; j < M; j++) + { + if (!double.IsNaN(data[i, j])) + { + if (data[i, j] > Max) Max = data[i, j]; + if (data[i, j] < Min) Min = data[i, j]; + } + } + if (double.IsNaN(Max) || double.IsNaN(Min)) + { + Max = 0; + Min = 0; + } + } + else + { + Max = Double.NaN; + Min = Double.NaN; + } + return new Range(Min, Max); ; + } + + /// + /// Calculates data range from specified 2d array of data and missing value. + /// + public static Range GetMaxMin(double[,] data, double missingValue) + { + double Max, Min; + if (data != null) + { + int N = data.GetLength(0); + int M = data.GetLength(1); + + Max = data[0, 0]; + Min = data[0, 0]; + + for (int i = 0; i < N; i++) + for (int j = 0; j < M; j++) + { + if (data[i, j] != missingValue) + { + if (data[i, j] > Max) Max = data[i, j]; + if (data[i, j] < Min) Min = data[i, j]; + } + } + if (Max == missingValue || Min == missingValue) + { + Max = 0; + Min = 0; + } + } + else + { + Max = missingValue; + Min = missingValue; + } + return new Range(Min, Max); ; + } + + } + + +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapGraph.cs b/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapGraph.cs new file mode 100644 index 000000000..def777a0e --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapGraph.cs @@ -0,0 +1,450 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.ComponentModel; +using System.Globalization; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Control to render 2d image as a heatmap. Heatmap is widely used graphical representation of 2D array + /// where the values of array items are represented as colors. + /// + [Description("Plots a heatmap graph")] + public class HeatmapGraph : BackgroundBitmapRenderer, ITooltipProvider + { + private object locker = new object(); // Instance to hold locks for multithread access + + private double[] xArr; + private double[] yArr; + private long dataVersion = 0; + private double[,] data; + private double missingValue; + + /// + /// Initializes a new instance of class with default tooltip. + /// + public HeatmapGraph() + { + TooltipContentFunc = GetTooltipForPoint; + } + + private static void VerifyDimensions(double[,] d, double[] x, double[] y) + { + double dlen0 = d.GetLength(0); + double dlen1 = d.GetLength(1); + double xlen = x.Length; + double ylen = y.Length; + if (dlen0 == xlen && dlen1 == ylen || + dlen0 == xlen - 1 && xlen > 1 && dlen1 == ylen - 1 && ylen > 1) + return; + throw new ArgumentException("Array dimensions do not match"); + } + + /// Plots rectangular heatmap. + /// If size dimensions are equal to lenghtes of corresponding grid parameters + /// and then Gradient render method is used. If + /// dimension are smaller by one then Bitmap render method is used for heatmap. In all other cases exception is thrown. + /// + /// Two dimensional array of data. + /// Grid along x axis. + /// Grid along y axis. + /// ID of background operation. You can subscribe to + /// notification to be notified when this operation is completed or request is dropped. + public long Plot(double[,] data, double[] x, double[] y) + { + return Plot(data, x, y, Double.NaN); + } + + /// Plots rectangular heatmap where some data may be missing. + /// If size dimensions are equal to lenghtes of corresponding grid parameters + /// and then Gradient render method is used. If + /// dimension are smaller by one then Bitmap render method is used for heatmap. In all other cases exception is thrown. + /// + /// Two dimensional array of data. + /// Grid along x axis. + /// Grid along y axis. + /// Missing value. Data items equal to aren't shown. + /// ID of background operation. You can subscribe to + /// notification to be notified when this operation is completed or request is dropped. + public long Plot(double[,] data, double[] x, double[] y, double missingValue) + { + VerifyDimensions(data, x, y); + lock (locker) + { + this.xArr = x; + this.yArr = y; + this.data = data; + this.missingValue = missingValue; + dataVersion++; + } + + InvalidateBounds(); + + return QueueRenderTask(); + } + + /// Plots rectangular heatmap where some data may be missing. + /// If size dimensions are equal to lenghtes of corresponding grid parameters + /// and then Gradient render method is used. If + /// dimension are smaller by one then Bitmap render method is used for heatmap. In all other cases exception is thrown. + /// Double, float, integer and boolean types are supported as data and grid array elements + /// Two dimensional array of data. + /// Grid along x axis. + /// Grid along y axis. + /// Missing value. Data items equal to aren't shown. + /// ID of background operation. You can subscribe to + /// notification to be notified when this operation is completed or request is dropped. + public long Plot(T[,] data, A[] x, A[] y, T missingValue) + { + return Plot(ArrayExtensions.ToDoubleArray2D(data), + ArrayExtensions.ToDoubleArray(x), + ArrayExtensions.ToDoubleArray(y), + Convert.ToDouble(missingValue, CultureInfo.InvariantCulture)); + } + + /// Plots rectangular heatmap where some data may be missing. + /// If size dimensions are equal to lenghtes of corresponding grid parameters + /// and then Gradient render method is used. If + /// dimension are smaller by one then Bitmap render method is used for heatmap. In all other cases exception is thrown. + /// Double, float, integer and boolean types are supported as data and grid array elements + /// Two dimensional array of data. + /// Grid along x axis. + /// Grid along y axis. + /// ID of background operation. You can subscribe to + /// notification to be notified when this operation is completed or request is dropped. + public long Plot(T[,] data, A[] x, A[] y) + { + return Plot(ArrayExtensions.ToDoubleArray2D(data), + ArrayExtensions.ToDoubleArray(x), + ArrayExtensions.ToDoubleArray(y), + Double.NaN); + } + + + /// Returns content bounds of this elements in cartesian coordinates. + /// Rectangle with content bounds. + protected override DataRect ComputeBounds() + { + if (xArr != null && yArr != null) + return new DataRect(xArr[0], yArr[0], xArr[xArr.Length - 1], yArr[yArr.Length - 1]); + else + return DataRect.Empty; + } + + /// + /// Cached value of property. Accessed both from UI and rendering thread. + /// + private IPalette palette = InteractiveDataDisplay.WPF.Palette.Heat; + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PaletteProperty = DependencyProperty.Register( + "Palette", + typeof(Palette), + typeof(HeatmapGraph), + new PropertyMetadata(InteractiveDataDisplay.WPF.Palette.Heat, OnPalettePropertyChanged)); + + /// Gets or sets the palette for heatmap rendering. + [TypeConverter(typeof(StringToPaletteTypeConverter))] + [Category("InteractiveDataDisplay")] + [Description("Defines mapping from values to color")] + public IPalette Palette + { + get { return (IPalette)GetValue(PaletteProperty); } + set { SetValue(PaletteProperty, value); } + } + + private bool paletteRangeUpdateRequired = true; + + private static void OnPalettePropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + HeatmapGraph heatmap = (HeatmapGraph)sender; + lock (heatmap.locker) + { + heatmap.paletteRangeUpdateRequired = true; + heatmap.palette = (Palette)e.NewValue; + } + heatmap.QueueRenderTask(); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PaletteRangeProperty = DependencyProperty.Register( + "PaletteRange", + typeof(Range), + typeof(HeatmapGraph), + new PropertyMetadata(new Range(0, 1), OnPaletteRangePropertyChanged)); + + /// + /// Cached range of data values. It is accessed from UI and rendering thread. + /// + private Range dataRange = new Range(0, 1); + + /// Version of data for current data range. If dataVersion != dataRangeVersion then + /// data range version should be recalculated. + private long dataRangeVersion = -1; + + private int insidePaletteRangeSetter = 0; + + /// Gets range of data values used in palette building. + /// This property cannot be set from outside code. Attempt to set it from + /// bindings result in exception. + [Browsable(false)] + public Range PaletteRange + { + get { return (Range)GetValue(PaletteRangeProperty); } + protected set + { + try + { + insidePaletteRangeSetter++; + SetValue(PaletteRangeProperty, value); + } + finally + { + insidePaletteRangeSetter--; + } + } + } + + private static void OnPaletteRangePropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + var heatmap = (HeatmapGraph)sender; + if (heatmap.insidePaletteRangeSetter <= 0) + throw new InvalidOperationException("Palette Range property cannot be changed by binding. Use Palette property instead"); + } + + private void UpdatePaletteRange(long localDataVersion) + { + if (dataVersion != localDataVersion) + return; + paletteRangeUpdateRequired = false; + if (palette.IsNormalized) + PaletteRange = dataRange; + else + PaletteRange = palette.Range; + } + + /// Gets range of data values for current data. + public Range DataRange + { + get + { + if (data != null && dataVersion != dataRangeVersion) + { + var r = Double.IsNaN(missingValue) ? + HeatmapBuilder.GetMaxMin(data) : + HeatmapBuilder.GetMaxMin(data, missingValue); + lock (locker) + { + dataRangeVersion = dataVersion; + dataRange = r; + } + UpdatePaletteRange(dataVersion); + } + return dataRange; + } + } + + /// + /// Renders frame and returns it as a render result. + /// + /// Render task state for rendering frame. + /// Render result of rendered frame. + protected override RenderResult RenderFrame(RenderTaskState state) + { + if (state == null) + throw new ArgumentNullException("state"); + + if (!state.Bounds.IsEmpty && !state.IsCanceled && data != null) + { + //DataRect dataRect = new DataRect(state.Transform.Visible); + //Rect output = state.Transform.Screen; + DataRect dataRect = state.ActualPlotRect; + DataRect output = new DataRect(0, 0, state.ScreenSize.Width, state.ScreenSize.Height); + DataRect bounds = state.Bounds; + + if (dataRect.XMin >= bounds.XMax || dataRect.XMax <= bounds.XMin || + dataRect.YMin >= bounds.YMax || dataRect.YMax <= bounds.YMin) + return null; + + double left = 0; + double xmin = dataRect.XMin; + double scale = output.Width / dataRect.Width; + if (xmin < bounds.XMin) + { + left = (bounds.XMin - dataRect.XMin) * scale; + xmin = bounds.XMin; + } + + double width = output.Width - left; + double xmax = dataRect.XMax; + if (xmax > bounds.XMax) + { + width -= (dataRect.XMax - bounds.XMax) * scale; + xmax = bounds.XMax; + } + + scale = output.Height / dataRect.Height; + double top = 0; + double ymax = dataRect.YMax; + if (ymax > bounds.YMax) + { + top = (dataRect.YMax - bounds.YMax) * scale; + ymax = bounds.YMax; + } + + double height = output.Height - top; + double ymin = dataRect.YMin; + if (ymin < bounds.YMin) + { + height -= (bounds.YMin - dataRect.YMin) * scale; + ymin = bounds.YMin; + } + + if (xmin < bounds.XMin) + xmin = bounds.XMin; + if (xmax > bounds.XMax) + xmax = bounds.XMax; + if (ymin < bounds.YMin) + ymin = bounds.YMin; + if (ymax > bounds.YMax) + ymax = bounds.YMax; + + DataRect visibleData = new DataRect(xmin, ymin, xmax, ymax); + + // Capture data to local variable + double[,] localData; + double[] localX, localY; + long localDataVersion; + IPalette localPalette; + double localMV; + Range localDataRange; + bool getMaxMin = false; + lock (locker) + { + localData = data; + localX = xArr; + localY = yArr; + localDataVersion = dataVersion; + localPalette = palette; + localMV = missingValue; + localDataRange = dataRange; + if (palette.IsNormalized && dataVersion != dataRangeVersion) + getMaxMin = true; + } + if (getMaxMin) + { + localDataRange = Double.IsNaN(missingValue) ? + HeatmapBuilder.GetMaxMin(data) : + HeatmapBuilder.GetMaxMin(data, missingValue); + lock (locker) + { + if (dataVersion == localDataVersion) + { + dataRangeVersion = dataVersion; + dataRange = localDataRange; + } + else + return null; // New data was passed to Plot method so this render task is obsolete + } + } + if (paletteRangeUpdateRequired) + Dispatcher.BeginInvoke(new Action(UpdatePaletteRange), localDataVersion); + return new RenderResult(HeatmapBuilder.BuildHeatMap(new Rect(0, 0, width, height), + visibleData, localX, localY, localData, localMV, localPalette, localDataRange), visibleData, new Point(left, top), width, height); + } + else + return null; + } + + /// Gets or sets function to get tooltip object (string or UIElement) + /// for given screen point. + /// method is called by default. + public Func TooltipContentFunc + { + get; + set; + } + + /// + /// Returns the string that is shown in tooltip for the screen point. If there is no data for this point (or nearest points) on a screen then returns null. + /// + /// A point to show tooltip for. + /// An object. + public object GetTooltipForPoint(Point screenPoint) + { + double pointData; + Point nearest; + if (GetNearestPointAndValue(screenPoint, out nearest, out pointData)) + return String.Format(CultureInfo.InvariantCulture, "Data: {0}; X: {1}; Y: {2}", pointData, nearest.X, nearest.Y); + else + return null; + } + + /// + /// Finds the point nearest to a specified point on a screen. + /// + /// The point to search nearest for. + /// The out parameter to handle the founded point. + /// The out parameter to handle data of founded point. + /// Boolen value indicating whether the nearest point was found or not. + public bool GetNearestPointAndValue(Point screenPoint, out Point nearest, out double vd) + { + nearest = new Point(Double.NaN, Double.NaN); + vd = Double.NaN; + if (data == null || xArr == null || yArr == null) + return false; + Point dataPoint = new Point(XDataTransform.PlotToData(XFromLeft(screenPoint.X)), YDataTransform.PlotToData(YFromTop(screenPoint.Y)));//PlotContext.ScreenToData(screenPoint); + int i = ArrayExtensions.GetNearestIndex(xArr, dataPoint.X); + if (i < 0) + return false; + int j = ArrayExtensions.GetNearestIndex(yArr, dataPoint.Y); + if (j < 0) + return false; + if (IsBitmap) + { + if (i > 0 && xArr[i - 1] > dataPoint.X) + i--; + if (j > 0 && yArr[j - 1] > dataPoint.Y) + j--; + } + if (i < data.GetLength(0) && j < data.GetLength(1)) + { + vd = data[i, j]; + nearest = new Point(xArr[i], yArr[j]); + return true; + } + else + return false; + } + + /// + /// Gets the boolen value indicating whether heatmap is rendered using gradient filling. + /// + public bool IsGradient + { + get + { + return (data == null || xArr == null) ? false : (data.GetLength(0) == xArr.Length); + } + } + + /// + /// Gets the boolen value indicating whether heatmap is rendered as a bitmap. + /// + public bool IsBitmap + { + get + { + return (data == null || xArr == null) ? false : (data.GetLength(0) == xArr.Length - 1); + } + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapTooltipLayer.cs b/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapTooltipLayer.cs new file mode 100644 index 000000000..a6a207d3b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Heatmap/HeatmapTooltipLayer.cs @@ -0,0 +1,219 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +//// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. +// +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Collections.Generic; +using System.Windows.Threading; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Control for displaying tooltips for . + /// Shows a value of heatmap in specified point. + /// + [Description("Displays value of heatmap under cursor")] + public class HeatmapTooltipLayer : Panel + { + private Point location; + private Object content; + private DispatcherTimer dispatcherTimer; + private ToolTip toolTip = new ToolTip(); + private TimeSpan dueTime = new TimeSpan(0, 0, 1); + private TimeSpan durationInterval = new TimeSpan(0, 0, 7); + private IDisposable subscription; + private Dictionary heatmapSubscriptions = new Dictionary(); + private PlotBase parent = null; + + /// + /// Initializes new instance of class. + /// + public HeatmapTooltipLayer() + { + ContentFunc = DefaultContentFunc; + this.toolTip.Placement = System.Windows.Controls.Primitives.PlacementMode.Mouse; + this.toolTip.VerticalOffset = 10; + this.toolTip.HorizontalOffset = 5; + this.MouseMove += new MouseEventHandler(OnMouseMove); + this.MouseLeave += new MouseEventHandler(TooltipLayer_MouseLeave); + this.toolTip.IsOpen = false; + + this.dispatcherTimer = new DispatcherTimer(); + this.dispatcherTimer.Interval = dueTime; + this.dispatcherTimer.Tick += OnTick; + + this.Loaded += new RoutedEventHandler(HeatmapTooltipLayer_Loaded); + this.Unloaded += new RoutedEventHandler(HeatmapTooltipLayer_Unloaded); + } + + void HeatmapTooltipLayer_Loaded(object sender, RoutedEventArgs e) + { + var visualParent = VisualTreeHelper.GetParent(this); + parent = visualParent as PlotBase; + while(visualParent != null && parent == null) + { + visualParent = VisualTreeHelper.GetParent(visualParent); + parent = visualParent as PlotBase; + } + if (parent != null) + { + parent.MouseMove += new MouseEventHandler(OnMouseMove); + parent.MouseLeave += new MouseEventHandler(TooltipLayer_MouseLeave); + } + } + + void HeatmapTooltipLayer_Unloaded(object sender, RoutedEventArgs e) + { + subscription.Dispose(); + foreach (IDisposable s in heatmapSubscriptions.Values) + { + s.Dispose(); + } + heatmapSubscriptions.Clear(); + } + + private void RefreshTooltip() + { + object result = ContentFunc(this.location); + if (result == null) + { + toolTip.Content = null; + toolTip.IsOpen = false; + return; + } + toolTip.Content = result; + } + + private void TooltipLayer_MouseLeave(object sender, MouseEventArgs e) + { + this.Hide(); + } + + /// + /// Gets or sets the tooltip content. + /// + [Browsable(false)] + public Object Content + { + get { return content; } + set { content = value; } + } + + /// + /// Tooltip content function. + /// + [Browsable(false)] + public Func ContentFunc + { + get; + set; + } + + void OnMouseMove(object sender, MouseEventArgs e) + { + this.location = e.GetPosition(this); + if (toolTip.IsOpen) + { + this.Hide(); + } + + if (dispatcherTimer.IsEnabled) + { + dispatcherTimer.Stop(); + } + + dispatcherTimer.Interval = dueTime; + dispatcherTimer.Start(); + } + + void OnTick(object sender, EventArgs e) + { + dispatcherTimer.Stop(); + + bool show = !toolTip.IsOpen; + object result = ContentFunc(location); + if (result == null) + { + return; + } + toolTip.Content = result; + toolTip.IsOpen = show; + if (show) + HideDelayed(durationInterval); + } + + /// + /// Default function for building tooltip content. + /// + /// Point in which the value is requied. + /// A content for tooltip for specified point. + public virtual object DefaultContentFunc(Point pt) + { + if (parent == null) + return null; + StackPanel contentPanel = new StackPanel + { + Orientation = Orientation.Vertical, + }; + foreach (UIElement elem in parent.RelatedPlots) + { + if (elem is HeatmapGraph) + { + ITooltipProvider el = elem as ITooltipProvider; + if (el != null) + { + var control = new ContentControl(); + control.IsTabStop = false; + if (el.TooltipContentFunc != null) + { + var internalContent = el.TooltipContentFunc(pt); + if (internalContent != null) + { + control.Content = internalContent; + contentPanel.Children.Add(control); + } + } + } + } + } + return contentPanel.Children.Count > 0 ? contentPanel : null; + } + + private void Hide() + { + if (dispatcherTimer.IsEnabled) + dispatcherTimer.Stop(); + toolTip.IsOpen = false; + } + + + private void HideDelayed(TimeSpan delay) + { + if (dispatcherTimer.IsEnabled) + dispatcherTimer.Stop(); + + dispatcherTimer.Interval = delay; + dispatcherTimer.Start(); + } + } + + /// + /// Interface for elements which can provide content for tooltips. + /// + public interface ITooltipProvider + { + /// + /// Tooltip content generation function. + /// + Func TooltipContentFunc { get; } + } +} + + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Line/LineGraph.cs b/InteractiveDataDisplay_OneDll/src/Plots/Line/LineGraph.cs new file mode 100644 index 000000000..84b8f08f4 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Line/LineGraph.cs @@ -0,0 +1,275 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Windows.Data; +using System.Collections; +using System.Globalization; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// A plot to draw simple line. + /// + [Description("Plots line graph")] + public class LineGraph : Plot + { + private Polyline polyline; + + /// + /// Gets or sets line graph points. + /// + [Category("InteractiveDataDisplay")] + [Description("Line graph points")] + public PointCollection Points + { + get { return (PointCollection)GetValue(PointsProperty); } + set { SetValue(PointsProperty, value); } + } + + private static void PointsPropertyChangedHandler(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + LineGraph linePlot = (LineGraph)d; + if (linePlot != null) + { + InteractiveDataDisplay.WPF.Plot.SetPoints(linePlot.polyline, (PointCollection)e.NewValue); + } + } + + /// + /// Initializes a new instance of class. + /// + public LineGraph() + { + polyline = new Polyline + { + Stroke = new SolidColorBrush(Colors.Black), + StrokeLineJoin = PenLineJoin.Round + }; + + BindingOperations.SetBinding(polyline, Polyline.StrokeThicknessProperty, new Binding("StrokeThickness") { Source = this }); + BindingOperations.SetBinding(this, PlotBase.PaddingProperty, new Binding("StrokeThickness") { Source = this, Converter = new LineGraphThicknessConverter() }); + + Children.Add(polyline); + } + static LineGraph() + { + PointsProperty.OverrideMetadata(typeof(LineGraph), new PropertyMetadata(new PointCollection(), PointsPropertyChangedHandler) ); + } + + /// + /// Updates data in and causes a redrawing of line graph. + /// + /// A set of x coordinates of new points. + /// A set of y coordinates of new points. + public void Plot(IEnumerable x, IEnumerable y) + { + if (x == null) + throw new ArgumentNullException("x"); + if (y == null) + throw new ArgumentNullException("y"); + + var points = new PointCollection(); + var enx = x.GetEnumerator(); + var eny = y.GetEnumerator(); + while (true) + { + var nx = enx.MoveNext(); + var ny = eny.MoveNext(); + if (nx && ny) + points.Add(new Point(Convert.ToDouble(enx.Current, CultureInfo.InvariantCulture), + Convert.ToDouble(eny.Current, CultureInfo.InvariantCulture))); + else if (!nx && !ny) + break; + else + throw new ArgumentException("x and y have different lengthes"); + } + + Points = points; + } + + /// + /// Updates data in and causes a redrawing of line graph. + /// In this version a set of x coordinates is a sequence of integers starting with zero. + /// + /// A set of y coordinates of new points. + public void PlotY(IEnumerable y) + { + if (y == null) + throw new ArgumentNullException("y"); + int x = 0; + var en = y.GetEnumerator(); + var points = new PointCollection(); + while (en.MoveNext()) + points.Add(new Point(x++, Convert.ToDouble(en.Current, CultureInfo.InvariantCulture))); + + Points = points; + } + + #region Description + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DescriptionProperty = + DependencyProperty.Register("Description", + typeof(string), + typeof(LineGraph), + new PropertyMetadata(null, + (s, a) => + { + var lg = (LineGraph)s; + ToolTipService.SetToolTip(lg, a.NewValue); + })); + + /// + /// Gets or sets description text for line graph. Description text appears in default + /// legend and tooltip. + /// + [Category("InteractiveDataDisplay")] + public string Description + { + get + { + return (string)GetValue(DescriptionProperty); + } + set + { + SetValue(DescriptionProperty, value); + } + } + + #endregion + + #region Thickness + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty StrokeThicknessProperty = + DependencyProperty.Register("StrokeThickness", + typeof(double), + typeof(LineGraph), + new PropertyMetadata(1.0)); + + /// + /// Gets or sets the line thickness. + /// + /// + /// The default stroke thickness is 1.0 + /// + [Category("Appearance")] + public double StrokeThickness + { + get + { + return (double)GetValue(StrokeThicknessProperty); + } + set + { + SetValue(StrokeThicknessProperty, value); + } + } + #endregion + + #region Stroke + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty StrokeProperty = + DependencyProperty.Register("Stroke", + typeof(Brush), + typeof(LineGraph), + new PropertyMetadata(new SolidColorBrush(Colors.Black), OnStrokeChanged)); + + private static void OnStrokeChanged(object target, DependencyPropertyChangedEventArgs e) + { + LineGraph lineGraph = (LineGraph)target; + lineGraph.polyline.Stroke = e.NewValue as Brush; + } + + /// + /// Gets or sets the brush to draw the line. + /// + /// + /// The default color of stroke is black + /// + [Category("Appearance")] + public Brush Stroke + { + get + { + return (Brush)GetValue(StrokeProperty); + } + set + { + SetValue(StrokeProperty, value); + } + } + #endregion + + #region StrokeDashArray + + private static DoubleCollection EmptyDoubleCollection + { + get + { + var result = new DoubleCollection(0); + result.Freeze(); + return result; + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty StrokeDashArrayProperty = + DependencyProperty.Register("StrokeDashArray", + typeof(DoubleCollection), + typeof(LineGraph), + new PropertyMetadata(EmptyDoubleCollection, OnStrokeDashArrayChanged)); + + private static void OnStrokeDashArrayChanged(object target, DependencyPropertyChangedEventArgs e) + { + LineGraph lineGraph = (LineGraph)target; + lineGraph.polyline.StrokeDashArray = e.NewValue as DoubleCollection; + } + + /// + /// Gets or sets a collection of values that indicate the pattern of dashes and gaps that is used to draw the line. + /// + [Category("Appearance")] + public DoubleCollection StrokeDashArray + { + get + { + return (DoubleCollection)GetValue(StrokeDashArrayProperty); + } + set + { + SetValue(StrokeDashArrayProperty, value); + } + } + #endregion + } + + internal class LineGraphThicknessConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + double thickness = (double)value; + return new Thickness(thickness / 2.0); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } + +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/CartesianMarkerGraph.cs b/InteractiveDataDisplay_OneDll/src/Plots/Markers/CartesianMarkerGraph.cs new file mode 100644 index 000000000..d48ac4fb5 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/CartesianMarkerGraph.cs @@ -0,0 +1,1001 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Globalization; +using System.ComponentModel; +using System.Collections; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// A base class for all instances in cartesian coordinates. + /// Has predefined X and Y . + /// + [Description("Marker graph in cartesian coordinates")] + public class CartesianMarkerGraph : MarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public CartesianMarkerGraph() + { + Sources.Add(new DataSeries { Key = "X", Description = "X" }); + Sources.Add(new DataSeries { Key = "Y", Description = "Y" }); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// This version does not need specification of X . Default value is a sequence of integers starting with zero. + /// + /// Data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + public virtual long PlotY(object y) + { + return Plot(null, y); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// + /// Data for X defining x coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// Data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + public virtual long PlotXY(object x, object y) + { + return Plot(x, y); + } + #region X + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty XProperty = DependencyProperty.Register( + "X", typeof(object), typeof(CartesianMarkerGraph), new PropertyMetadata(null, OnXChanged)); + /// + /// Gets or sets the data for X defining x coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("X coordinates")] + public object X + { + get { return (object)GetValue(XProperty); } + set { SetValue(XProperty, value); } + } + private static void OnXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianMarkerGraph mg = (CartesianMarkerGraph)d; + mg.Sources["X"].Data = e.NewValue; + } + #endregion + #region Y + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty YProperty = DependencyProperty.Register( + "Y", typeof(object), typeof(CartesianMarkerGraph), new PropertyMetadata(null, OnYChanged)); + + /// + /// Gets or sets the data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Y coordinates")] + public object Y + { + get { return (object)GetValue(YProperty); } + set { SetValue(YProperty, value); } + } + + /// + /// Called when property changes. + /// + /// Old value of propery . + /// New value of propery . + protected virtual void OnYChanged(object oldValue, object newValue) + { + Sources["Y"].Data = newValue; + } + + private static void OnYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianMarkerGraph mg = (CartesianMarkerGraph)d; + mg.OnYChanged(e.OldValue, e.NewValue); + } + #endregion + #region Description + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + "Description", typeof(string), typeof(CartesianMarkerGraph), new PropertyMetadata(null, OnDescriptionChanged)); + + /// + /// Gets or sets the description of Y . Is frequently used in legend and tooltip. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Text for tooltip and legend")] + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } + } + + private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianMarkerGraph mg = (CartesianMarkerGraph)d; + mg.Sources["Y"].Description = (string)e.NewValue; + } + #endregion + } + /// + /// Is a with defined and + /// to draw markers in cartesian coordinates in color and of different size. + /// + /// Note that legend and tooltip templates are choosing automaticaly depending on a using version of Plot method. + /// + /// + [Description("Marker graph in cartesian space with color and size as additional series")] + public class CartesianSizeColorMarkerGraph : CartesianMarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public CartesianSizeColorMarkerGraph() + { + Sources.Add(new ColorSeries()); + Sources.Add(new SizeSeries()); + } + + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is not thread safe. + /// This version does not need specification of X . Default value is a sequence of integers starting with zero. + /// Color and size of markers are taken from and dependency properties. + /// + /// Data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + public override long PlotY(object y) + { + LegendTemplate = GetLegendTemplate(Color, Size); + TooltipTemplate = GetTooltipTemplate(Color, Size); + return Plot(null, y, Color, Size); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is not thread safe. + /// Color and size of markers are getting from and dependency properties. + /// + /// Data for X defining x coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// Data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + public override long PlotXY(object x, object y) + { + LegendTemplate = GetLegendTemplate(Color, Size); + TooltipTemplate = GetTooltipTemplate(Color, Size); + + return Plot(x, y, Color, Size); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is not thread safe. + /// Size of markers is getting from dependency property. + /// + /// Data for X defining x coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// Data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// Data for defining color of markers. + /// Can be a single value (to draw markers of one color) or an array or IEnumerable + /// (to draw markers of different colors) of any numeric type, System.Windows.Media.Color, + /// or string defining system name of a color. + /// Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + public long PlotColor(object x, object y, object c) + { + LegendTemplate = GetLegendTemplate(c, Size); + TooltipTemplate = GetTooltipTemplate(c, Size); + + return Plot(x, y, c, Size); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// Color of markers is getting from dependency property. + /// + /// Data for X defining x coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// Data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// Data for defining size of markers in screen coordinates. + /// Can be a single value (to draw markers of one size) or an array or IEnumerable (to draw markers of different sizes) of + /// any numeric type. Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + public long PlotSize(object x, object y, object d) + { + LegendTemplate = GetLegendTemplate(Color, d); + TooltipTemplate = GetTooltipTemplate(Color, d); + + return Plot(x, y, Color, d); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is not thread safe. + /// + /// Data for X defining x coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// Data for Y defining y coordinates of markers. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// Data for defining color of markers. + /// Can be a single value (to draw markers of one color) or an array or IEnumerable (to draw markers of different colors) of + /// any numeric type, System.Windows.Media.Color, or string defining system name of a color. + /// Can be null then no markers will be drawn. + /// + /// Data for defining size of markers in screen coordinates. + /// Can be a single value (to draw markers of one size) or an array or IEnumerable (to draw markers of different sizes) of + /// any numeric type. Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + public long PlotColorSize(object x, object y, object c, object d) + { + LegendTemplate = GetLegendTemplate(c, d); + TooltipTemplate = GetTooltipTemplate(c, d); + + return Plot(x, y, c, d); + } + private static bool IsScalarOrNull(object obj) + { + if (obj == null || obj is string) + { + return true; + } + IEnumerable ienum = obj as IEnumerable; + if (ienum != null) + { + int n = DataSeries.GetArrayFromEnumerable(ienum).Length; + if (n == 0 || n == 1) + return true; + return false; + } + Array array = obj as Array; + if (array != null) + { + int n = array.Length; + if (n == 0 || n == 1) + return true; + return array.Rank == 1; + } + return true; + } + private DataTemplate GetLegendTemplate(object c, object d) + { + if (!IsScalarOrNull(c) && !IsScalarOrNull(d)) + return MarkerType.GetColorSizeLegendTemplate(this); + else if (!IsScalarOrNull(c)) + return MarkerType.GetColorLegendTemplate(this); + else if (!IsScalarOrNull(d)) + return MarkerType.GetSizeLegendTemplate(this); + else + return MarkerType.GetYLegendTemplate(this); + } + private DataTemplate GetTooltipTemplate(object c, object d) + { + if (!IsScalarOrNull(c) && !IsScalarOrNull(d)) + return ColorSizeMarker.GetColorSizeTooltipTemplate(this); + else if (!IsScalarOrNull(c)) + return ColorSizeMarker.GetColorTooltipTemplate(this); + else if (!IsScalarOrNull(d)) + return ColorSizeMarker.GetSizeTooltipTemplate(this); + else + return ColorSizeMarker.GetYTooltipTemplate(this); + } + /// + /// Called when property Y changes. + /// + /// Old value of propery Y. + /// New value of propery Y. + protected override void OnYChanged(object oldValue, object newValue) + { + LegendTemplate = GetLegendTemplate(Color, Size); + TooltipTemplate = GetTooltipTemplate(Color, Size); + base.OnYChanged(oldValue, newValue); + } + #region Size + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SizeProperty = DependencyProperty.Register( + "Size", typeof(object), typeof(CartesianSizeColorMarkerGraph), new PropertyMetadata(10.0, OnSizeChanged)); + + /// + /// Gets or sets the data for (key 'D'). + /// Can be a single value (to draw markers of one size) or an array or IEnumerable (to draw markers of different sizes) of + /// any numeric type. Can be null then no markers will be drawn. + /// If properties and are not Double.NaN then + /// is used to transform values from original range of data to the range created from and . + /// Default value is 10. + /// + [Category("InteractiveDataDisplay")] + [Description("Series to define marker sizes")] + public object Size + { + get { return (object)GetValue(SizeProperty); } + set { SetValue(SizeProperty, value); } + } + + private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + mg.LegendTemplate = mg.GetLegendTemplate(mg.Color, e.NewValue); + mg.TooltipTemplate = mg.GetTooltipTemplate(mg.Color, e.NewValue); + mg.Sources["D"].Data = e.NewValue; + } + #endregion + #region Color + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register( + "Color", typeof(object), typeof(CartesianSizeColorMarkerGraph), new PropertyMetadata("Black", OnColorChanged)); + + /// + /// Gets or sets the data for (key 'C'). + /// Can be a single value (to draw markers of one color) or an array or IEnumerable (to draw markers of different colors) of + /// any numeric type, System.Windows.Media.Color, or string defining system name or hexadecimal representation (#AARRGGBB) of a color. + /// Can be null then no markers will be drawn. + /// Default value is black color. + /// + [Category("InteractiveDataDisplay")] + [Description("Series to define marker colors")] + public object Color + { + get { return (object)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + mg.LegendTemplate = mg.GetLegendTemplate(e.NewValue, mg.Size); + mg.TooltipTemplate = mg.GetTooltipTemplate(e.NewValue, mg.Size); + mg.Sources["C"].Data = e.NewValue; + } + #endregion + #region SizeDescription + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SizeDescriptionProperty = DependencyProperty.Register( + "SizeDescription", typeof(string), typeof(CartesianSizeColorMarkerGraph), + new PropertyMetadata(null, OnSizeDescriptionChanged)); + + /// + /// Gets or sets the description of . + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Size series description")] + public string SizeDescription + { + get { return (string)GetValue(SizeDescriptionProperty); } + set { SetValue(SizeDescriptionProperty, value); } + } + + private static void OnSizeDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + mg.Sources["D"].Description = (string)e.NewValue; + } + #endregion + #region ColorDescription + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorDescriptionProperty = DependencyProperty.Register( + "ColorDescription", typeof(string), typeof(CartesianSizeColorMarkerGraph), + new PropertyMetadata(null, OnColorDescriptionChanged)); + + /// + /// Gets or sets the description of . + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Color series description")] + public string ColorDescription + { + get { return (string)GetValue(ColorDescriptionProperty); } + set { SetValue(ColorDescriptionProperty, value); } + } + + private static void OnColorDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + mg.Sources["C"].Description = (string)e.NewValue; + } + #endregion + #region Min + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MinProperty = DependencyProperty.Register( + "Min", typeof(double), typeof(CartesianSizeColorMarkerGraph), new PropertyMetadata(Double.NaN, OnMinChanged)); + + /// + /// Gets or sets screen size corresponding to minimal value in size series. + /// Default value is Double.NaN. + /// + [Category("InteractiveDataDisplay")] + [Description("Minimal screen size of marker")] + public double Min + { + get { return (double)GetValue(MinProperty); } + set { SetValue(MinProperty, value); } + } + + private static void OnMinChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + (mg.Sources["D"] as SizeSeries).Min = Convert.ToDouble(e.NewValue, CultureInfo.InvariantCulture); ; + } + #endregion + #region Max + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MaxProperty = DependencyProperty.Register( + "Max", typeof(double), typeof(CartesianSizeColorMarkerGraph), new PropertyMetadata(Double.NaN, OnMaxChanged)); + + /// + /// Gets or sets screen size corresponding to maximum value in size series. + /// Default value is Double.NaN. + /// + [Category("InteractiveDataDisplay")] + [Description("Maximum screen size of marker")] + public double Max + { + get { return (double)GetValue(MaxProperty); } + set { SetValue(MaxProperty, value); } + } + + private static void OnMaxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + (mg.Sources["D"] as SizeSeries).Max = Convert.ToDouble(e.NewValue, CultureInfo.InvariantCulture); ; + } + #endregion + #region Palette + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PaletteProperty = DependencyProperty.Register( + "Palette", typeof(IPalette), typeof(CartesianSizeColorMarkerGraph), new PropertyMetadata(null, OnPaletteChanged)); + + /// + /// Gets or sets the color palette for markers. Defines mapping of values to colors. + /// Is used only if the data of is of numeric type. + /// Default value is null. + /// + [TypeConverter(typeof(StringToPaletteTypeConverter))] + [Category("InteractiveDataDisplay")] + [Description("Defines mapping of values to colors")] + public IPalette Palette + { + get { return (IPalette)GetValue(PaletteProperty); } + set { SetValue(PaletteProperty, value); } + } + + private static void OnPaletteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + (mg.Sources["C"] as ColorSeries).Palette = e.NewValue as IPalette; + } + #endregion + #region MarkerType + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MarkerTypeProperty = DependencyProperty.Register( + "MarkerType", typeof(ColorSizeMarker), typeof(CartesianSizeColorMarkerGraph), new PropertyMetadata(null, OnMarkerTypeChanged)); + + /// + /// Gets or sets one of predefined types for markers. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("One of predefined marker types")] + public ColorSizeMarker MarkerType + { + get { return (ColorSizeMarker)GetValue(MarkerTypeProperty); } + set { SetValue(MarkerTypeProperty, value); } + } + + private static void OnMarkerTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CartesianSizeColorMarkerGraph mg = (CartesianSizeColorMarkerGraph)d; + mg.MarkerTemplate = mg.MarkerType.GetMarkerTemplate(mg); + } + #endregion + } + /// + /// An abstract class providing methods to get templates for markers that use color and size. + /// + public abstract class ColorSizeMarker + { + /// + /// Gets a template for markers. + /// + /// Marker graph to get template for. + /// A template used for markers of particular marker graph. + public abstract DataTemplate GetMarkerTemplate(MarkerGraph mg); + /// + /// Gets template for marker legend with information about Y . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph. + public abstract DataTemplate GetYLegendTemplate(MarkerGraph mg); + /// + /// Gets template for marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph. + public abstract DataTemplate GetColorLegendTemplate(MarkerGraph mg); + /// + /// Gets template for marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph. + public abstract DataTemplate GetSizeLegendTemplate(MarkerGraph mg); + /// + /// Gets template for marker legend with information about Y , + /// and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph. + public abstract DataTemplate GetColorSizeLegendTemplate(MarkerGraph mg); + /// + /// Gets template for marker tooltip with information about Y . + /// + /// Marker graph to get template for. + /// A template used for tooltip of particular marker graph or null if marker graph is null. + public static DataTemplate GetYTooltipTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.DefaultTooltipTemplate; + } + /// + /// Gets template for marker tooltip with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for tooltip of particular marker graph or null if marker graph is null. + public static DataTemplate GetColorTooltipTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.ColorTooltip; + } + /// + /// Gets template for marker tooltip with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for tooltip of particular marker graph or null if marker graph is null. + public static DataTemplate GetSizeTooltipTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.SizeTooltip; + } + /// + /// Gets template for marker tooltip with information about Y , and . + /// + /// Marker graph to get template for. + /// A template used for tooltip of particular marker graph or null if marker graph is null. + public static DataTemplate GetColorSizeTooltipTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.ColorSizeTooltip; + } + } + /// + /// Provides methods to get predefined templates for circle markers. + /// + public class CircleMarker : ColorSizeMarker + { + /// + /// Gets template for circle markers. + /// + /// Marker graph to get template for. + /// A template used for markers of particular marker graph or null if marker graph is null. + public override DataTemplate GetMarkerTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.Circle; + } + /// + /// Gets template for circle marker legend with information about Y . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetYLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CircleLegend; + } + /// + /// Gets template for circle marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CircleColorLegend; + } + + /// + /// Gets template for circle marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CircleSizeLegend; + } + + /// + /// Gets template for circle marker legend with information about Y , and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CircleColorSizeLegend; + } + } + /// + /// Provides methods to get predefined templates for box markers. + /// + public class BoxMarker : ColorSizeMarker + { + /// + /// Gets template for box markers. + /// + /// Marker graph to get template for. + /// A template used for markers of particular marker graph or null if marker graph is null. + public override DataTemplate GetMarkerTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.Box; + } + + /// + /// Gets template for box marker legend with information about Y . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetYLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.BoxLegend; + } + + /// + /// Gets template for box marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.BoxColorLegend; + } + + /// + /// Gets template for box marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.BoxSizeLegend; + } + + /// + /// Gets template for box marker legend with information about Y , and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.BoxColorSizeLegend; + } + } + + /// + /// Provides methods to get predefined templates for cross markers. + /// + public class CrossMarker : ColorSizeMarker + { + /// + /// Gets template for cross markers. + /// + /// Marker graph to get template for. + /// A template used for markers of particular marker graph or null if marker graph is null. + public override DataTemplate GetMarkerTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.Cross; + } + + /// + /// Gets template for cross marker legend with information about Y . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetYLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CrossLegend; + } + + /// + /// Gets template for cross marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CrossColorLegend; + } + + /// + /// Gets template for cross marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CrossSizeLegend; + } + + /// + /// Gets template for cross marker legend with information about Y , and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.CrossColorSizeLegend; + } + } + + /// + /// Provides methods to get predefined templates for diamond markers. + /// + public class DiamondMarker : ColorSizeMarker + { + /// + /// Gets template for diamond markers. + /// + /// Marker graph to get template for. + /// A template used for markers of particular marker graph or null if marker graph is null. + public override DataTemplate GetMarkerTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.Diamond; + } + + /// + /// Gets template for diamond marker legend with information about Y . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetYLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.DiamondLegend; + } + + /// + /// Gets template for diamond marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.DiamondColorLegend; + } + + /// + /// Gets template for diamond marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.DiamondSizeLegend; + } + + /// + /// Gets template for diamond marker legend with information about Y , and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.DiamondColorSizeLegend; + } + } + + /// + /// Provides methods to get predefined templates for triangle markers. + /// + public class TriangleMarker : ColorSizeMarker + { + /// + /// Gets template for triangle markers. + /// + /// Marker graph to get template for. + /// A template used for markers of particular marker graph or null if marker graph is null. + public override DataTemplate GetMarkerTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.Triangle; + } + + /// + /// Gets template for triangle marker legend with information about Y . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetYLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.TriangleLegend; + } + + /// + /// Gets template for triangle marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.TriangleColorLegend; + } + + /// + /// Gets template for triangle marker legend with information about Y and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.TriangleSizeLegend; + } + + /// + /// Gets template for triangle marker legend with information about Y , and . + /// + /// Marker graph to get template for. + /// A template used for legend of particular marker graph or null if marker graph is null. + public override DataTemplate GetColorSizeLegendTemplate(MarkerGraph mg) + { + if (mg == null) + return null; + return mg.TriangleColorSizeLegend; + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/DataSeries.cs b/InteractiveDataDisplay_OneDll/src/Plots/Markers/DataSeries.cs new file mode 100644 index 000000000..0c28fc1a4 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/DataSeries.cs @@ -0,0 +1,806 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Windows; +using System.Collections.Generic; +using System.Windows.Data; +using System.Collections.ObjectModel; +using System.Windows.Media; +using System.Collections; +using System.Globalization; +using System.Diagnostics; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Represents one data series (or variable). + /// + public class DataSeries : DependencyObject + { + /// + /// Occurs when a value of property was changed. + /// + public event EventHandler DataChanged; + + private double minValue = Double.NaN; + private double maxValue = Double.NaN; + private Array cachedEnum; // Cached values from IEnumerable + + /// + /// Gets or sets the unique key of data series. Data series is accessed by this key when + /// drawing markers and composing a legend. + /// + public string Key { get; set; } + + /// + /// Gets or sets the readable description of this data series. It is shown in legend by default. + /// Default value is null. + /// + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, (string)value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DescriptionProperty = + DependencyProperty.Register("Description", typeof(string), typeof(DataSeries), + new PropertyMetadata(null, OnDescriptionPropertyChanged)); + + private static void OnDescriptionPropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + var ds = sender as DataSeries; + if (ds.DataChanged != null) ds.DataChanged(ds, new EventArgs()); + } + + /// + /// Gets or sets the data source for data series. This can be IEnumerable or 1D array for vector data or scalar objects. + /// Default value is null. + /// + public object Data + { + get { return GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DataProperty = + DependencyProperty.Register("Data", typeof(object), typeof(DataSeries), + new PropertyMetadata(OnDataPropertyChanged)); + + private static void OnDataPropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + ((DataSeries)sender).Update(); + } + + /// + /// A method to raise event. + /// + protected void RaiseDataChanged() + { + if (DataChanged != null) + DataChanged(this, EventArgs.Empty); + } + + /// + /// Forces this data series to be updated. + /// Is usually called if items of data array were changed, but reference to array itself remained the same. + /// + public void Update() + { + minValue = Double.NaN; + maxValue = Double.NaN; + cachedEnum = null; + First = FindFirstDataItem(); + + RaiseDataChanged(); + } + + /// + /// Gets the first value in data series. + /// Returns null if is null. + /// + public object First + { + get { return (object)GetValue(FirstProperty); } + internal set { SetValue(FirstProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty FirstProperty = + DependencyProperty.Register("First", typeof(object), typeof(DataSeries), new PropertyMetadata(null)); + + /// + /// Gets the first value in data series and converts it if is specified. + /// + /// Converted first value. + public object FindFirstDataItem() + { + object uc = GetFirstUnconvertedDataItem(); + if (uc != null && Converter != null) + return Converter.Convert(uc, null, this, CultureInfo.InvariantCulture); + else + return uc; + } + + /// + /// Converts the elements of an System.Collection.IEnumerable to System.Array. + /// + /// An instance of System.Collection.IEnumerable class. + /// An instance of System.Array class. + public static Array GetArrayFromEnumerable(IEnumerable enumerable) + { + return enumerable.Cast().ToArray(); + } + + internal Array GetCachedEnumerable(IEnumerable ie) + { + if (cachedEnum == null) + cachedEnum = GetArrayFromEnumerable(ie); + return cachedEnum; + } + + private object GetFirstUnconvertedDataItem() + { + Array a = Data as Array; + if (a != null) + { + if (a.Length > 0) + return a.GetValue(0); + else + return null; // Data series has zero length - no first item available + } + else + { + IEnumerable ie = Data as IEnumerable; + if (ie != null && !(Data is string)) + { + IEnumerator ir = ie.GetEnumerator(); + if (ir.MoveNext()) + return ir.Current; + else + return null; // Data series has no elements - no first item available + } + else if (Data != null) + return Data; + else + return null; // Data series is null - no first item available + } + } + + /// + /// Returns the minimum value in data series if it is numeric or Double.NaN in other cases. + /// + public double MinValue + { + get + { + if (Double.IsNaN(minValue)) + { + try + { + var a = Data as Array; + if (a == null) + { + var ie = Data as IEnumerable; + if(ie != null) + a = GetArrayFromEnumerable(ie); + } + if (a != null && !(Data is string)) + { + double min = a.Length > 0 ? Convert.ToDouble(a.GetValue(0), CultureInfo.InvariantCulture) : Double.NaN; + double temp = 0; + foreach (object obj in a) + { + temp = Convert.ToDouble(obj, CultureInfo.InvariantCulture); + if (temp < min) + min = temp; + } + minValue = min; + } + else + { + if (this.Data is Byte || this.Data is SByte || + this.Data is Int16 || this.Data is Int32 || this.Data is Int64 || + this.Data is UInt16 || this.Data is UInt32 || this.Data is UInt64 || + this.Data is Single || this.Data is Double || this.Data is Decimal) + { + double d = Convert.ToDouble(this.Data, CultureInfo.InvariantCulture); + minValue = d; + } + } + } + catch (Exception exc) + { + Debug.WriteLine("Cannot find Min in " + this.Key + " DataSeries: " + exc.Message); + minValue = Double.NaN; + } + } + return minValue; + } + } + + /// + /// Returns the maximum value in data series if it is numeric or Double.NaN in other cases. + /// + public double MaxValue + { + get + { + if (Double.IsNaN(maxValue)) + try + { + Array a = Data as Array; + if (a == null) + { + var ie = Data as IEnumerable; + if(ie != null) + a = GetArrayFromEnumerable(ie); + } + if (a != null && !(Data is string)) + { + double max = a.Length > 0 ? Convert.ToDouble(a.GetValue(0), CultureInfo.InvariantCulture) : Double.NaN; + double temp = 0; + foreach (object obj in a) + { + temp = Convert.ToDouble(obj, CultureInfo.InvariantCulture); + if (temp > max) + max = temp; + } + maxValue = max; + } + else + { + if (this.Data is Byte || this.Data is SByte || + this.Data is Int16 || this.Data is Int32 || this.Data is Int64 || + this.Data is UInt16 || this.Data is UInt32 || this.Data is UInt64 || + this.Data is Single || this.Data is Double || this.Data is Decimal) + { + double d = Convert.ToDouble(this.Data, CultureInfo.InvariantCulture); + maxValue = d; + } + } + } + catch (Exception exc) + { + Debug.WriteLine("Cannot find Max in " + this.Key + " DataSeries: " + exc.Message); + } + return maxValue; + } + } + + /// + /// Gets data series length (the length of if it is vector or one if it is scalar). + /// Returns 0 for series with null properties. + /// + public int Length + { + get + { + var arr = Data as Array; + if (arr != null) + return arr.Length; + else + { + var ie = Data as IEnumerable; + if (ie != null && !(Data is string)) // String is also IEnumerable, but we tread them as scalars + return GetCachedEnumerable(ie).Length; + else + return (Data == null) ? (0) : (1); + } + } + } + + /// + /// Returns true if data series is scalar or false otherwise. + /// Returns true for series with null properties. + /// + public bool IsScalar + { + get + { + if (Data == null || Data is string) + return true; + else if (Data is Array || Data is IEnumerable) + return false; + else + return true; + } + } + + /// + /// Gets or sets the converter which is applied to all objects in data series before binding to marker template. + /// May be null, which means identity conversion. + /// + public IValueConverter Converter { get; set; } + + /// + /// Gets the marker graph than owns that series. + /// + public MarkerGraph Owner { get; internal set; } + } + + /// + /// Represents a collection of data series. + /// + public class DataCollection : ObservableCollection + { + private Dictionary lookup; + DynamicDataCollection dynamicCollection; + bool isValid; + int markerCount; + + /// + /// Occurs when an item in the collection or entire collection changes. + /// + public event EventHandler DataSeriesUpdated; + + /// + /// Initializes a new empty instance of class. + /// + public DataCollection() + { } + + /// + /// Looks up for with specified . If found assigns it to the + /// output parameter and returns true. Returns false if no series with specified name was found. + /// + /// The key of data series to search for. + /// Output parameter for found series. + /// True if data series was found or false otherwise. + public bool TryGetValue(string name, out DataSeries result) + { + if (lookup == null) + { + lookup = new Dictionary(); + foreach (var ds in this) + lookup.Add(ds.Key, ds); + } + return lookup.TryGetValue(name, out result); + } + + /// + /// Checks whether a collection contains with specified . + /// + /// The key of data series. + /// True is data series is in collection or false otherwise. + public bool ContainsSeries(string name) + { + DataSeries dummy; + return TryGetValue(name, out dummy); + } + + /// + /// Gets the index of with "X". If there is no one returns -1. + /// + /// Index of X in current collection. + public int XSeriesNumber + { + get + { + DataSeries dummy; + TryGetValue("X", out dummy); + if (dummy == null) + return -1; + else + return this.IndexOf(dummy); + } + } + + /// + /// Gets the first in collection with specified . + /// + /// The key of data series. + /// The first data series in collection with specified key. + public DataSeries this[string name] + { + get + { + DataSeries result; + if (TryGetValue(name, out result)) + return result; + else + return null; + } + } + + /// + /// Gets with generated properties to get data series by key. + /// Used in bindings in legend templates. + /// + public DynamicDataCollection Emitted + { + get { return dynamicCollection; } + } + + /// + /// Return true if collection is valid and can be drawn, false otherwise. + /// + /// Collection is valid if all the vector data of data series are of equal length and + /// all data series have a defined unique key. + /// + /// + public bool IsValid + { + get { return isValid; } + } + + /// + /// Gets the count of markers to draw. + /// + /// If any data of data series in collection is a vector then the count of markers is vector's length. + /// Otherwise only one marker will be drawn. + /// If any data series (except for data series with key "X") has null data then no markers will be drawn. + /// + /// + public int MarkerCount + { + get { return markerCount; } + } + + /// + /// Raises the event with the provided event data. + /// + /// Provided data for collection changing event. + protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + Type dynamic = DynamicTypeGenerator.GenerateDataCollectionType(this); + dynamicCollection = Activator.CreateInstance(dynamic, this) as DynamicDataCollection; + + CheckDataCollection(); + base.OnCollectionChanged(e); + if (e == null) + { + throw new ArgumentNullException("e"); + } + if (e.OldItems != null) + foreach (DataSeries item in e.OldItems) + item.DataChanged -= OnSeriesDataChanged; + if (e.NewItems != null) + foreach (DataSeries item in e.NewItems) + item.DataChanged += OnSeriesDataChanged; + lookup = null; + + OnPropertyChanged(new PropertyChangedEventArgs("Emitted")); + } + + private void OnSeriesDataChanged(object sender, EventArgs e) + { + CheckDataCollection(); + if (DataSeriesUpdated != null) + { + var ds = sender as DataSeries; + if (ds != null) + DataSeriesUpdated(this, new DataSeriesUpdatedEventArgs(ds.Key)); + } + lookup = null; + } + + private void CheckDataCollection() + { + int n = 0; + isValid = true; + + var vs = this.Where(s => !s.IsScalar).FirstOrDefault(); + if (vs != null) + n = vs.Length; // Take length of first vector series + else + { + var ns = this.Where(s => s.Data != null).FirstOrDefault(); // Find any scalar series with non-null data + if (ns != null) + n = 1; + } + + var ser = this.Where(s => (s.Data == null && s.Key != "X")).FirstOrDefault(); + if (ser == null) + markerCount = n; + else + markerCount = 0; + + foreach (DataSeries ds in this) + { + if (!ds.IsScalar && n != ds.Length) + isValid = false; + + if (String.IsNullOrEmpty(ds.Key)) + isValid = false; + + for (int i = 0; i < this.Count; i++) + if (this[i] != ds && this[i].Key == ds.Key) + { + this.Remove(ds); + isValid = false; + } + } + } + } + + /// + /// Provides event data for the event. + /// + public class DataSeriesUpdatedEventArgs : EventArgs + { + private string key; + + /// + /// Initializes a new instance of the class. + /// + /// The of the updated . + public DataSeriesUpdatedEventArgs(string key) + { + this.key = key; + } + + /// + /// Gets the of the updated . + /// + public string Key { get { return key; } } + } + + /// + /// Provides an access to all the information about one specific marker. + /// + public class MarkerViewModel : INotifyPropertyChanged + { + int row; + bool useConverters = true; + DataCollection dataCollection; + MarkerViewModel sourceModel; + + /// + /// Initializes a new instance of class that uses converters. + /// + /// The number of marker. + /// A collection of data series. + public MarkerViewModel(int row, DataCollection collection) : + this(row, collection, true) + { /* Nothing to do here */ } + + /// + /// Initializes a new instance of class. + /// + /// The number of marker. + /// A collection of data series. + /// A value indicating should converters be used or not. + protected MarkerViewModel(int row, DataCollection collection, bool converters) + { + this.row = row; + this.dataCollection = collection; + this.useConverters = converters; + } + + /// + /// Gets the (a specially created wrapper for ) + /// this instance of class is associated with. + /// + public DynamicDataCollection Series + { + get { return dataCollection.Emitted; } + } + + /// + /// Gets a new instance of class with the same fields but which doesn't use converters. + /// + public MarkerViewModel Sources + { + get + { + if (sourceModel == null) + sourceModel = new MarkerViewModel(row, dataCollection, false); + return sourceModel; + } + } + internal int Row + { + get { return row; } + set + { + row = value; + if (sourceModel != null) + sourceModel.Row = value; + } + } + /// + /// Returns the value of a specific property. + /// + /// The of data series associated with the property. + /// + public object this[string name] + { + get + { + // Find data series by name + var dataSeries = dataCollection[name]; + if (dataSeries == null || dataSeries.Data == null) + if(name == "X") + return row; + else + return null; + + // Get value + object value = null; + var arr = dataSeries.Data as Array; + if (arr != null) + { + if(row < arr.Length) + value = arr.GetValue(row); + } + else + { + var ie = dataSeries.Data as IEnumerable; + if (ie != null && !(dataSeries.Data is string)) // String is also IEnumerable + { + var ce = dataSeries.GetCachedEnumerable(ie); + if (row < ce.Length) + value = ce.GetValue(row); + } + else + value = dataSeries.Data; + } + + // Apply converter if needed + if (useConverters && value != null && dataSeries.Converter != null) + return dataSeries.Converter.Convert(value, typeof(object), dataSeries, null); + else + return value; + } + } + /// + /// Returns the value of a specific property. + /// + /// The index of associated with the property. + /// Value of a property with index . + public object GetValue(int i) + { + // Get data series by inex + var dataSeries = dataCollection[i]; + if (dataSeries.Data == null) + if (dataSeries.Key == "X") + return row; + else + return null; + + // Get value + object value = null; + var arr = dataSeries.Data as Array; + if (arr != null) + { + if(row < arr.Length) + value = arr.GetValue(row); + } + else + { + var ie = dataSeries.Data as IEnumerable; + if (ie != null && !(dataSeries.Data is string)) // String is also IEnumerable + { + var ce = dataSeries.GetCachedEnumerable(ie); + if(row < ce.Length) + value = ce.GetValue(row); + } + else + value = dataSeries.Data; + } + + // Apply converter if needed + if (useConverters && value != null && dataSeries.Converter != null) + return dataSeries.Converter.Convert(value, typeof(object), dataSeries, null); + else + return value; + } + + /// + /// Returns the value of a specific property without convertion. + /// + /// The index of data series associated with the property. + /// Value of a property with index without convertion. + public object GetOriginalValue(int i) + { + return Sources.GetValue(i); + } + + /// + /// Gets the value of with key "X". + /// + public object X + { + get + { + int i = dataCollection.XSeriesNumber; + if (i == -1) + return row; + else + return GetValue(i); + } + } + + /// + /// Gets the value of with key "X" without convertion. + /// + public object OriginalX + { + get + { + int i = dataCollection.XSeriesNumber; + if (i == -1) + return row; + else + return GetOriginalValue(i); + } + } + + /// + /// Gets the stroke of a parent of . + /// + public SolidColorBrush Stroke + { + get + { + if (dataCollection.Count > 0) + if (dataCollection[0].Owner != null) + return dataCollection[0].Owner.Stroke; + return null; + } + } + + /// + /// Gets the stroke thickness of a parent of . + /// + public double StrokeThickness + { + get + { + if (dataCollection.Count > 0) + if (dataCollection[0].Owner != null) + return dataCollection[0].Owner.StrokeThickness; + return 0; + } + } + + /// + /// Gets current instance of . + /// + public MarkerViewModel This + { + get { return this; } + } + + /// + /// Event that occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyPropertyChanged(String info) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(info)); + } + } + + /// + /// Notifies all bindings that all properies of have changed. + /// + public void Notify(string[] props) + { + if (props == null) + NotifyPropertyChanged(null); + else + { + foreach (var p in props) + NotifyPropertyChanged(p); + if(props.Length > 0) + NotifyPropertyChanged("This"); + } + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/DynamicMarkerViewModel.cs b/InteractiveDataDisplay_OneDll/src/Plots/Markers/DynamicMarkerViewModel.cs new file mode 100644 index 000000000..76389a898 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/DynamicMarkerViewModel.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Reflection.Emit; +using System.Reflection; +using System.Globalization; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Class that inherits and is created dynamically. + /// Provides properties for bindings to get values from by its key. + /// + public class DynamicMarkerViewModel : MarkerViewModel + { + /// + /// Creates a new instance of class. + /// + /// The number of marker. + /// A collection of data series. + /// A value indicating should converters be used or not. + public DynamicMarkerViewModel(int row, DataCollection collection, bool converters) + : base(row, collection, converters) { } + } + /// + /// A wrapper to that is created dynamically. + /// Provides generated properties for bindings to get from collection by key. + /// + public class DynamicDataCollection + { + private DataCollection dataCollection; + + /// + /// Creates a new instance of class. + /// + /// A collection of data series. + public DynamicDataCollection(DataCollection collection) + { + dataCollection = collection; + } + + /// + /// Gets from by index. + /// + /// Index of required data series. + /// Data series of specific index. + public DataSeries GetDataSeries(int i) + { + return dataCollection[i]; + } + } + internal static class DynamicTypeGenerator + { + private static ModuleBuilder mb = null; + private static int typeModelCount = 0; + private static int typeCollectionCount = 0; + + public static Type GenerateMarkerViewModelType(DataCollection collection) + { + if (mb == null) + { + AssemblyName aName = new AssemblyName("InteractiveDataDisplayAssembly2"); + AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run); + mb = ab.DefineDynamicModule(aName.Name); + } + TypeBuilder tb = mb.DefineType("DynamicMarkerViewModel_" + (typeModelCount++).ToString(CultureInfo.InvariantCulture), + TypeAttributes.Public, + typeof(DynamicMarkerViewModel)); + + Type[] parameterTypes = { typeof(int), typeof(DataCollection), typeof(bool) }; + ConstructorBuilder ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameterTypes); + ILGenerator ctorIL = ctor.GetILGenerator(); + ctorIL.Emit(OpCodes.Ldarg_0); + ctorIL.Emit(OpCodes.Ldarg_1); + ctorIL.Emit(OpCodes.Ldarg_2); + ctorIL.Emit(OpCodes.Ldarg_3); + ctorIL.Emit(OpCodes.Call, typeof(DynamicMarkerViewModel).GetConstructor(parameterTypes)); + ctorIL.Emit(OpCodes.Ret); + for (int i = 0; i < collection.Count; i++) + { + string name = collection[i].Key; + + if (name != "X") + { + PropertyBuilder property = tb.DefineProperty(name, PropertyAttributes.None, typeof(object), null); + MethodAttributes getAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; + + MethodBuilder propertyGetAccessor = tb.DefineMethod("get_" + name, getAttr, typeof(object), null); + ILGenerator propertyGetIL = propertyGetAccessor.GetILGenerator(); + MethodInfo method = typeof(MarkerViewModel).GetMethod("GetValue"); + propertyGetIL.Emit(OpCodes.Ldarg_0); + propertyGetIL.Emit(OpCodes.Ldc_I4, i); + propertyGetIL.Emit(OpCodes.Call, method); + propertyGetIL.Emit(OpCodes.Ret); + property.SetGetMethod(propertyGetAccessor); + + PropertyBuilder propertyOriginal = tb.DefineProperty("Original" + name, PropertyAttributes.None, typeof(object), null); + MethodAttributes getAttrOriginal = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; + + MethodBuilder propertyOriginalGetAccessor = tb.DefineMethod("get_Original" + name, getAttrOriginal, typeof(object), null); + ILGenerator propertyOriginalGetIL = propertyOriginalGetAccessor.GetILGenerator(); + MethodInfo methodOriginal = typeof(MarkerViewModel).GetMethod("GetOriginalValue"); + propertyOriginalGetIL.Emit(OpCodes.Ldarg_0); + propertyOriginalGetIL.Emit(OpCodes.Ldc_I4, i); + propertyOriginalGetIL.Emit(OpCodes.Call, methodOriginal); + propertyOriginalGetIL.Emit(OpCodes.Ret); + propertyOriginal.SetGetMethod(propertyOriginalGetAccessor); + } + } + return tb.CreateType(); + } + public static Type GenerateDataCollectionType(DataCollection collection) + { + if (mb == null) + { + AssemblyName aName = new AssemblyName("InteractiveDataDisplayAssembly1"); + AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run); + mb = ab.DefineDynamicModule(aName.Name); + } + + TypeBuilder tb = mb.DefineType("DynamicDataCollection_" + (typeCollectionCount++).ToString(CultureInfo.InvariantCulture), + TypeAttributes.Public, + typeof(DynamicDataCollection)); + Type[] parameterTypes = { typeof(DataCollection) }; + ConstructorBuilder ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameterTypes); + ILGenerator ctorIL = ctor.GetILGenerator(); + ctorIL.Emit(OpCodes.Ldarg_0); + ctorIL.Emit(OpCodes.Ldarg_1); + ctorIL.Emit(OpCodes.Call, typeof(DynamicDataCollection).GetConstructor(parameterTypes)); + ctorIL.Emit(OpCodes.Ret); + for (int i = 0; i < collection.Count; i++) + { + string name = collection[i].Key; + + PropertyBuilder property = tb.DefineProperty(name, PropertyAttributes.None, typeof(DataSeries), null); + MethodAttributes getAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; + + MethodBuilder propertyGetAccessor = tb.DefineMethod("get_" + name, getAttr, typeof(DataSeries), null); + ILGenerator propertyGetIL = propertyGetAccessor.GetILGenerator(); + MethodInfo method = typeof(DynamicDataCollection).GetMethod("GetDataSeries"); + propertyGetIL.Emit(OpCodes.Ldarg_0); + propertyGetIL.Emit(OpCodes.Ldc_I4, i); + propertyGetIL.Emit(OpCodes.Call, method); + propertyGetIL.Emit(OpCodes.Ret); + property.SetGetMethod(propertyGetAccessor); + } + return tb.CreateType(); + } + } +} \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/GenericDataSeries.cs b/InteractiveDataDisplay_OneDll/src/Plots/Markers/GenericDataSeries.cs new file mode 100644 index 000000000..81f2ddc3d --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/GenericDataSeries.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Diagnostics; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Data series to define the color of markers. + /// Converts value to brush (using ) and provides properties to control this convertion. + /// + public class ColorSeries : DataSeries + { + /// + /// Initializes a new instance of the class. + /// The key for this data series is "C", default converter is , + /// default value is black color. + /// + public ColorSeries() + { + this.Key = "C"; + this.Description = "Color of the points"; + this.Data = "Black"; + this.Converter = new PaletteConverter(); + this.DataChanged += new EventHandler(ColorSeries_DataChanged); + } + + void ColorSeries_DataChanged(object sender, EventArgs e) + { + try + { + if (this.Data != null) + { + if (Palette.IsNormalized) + { + if (double.IsNaN(this.MinValue) || double.IsNaN(this.MaxValue)) + PaletteRange = Range.Empty; + else + PaletteRange = new Range(this.MinValue, this.MaxValue); + } + } + } + catch (Exception exc) + { + Debug.WriteLine("Wrong value in ColorSeries: " + exc.Message); + } + } + + #region Palette + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PaletteProperty = + DependencyProperty.Register("Palette", + typeof(IPalette), + typeof(ColorSeries), + new PropertyMetadata(InteractiveDataDisplay.WPF.Palette.Heat, (s,a) => ((ColorSeries)s).OnPalettePropertyChanged(a))); + + private void OnPalettePropertyChanged(DependencyPropertyChangedEventArgs e) + { + Palette newPalette = (Palette)e.NewValue; + (Converter as PaletteConverter).Palette = newPalette; + if (newPalette.IsNormalized && Data != null) + { + if (double.IsNaN(MinValue) || double.IsNaN(MaxValue)) + PaletteRange = Range.Empty; + else + PaletteRange = new Range(MinValue, MaxValue); + } + else if (!newPalette.IsNormalized) + PaletteRange = new Range(newPalette.Range.Min, newPalette.Range.Max); + RaiseDataChanged(); + } + + /// + /// Gets or sets the color palette for markers. Defines mapping of values to colors. + /// Is used only if the data is of any numeric type. + /// Default value is heat palette. + /// + [TypeConverter(typeof(StringToPaletteTypeConverter))] + public IPalette Palette + { + get { return (IPalette)GetValue(PaletteProperty); } + set { SetValue(PaletteProperty, value); } + } + #endregion + #region PaletteRange + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PaletteRangeProperty = + DependencyProperty.Register("PaletteRange", + typeof(Range), + typeof(ColorSeries), + new PropertyMetadata(new Range(0, 1), null)); + /// + /// Gets or sets the range which is displayed in legend if is normalized. + /// Otherwise this property is ignored. + /// Default value is (0, 1). + /// + public Range PaletteRange + { + get { return (Range)GetValue(PaletteRangeProperty); } + set { SetValue(PaletteRangeProperty, value); } + } + #endregion + } + /// + /// Data series to define the size of markers. + /// Provides properties to set minimum and maximum sizes of markers to display. + /// + public class SizeSeries : DataSeries + { + /// + /// Initializes a new instance of the class. + /// The key for this data series is "D", default value is 10. + /// + public SizeSeries() + { + this.Key = "D"; + this.Description = "Size of the points"; + this.Data = 10; + this.DataChanged += new EventHandler(SizeSeries_DataChanged); + } + void SizeSeries_DataChanged(object sender, EventArgs e) + { + try + { + if (this.Data != null) + { + Range range = Range.Empty; + if (!double.IsNaN(this.MinValue) && !double.IsNaN(this.MaxValue)) + range = new Range(this.MinValue, this.MaxValue); + + if (this.Converter != null) + (this.Converter as ResizeConverter).Origin = range; + Range = range; + First = FindFirstDataItem(); + } + } + catch (Exception exc) + { + Debug.WriteLine("Wrong value in SizeSeries: " + exc.Message); + } + } + #region Range + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RangeProperty = + DependencyProperty.Register("Range", + typeof(Range), + typeof(SizeSeries), + new PropertyMetadata(new Range(0, 1), null)); + + /// + /// Gets the actual size range of markers. + /// + public Range Range + { + get { return (Range)GetValue(RangeProperty); } + internal set { SetValue(RangeProperty, value); } + } + #endregion + #region Min, Max + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MinProperty = + DependencyProperty.Register("Min", + typeof(double), + typeof(SizeSeries), + new PropertyMetadata(Double.NaN, OnMinMaxPropertyChanged)); + /// + /// Gets or sets the minimum of size of markers to draw. + /// Default value is Double.NaN. + /// + public double Min + { + get { return (double)GetValue(MinProperty); } + set { SetValue(MinProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MaxProperty = + DependencyProperty.Register("Max", + typeof(double), + typeof(SizeSeries), + new PropertyMetadata(Double.NaN, OnMinMaxPropertyChanged)); + /// + /// Gets or sets the maximum of size of markers to draw. + /// Default value is Double.NaN. + /// + public double Max + { + get { return (double)GetValue(MaxProperty); } + set { SetValue(MaxProperty, value); } + } + private static void OnMinMaxPropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + SizeSeries m = sender as SizeSeries; + if (Double.IsNaN(m.Min) || Double.IsNaN(m.Max)) + m.Converter = null; + else + { + if (m.Converter == null) + { + if (m.Data != null) + m.Converter = new ResizeConverter(new Range(m.MinValue, m.MaxValue), + new Range(m.Min, m.Max)); + else + m.Converter = new ResizeConverter(new Range(0, 1), + new Range(m.Min, m.Max)); + } + else + (m.Converter as ResizeConverter).Resized = new Range(m.Min, m.Max); + } + m.RaiseDataChanged(); + } + #endregion + } +} \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraph.Converters.cs b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraph.Converters.cs new file mode 100644 index 000000000..ca84f33b5 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraph.Converters.cs @@ -0,0 +1,1076 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Media; +using System.Windows.Data; +using System.Diagnostics; +using System.Globalization; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Converts a value to (see method for details). + /// + public class PaletteConverter : IValueConverter + { + Palette palette = InteractiveDataDisplay.WPF.Palette.Heat; + + /// + /// Gets or sets the palette for convertion. + /// + public Palette Palette + { + set { palette = value; } + get { return palette; } + } + + /// + /// Converts values to . + /// + /// Value to convert. It can be one of the following types: + /// , , + /// defining system name or hexadecimal representation (#AARRGGBB) of color + /// or any numeric. If the value is of numeric type then is used for convertion. + /// + /// The instance of representing colors. + /// + /// from specified value. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + DataSeries data = parameter as DataSeries; + + if (value is double) + { + if (palette.IsNormalized) + palette = new Palette(false, new Range(data.MinValue, data.MaxValue), palette); + return new SolidColorBrush(Palette.GetColor((double)value)); + } + else + { + SolidColorBrush solidColorBrush = value as SolidColorBrush; + if (solidColorBrush != null) + { + return solidColorBrush; + } + else if (value is Color) + { + return new SolidColorBrush((Color)value); + } + else + { + string str = value as string; + if (str != null) + { + Color color = new Color(); + if (Char.IsLetter(str[0])) + { + bool isNamed = false; + Type colors = typeof(Colors); + foreach (var property in colors.GetProperties()) + { + if (property.Name == str) + { + color = (Color)property.GetValue(null, null); + isNamed = true; + } + } + if (!isNamed) + throw new ArgumentException("Wrong name of color"); + } + else if (str[0] == '#') + { + if (str.Length == 7) + { + color.A = 255; + color.R = System.Convert.ToByte(str.Substring(1, 2), 16); + color.G = System.Convert.ToByte(str.Substring(3, 2), 16); + color.B = System.Convert.ToByte(str.Substring(5, 2), 16); + } + else if (str.Length == 9) + { + color.A = System.Convert.ToByte(str.Substring(1, 2), 16); + color.R = System.Convert.ToByte(str.Substring(3, 2), 16); + color.G = System.Convert.ToByte(str.Substring(5, 2), 16); + color.B = System.Convert.ToByte(str.Substring(7, 2), 16); + } + else throw new ArgumentException("Wrong name of color"); + } + else throw new ArgumentException("Wrong name of color"); + return new SolidColorBrush(color); + } + else + { + double d = System.Convert.ToDouble(value, CultureInfo.InvariantCulture); + if (palette.IsNormalized) + { + if (data.MinValue == data.MaxValue) + return new SolidColorBrush(new Palette(false, + new Range(data.MinValue - 0.5, data.MaxValue + 0.5), palette).GetColor(d)); + else + return new SolidColorBrush(new Palette(false, + new Range(data.MinValue, data.MaxValue), palette).GetColor(d)); + } + return new SolidColorBrush(palette.GetColor(d)); + } + } + } + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return new SolidColorBrush(Colors.Black); + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + /// + /// A converter to translate a value into the half of another value of opposite sign. + /// + public class TranslateConverter : IValueConverter + { + /// + /// Returns an opposite signed result calculated from specified value and parameter. + /// + /// A value to convert. + /// + /// A value to add before convertion. + /// + /// Half of a sum of value and parameter with opposite sign. Value if value or parameter is null. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + if (value == null || parameter == null) + return value; + return -(System.Convert.ToDouble(value, CultureInfo.InvariantCulture) + + System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture)) / 2; + } + catch (InvalidCastException exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// Returns an opposite signed result calculated from specified value and parameter. + /// + /// A value to convert. + /// + /// A value to add before convertion. + /// + /// Sum of value and parameter with opposite sign multiplied by 2. Value if value or parameter is null. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + if (value == null || parameter == null) + return value; + return -(System.Convert.ToDouble(value, CultureInfo.InvariantCulture) + + System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture)) * 2; + } + catch (InvalidCastException exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + } + + /// + /// A converter to multiply two values. + /// + public class ValueScaleConverter : IValueConverter + { + /// + /// Multiplies a value by another. The second value should be passed as a parameter. + /// + /// First multiplier. + /// + /// Second multiplier. + /// + /// Product of value and parameter. Value if value or parameter is null. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + if (value == null || parameter == null) + return value; + return System.Convert.ToDouble(value, CultureInfo.InvariantCulture) * + System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// Devides a value by another. The second value should be passed as a parameter. + /// + /// A dividend. + /// + /// A divisor. + /// + /// Quotient of value and parameter. Value if value or parameter is null. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + if (value == null || parameter == null) + return value; + return System.Convert.ToDouble(value, CultureInfo.InvariantCulture) / + System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value back: " + exc.Message); + return 0; + } + } + } + + /// + /// A converter to transform a value from one range to another saving the ratio. + /// + public class ResizeConverter : IValueConverter + { + private Range origin; + private Range resized; + + /// + /// Initializes a new instance of class. + /// + /// The range for origin value. + /// The range for result value. + public ResizeConverter(Range origin, Range resized) + { + this.origin = origin; + this.resized = resized; + } + + /// + /// Gets or sets the range for origin value. + /// + public Range Origin + { + get { return origin; } + set { origin = value; } + } + /// + /// Gets or sets the range for result value. + /// + public Range Resized + { + get { return resized; } + set { resized = value; } + } + + /// + /// Convertes a value from range to the value in range saving the ratio. + /// + /// A value to convert. Should be of numeric type. + /// + /// + /// + /// Converted value from resized range. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + double x = System.Convert.ToDouble(value, CultureInfo.InvariantCulture); + double res = 0; + if (Double.IsNaN(origin.Min) || Double.IsNaN(origin.Max)) + res = x; + else if (origin.Min == origin.Max) + res = (resized.Max - resized.Min) / 2; + else + res = resized.Min + (resized.Max - resized.Min) * (x - origin.Min) / (origin.Max - origin.Min); + return res; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// Convertes a value from range to the value in range. + /// + /// A value to convert. Should be of numeric type. + /// + /// + /// + /// Converted value from origin range. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + try + { + double x = System.Convert.ToDouble(value, CultureInfo.InvariantCulture); + double res = 0; + if (Double.IsNaN(resized.Min) || Double.IsNaN(resized.Max)) + res = x; + else if (resized.Min == resized.Max) + res = (origin.Max - origin.Min) / 2; + else + res = origin.Min + (origin.Max - origin.Min) * (x - resized.Min) / (resized.Max - resized.Min); + return res; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value back: " + exc.Message); + return 0; + } + } + } + + /// + /// Converter, which returns input value if it is not null, parameter otherwise + /// + public class NullToDefaultConverter : IValueConverter + { + /// + /// Returns the same value if it is not null, parameter otherwise. + /// + /// + /// + /// + /// + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return parameter; + else + return value; + } + + /// + /// Returns the same value if value is not equal to parameter, null otherwise. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (Object.Equals(value, parameter)) + return null; + else + return value; + } + } + + /// + /// Default converter to transform instances of to their data bounds. + /// Default data bounds rect is point defined by X and Y data series. + /// + public class DefaultDataBoundsConverter : IValueConverter + { + /// + /// Gets data bounds of a marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Data bounds of specified marker of marker graph. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + double x = System.Convert.ToDouble(model.Sources["X"], CultureInfo.InvariantCulture); + double y = System.Convert.ToDouble(model.Sources["Y"], CultureInfo.InvariantCulture); + return new DataRect(x, y, x, y); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value to DataRect: " + exc.Message); + return new DataRect(); + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Convert back is impossible"); + } + } + + /// + /// A converter to transform instances of to their data bounds. + /// + public class ErrorBarDataBoundsConverter : IValueConverter + { + /// + /// Gets data bounds of a marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Data bounds of specified marker of error bar. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + double y = System.Convert.ToDouble(model.Sources["Y"], CultureInfo.InvariantCulture); + double h = System.Convert.ToDouble(model.Sources["H"], CultureInfo.InvariantCulture); + double x = System.Convert.ToDouble(model.Sources["X"], CultureInfo.InvariantCulture); + return new DataRect(x, y - h / 2, x, y + h / 2); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value to DataRect: " + exc.Message); + return new DataRect(); + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Cannot convert Data Rect to Marker View Model"); + } + } + + + /// + /// A converter to transform instances of to their data bounds. + /// + public class VerticalIntervalDataBoundsConverter : IValueConverter + { + /// + /// Gets data bounds of a marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Data bounds of specified marker of graph. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + double y1 = System.Convert.ToDouble(model.Sources["Y1"], CultureInfo.InvariantCulture); + double y2 = System.Convert.ToDouble(model.Sources["Y2"], CultureInfo.InvariantCulture); + double x = System.Convert.ToDouble(model.Sources["X"], CultureInfo.InvariantCulture); + return new DataRect(x, Math.Min(y1,y2), x, Math.Max(y1,y2)); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value to DataRect: " + exc.Message); + return new DataRect(); + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Cannot convert Data Rect to Marker View Model"); + } + } + + /// + /// A converter to transform instances of to their data bounds. + /// + public class BarGraphDataBoundsConverter : IValueConverter + { + /// + /// Gets data bounds of a marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Data bounds of specified marker of bar graph. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + double y = Math.Max(System.Convert.ToDouble(model.Sources["Y"], CultureInfo.InvariantCulture), 0); + double h = Math.Abs(System.Convert.ToDouble(model.Sources["Y"], CultureInfo.InvariantCulture)); + double w = System.Convert.ToDouble(model.Sources["W"], CultureInfo.InvariantCulture); + double x = System.Convert.ToDouble(model.Sources["X"], CultureInfo.InvariantCulture); + return new DataRect(x - w / 2, y - h, x + w / 2, h); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value to DataRect: " + exc.Message); + return new DataRect(); + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Cannot convert Data Rect to Marker View Model"); + } + } + + /// + /// Default converter to transform instances of to their screen thicknesses. + /// Default screen thickness is defined as a half of D series value. + /// + public class DefaultScreenThicknessConverter : IValueConverter + { + private string seriesName; + + /// + /// Initializes a new instance of class. + /// + /// + /// A of used to calculate screen thickness. + /// + protected DefaultScreenThicknessConverter(string seriesName) + { + this.seriesName = seriesName; + } + + /// + /// Initializes a new instance of class. + /// The name of used to calculate screen thickness is "D". + /// + public DefaultScreenThicknessConverter() + : this("D"){} + + /// + /// Gets screen thickness of a marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Screen thickness of specified marker of marker graph. If it is 0 than returns 5. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + double d = System.Convert.ToDouble(model[seriesName], CultureInfo.InvariantCulture); + if (d == 0) + d = 10; + return new Thickness(d / 2.0); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value to Thickness: " + exc.Message); + return new Thickness(); + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Cannot convert Thickness to Marker View Model"); + } + } + + /// + /// A converter to transform instances of to their screen thicknesses. + /// Screen thickness of each bar is defined as a half of W series value. + /// + public class ErrorBarScreenThicknessConverter : DefaultScreenThicknessConverter + { + /// + /// Initializes a new instance of class. + /// The name of used to calculate screen thickness is "W". + /// + public ErrorBarScreenThicknessConverter() + : base("W"){} + } + + #region Bar graph converters + + /// + /// A converter to get maximum between the value and 0. + /// Is used in template of to get the top y coordinate of each bar. + /// + public class BarGraphTopConverter : IValueConverter + { + /// + /// Finds maximum between the value and 0. + /// + /// A value to convert. + /// + /// + /// + /// Value if it is positive, 0 otherwise. Null if value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + return Math.Max(System.Convert.ToDouble(value, CultureInfo.InvariantCulture), 0); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + /// + /// A converter to get minimum between the value and 0. + /// Is used in template of to get the bottom y coordinate of each bar. + /// + public class BarGraphBottomConverter : IValueConverter + { + /// + /// Finds minimum between the value and 0. + /// + /// A value to convert. + /// + /// + /// + /// Value if it is negative, 0 otherwise. Null if value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + return Math.Min(0, System.Convert.ToDouble(value, CultureInfo.InvariantCulture)); + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + /// + /// A converter to get x coordinate of left side of specified marker of . + /// + public class BarGraphLeftConverter : IValueConverter + { + /// + /// Gets the value of x coordinate of left side of specified marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// X coordinate of left side of bar. Null if the value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + if (model != null) + { + return System.Convert.ToDouble(model.Sources["X"], CultureInfo.InvariantCulture) - + System.Convert.ToDouble(model.Sources["W"], CultureInfo.InvariantCulture) / 2; + } + else + return 0; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + /// + /// A converter to get x coordinate of right side of specified marker of . + /// + public class BarGraphRightConverter : IValueConverter + { + /// + /// Gets the value of x coordinate of right side of specified marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// X coordinate of left side of bar. Null if the value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + if (model != null) + { + return System.Convert.ToDouble(model.Sources["X"], CultureInfo.InvariantCulture) + + System.Convert.ToDouble(model.Sources["W"], CultureInfo.InvariantCulture) / 2; + } + else + return 0; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + #endregion + + #region ErrorBar converters + + /// + /// A converter to get the bottom y coordinate of each marker of . + /// + public class ErrorBarBottomConverter : IValueConverter + { + /// + /// Gets the value of bottom y coordinate specified marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Bottom y coordinate of bar. Null if the value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + if (model != null) + { + return System.Convert.ToDouble(model.Sources["Y"], CultureInfo.InvariantCulture) - + System.Convert.ToDouble(model.Sources["H"], CultureInfo.InvariantCulture) / 2; + } + else + return 0; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + /// + /// A converter to get the top y coordinate of each marker of . + /// + public class ErrorBarTopConverter : IValueConverter + { + /// + /// Gets the value of top y coordinate specified marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Top y coordinate of bar. Null if the value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + if (model != null) + { + return System.Convert.ToDouble(model.Sources["Y"], CultureInfo.InvariantCulture) + + System.Convert.ToDouble(model.Sources["H"], CultureInfo.InvariantCulture) / 2; + } + else + return 0; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + #endregion + + #region VerticalIntervalTopConverter + + /// + /// A converter to get the top y coordinate of each marker of . + /// + public class VerticalIntervalTopConverter : IValueConverter + { + /// + /// Gets the value of top y coordinate specified marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Top y coordinate of bar. Null if the value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + if (model != null) + { + return Math.Max(System.Convert.ToDouble(model.Sources["Y1"], CultureInfo.InvariantCulture), + System.Convert.ToDouble(model.Sources["Y2"], CultureInfo.InvariantCulture)); + } + else + return 0; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + #endregion + + #region VerticalIntervalBottomConverter + + /// + /// A converter to get the bottom y coordinate of each marker of . + /// + public class VerticalIntervalBottomConverter : IValueConverter + { + /// + /// Gets the value of bottom y coordinate specified marker of by its . + /// + /// An instance of class describing specified marker. + /// + /// + /// + /// Bottom y coordinate of bar. Null if the value is null. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (value == null) + return value; + DynamicMarkerViewModel model = value as DynamicMarkerViewModel; + if (model != null) + { + return Math.Min(System.Convert.ToDouble(model.Sources["Y1"], CultureInfo.InvariantCulture), + System.Convert.ToDouble(model.Sources["Y2"], CultureInfo.InvariantCulture)); + } + else + return 0; + } + catch (Exception exc) + { + Debug.WriteLine("Cannot convert value: " + exc.Message); + return 0; + } + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + #endregion + + /// + /// Gets triangle points by its given height. + /// Is used in templates with triangle markers. + /// + public class DoubleToTrianglePointsConverter : IValueConverter + { + /// + /// Constructs a with triangle vertexes from a given height. + /// + /// A height of triangle. + /// + /// + /// + /// A collection of triangle vertexes. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + double d = System.Convert.ToDouble(value, CultureInfo.InvariantCulture); + PointCollection pc = new PointCollection(); + pc.Add(new Point(-0.5 * d, 0.288675 * d)); + pc.Add(new Point(0, -0.711324865 * d)); + pc.Add(new Point(0.5 * d, 0.288675 * d)); + return pc; + } + + /// + /// This method is not supported. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Cannot convert point collection to double"); + } + } +} + + diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraph.cs b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraph.cs new file mode 100644 index 000000000..74007aff4 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraph.cs @@ -0,0 +1,1183 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Threading; +using System.Collections.Generic; +using System.Windows.Data; +using System.Globalization; +using System.Windows.Markup; +using System.Collections.Specialized; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// Displays supplied data series as a collection of markers. Coordinates of marker and its visual properties + /// are defined by different data series (sometimes called variables). Example is scatter plot where two data series + /// X and Y define cartesian coordinates of points on the screen. + /// Marker graph appearance is defined by three properties: , + /// and . Data series are specified using + /// property. + [ContentProperty("Sources")] + [Description("Plots scattered points")] + public partial class MarkerGraph : PlotBase + { + private List models = new List(); + private Type modelType; + private List batches = new List(); + /// Dispatchers invocation of method in background (e.g. all operations + /// with higher priority are complete. + private DispatcherTimer idleTask = new DispatcherTimer(); + private long plotVersion = 0; // Each visible change will increase plot version + private volatile bool isDrawing = false; + private long currentTaskId; + private long nextTaskId = 0; + private int markersDrawn = 0; // Number of drawn markers + /// ID of UI thread. Typically used to determine if BeginInvoke is required or not. + protected int UIThreadID; + /// + /// Initializes a new instance of the class. + /// + public MarkerGraph() + { + var rd = new ResourceDictionary(); + rd = (ResourceDictionary)Application.LoadComponent(new System.Uri("/InteractiveDataDisplay.WPF;component/Plots/Markers/MarkerTemplates.xaml", System.UriKind.Relative)); + Resources.MergedDictionaries.Add(rd); + UIThreadID = Thread.CurrentThread.ManagedThreadId; + + LegendTemplate = Resources["DefaultLegendTemplate"] as DataTemplate; + TooltipTemplate = Resources["DefaultTooltipTemplate"] as DataTemplate; + + idleTask.Interval = new TimeSpan(0); + idleTask.Tick += new EventHandler(IdleDraw); + + Sources = new DataCollection(); + } + #region MaxSnapshotSize + /// Gets or sets maximum bitmap size in pixels used as placeholder when navigating over marker graph. + /// Larger values result in crisper image when moving and scaling, + /// but higher memory consumption on marker graphs with a lot of batches. + /// Default value is 1920 x 1080. + [Category("InteractiveDataDisplay")] + public Size MaxSnapshotSize + { + get { return (Size)GetValue(MaxSnapshotSizeProperty); } + set { SetValue(MaxSnapshotSizeProperty, value); } + } + /// Identifies dependency property + public static readonly DependencyProperty MaxSnapshotSizeProperty = + DependencyProperty.Register("MaxSnapshotSize", typeof(Size), typeof(MarkerGraph), new PropertyMetadata(new Size(1920, 1080))); + #endregion + #region Templates + #region Default Templates + /// + /// Gets default template for legend. + /// + public DataTemplate DefaultLegendTemplate + { + get { return Resources["DefaultLegendTemplate"] as DataTemplate; } + } + /// + /// Gets default template for tooltip. + /// + public DataTemplate DefaultTooltipTemplate + { + get { return Resources["DefaultTooltipTemplate"] as DataTemplate; } + } + #endregion + #region Circle + /// + /// Gets template for circle markers. + /// + public DataTemplate Circle + { + get { return Resources["Circle"] as DataTemplate; } + } + /// + /// Gets simple legend template for circle markers. + /// + public DataTemplate CircleLegend + { + get { return Resources["CircleLegend"] as DataTemplate; } + } + /// + /// Gets legend template for circle markers with information about color. + /// + public DataTemplate CircleColorLegend + { + get { return Resources["CircleColorLegend"] as DataTemplate; } + } + /// + /// Gets legend template for circle markers with information about size. + /// + public DataTemplate CircleSizeLegend + { + get { return Resources["CircleSizeLegend"] as DataTemplate; } + } + /// + /// Gets legend template for circle markers with information about color and size. + /// + public DataTemplate CircleColorSizeLegend + { + get { return Resources["CircleColorSizeLegend"] as DataTemplate; } + } + #endregion + #region Box + /// + /// Gets template for box markers. + /// + public DataTemplate Box + { + get { return Resources["Box"] as DataTemplate; } + } + /// + /// Gets simple legend template for box markers. + /// + public DataTemplate BoxLegend + { + get { return Resources["BoxLegend"] as DataTemplate; } + } + /// + /// Gets legend template for box markers with information about color. + /// + public DataTemplate BoxColorLegend + { + get { return Resources["BoxColorLegend"] as DataTemplate; } + } + /// + /// Gets legend template for box markers with information about size. + /// + public DataTemplate BoxSizeLegend + { + get { return Resources["BoxSizeLegend"] as DataTemplate; } + } + /// + /// Gets legend template for box markers with information about color and size. + /// + public DataTemplate BoxColorSizeLegend + { + get { return Resources["BoxColorSizeLegend"] as DataTemplate; } + } + #endregion + #region Diamond + /// + /// Gets template for diamond markers. + /// + public DataTemplate Diamond + { + get { return Resources["Diamond"] as DataTemplate; } + } + /// + /// Gets simple legend template for diamond markers. + /// + public DataTemplate DiamondLegend + { + get { return Resources["DiamondLegend"] as DataTemplate; } + } + /// + /// Gets legend template for diamond markers with information about color. + /// + public DataTemplate DiamondColorLegend + { + get { return Resources["DiamondColorLegend"] as DataTemplate; } + } + /// + /// Gets legend template for diamond markers with information about size. + /// + public DataTemplate DiamondSizeLegend + { + get { return Resources["DiamondSizeLegend"] as DataTemplate; } + } + /// + /// Gets legend template for diamond markers with information about color and size. + /// + public DataTemplate DiamondColorSizeLegend + { + get { return Resources["DiamondColorSizeLegend"] as DataTemplate; } + } + #endregion + #region Triangle + /// + /// Gets template for triangle markers. + /// + public DataTemplate Triangle + { + get { return Resources["Triangle"] as DataTemplate; } + } + /// + /// Gets simple legend template for triangle markers. + /// + public DataTemplate TriangleLegend + { + get { return Resources["TriangleLegend"] as DataTemplate; } + } + /// + /// Gets legend template for triangle markers with information about color. + /// + public DataTemplate TriangleColorLegend + { + get { return Resources["TriangleColorLegend"] as DataTemplate; } + } + /// + /// Gets legend template for triangle markers with information about size. + /// + public DataTemplate TriangleSizeLegend + { + get { return Resources["TriangleSizeLegend"] as DataTemplate; } + } + /// + /// Gets legend template for triangle markers with information about color and size. + /// + public DataTemplate TriangleColorSizeLegend + { + get { return Resources["TriangleColorSizeLegend"] as DataTemplate; } + } + #endregion + #region Cross + /// + /// Gets template for cross markers. + /// + public DataTemplate Cross + { + get { return Resources["Cross"] as DataTemplate; } + } + /// + /// Gets simple legend template for cross markers. + /// + public DataTemplate CrossLegend + { + get { return Resources["CrossLegend"] as DataTemplate; } + } + /// + /// Gets legend template for cross markers with information about color. + /// + public DataTemplate CrossColorLegend + { + get { return Resources["CrossColorLegend"] as DataTemplate; } + } + /// + /// Gets legend template for cross markers with information about size. + /// + public DataTemplate CrossSizeLegend + { + get { return Resources["CrossSizeLegend"] as DataTemplate; } + } + /// + /// Gets legend template for cross markers with information about color and size. + /// + public DataTemplate CrossColorSizeLegend + { + get { return Resources["CrossColorSizeLegend"] as DataTemplate; } + } + #endregion + #region ErrorBar + /// + /// Gets template for error bar markers. + /// + public DataTemplate ErrorBar + { + get { return Resources["ErrorBar"] as DataTemplate; } + } + /// + /// Gets legend template for error bar markers. + /// + public DataTemplate ErrorBarLegend + { + get { return Resources["ErrorBarLegend"] as DataTemplate; } + } + #endregion + #region VerticalInterval + /// + /// Gets template for VerticalInterval markers. + /// + public DataTemplate VerticalIntervalBar + { + get { return Resources["VerticalInterval"] as DataTemplate; } + } + /// + /// Gets legend template for VerticalInterval markers. + /// + public DataTemplate VerticalIntervalLegend + { + get { return Resources["VerticalIntervalLegend"] as DataTemplate; } + } + /// + /// Gets tooltip template for VerticalInterval markers. + /// + public DataTemplate VerticalIntervalTooltip + { + get { return Resources["VerticalIntervalTooltip"] as DataTemplate; } + } + #endregion + #region BarGraph + /// + /// Gets template for bar graph. + /// + public DataTemplate BarGraph + { + get { return Resources["BarGraph"] as DataTemplate; } + } + /// + /// Gets legend template for bar graph. + /// + public DataTemplate BarGraphLegend + { + get { return Resources["BarGraphLegend"] as DataTemplate; } + } + /// + /// Gets tooltip template for bar graph. + /// + public DataTemplate BarGraphTooltip + { + get { return Resources["BarGraphTooltip"] as DataTemplate; } + } + #endregion + #region Tooltip Templates + /// + /// Gets tooltip template with information about color. + /// + public DataTemplate ColorTooltip + { + get { return Resources["ColorTooltip"] as DataTemplate; } + } + /// + /// Gets tooltip template with information about size. + /// + public DataTemplate SizeTooltip + { + get { return Resources["SizeTooltip"] as DataTemplate; } + } + /// + /// Gets tooltip template with information about color and size. + /// + public DataTemplate ColorSizeTooltip + { + get { return Resources["ColorSizeTooltip"] as DataTemplate; } + } + #endregion + #endregion + #region Sources + /// + /// Gets or sets the collection of describing appearance and behavior of markers. + /// Default value is null. + /// + [Category("Appearance")] + [Description("Collection of data series")] + public DataCollection Sources + { + get { return (DataCollection)GetValue(SourcesProperty); } + set { SetValue(SourcesProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SourcesProperty = + DependencyProperty.Register("Sources", typeof(DataCollection), typeof(MarkerGraph), new PropertyMetadata(null, OnSourcesChanged)); + + private static void OnSourcesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + MarkerGraph me = (MarkerGraph)d; + DataCollection col = (DataCollection)e.NewValue; + if (e.OldValue != null) + { + ((DataCollection)e.OldValue).CollectionChanged -= me.OnSourceCollectionChanged; + ((DataCollection)e.OldValue).DataSeriesUpdated -= me.OnSourceDataUpdated; + } + col.CollectionChanged += me.OnSourceCollectionChanged; + col.DataSeriesUpdated += me.OnSourceDataUpdated; + + // Signal about total change of collection + me.OnSourceCollectionChanged(me, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + private void OnSourceDataUpdated(object s, DataSeriesUpdatedEventArgs e) + { + StartRenderTask(true); + foreach (var b in batches) + b.AddChangedProperties(new string[] { e.Key }); + InvalidateContentBounds(); // This will cause measure and arrange pass + } + + private void OnSourceCollectionChanged(object s, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (DataSeries ds in e.NewItems) + { + ds.Owner = this; + //var pc = ds.Converter as IPlotValueConverter; + //if (pc != null) + // pc.Plot = masterField; + } + } + + modelType = null; + StartRenderTask(true); + InvalidateContentBounds(); // This will cause measure and arrange pass + } + + #endregion + #region Sample marker model + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SampleMarkerModelProperty = + DependencyProperty.Register("SampleMarkerModel", + typeof(DynamicMarkerViewModel), + typeof(MarkerGraph), + new PropertyMetadata(null)); + + /// + /// Gets or sets the sample marker shown in default legend. + /// Default value is null. + [Browsable(false)] + public DynamicMarkerViewModel SampleMarkerModel + { + get + { + return (DynamicMarkerViewModel)GetValue(SampleMarkerModelProperty); + } + set + { + SetValue(SampleMarkerModelProperty, value); + } + } + #endregion + #region MarkersBatchSize + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MarkersBatchSizeProperty = + DependencyProperty.Register("MarkersBatchSize", + typeof(int), + typeof(MarkerGraph), + new PropertyMetadata(500, OnMarkersBatchSizePropertyChanged)); + + private static void OnMarkersBatchSizePropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + MarkerGraph m = sender as MarkerGraph; + m.Children.Clear(); + m.batches.Clear(); + m.StartRenderTask(false); + m.InvalidateBounds(); // This will cause new measure cycle + } + + /// + /// Gets or sets the number of markers to draw as a single batch operation. Large values for + /// complex marker templates may decrease application responsiveness. Small values may result in + /// longer times before marker graph fully updates. + /// Default value is 500. + /// + [Category("InteractiveDataDisplay")] + public int MarkersBatchSize + { + get + { + return (int)GetValue(MarkersBatchSizeProperty); + } + set + { + SetValue(MarkersBatchSizeProperty, value); + } + } + + + #endregion + #region Stroke + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty StrokeProperty = + DependencyProperty.Register("Stroke", + typeof(SolidColorBrush), + typeof(MarkerGraph), + new PropertyMetadata(new SolidColorBrush(Colors.Black), OnStrokePropertyChanged)); + + private static void OnStrokePropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + MarkerGraph m = sender as MarkerGraph; + m.StartRenderTask(false); + foreach (var b in m.batches) + b.AddChangedProperties(new string[] { "Stroke" }); + m.InvalidateMeasure(); + } + + /// + /// Gets or sets the stroke for markers. + /// Default value is black color. + /// + [Category("Appearance")] + public SolidColorBrush Stroke + { + get { return (SolidColorBrush)GetValue(StrokeProperty); } + set { SetValue(StrokeProperty, value); } + } + + + #endregion + #region StrokeThickness + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty StrokeThicknessProperty = + DependencyProperty.Register("StrokeThickness", + typeof(double), + typeof(MarkerGraph), + new PropertyMetadata(1.0, OnStrokeThicknessPropertyChanged)); + + private static void OnStrokeThicknessPropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + MarkerGraph m = sender as MarkerGraph; + m.StartRenderTask(false); + foreach (var b in m.batches) + b.AddChangedProperties(new string[] { "StrokeThickness" }); + m.InvalidateMeasure(); + } + + /// + /// Gets or sets stroke thickness of markers. + /// Default value is 1. + /// + [Category("Appearance")] + public double StrokeThickness + { + get { return (double)GetValue(StrokeThicknessProperty); } + set { SetValue(StrokeThicknessProperty, value); } + } + + + #endregion + /// Ensures that models array is up to date according to current collection + private void SyncMarkerViewModels() + { + int markerCount = (Sources != null && Sources.IsValid) ? (Sources.MarkerCount) : (0); + if (markerCount == 0) + { + if (models.Count > 0) + models.Clear(); + if (SampleMarkerModel != null) + SampleMarkerModel = null; + } + else + { + if (modelType == null) + { + modelType = DynamicTypeGenerator.GenerateMarkerViewModelType(Sources); + models.Clear(); + } + for (int i = models.Count; i < markerCount; i++) + models.Add(Activator.CreateInstance(modelType, i, Sources, true) as DynamicMarkerViewModel); + if (models.Count > markerCount) + models.RemoveRange(markerCount, models.Count - markerCount); + if (SampleMarkerModel != models[0]) + SampleMarkerModel = models[0]; + } + } + /// + /// Returns a converter used to transform specified marker graph to its data bounds. + /// + /// A marker graph's template to search converter. + /// + protected static IValueConverter GetDataBoundsConverter(DataTemplate markerTemplate) + { + if (markerTemplate == null) + return new DefaultDataBoundsConverter(); + + var marker = (FrameworkElement)markerTemplate.LoadContent(); + + IValueConverter conv = null; + if (marker.Resources.Contains("DataBoundsFunc")) + conv = marker.Resources["DataBoundsFunc"] as IValueConverter; + if (conv == null) + conv = new DefaultDataBoundsConverter(); + return conv; + } + /// + /// Returns a converter used to transform specified marker graph to its screen thickness. + /// + /// A marker graph's template to search converter. + /// + protected static IValueConverter GetScreenThicknessConverter(DataTemplate markerTemplate) + { + if (markerTemplate == null) + return new DefaultScreenThicknessConverter(); + var marker = (FrameworkElement)markerTemplate.LoadContent(); + IValueConverter conv = null; + if (marker.Resources.Contains("ScreenBoundsFunc")) + conv = marker.Resources["ScreenBoundsFunc"] as IValueConverter; + if (conv == null) + conv = new DefaultScreenThicknessConverter(); + return conv; + } + /// This method is invoked when application is in idle state + void IdleDraw(object sender, EventArgs e) + { + bool finished = true; + SyncMarkerViewModels(); + // Remove extra batches. Such removals take a lot of time, so we do it at idle handlers + if (batches.Count > Math.Ceiling(models.Count / (double)MarkersBatchSize)) + { + var b = batches[batches.Count - 1]; + Children.Remove(b.Panel); + Children.Remove(b.Image); + batches.RemoveAt(batches.Count - 1); + finished = false; + } + // Find first batch that is ready for Image updating + var batch = batches.FirstOrDefault(b => b.PanelVersion == plotVersion && + b.Panel.Visibility == System.Windows.Visibility.Visible && + b.IsLayoutUpdated && + b.ImageVersion != plotVersion); + if (batch != null) + { + batch.PlotRect = ActualPlotRect; + var panelSize = new Size(Math.Max(1, batch.Panel.RenderSize.Width), Math.Max(1, batch.Panel.RenderSize.Height)); + var renderSize = new Size( + Math.Min(MaxSnapshotSize.Width, panelSize.Width), + Math.Min(MaxSnapshotSize.Height, panelSize.Height)); + if (batch.Content == null || batch.Content.PixelWidth != (int)Math.Ceiling(renderSize.Width) || batch.Content.PixelHeight != (int)Math.Ceiling(renderSize.Height)) + batch.Content = new RenderTargetBitmap((int)renderSize.Width, (int)renderSize.Height, 96, 96, PixelFormats.Pbgra32); + else + batch.Content.Clear(); + + ScaleTransform transform = new ScaleTransform + { + ScaleX = renderSize.Width < panelSize.Width ? renderSize.Width / panelSize.Width : 1.0, + ScaleY = renderSize.Height < panelSize.Height ? renderSize.Height / panelSize.Height : 1.0 + }; + var panel = batch.Panel; + panel.RenderTransform = transform; + batch.Content = new RenderTargetBitmap((int)renderSize.Width, (int)renderSize.Height, 96, 96, PixelFormats.Pbgra32); + batch.Content.Render(panel); + batch.ImageVersion = plotVersion; + finished = false; + } + // Find first batch that should be rendered + batch = batches.FirstOrDefault(b => b.PanelVersion != plotVersion); + if (batch != null) + { + int idx = batches.IndexOf(batch); + if (!batch.Panel.IsMaster && idx * MarkersBatchSize < models.Count) + { + if (MarkerTemplate == null) + { + batch.Panel.Children.Clear(); + } + else + { + int batchSize = Math.Min(MarkersBatchSize, models.Count - idx * MarkersBatchSize); + while (batch.Panel.Children.Count > batchSize) + batch.Panel.Children.RemoveAt(batch.Panel.Children.Count - 1); + for (int i = 0; i < batch.Panel.Children.Count; i++) + { + var mvm = models[i + idx * MarkersBatchSize]; + var fe = batch.Panel.Children[i] as FrameworkElement; + if (fe.DataContext != mvm) + fe.DataContext = mvm; + else + mvm.Notify(batch.ChangedProperties); + } + for (int i = batch.Panel.Children.Count; i < batchSize; i++) + { + var fe = MarkerTemplate.LoadContent() as FrameworkElement; + fe.DataContext = models[i + idx * MarkersBatchSize]; + if (TooltipTemplate != null) + { + var tc = new ContentControl + { + Content = fe.DataContext, + ContentTemplate = TooltipTemplate + }; + ToolTipService.SetToolTip(fe, tc); + } + batch.Panel.Children.Add(fe); + } + markersDrawn += batchSize; + } + batch.IsLayoutUpdated = false; + batch.Image.Visibility = System.Windows.Visibility.Collapsed; + batch.Image.RenderTransform = null; + batch.Panel.Visibility = System.Windows.Visibility.Visible; + batch.Panel.InvalidateMeasure(); + batch.PanelVersion = plotVersion; + batch.ClearChangedProperties(); + } + finished = false; + } + if (finished) + { + idleTask.Stop(); + isDrawing = false; + if (currentTaskId != -1) + { + //renderCompletion.OnNext(new MarkerGraphRenderCompletion(currentTaskId, markersDrawn)); + currentTaskId = -1; + } + } + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// + /// List of new values for data series. Elements from are + /// assigned to data series in the order in which data series were defined in XAML or were added + /// to collection. + /// ID of rendering task. You can subscribe to to get notified when + /// marker graph is completely updated. + /// + /// Next code will set two arrays as values of two first data series: + /// + /// markerGraph.Plot(new int[] { 1,2,3 }, new double[] { -10.0, 0, 20.0 }); + /// + /// + public long Plot(params object[] list) + { + if (list == null) + throw new ArgumentNullException("list"); + long taskId = nextTaskId++; + if (Thread.CurrentThread.ManagedThreadId == UIThreadID) + { + PlotSync(taskId, list); + } + else + { + Dispatcher.BeginInvoke(new Action(() => + { + PlotSync(taskId, list); + })); + } + return taskId; + } + /// + /// This method is similar to method but can be called only from main thread. + /// + /// ID of rendering task. + /// List of new values for data series. Elements from are + /// assigned to data series in the order in which data series were defined in XAML or were added + /// to collection. + protected void PlotSync(long taskId, params object[] list) + { + if (list == null) + throw new ArgumentNullException("list"); + + //if (currentTaskId != -1) + // renderCompletion.OnNext(new MarkerGraphRenderCompletion(currentTaskId, markersDrawn)); + for (int i = 0; i < list.Length; i++) + { + if (Sources[i].Data == list[i]) + Sources[i].Update(); + else + Sources[i].Data = list[i]; + Sources[i].Owner = this; + } + currentTaskId = taskId; + } + #region Render completion notifications + /// + /// Gets the source of notification about render completion. + /// + public IObservable RenderCompletion + { + get + { + return null; + } + } + /// + /// Gets the state indicating whether rendering is completed or not. + /// + public bool IsBusy + { + get + { + lock (this) + { + return isDrawing; + } + } + } + private void StartRenderTask(bool completeCurrent) + { + if (completeCurrent && currentTaskId != -1) + { + //renderCompletion.OnNext(new MarkerGraphRenderCompletion(currentTaskId, markersDrawn)); + currentTaskId = -1; + } + isDrawing = true; + markersDrawn = 0; + plotVersion++; + idleTask.Start(); + } + #endregion + private bool isPaddingValid = false; + private Thickness localPadding; + private bool areContentBoundsValid = false; + private DataRect localPlotBounds; + /// + /// Invalidates effective plot coordinate ranges. + /// This usually schedules recalculation of plot layout. + /// + public override void InvalidateBounds() + { + areContentBoundsValid = false; + base.InvalidateBounds(); + } + private void InvalidateContentBounds() + { + isPaddingValid = false; + InvalidateBounds(); + } + private void UpdateLocalPadding() + { + if (!isPaddingValid) + { + IValueConverter screenThicknessConverter = GetScreenThicknessConverter(MarkerTemplate); + Thickness finalThickness = new Thickness(); + SyncMarkerViewModels(); + if (!(screenThicknessConverter is DefaultScreenThicknessConverter) || Sources.ContainsSeries("D")) + for (int i = 0; i < models.Count; i++) + { + Thickness thickness = (Thickness)screenThicknessConverter.Convert(models[i], typeof(Thickness), null, CultureInfo.InvariantCulture); + finalThickness = new Thickness( + Math.Max(finalThickness.Left, thickness.Left), + Math.Max(finalThickness.Top, thickness.Top), + Math.Max(finalThickness.Right, thickness.Right), + Math.Max(finalThickness.Bottom, thickness.Bottom) + ); + } + localPadding = finalThickness; + isPaddingValid = true; + } + } + /// + /// Gets the range of {x, y} plot coordinates that corresponds to all the markers shown on the plot. + /// + /// + protected override DataRect ComputeBounds() + { + if (!areContentBoundsValid) + { + IValueConverter boundsConverter = GetDataBoundsConverter(MarkerTemplate); + SyncMarkerViewModels(); + DataRect[] rects = new DataRect[models.Count]; + for (int i = 0; i < models.Count; i++) + { + rects[i] = (DataRect)boundsConverter.Convert(models[i], typeof(DataRect), null, CultureInfo.InvariantCulture); + } + DataRect union = rects.Length > 0 ? rects[0] : new DataRect(-0.5, -0.5, 0.5, 0.5); + for (int i = 1; i < rects.Length; i++) + union.Surround(rects[i]); + if (union.XMax == union.XMin) + union = new DataRect(union.XMin - 0.5, union.YMin, union.XMin - 0.5 + union.Width + 1.0, union.YMin + union.Height); + if (union.YMax == union.YMin) + union = new DataRect(union.XMin, union.YMin - 0.5, union.XMin + union.Width, union.YMin - 0.5 + union.Height + 1.0); + localPlotBounds = union; + areContentBoundsValid = true; + } + return localPlotBounds; + } + #region Marker Template + /// + /// Gets or sets the appearance of displaying markers. + /// Predefined static resources for marker template are: + /// Box, Circle, Diamond, Triangle, Cross, ErrorBar, BarGraph and VerticalInterval. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + public DataTemplate MarkerTemplate + { + get { return (DataTemplate)GetValue(MarkerTemplateProperty); } + set { SetValue(MarkerTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MarkerTemplateProperty = + DependencyProperty.Register("MarkerTemplate", typeof(DataTemplate), + typeof(MarkerGraph), new PropertyMetadata(null, OnMarkerTemplatePropertyChanged)); + + private static void OnMarkerTemplatePropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + MarkerGraph m = sender as MarkerGraph; + foreach (var b in m.batches) + { + b.Panel.Children.Clear(); + } + m.StartRenderTask(false); + m.InvalidateBounds(); + } + + #endregion + #region Legend Template + /// + /// Gets or sets the appearance of displaying legend. + /// Predefined static resources for legend template for each type of marker templates are: + /// DefaultLegendTemplate, simple Legend, ColorLegend, SizeLegend and ColorSizeLegend. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + public DataTemplate LegendTemplate + { + get { return (DataTemplate)GetValue(LegendTemplateProperty); } + set { SetValue(LegendTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty LegendTemplateProperty = + DependencyProperty.Register("LegendTemplate", typeof(DataTemplate), + typeof(MarkerGraph), new PropertyMetadata(null)); + #endregion + #region Tooltip Template + /// + /// Gets or sets the appearance of displaying tooltip. + /// Predefined static resources for tooltip template are: + /// DefaultTooltipTemplate, ColorTooltip, SizeTooltip and ColorSizeTooltip. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + public DataTemplate TooltipTemplate + { + get { return (DataTemplate)GetValue(TooltipTemplateProperty); } + set { SetValue(TooltipTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TooltipTemplateProperty = + DependencyProperty.Register("TooltipTemplate", typeof(DataTemplate), + typeof(MarkerGraph), new PropertyMetadata(null, OnTooltipTemplatePropertyChanged)); + + private static void OnTooltipTemplatePropertyChanged(object sender, DependencyPropertyChangedEventArgs e) + { + MarkerGraph m = sender as MarkerGraph; + foreach (var b in m.batches) + { + b.Panel.Children.Clear(); + } + m.StartRenderTask(false); + m.InvalidateBounds(); + } + + #endregion + /// + /// Gets padding of marker graph. + /// + /// + protected override Thickness ComputePadding() + { + UpdateLocalPadding(); + return new Thickness( + Math.Max(Padding.Left, localPadding.Left), + Math.Max(Padding.Top, localPadding.Top), + Math.Max(Padding.Right, localPadding.Right), + Math.Max(Padding.Bottom, localPadding.Bottom)); + } + private double prevScaleX = Double.NaN, prevScaleY = Double.NaN, + prevOffsetX = Double.NaN, prevOffsetY = Double.NaN; + /// + /// Measures the size in layout required for child elements and determines a size for a . + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + // Update number of batches + SyncMarkerViewModels(); + int batchCount = (int)Math.Ceiling(models.Count / (double)MarkersBatchSize); + while (batches.Count < batchCount) + { + var b = new Batch(); + batches.Add(b); + Children.Add(b.Image); + Children.Add(b.Panel); + // Ensure inner plots have the same data transforms + b.Panel.SetBinding(PlotBase.XDataTransformProperty, + new Binding("XDataTransform") + { + Source = this + }); + b.Panel.SetBinding(PlotBase.YDataTransformProperty, + new Binding("YDataTransform") + { + Source = this + }); + } + availableSize = PerformAsMaster(availableSize); + // Request redraw of all batches if transform is changed + if (ScaleX != prevScaleX || ScaleY != prevScaleY || OffsetX != prevOffsetX || OffsetY != prevOffsetY) + { + prevScaleX = ScaleX; + prevScaleY = ScaleY; + prevOffsetX = OffsetX; + prevOffsetY = OffsetY; + idleTask.Start(); + plotVersion++; + foreach (var b in batches) + { + b.ChangedProperties = null; // Update all properties + b.Panel.Visibility = System.Windows.Visibility.Collapsed; + b.Image.Visibility = System.Windows.Visibility.Visible; + } + } + foreach (UIElement elt in Children) + { + Image img = elt as Image; + if (img != null) + { + Batch batch = img.Tag as Batch; + if (batch != null && batch.Image == elt) // Special algorithm for snapshot image + { + DataRect plotRect = batch.PlotRect; + var newLT = new Point(LeftFromX(plotRect.XMin), TopFromY(plotRect.YMax)); + var newRB = new Point(LeftFromX(plotRect.XMax), TopFromY(plotRect.YMin)); + batch.Image.Measure(new Size(newRB.X - newLT.X, newRB.Y - newLT.Y)); + } + else + elt.Measure(availableSize); + } + else + elt.Measure(availableSize); + } + return availableSize; + } + /// + /// Positions child elements and determines a size for a . + /// + /// The final area within the parent that Figure should use to arrange itself and its children. + /// The actual size used. + protected override Size ArrangeOverride(Size finalSize) + { + finalSize.Width = Math.Min(finalSize.Width, DesiredSize.Width); + finalSize.Height = Math.Min(finalSize.Height, DesiredSize.Height); + // Arranging child elements + foreach (UIElement elt in Children) + { + Image img = elt as Image; + if (img != null) + { + Batch batch = img.Tag as Batch; + if (batch != null && batch.Image == elt) // Special algorithm for snapshot image + { + DataRect plotRect = batch.PlotRect; + var newLT = new Point(LeftFromX(plotRect.XMin), TopFromY(plotRect.YMax)); + var newRB = new Point(LeftFromX(plotRect.XMax), TopFromY(plotRect.YMin)); + batch.Image.Arrange(new Rect(newLT.X, newLT.Y, newRB.X - newLT.X, newRB.Y - newLT.Y)); + } + else + elt.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + else + elt.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + if (ClipToBounds) + Clip = new RectangleGeometry + { + Rect = new Rect(new Point(0, 0), finalSize) + }; + else + Clip = null; + return finalSize; + } + } + /// + /// Information about completed rendering pass for . + /// + public class MarkerGraphRenderCompletion : RenderCompletion + { + private int markerCount; + /// + /// Initializes a new instance of class. + /// + /// ID of rendering task. + /// Number of markers actually rendered. + public MarkerGraphRenderCompletion(long taskId, int markerCount) + { + TaskId = taskId; + this.markerCount = markerCount; + } + /// + /// Gets the number of actually rendered markers. + /// + public int MarkerCount + { + get { return markerCount; } + } + } + internal class Batch + { + public Batch() + { + PanelVersion = -1; + ImageVersion = -1; + Image = new Image + { + Stretch = Stretch.Fill, + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch, + Tag = this + }; + Content = new RenderTargetBitmap(1, 1, 96, 96, PixelFormats.Pbgra32); + Panel = new SingleBatchPlot(); + Panel.LayoutUpdated += (s, a) => IsLayoutUpdated = true; + ChangedProperties = emptyArray; + } + public SingleBatchPlot Panel { get; set; } + public Image Image { get; set; } + public DataRect PlotRect { get; set; } + public RenderTargetBitmap Content + { + get { return (RenderTargetBitmap)Image.Source; } + set { Image.Source = value; } + } + public long PanelVersion { get; set; } + public long ImageVersion { get; set; } + /// List of properties that needs to be updated for this batch. Empty array means no + /// properties, null array means all properties + public string[] ChangedProperties { get; set; } + public bool IsLayoutUpdated { get; set; } + public void AddChangedProperties(IEnumerable props) + { + if (ChangedProperties == null) + ChangedProperties = props.ToArray(); + else + ChangedProperties = ChangedProperties.Concat(props.Where(p => !ChangedProperties.Contains(p))).ToArray(); + } + private static string[] emptyArray = new string[0]; + public void ClearChangedProperties() + { + ChangedProperties = emptyArray; + } + } + + /// Internal plotter with local plot rectangle control turned off + internal class SingleBatchPlot : Plot + { + public SingleBatchPlot() + { + ClipToBounds = false; + } + protected override DataRect ComputeBounds() + { + return DataRect.Empty; + } + public override void InvalidateBounds() + { + // Do nothing + } + } +} \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraphsWithTemplates.cs b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraphsWithTemplates.cs new file mode 100644 index 000000000..db67c25c9 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerGraphsWithTemplates.cs @@ -0,0 +1,743 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System.Windows; +using System.Windows.Media; +using System.ComponentModel; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// A particular case of with circle markers. + /// + [Description("Circle markers graph")] + public class CircleMarkerGraph : CartesianSizeColorMarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public CircleMarkerGraph() + { + MarkerType = new CircleMarker(); + } + } + /// + /// A particular case of with box markers. + /// + [Description("Box markers graph")] + public class BoxMarkerGraph : CartesianSizeColorMarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public BoxMarkerGraph() + { + MarkerType = new BoxMarker(); + } + } + /// + /// A particular case of with diamond markers. + /// + [Description("Diamonds markers graph")] + public class DiamondMarkerGraph : CartesianSizeColorMarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public DiamondMarkerGraph() + { + MarkerType = new DiamondMarker(); + } + } + /// + /// A particular case of with triangle markers. + /// + [Description("Triangles markers graph")] + public class TriangleMarkerGraph : CartesianSizeColorMarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public TriangleMarkerGraph() + { + MarkerType = new TriangleMarker(); + } + } + /// + /// A particular case of with cross markers. + /// + [Description("Cross markers graph")] + public class CrossMarkerGraph : CartesianSizeColorMarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public CrossMarkerGraph() + { + MarkerType = new CrossMarker(); + } + } + /// + /// Displays error bar marker graph as a particular case of . + /// Has a set of additional data series: (key 'C') and (keys 'W' and 'H') to define width + /// (in screen coordinates) and height (in plot coordinates) of each marker. + /// + [Description("Marker graph showing verical intervals (center, error)")] + public class ErrorBarGraph : CartesianMarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public ErrorBarGraph() + { + Sources.Add(new DataSeries + { + Key = "H", + Description = "Observation error", + }); + Sources.Add(new DataSeries + { + Key = "W", + Description = "Width", + Data = 5 + }); + Sources.Add(new ColorSeries()); + + MarkerTemplate = ErrorBar; + LegendTemplate = ErrorBarLegend; + TooltipTemplate = DefaultTooltipTemplate; + + Description = "Observation error"; + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// This version does not need specification of X . + /// Default value is a sequence of integers starting with zero. + /// + /// + /// Data for Y defining y coordinates of the center of each bar. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Data for of observation error or the height of each bar. + /// Can be a single value (to draw markers of one height) + /// or an array or IEnumerable (to draw markers of different heights) of any numeric type. + /// Should be defined in plot coordinates. Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + /// + public long PlotError(object y, object error) + { + return Plot(null, y, error); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// + /// + /// Data for X defining x coordinates of the center of each bar. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// + /// Data for Y defining y coordinates of the center of each bar. + /// Can be a single value (to draw one marker or markers with the same y coordinates) + /// or an array or IEnumerable (to draw markers with different y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Data for of observation error or the height of each bar. + /// Can be a single value (to draw markers of one height) or an array or IEnumerable (to draw markers of different heights) of + /// any numeric type. Should be defined in plot coordinates. Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + /// + public long PlotError(object x, object y, object error) + { + return Plot(x, y, error); + } + #region Size + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SizeProperty = DependencyProperty.Register( + "Size", typeof(object), typeof(ErrorBarGraph), new PropertyMetadata(5.0, OnSizeChanged)); + + /// + /// Gets or sets the data for width . + /// Can be a single value (to draw markers of one width) or an array or IEnumerable (to draw markers of different widths) of + /// any numeric type. Can be null then default value will be used for all markers. + /// Default value is 5. + /// + [Category("InteractiveDataDisplay")] + [Description("Screen width of intervals")] + public object Size + { + get { return (object)GetValue(SizeProperty); } + set { SetValue(SizeProperty, value); } + } + + private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ErrorBarGraph mg = (ErrorBarGraph)d; + mg.Sources["W"].Data = e.NewValue; + } + #endregion + #region Color + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register( + "Color", typeof(SolidColorBrush), typeof(ErrorBarGraph), new PropertyMetadata(new SolidColorBrush(Colors.Black), OnColorChanged)); + + /// + /// Gets or sets the data for . + /// Default value is black color. + /// + [Category("InteractiveDataDisplay")] + [Description("Color of interval markers")] + public SolidColorBrush Color + { + get { return (SolidColorBrush)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ErrorBarGraph mg = (ErrorBarGraph)d; + mg.Sources["C"].Data = e.NewValue; + } + #endregion + #region Error + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ErrorProperty = DependencyProperty.Register( + "Error", typeof(object), typeof(ErrorBarGraph), new PropertyMetadata(null, OnErrorChanged)); + + /// + /// Gets or sets the data for of observation error or the height of each bar. + /// + /// Can be a single value (to draw markers of one height) or an array or IEnumerable (to draw markers of different heigths) of + /// any numeric type. Should be defined in plot coordinates. Can be null then no markers will be drawn. + /// + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Half height of interval")] + public object Error + { + get { return (object)GetValue(ErrorProperty); } + set { SetValue(ErrorProperty, value); } + } + + private static void OnErrorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ErrorBarGraph mg = (ErrorBarGraph)d; + mg.Sources["H"].Data = e.NewValue; + } + #endregion + #region ErrorDescription + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ErrorDescriptionProperty = DependencyProperty.Register( + "ErrorDescription", typeof(string), typeof(ErrorBarGraph), + new PropertyMetadata(null, OnErrorDescriptionChanged)); + + /// + /// Gets or sets the description for of the height of bars. + /// Is frequently used in legend and tooltip. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Description of interval series")] + public string ErrorDescription + { + get { return (string)GetValue(ErrorDescriptionProperty); } + set { SetValue(ErrorDescriptionProperty, value); } + } + + private static void OnErrorDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ErrorBarGraph mg = (ErrorBarGraph)d; + mg.Sources["H"].Description = (string)e.NewValue; + mg.Sources["Y"].Description = (string)e.NewValue; + } + #endregion + } + /// + /// Displays bar chart graph as a particular case of marker graph. Has a set of data series: + /// X defining x coordinates of the center of each bar, + /// Y defining the value for each bar, + /// (key 'C') and (key 'W') to set width of each bar. + /// + [Description("Represents a bar chart")] + public class BarGraph : MarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public BarGraph() + { + Sources.Add (new DataSeries + { + Key = "X", + Description = "X" + }); + Sources.Add (new DataSeries + { + Key = "Y", + Description = "Y" + }); + Sources.Add(new DataSeries + { + Key = "W", + Description = "Width", + Data = 1.0 + }); + Sources.Add(new ColorSeries()); + Sources["C"].Data = "Blue"; + + LegendTemplate = BarGraphLegend; + TooltipTemplate = BarGraphTooltip; + MarkerTemplate = BarGraph; + + StrokeThickness = 2; // To compensate layout subpixel rounding + UseLayoutRounding = false; + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// Color and width of markers are getting from and dependency properties. + /// + /// + /// Data for X defining x coordinates of the center of each bar. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// + /// Data for Y defining the value of each bar. + /// Can be a single value (to draw one marker or markers with the same y values) + /// or an array or IEnumerable (to draw markers with different y values) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. + /// Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + /// + public long PlotBars(object x, object y) + { + return Plot(x, y, BarsWidth, Color); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// This version does not need specification of X . Default value is a sequence of integers starting with zero. + /// Color and width of markers are getting from and dependency properties. + /// + /// + /// Data for Y defining the value of each bar. + /// Can be a single value (to draw one marker or markers with the same y values) + /// or an array or IEnumerable (to draw markers with different y values) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + /// + public long PlotBars(object y) + { + return Plot(null, y, BarsWidth, Color); + } + #region X + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty XProperty = DependencyProperty.Register( + "X", typeof(object), typeof(BarGraph), new PropertyMetadata(null, OnXChanged)); + + /// + /// Gets or sets the data of X defining x coordinates of the center of bars. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw a set of different markers) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Centers of vertical bars")] + public object X + { + get { return (object)GetValue(XProperty); } + set { SetValue(XProperty, value); } + } + + private static void OnXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BarGraph mg = (BarGraph)d; + mg.Sources["X"].Data = e.NewValue; + } + #endregion + #region Y + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty YProperty = DependencyProperty.Register( + "Y", typeof(object), typeof(BarGraph), new PropertyMetadata(null, OnYChanged)); + + /// + /// Gets or sets the data for Y defining the value of bars. + /// Can be a single value (to draw one marker or markers with the same y values) + /// or an array or IEnumerable (to draw markers with different y values) of any numeric type. + /// Can be null then no markers will be drawn. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Vertical sizes of vars")] + public object Y + { + get { return (object)GetValue(YProperty); } + set { SetValue(YProperty, value); } + } + + private static void OnYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BarGraph mg = (BarGraph)d; + mg.Sources["Y"].Data = e.NewValue; + + } + #endregion + #region Description + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + "Description", typeof(string), typeof(BarGraph), new PropertyMetadata(null, OnDescriptionChanged)); + + /// + /// Gets or sets the description of Y . Is frequently used in legend and tooltip. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Text for legend and tooltip")] + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } + } + + private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BarGraph mg = (BarGraph)d; + mg.Sources["Y"].Description = (string)e.NewValue; + } + #endregion + #region BarsWidth + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BarsWidthProperty = DependencyProperty.Register( + "BarsWidth", typeof(object), typeof(BarGraph), new PropertyMetadata(1.0, OnBarsWidthChanged)); + + /// + /// Gets or sets the data for (key 'W') of width of bars in plot coordinates. + /// Can be a single value (to draw markers of one width) or an array or IEnumerable + /// (to draw markers with different widths) of any numeric type. + /// Default value is 1. + /// + [Category("InteractiveDataDisplay")] + [Description("Widths of bars in plot coordinates")] + public object BarsWidth + { + get { return (object)GetValue(BarsWidthProperty); } + set { SetValue(BarsWidthProperty, value); } + } + + private static void OnBarsWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BarGraph mg = (BarGraph)d; + mg.Sources["W"].Data = e.NewValue; + } + #endregion + #region Color + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register( + "Color", typeof(SolidColorBrush), typeof(BarGraph), new PropertyMetadata(new SolidColorBrush(Colors.Blue), OnColorChanged)); + + /// + /// Gets or sets the color of bars. + /// Default value is blue color. + /// + [Category("InteractiveDataDisplay")] + [Description("Bars fill color")] + public SolidColorBrush Color + { + get { return (SolidColorBrush)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BarGraph mg = (BarGraph)d; + mg.Sources["C"].Data = e.NewValue; + } + #endregion + } + /// + /// A particular case of marker graph that enables visualizations of vertical intervals (bars) + /// by defining top and bottom coordinates of each bar. + /// + [Description("Marker graph showing verical intervals (min,max)")] + public class VerticalIntervalGraph : MarkerGraph + { + /// + /// Initializes a new instance of the class. + /// + public VerticalIntervalGraph() + { + Sources.Add(new DataSeries + { + Key = "X", + Description = "X", + }); + Sources.Add(new DataSeries + { + Key = "Y1", + Description = "Interval bound" + }); + Sources.Add(new DataSeries + { + Key = "Y2", + Description = "Interval bound" + }); + Sources.Add(new DataSeries + { + Key = "W", + Description = "Width", + Data = 5 + }); + Sources.Add(new ColorSeries()); + + MarkerTemplate = VerticalIntervalBar; + LegendTemplate = VerticalIntervalLegend; + TooltipTemplate = VerticalIntervalTooltip; + + Description = "Interval"; + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// This version does not need specification of X . + /// Default value is a sequence of integers starting with zero. + /// Color and width of markers are getting from and dependency properties. + /// + /// + /// Data for defining y coordinates of the bottom of each bar. + /// Can be a single value (to draw one marker or markers with the same bottom y coordinates) + /// or an array or IEnumerable (to draw markers with different bottom y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Data for defining y coordinates of the top of each bar. + /// Can be a single value (to draw one marker or markers with the same top y coordinates) + /// or an array or IEnumerable (to draw markers with different top y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. + /// Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + /// + public long PlotIntervals(object y1, object y2) + { + return Plot(null, y1, y2); + } + /// + /// Updates data in data series and starts redrawing marker graph. This method returns before + /// marker graph is redrawn. This method is thread safe. + /// Color and width of markers are getting from and dependency properties. + /// + /// + /// Data for X defining x coordinates of the center of each bar. + /// Can be a single value (to draw one marker or markers with the same x coordinates) + /// or an array or IEnumerable (to draw markers with different x coordinates) of any numeric type. + /// Can be null then x coordinates will be a sequence of integers starting with zero. + /// + /// + /// Data for defining y coordinates of the bottom of each bar. + /// Can be a single value (to draw one marker or markers with the same bottom y coordinates) + /// or an array or IEnumerable (to draw markers with different bottom y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Data for defining y coordinates of the top of each bar. + /// Can be a single value (to draw one marker or markers with the same top y coordinates) + /// or an array or IEnumerable (to draw markers with different top y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + /// + /// Note that all vector data for should be of the same length. + /// Otherwise no markers will be drawn. + /// + /// ID of rendering task. You can subscribe to notification about rendering completion events + /// as observer of RenderCompletion. + /// + public long PlotIntervals(object x, object y1, object y2) + { + return Plot(x, y1, y2); + } + #region Size + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SizeProperty = DependencyProperty.Register( + "Size", typeof(object), typeof(VerticalIntervalGraph), new PropertyMetadata(5.0, OnSizeChanged)); + + /// + /// Gets or sets the data for width (key 'W'). + /// Can be a single value (to draw markers of one width) + /// or an array or IEnumerable (to draw markers of different widths) of + /// any numeric type. Can be null then no markers will be drawn. + /// Default value is 5. + /// + [Category("InteractiveDataDisplay")] + [Description("Screen width of intervals")] + public object Size + { + get { return (object)GetValue(SizeProperty); } + set { SetValue(SizeProperty, value); } + } + + private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + VerticalIntervalGraph mg = (VerticalIntervalGraph)d; + mg.Sources["W"].Data = e.NewValue; + } + #endregion + #region Color + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register( + "Color", typeof(SolidColorBrush), typeof(VerticalIntervalGraph), new PropertyMetadata(new SolidColorBrush(Colors.Black), OnColorChanged)); + + /// + /// Gets or sets the data for . + /// Default value is black color. + /// + [Category("InteractiveDataDisplay")] + [Description("Color of interval markers")] + public SolidColorBrush Color + { + get { return (SolidColorBrush)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + VerticalIntervalGraph mg = (VerticalIntervalGraph)d; + mg.Sources["C"].Data = e.NewValue; + } + #endregion + #region Y1 + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty Y1Property = DependencyProperty.Register( + "Y1", typeof(object), typeof(VerticalIntervalGraph), new PropertyMetadata(null, OnY1Changed)); + + /// + /// Gets or sets the data for (key 'Y1') defining y coordinates of the bottom of each bar. + /// Can be a single value (to draw one marker or markers with the same bottom y coordinates) + /// or an array or IEnumerable (to draw markers with different bottom y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + [Category("InteractiveDataDisplay")] + [Description("Bound of interval")] + public object Y1 + { + get { return (object)GetValue(Y2Property); } + set { SetValue(Y2Property, value); } + } + + private static void OnY1Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + VerticalIntervalGraph mg = (VerticalIntervalGraph)d; + mg.Sources["Y1"].Data = e.NewValue; + } + #endregion + #region Y2 + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty Y2Property = DependencyProperty.Register( + "Y2", typeof(object), typeof(VerticalIntervalGraph), new PropertyMetadata(null, OnY1Changed)); + + /// + /// Gets or sets the data for (key 'Y2') defining y coordinates of the top of each bar. + /// Can be a single value (to draw one marker or markers with the same top y coordinates) + /// or an array or IEnumerable (to draw markers with different top y coordinates) of any numeric type. + /// Can be null then no markers will be drawn. + /// + [Category("InteractiveDataDisplay")] + [Description("Bound of interval")] + public object Y2 + { + get { return (object)GetValue(Y2Property); } + set { SetValue(Y2Property, value); } + } + + private static void OnY2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + VerticalIntervalGraph mg = (VerticalIntervalGraph)d; + mg.Sources["Y2"].Data = e.NewValue; + } + #endregion + #region Description + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + "Description", typeof(string), typeof(VerticalIntervalGraph), + new PropertyMetadata(null, OnDescriptionChanged)); + + /// + /// Gets or sets the description for of bounds of intervals. + /// Default value is null. + /// + [Category("InteractiveDataDisplay")] + [Description("Description of interval series")] + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } + } + + private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + VerticalIntervalGraph mg = (VerticalIntervalGraph)d; + mg.Sources["Y1"].Description = (string)e.NewValue; + mg.Sources["Y2"].Description = (string)e.NewValue; + } + #endregion + } +} \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerTemplates.xaml b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerTemplates.xaml new file mode 100644 index 000000000..4f3374c97 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Plots/Markers/MarkerTemplates.xaml @@ -0,0 +1,756 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InteractiveDataDisplay_OneDll/src/Properties/AssemblyInfo.cs b/InteractiveDataDisplay_OneDll/src/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..41a980404 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +//// General Information about an assembly is controlled through the following +//// set of attributes. Change these attribute values to modify the information +//// associated with an assembly. +//[assembly: AssemblyTitle("InteractiveDataDisplay.WPF")] +//[assembly: AssemblyDescription("")] +//[assembly: AssemblyConfiguration("")] +//[assembly: AssemblyCompany("")] +//[assembly: AssemblyProduct("InteractiveDataDisplay.WPF")] +//[assembly: AssemblyCopyright("Copyright © 2017")] +//[assembly: AssemblyTrademark("")] +//[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] +[assembly: CLSCompliant(true)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/InteractiveDataDisplay_OneDll/src/Themes/Generic.xaml b/InteractiveDataDisplay_OneDll/src/Themes/Generic.xaml new file mode 100644 index 000000000..7a1788d1b --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Themes/Generic.xaml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + diff --git a/InteractiveDataDisplay_OneDll/src/Transforms/DataTransforms.cs b/InteractiveDataDisplay_OneDll/src/Transforms/DataTransforms.cs new file mode 100644 index 000000000..a0779071c --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/Transforms/DataTransforms.cs @@ -0,0 +1,231 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System; +using System.Windows; + +namespace InteractiveDataDisplay.WPF { + /// + /// Performs transformations between data values and plot coordinates. + /// + public abstract class DataTransform : DependencyObject + { + /// Gets range of valid data values. method returns NaN for + /// values outside this range. + /// + public Range Domain { get; private set; } + + /// + /// Initializes a new instance of class. + /// + /// A range of valid data. + protected DataTransform(Range domain) + { + Domain = domain; + } + + /// + /// Converts value from data to plot coordinates. + /// + /// A value in data coordinates. + /// Value converted to plot coordinates or NaN if + /// falls outside of . + public abstract double DataToPlot(double dataValue); + + /// + /// Converts value from plot coordinates to data. + /// + /// A value in plot coordinates. + /// Value converted to data coordinates or NaN if no value in data coordinates + /// matches . + public abstract double PlotToData(double plotValue); + + /// Identity transformation + public static readonly DataTransform Identity = new IdentityDataTransform(); + } + + /// + /// Provides identity transformation between data and plot coordinates. + /// + public class IdentityDataTransform : DataTransform + { + /// + /// Initializes a new instance of class. + /// + public IdentityDataTransform() + : base(new Range(double.MinValue, double.MaxValue)) + { + } + + /// + /// Returns a value in data coordinates without convertion. + /// + /// A value in data coordinates. + /// + public override double DataToPlot(double dataValue) + { + return dataValue; + } + + /// + /// Returns a value in plot coordinates without convertion. + /// + /// A value in plot coordinates. + /// + public override double PlotToData(double plotValue) + { + return plotValue; + } + } + + /// + /// Represents a mercator transform, used in maps. + /// Transforms y coordinates. + /// + public sealed class MercatorTransform : DataTransform + { + /// + /// Initializes a new instance of the class. + /// + public MercatorTransform() + : base(new Range(-85, 85)) + { + CalcScale(maxLatitude); + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximal latitude. + public MercatorTransform(double maxLatitude) + : base(new Range(-maxLatitude, maxLatitude)) + { + this.maxLatitude = maxLatitude; + CalcScale(maxLatitude); + } + + private void CalcScale(double inputMaxLatitude) + { + double maxLatDeg = inputMaxLatitude; + double maxLatRad = maxLatDeg * Math.PI / 180; + scale = maxLatDeg / Math.Log(Math.Tan(maxLatRad / 2 + Math.PI / 4)); + } + + private double scale; + /// + /// Gets the scale. + /// + /// The scale. + public double Scale + { + get { return scale; } + } + + private double maxLatitude = 85; + /// + /// Gets the maximal latitude. + /// + /// The max latitude. + public double MaxLatitude + { + get { return maxLatitude; } + } + + /// + /// Converts value from mercator to plot coordinates. + /// + /// A value in mercator coordinates. + /// Value converted to plot coordinates. + public override double DataToPlot(double dataValue) + { + if (-maxLatitude <= dataValue && dataValue <= maxLatitude) + { + dataValue = scale * Math.Log(Math.Tan(Math.PI * (dataValue + 90) / 360)); + } + return dataValue; + } + + /// + /// Converts value from plot to mercator coordinates. + /// + /// A value in plot coordinates. + /// Value converted to mercator coordinates. + public override double PlotToData(double plotValue) + { + if (-maxLatitude <= plotValue && plotValue <= maxLatitude) + { + double e = Math.Exp(plotValue / scale); + plotValue = 360 * Math.Atan(e) / Math.PI - 90; + } + return plotValue; + } + } + + /// + /// Provides linear transform u = * d + from data value d to plot coordinate u. + /// + public sealed class LinearDataTransform : DataTransform + { + /// + /// Gets or sets the scale factor. + /// + public double Scale + { + get { return (double)GetValue(ScaleProperty); } + set { SetValue(ScaleProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ScaleProperty = + DependencyProperty.Register("Scale", typeof(double), typeof(LinearDataTransform), new PropertyMetadata(1.0)); + + /// + /// Gets or sets the distance to translate an value. + /// + public double Offset + { + get { return (double)GetValue(OffsetProperty); } + set { SetValue(OffsetProperty, value); } + } + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty OffsetProperty = + DependencyProperty.Register("Offset", typeof(double), typeof(LinearDataTransform), new PropertyMetadata(0.0)); + + /// + /// Initializes a new instance of the class. + /// + public LinearDataTransform() + : base(new Range(double.MinValue, double.MaxValue)) + { + } + + /// + /// Transforms a value according to defined and . + /// + /// A value in data coordinates. + /// Transformed value. + public override double DataToPlot(double dataValue) + { + return dataValue * Scale + Offset; + } + + /// + /// Returns a value in data coordinates from its transformed value. + /// + /// Transformed value. + /// Original value or NaN if is 0. + public override double PlotToData(double plotValue) + { + if (Scale != 0) + { + return (plotValue - Offset) / Scale; + } + else + return double.NaN; + } + } +} + diff --git a/InteractiveDataDisplay_OneDll/src/VerticalContentControl.cs b/InteractiveDataDisplay_OneDll/src/VerticalContentControl.cs new file mode 100644 index 000000000..894163ef4 --- /dev/null +++ b/InteractiveDataDisplay_OneDll/src/VerticalContentControl.cs @@ -0,0 +1,69 @@ +// Copyright © Microsoft Corporation. All Rights Reserved. +// Licensed under the MIT License. + +using System.Windows; +using System.Windows.Controls; +using System.ComponentModel; +using System.Windows.Media; + +namespace InteractiveDataDisplay.WPF +{ + /// + /// Provides control that presents its content rotated by 90 degrees counterclockwise + /// + [TemplatePart(Name = "Presenter", Type = typeof(ContentPresenter))] + [Description("Presents content vertically")] + public class VerticalContentControl : ContentControl + { + private FrameworkElement contentPresenter; + + /// + /// Initializes new instance of class + /// + public VerticalContentControl() + { + DefaultStyleKey = typeof(VerticalContentControl); + } + + /// + /// Invoked whenever application code or internal processes call ApplyTemplate + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + contentPresenter = GetTemplateChild("Presenter") as FrameworkElement; + } + + /// + /// Positions child elements and determines a size for a VerticalContentControl + /// + /// The final area within the parent that VerticalContentControl should use to arrange itself and its children + /// The actual size used + protected override Size ArrangeOverride(Size finalSize) + { + if (contentPresenter != null) + contentPresenter.Arrange(new Rect(new Point(0, 0), new Size(finalSize.Height, finalSize.Width))); + return finalSize; + } + + /// + /// Measures the size in layout required for child elements and determines a size for the VerticalContentControl. + /// + /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + if (contentPresenter == null) + return Size.Empty; + contentPresenter.Measure(new Size(availableSize.Height, availableSize.Width)); + contentPresenter.RenderTransform = new TransformGroup + { + Children = new TransformCollection(new Transform[] { + new RotateTransform { Angle = -90 }, + new TranslateTransform { Y = contentPresenter.DesiredSize.Width } + }) + }; + return new Size(contentPresenter.DesiredSize.Height, contentPresenter.DesiredSize.Width); + } + } +} \ No newline at end of file diff --git a/InteractiveDataDisplay_OneDll/src/sn.key.snk b/InteractiveDataDisplay_OneDll/src/sn.key.snk new file mode 100644 index 000000000..9e87102d0 Binary files /dev/null and b/InteractiveDataDisplay_OneDll/src/sn.key.snk differ diff --git a/Release/pabcversion.txt b/Release/pabcversion.txt index fe373a926..21f2c9521 100644 --- a/Release/pabcversion.txt +++ b/Release/pabcversion.txt @@ -1 +1 @@ -3.8.0.2932 +3.8.0.2934 diff --git a/ReleaseGenerators/PascalABCNET_end.nsh b/ReleaseGenerators/PascalABCNET_end.nsh index ee2672030..0ab74d4e3 100644 --- a/ReleaseGenerators/PascalABCNET_end.nsh +++ b/ReleaseGenerators/PascalABCNET_end.nsh @@ -12,6 +12,10 @@ ExecWait '"$INSTDIR\ExecHide.exe" "$INSTDIR\gacutil.exe" /u "nunit.framework"' ExecWait '"$INSTDIR\ExecHide.exe" "$INSTDIR\gacutil.exe" /i "$INSTDIR\Lib\nunit.framework.dll"' + ExecWait '"$INSTDIR\ExecHide.exe" "$INSTDIR\gacutil.exe" /u "InteractiveDataDisplay.WPF"' + ExecWait '"$INSTDIR\ExecHide.exe" "$INSTDIR\gacutil.exe" /i "$INSTDIR\Lib\InteractiveDataDisplay.WPF.dll"' + + ;\MikhailoMMX Delete "$INSTDIR\gacutil.exe" Delete "$INSTDIR\gacutil.exe.config" diff --git a/ReleaseGenerators/PascalABCNET_version.nsh b/ReleaseGenerators/PascalABCNET_version.nsh index 27853004b..151917363 100644 --- a/ReleaseGenerators/PascalABCNET_version.nsh +++ b/ReleaseGenerators/PascalABCNET_version.nsh @@ -1 +1 @@ -!define VERSION '3.8.0.2932' +!define VERSION '3.8.0.2934' diff --git a/ReleaseGenerators/RebuildStandartModules.pas b/ReleaseGenerators/RebuildStandartModules.pas index 1f70117c7..1fbbb25c9 100644 --- a/ReleaseGenerators/RebuildStandartModules.pas +++ b/ReleaseGenerators/RebuildStandartModules.pas @@ -15,7 +15,7 @@ ABCDatabases,PT4Databases,BlockFileOfT,Controls, OpenCLABC, OpenCL, OpenGLABC, OpenGL, - BBCMicroBit, School, SF, TwoPanelsWindow, NUnitABC + BBCMicroBit, School, SF, TwoPanelsWindow, NUnitABC, PlotWPF ; begin diff --git a/ReleaseGenerators/sect_Core.nsh b/ReleaseGenerators/sect_Core.nsh index b7e122af4..0d3b33a92 100644 --- a/ReleaseGenerators/sect_Core.nsh +++ b/ReleaseGenerators/sect_Core.nsh @@ -138,11 +138,14 @@ File ..\bin\Lib\Turtle.pcu File ..\bin\Lib\TwoPanelsWindow.pcu File ..\bin\Lib\NUnitABC.pcu + File ..\bin\Lib\PlotWPF.pcu + File ..\bin\Lib\PABCRtl.dll File ..\bin\Lib\HelixToolkit.Wpf.dll File ..\bin\Lib\HelixToolkit.dll File ..\bin\Lib\nunit.framework.dll + File ..\bin\Lib\InteractiveDataDisplay.WPF.dll ${AddFile} "__RedirectIOMode.pcu" ${AddFile} "__RunMode.pcu" @@ -210,12 +213,14 @@ ${AddFile} "Turtle.pcu" ${AddFile} "TwoPanelsWindow.pcu" ${AddFile} "NUnitABC.pcu" + ${AddFile} "PlotWPF.pcu" ${AddFile} "PABCRtl.dll" ${AddFile} "HelixToolkit.Wpf.dll" ${AddFile} "HelixToolkit.dll" ${AddFile} "nunit.framework.dll" + ${AddFile} "InteractiveDataDisplay.WPF.dll" ${AddFile} "PABCRtl.pdb" @@ -232,6 +237,9 @@ Call NGEN Push "Lib\nunit.framework.dll" Call NGEN + Push "Lib\InteractiveDataDisplay.WPF.dll" + Call NGEN + SetOutPath "$INSTDIR\LibSource" File ..\bin\Lib\__RedirectIOMode.pas @@ -298,6 +306,7 @@ File ..\bin\Lib\Turtle.pas File ..\bin\Lib\TwoPanelsWindow.pas File ..\bin\Lib\NUnitABC.pas + File ..\bin\Lib\PlotWPF.pas File ..\bin\Lib\__RedirectIOMode.vb File ..\bin\Lib\VBSystem.vb @@ -366,6 +375,7 @@ ${AddFile} "Turtle.pas" ${AddFile} "TwoPanelsWindow.pas" ${AddFile} "NUnitABC.pas" + ${AddFile} "PlotWPF.pas" ${AddFile} "__RedirectIOMode.vb" diff --git a/_RegisterHelixNUnit.bat b/_RegisterHelixNUnit.bat index eb29a0b51..8f251ee01 100644 --- a/_RegisterHelixNUnit.bat +++ b/_RegisterHelixNUnit.bat @@ -3,4 +3,6 @@ ReleaseGenerators\gacutil.exe /i bin\Lib\HelixToolkit.Wpf.dll ReleaseGenerators\gacutil.exe /u HelixToolkit ReleaseGenerators\gacutil.exe /i bin\Lib\HelixToolkit.dll ReleaseGenerators\gacutil.exe /u nunit.framework -ReleaseGenerators\gacutil.exe /i bin\Lib\nunit.framework.dll \ No newline at end of file +ReleaseGenerators\gacutil.exe /i bin\Lib\nunit.framework.dll +ReleaseGenerators\gacutil.exe /u InteractiveDataDisplay.WPF +ReleaseGenerators\gacutil.exe /i bin\Lib\InteractiveDataDisplay.WPF.dll diff --git a/bin/Lib/Controls.pas b/bin/Lib/Controls.pas index d0358ebc1..2826b801b 100644 --- a/bin/Lib/Controls.pas +++ b/bin/Lib/Controls.pas @@ -44,19 +44,52 @@ procedure Invoke(d: System.Delegate; params args: array of object); procedure InvokeP(p: procedure(r: real); r: real); type + ///!# + /// Базовый класс для всех элементов управления + CommonElementWPF = class + protected + element: FrameworkElement; + procedure Init0(el: FrameworkElement; width: real := 0; x: real := -1; y: real := -1); + private + procedure SetWP(w: real) := element.Width := w; + procedure SetHP(h: real) := element.Height := h; + procedure SetMPB(m: real) := element.Margin := new Thickness(0,0,0,m); + procedure SetMPs(m: (real, real, real, real)) := element.Margin := new Thickness(m[0],m[1],m[2],m[3]); + procedure SetTTP(s: string) := element.Tooltip := s; + procedure SetEP(enabled: boolean) := element.IsEnabled := enabled; + public + /// Ширина элемента управления + property Width: real read InvokeReal(()->element.Width) write Invoke(SetWP, value); + /// Высота элемента управления + property Height: real read InvokeReal(()->element.Height) write Invoke(SetHP, value); virtual; + /// Отступ элемента управления + //property Margin: real read InvokeReal(()->element.Margin.Bottom) write Invoke(SetMPB, value); + /// Нижний отступ элемента управления + property BottomMargin: real read InvokeReal(()->element.Margin.Bottom) write Invoke(SetMPB, value); + /// Отступы элемента управления + property Margins: (real,real,real,real) + //read InvokeReal(()->element.Margin.Bottom) + write Invoke(SetMPs, value); + /// Активирован ли элемент управления + property Enabled: boolean read InvokeBoolean(()->element.IsEnabled) write Invoke(SetEP, value); + /// Всплывающая подсказка + property Tooltip: string read Invokestring(()->element.ToolTip as string) write Invoke(SetTTP, value); + end; + ///!# /// Базовый класс для панелей - PanelWPF = class + PanelWPF = class(CommonElementWPF) public fsize: real := 12; private - p: StackPanel; bb: Border; + property p: StackPanel read element as StackPanel; procedure CreateP(wh: real; d: Dock; c: Color; InternalMargin: real); begin bb := new Border(); bb.Background := new SolidColorBrush(c); - p := new StackPanel; + var p := new StackPanel; + element := p; bb.Child := p; p.Background := GetBrush(c); p.Margin := new System.Windows.Thickness(InternalMargin); @@ -149,49 +182,6 @@ type constructor Create(width: real := 150) := Create(width,PanelsColor,10); end; - ///!# - /// Базовый класс для всех элементов управления - CommonElementWPF = class - private - element: FrameworkElement; - procedure Init0(el: FrameworkElement; width: real := 0; x: real := -1; y: real := -1); - begin - element := el; - element.Focusable := False; - if __ActivePanelInternal is StackPanel(var sp) then - if sp.Orientation = Orientation.Vertical then - element.Margin := new Thickness(0,0,0,GlobalHMargin); - //else element.Margin := new Thickness(0,0,GlobalHMargin,0); - if x<>-1 then - begin - element.SetLeft(x); - element.SetTop(y); - end; - if width>0 then - element.Width := width; - __ActivePanelInternal.Children.Add(element); - if __ActivePanelInternal.Tag is PanelWPF(var pwpf) then - PanelWPF.SetFSzElement(element,pwpf.fsize); - end; - - procedure SetWP(w: real) := element.Width := w; - procedure SetHP(h: real) := element.Height := h; - procedure SetMP(m: real) := element.Margin := new Thickness(0,0,0,m); - procedure SetTTP(s: string) := element.Tooltip := s; - procedure SetEP(enabled: boolean) := element.IsEnabled := enabled; - public - /// Ширина элемента управления - property Width: real read InvokeReal(()->element.Width) write Invoke(SetWP, value); - /// Высота элемента управления - property Height: real read InvokeReal(()->element.Height) write Invoke(SetHP, value); virtual; - /// Нижний отступ элемента управления - property Margin: real read InvokeReal(()->element.Margin.Bottom) write Invoke(SetMP, value); - /// Активирован ли элемент управления - property Enabled: boolean read InvokeBoolean(()->element.IsEnabled) write Invoke(SetEP, value); - /// Всплывающая подсказка - property Tooltip: string read Invokestring(()->element.ToolTip as string) write Invoke(SetTTP, value); - end; - CommonControlWPF = class(CommonElementWPF) private property Control: GControl read element as GControl; @@ -203,7 +193,7 @@ type /// Имя шрифта property FontName: string write Invoke(SetFontNameP, value); virtual; end; - + ///!# ClickableControlWPF = class(CommonControlWPF) protected @@ -246,6 +236,41 @@ type property Color: GColor read Invoke&(GetColorP) write Invoke(SetColorP, value); end; + /// Элемент управления Grid + GridWPF = class(CommonElementWPF) + private + property gr: UniformGrid read element as UniformGrid; + procedure CreatePP(rows,columns: integer); // только для добавления как главной панели + begin + element := new UniformGrid(); + gr.Rows := rows; + gr.Columns := columns; + element.Focusable := False; + MainDockPanel.children.RemoveAt(MainDockPanel.children.Count-1); + MainDockPanel.children.Add(gr); + end; + procedure CreateP(rows,columns,InternalMargin: integer); + begin + element := new UniformGrid(); + element.Focusable := False; + gr.Rows := rows; + gr.Columns := columns; + __ActivePanelInternal.Children.Add(gr); + _ActivePanel := gr; + _ActivePanel.Tag := Self; + Self.InternalMargin := InternalMargin; + end; + constructor (i: boolean; rows,columns: integer) := Invoke(CreatePP,rows,columns); // фиктивный первый параметр + public + constructor (rows: integer := 1; columns: integer := 1; InternalMargin: integer := 5) + := Invoke(CreateP,rows,columns,InternalMargin); + property Rows: integer read InvokeInteger(()->gr.Rows) + write Invoke(procedure->if value>0 then gr.Rows := value); + property Columns: integer read InvokeInteger(()->gr.Columns) + write Invoke(procedure->if value>0 then gr.Columns := value); + auto property InternalMargin: real := 5; + end; + /// Элемент управления "Изображение" ImageWPF = class(CommonControlWPF) private @@ -1092,6 +1117,7 @@ type static function AsCanvas: CanvasWPF := new CanvasWPF(0); static function AsListView: ListViewWPF := new ListViewWPF(0); static function AsTextBox: TextBoxNoTitleWPF := new TextBoxNoTitleWPF(0); + static function AsGrid(rows: integer := 1; columns: integer := 1): GridWPF := new GridWPF(true,rows,columns); end; ///!# @@ -1242,9 +1268,13 @@ function StatusBar(height: real := 24; itemWidth: real := 0): StatusBarWPF; procedure EmptyBlock(sz: integer := 16); /// Установка активной панели, на которую размещаются элементы управления -procedure SetActivePanel(p: PanelWPF); +procedure SetActivePanel(p: CommonElementWPF); +/// Толщина границ элемента +function Margins(l,t,r,b: real): (real,real,real,real); +/// Толщина границ элемента +function Margins(all: real): (real,real,real,real); /// Графическое окно //function Window: WindowType; @@ -1392,9 +1422,10 @@ begin end); end; -procedure SetActivePanel(p: PanelWPF); +procedure SetActivePanel(p: CommonElementWPF); begin - __SetActivePanelInternal(p.p) + if p.element is GPanel then + __SetActivePanelInternal(p.element as GPanel) end; procedure SetActivePanelInit; @@ -1410,6 +1441,33 @@ begin Result := _ActivePanel; end; +function Margins(l,t,r,b: real): (real,real,real,real) := (l,t,r,b); + +function Margins(all: real): (real,real,real,real) := (all,all,all,all); + +procedure CommonElementWPF.Init0(el: FrameworkElement; width: real; x: real; y: real); +begin + element := el; + element.Focusable := False; + if __ActivePanelInternal is StackPanel(var sp) then + if sp.Orientation = Orientation.Vertical then + element.Margin := new Thickness(0,0,0,GlobalHMargin); + //else element.Margin := new Thickness(0,0,GlobalHMargin,0); + if x<>-1 then + begin + element.SetLeft(x); + element.SetTop(y); + end; + if width>0 then + element.Width := width; + __ActivePanelInternal.Children.Add(element); + if __ActivePanelInternal.Tag is PanelWPF(var pwpf) then + PanelWPF.SetFSzElement(element,pwpf.fsize); + if __ActivePanelInternal.Tag is GridWPF(var grwpf) then + element.Margin := new Thickness(grwpf.InternalMargin); +end; + + var ///-- __initialized := false; diff --git a/bin/Lib/InteractiveDataDisplay.WPF.dll b/bin/Lib/InteractiveDataDisplay.WPF.dll new file mode 100644 index 000000000..a075fb751 Binary files /dev/null and b/bin/Lib/InteractiveDataDisplay.WPF.dll differ diff --git a/bin/Lib/PlotWPF.pas b/bin/Lib/PlotWPF.pas new file mode 100644 index 000000000..852130c4b --- /dev/null +++ b/bin/Lib/PlotWPF.pas @@ -0,0 +1,193 @@ +{$reference InteractiveDataDisplay.WPF.dll} +unit PlotWPF; + +uses GraphWPF; +uses GraphWPFBase; +uses Controls; + +uses InteractiveDataDisplay.WPF; +uses System.Windows.Controls; +uses System.Windows.Media; +uses System.Windows; + + +function RGB(r,g,b: byte) := Color.Fromrgb(r, g, b); +function ARGB(a,r,g,b: byte) := Color.FromArgb(a, r, g, b); +function GrayColor(b: byte): Color := RGB(b, b, b); +function RandomColor := RGB(PABCSystem.Random(256), PABCSystem.Random(256), PABCSystem.Random(256)); +function EmptyColor: Color := ARGB(0,0,0,0); +function Pnt(x,y: real) := new Point(x,y); +function Rect(x,y,w,h: real) := new System.Windows.Rect(x,y,w,h); + +type + GridWPF = Controls.GridWPF; + Colors = System.Windows.Media.Colors; + MarkerType = (Circle,Box,Triangle,Diamond,Cross); + MarkerTyp = MarkerType; + + ChartGr = class + private + linegr: PlotBase; + constructor (linegr: PlotBase); + begin + Self.linegr := linegr; + end; + public + procedure ChangeData(a,b: real; f: real -> real); + begin + Invoke(()-> begin + var xx := PartitionPoints(a,b,200); + var yy := xx.Select(f); + ChangeData(xx,yy) + end + ); + end; + procedure ChangeData(xx,yy: sequence of real); + begin + Invoke(()-> begin + match linegr with + LineGraph(lg): lg.Plot(xx,yy); + CircleMarkerGraph(mg): mg.Plot(xx,yy); + end; + end + ); + end; + property Color: GColor + read Invoke&(()->begin + if linegr is LineGraph(var lg) then + Result := (lg.Stroke as SolidColorBrush).Color + else if linegr is CircleMarkerGraph(var mg) then + Result := (mg.Color as SolidColorBrush).Color + end) + write Invoke(procedure -> begin + match linegr with + LineGraph(lg): lg.Stroke := new SolidColorBrush(value); + CircleMarkerGraph(mg): mg.Color := new SolidColorBrush(value); + end; + end); + property Thickness: real + write Invoke(procedure -> begin + match linegr with + LineGraph(lg): lg.StrokeThickness := value; + MarkerGraph(mg): mg.StrokeThickness := value; + end; + end); + property MarkerType: PlotWPF.MarkerType + write Invoke(procedure -> begin + match linegr with + CircleMarkerGraph(mg): + case value of + MarkerTyp.Circle: mg.MarkerType := new CircleMarker(); + MarkerTyp.Box: mg.MarkerType := new BoxMarker(); + MarkerTyp.Triangle: mg.MarkerType := new TriangleMarker(); + MarkerTyp.Diamond: mg.MarkerType := new DiamondMarker(); + MarkerTyp.Cross: mg.MarkerType := new CrossMarker(); + end; + end; + end); + end; + + BaseGraphWPF = class(CommonControlWPF) + private + grd: Grid; + property chrt: Chart read element as Chart; + procedure Create0; + begin + var c := new Chart(); + c.LegendVisibility := Visibility.Hidden; + grd := new Grid; + c.Content := grd; + element := c; + end; + procedure CreateLineP(a,b: real; f: real -> real; color: GColor); + begin + Create0; + AddLineGraph(a,b,f,color); + Init0(element,0,-1,-1); + end; + procedure CreateLineP1(xx,yy: sequence of real; color: GColor); + begin + Create0; + AddLineGraph(xx,yy,color); + Init0(element,0,-1,-1); + end; + procedure CreateMarkerP1(xx,yy: sequence of real; color: GColor; MType: MarkerType; size: real); + begin + Create0; + AddMarkerGraph(xx,yy,color,MType,size); + Init0(element,0,-1,-1); + end; + public + property Title: string read InvokeString(()->chrt.Title.ToString) + write Invoke(procedure -> chrt.Title := value); + property PlotRect: GRect + read Invoke&(()->GraphWPF.Rect(chrt.PlotOriginX,chrt.PlotOriginY,chrt.PlotWidth,chrt.PlotHeight)) + write Invoke(procedure -> begin + chrt.PlotOriginX := value.x; + chrt.PlotOriginY := value.y; + chrt.PlotWidth := value.width; + chrt.PlotHeight := value.height; + end); + property Graph[i: integer]: ChartGr + read new ChartGr(Invoke&(()->grd.Children[i] as PlotBase)); + procedure AddLineGraph(xx,yy: sequence of real; color: GColor := Colors.Red); + begin + Invoke(procedure -> begin + var linegr := new LineGraph(); + grd.Children.Add(linegr); + + linegr.Stroke := new SolidColorBrush(color); + linegr.StrokeThickness := 1.4; + linegr.Plot(xx, yy); + end); + end; + procedure AddLineGraph(a,b: real; f: real -> real; color: GColor := Colors.Red); + begin + var x := PartitionPoints(a,b,200); + var y := x.Select(f); + AddLineGraph(x,y,color); + end; + procedure AddMarkerGraph(xx,yy: sequence of real; color: GColor := Colors.Aqua; MType: MarkerType := MarkerType.Circle; MarkerSize: real := 10); + begin + Invoke(procedure -> begin + var markergr := new CircleMarkerGraph(); + grd.Children.Add(markergr); + + markergr.Color := new SolidColorBrush(color); + markergr.StrokeThickness := 1.4; + case MType of + MarkerType.Circle: markergr.MarkerType := new CircleMarker(); + MarkerType.Box: markergr.MarkerType := new BoxMarker(); + MarkerType.Triangle: markergr.MarkerType := new TriangleMarker(); + MarkerType.Diamond: markergr.MarkerType := new DiamondMarker(); + MarkerType.Cross: markergr.MarkerType := new CrossMarker(); + end; + markergr.PlotSize(xx, yy, MarkerSize); + end); + end; + procedure AddMarkerGraph(xx,yy: sequence of real; MType: MarkerType) := + AddMarkerGraph(xx,yy,Colors.Aqua,MType,10); + procedure AddMarkerGraph(xx,yy: sequence of real; MarkerSize: real) := + AddMarkerGraph(xx,yy,Colors.Aqua,MarkerType.Circle,MarkerSize); + end; + + LineGraphWPF = class(BaseGraphWPF) + public + constructor (a,b: real; f: real -> real; color: GColor := Colors.Red) := Invoke(CreateLineP,a,b,f,color); + constructor (xx,yy: sequence of real; color: GColor := Colors.Red) := Invoke(CreateLineP1,xx,yy,color); + end; + + MarkerGraphWPF = class(BaseGraphWPF) + public + constructor (xx,yy: sequence of real; color: GColor := Colors.Aqua; MType: MarkerType := MarkerType.Circle; MarkerSize: real := 10) + := Invoke(CreateMarkerP1,xx,yy,color,MType,MarkerSize); + constructor (xx,yy: sequence of real; MType: MarkerType) + := Invoke(CreateMarkerP1,xx,yy,Colors.Aqua,MType,10); + constructor (xx,yy: sequence of real; color: GColor; MarkerSize: real) + := Invoke(CreateMarkerP1,xx,yy,Colors.Aqua,MarkerType.Circle,MarkerSize); + end; + +begin + SetMainControl.AsGrid(); + GraphWPF.Window.Title := 'PlotWPF'; +end. \ No newline at end of file