From 820abfcd2177f3936ed157f1c11342bdefc9614e Mon Sep 17 00:00:00 2001 From: Matthew Moloney Date: Fri, 23 Aug 2013 16:26:26 +0100 Subject: [PATCH] Updating samples to match release --- .gitignore | 113 + Excel/README.md | 30 - Excel/ShellToUdf.fsx | 97 - Excel/StringToUdf.fsx | 46 - General/Charts.fsx | 99 - General/EditRibbon.fsx | 30 - Licence.txt | 24 - README.md | 11 +- Samples/3D/geometry.fsx | 493 ++++ Samples/3D/scene.fsx | 80 + Samples/3D/shared.fsx | 250 ++ Samples/EventStore/EventStore.fsx | 140 + Samples/EventStore/EventStoreTest.fsx | 139 + Samples/EventStore/EventStoreTestSL.fsx | 303 ++ Samples/Excel/ABTesting.fsx | 98 + Samples/Excel/BurndownChart.fsx | 131 + .../Excel/ExcelCharts.fsx | 23 +- {Excel/Charting => Samples/Excel}/README.md | 0 .../Excel/Stocks/Demo.fsx | 9 +- .../Excel/Stocks}/StocksScript.fsx | 23 +- Samples/Excel/UDFs/CSharp.cs | 14 + Samples/Excel/UDFs/Nasdaq100.xlsx | Bin 0 -> 14347 bytes Samples/Excel/UDFs/VisualBasic.vb | 18 + Samples/Excel/Widget/Widget.xlsx | Bin 0 -> 90693 bytes Samples/Excel/Widget/WidgetDemo.fsx | 84 + Samples/Excel/Zero Install/ExcelDemo.cs | 5 + Samples/Excel/Zero Install/ExcelDemo.fs | 12 + Samples/Excel/Zero Install/ExcelDemo.vb | 7 + Samples/Excel/Zero Install/ExcelDemoSetup.fsx | 68 + Samples/FunScript/ChatClient.fsx | 372 +++ Samples/FunScript/ChatServer.fsx | 166 ++ Samples/FunScript/files/mesh.png | Bin 0 -> 189 bytes Samples/FunScript/files/theme.css | 138 + Samples/GPGPU/SimpleTest.fsx | 29 + Samples/GPGPU/WaveSurfacePlotting.fsx | 66 + Samples/Games/Games.fsx | 11 + Samples/Games/MissileCommand.fsx | 261 ++ Samples/Games/Pacman.fsx | 1380 +++++++++ Samples/Games/Tetris.fsx | 247 ++ Samples/Hive/TsunamiHiveDemo.fsx | 71 + Samples/Languages/Assembly.asm | 0 Samples/Languages/BatchFile.bat | 0 Samples/Languages/C++.cpp | 0 Samples/Languages/C.c | 0 Samples/Languages/CSS.css | 0 Samples/Languages/CSharp.cs | 0 Samples/Languages/Ebnf.el | 0 Samples/Languages/FSharp.fsx | 0 Samples/Languages/HTML.html | 0 Samples/Languages/IniFile.ini | 0 Samples/Languages/Java.java | 0 Samples/Languages/JavaScript.js | 0 Samples/Languages/Lua.lua | 0 Samples/Languages/MSIL.msil | 0 Samples/Languages/MarkDown.md | 0 Samples/Languages/Pascal.pas | 0 Samples/Languages/Perl.pl | 0 Samples/Languages/PowerShell.ps | Bin Samples/Languages/Python.py | 0 Samples/Languages/Rtf.rtf | 0 Samples/Languages/Ruby.rb | 0 Samples/Languages/SQL.sql | 0 Samples/Languages/Text.txt | 0 Samples/Languages/VB.vb | 0 Samples/Languages/VBScript.vbs | 0 Samples/Languages/XAML.xaml | 0 Samples/Languages/XML.xml | 0 Samples/MBrace/Demo.fsx | 31 + Samples/MBrace/Setup.fsx | 9 + .../Examples/Digits Nearest Neighbour.fsx | 31 + .../Examples/Digits Random Forest.fsx | 28 + .../Machine Learning/Examples/Iris KMeans.fsx | 13 + .../Examples/Synthetic LinearRegression.fsx | 75 + Samples/Machine Learning/KMeans.fsx | 11 + .../Machine Learning/KNearestNeighbours.fsx | 10 + Samples/Machine Learning/LinearRegression.fsx | 27 + .../Machine Learning/LogisticRegression.fsx | 49 + Samples/Machine Learning/MathNet.fsx | 102 + Samples/Machine Learning/NeuralNetwork.fsx | 191 ++ Samples/Machine Learning/RandomForest.fsx | 125 + Samples/PhoneGap/PhoneGap.fs | 283 ++ Samples/PhoneGap/www/config.xml | 97 + Samples/PhoneGap/www/css/index.css | 115 + Samples/PhoneGap/www/icon.png | Bin 0 -> 1733 bytes Samples/PhoneGap/www/index.html | 42 + Samples/PhoneGap/www/js/index.js | 49 + Samples/PhoneGap/www/spec.html | 68 + Samples/PhoneGap/www/spec/helper.js | 33 + Samples/PhoneGap/www/spec/index.js | 67 + .../www/spec/lib/jasmine-1.2.0/MIT.LICENSE | 20 + .../spec/lib/jasmine-1.2.0/jasmine-html.js | 616 ++++ .../www/spec/lib/jasmine-1.2.0/jasmine.css | 81 + .../www/spec/lib/jasmine-1.2.0/jasmine.js | 2529 +++++++++++++++++ Samples/Plugins/Fantomas/Main.fs | 38 + .../Rhino 3D/Circles On Sphere.fsx | 7 +- {Rhino3D => Samples/Rhino 3D}/README.md | 0 .../Rhino 3D/Sierpinski Triangles.fsx | 6 + Samples/Turtle/TurtleExamples.fsx | 112 + Samples/Turtle/TurtleSVG.fsx | 355 +++ .../Type Providers/DocumentTypeProvider.fsx | 41 + .../Type Providers/FacebookTypeProvider.fsx | 65 + Samples/Type Providers/NuGetTypeProvider.fsx | 52 + Samples/Type Providers/S3TypeProvider.fsx | 49 + Samples/Utilities/MachineLearning.fsx | 63 + Samples/Utilities/Utilities.fsx | 44 + Samples/Utilities/WebData.fsx | 34 + .../iPhone/iphone_client.html | 0 Samples/samples.fsx | 417 +++ 108 files changed, 10710 insertions(+), 363 deletions(-) create mode 100644 .gitignore delete mode 100644 Excel/README.md delete mode 100644 Excel/ShellToUdf.fsx delete mode 100644 Excel/StringToUdf.fsx delete mode 100644 General/Charts.fsx delete mode 100644 General/EditRibbon.fsx delete mode 100644 Licence.txt create mode 100644 Samples/3D/geometry.fsx create mode 100644 Samples/3D/scene.fsx create mode 100644 Samples/3D/shared.fsx create mode 100644 Samples/EventStore/EventStore.fsx create mode 100644 Samples/EventStore/EventStoreTest.fsx create mode 100644 Samples/EventStore/EventStoreTestSL.fsx create mode 100644 Samples/Excel/ABTesting.fsx create mode 100644 Samples/Excel/BurndownChart.fsx rename Excel/Charting/ExcelEnv.fsx => Samples/Excel/ExcelCharts.fsx (89%) rename {Excel/Charting => Samples/Excel}/README.md (100%) rename Excel/Charting/ChartingDemo.fsx => Samples/Excel/Stocks/Demo.fsx (62%) rename {Excel/Charting => Samples/Excel/Stocks}/StocksScript.fsx (84%) create mode 100644 Samples/Excel/UDFs/CSharp.cs create mode 100644 Samples/Excel/UDFs/Nasdaq100.xlsx create mode 100644 Samples/Excel/UDFs/VisualBasic.vb create mode 100644 Samples/Excel/Widget/Widget.xlsx create mode 100644 Samples/Excel/Widget/WidgetDemo.fsx create mode 100644 Samples/Excel/Zero Install/ExcelDemo.cs create mode 100644 Samples/Excel/Zero Install/ExcelDemo.fs create mode 100644 Samples/Excel/Zero Install/ExcelDemo.vb create mode 100644 Samples/Excel/Zero Install/ExcelDemoSetup.fsx create mode 100644 Samples/FunScript/ChatClient.fsx create mode 100644 Samples/FunScript/ChatServer.fsx create mode 100644 Samples/FunScript/files/mesh.png create mode 100644 Samples/FunScript/files/theme.css create mode 100644 Samples/GPGPU/SimpleTest.fsx create mode 100644 Samples/GPGPU/WaveSurfacePlotting.fsx create mode 100644 Samples/Games/Games.fsx create mode 100644 Samples/Games/MissileCommand.fsx create mode 100644 Samples/Games/Pacman.fsx create mode 100644 Samples/Games/Tetris.fsx create mode 100644 Samples/Hive/TsunamiHiveDemo.fsx create mode 100644 Samples/Languages/Assembly.asm create mode 100644 Samples/Languages/BatchFile.bat create mode 100644 Samples/Languages/C++.cpp create mode 100644 Samples/Languages/C.c create mode 100644 Samples/Languages/CSS.css create mode 100644 Samples/Languages/CSharp.cs create mode 100644 Samples/Languages/Ebnf.el create mode 100644 Samples/Languages/FSharp.fsx create mode 100644 Samples/Languages/HTML.html create mode 100644 Samples/Languages/IniFile.ini create mode 100644 Samples/Languages/Java.java create mode 100644 Samples/Languages/JavaScript.js create mode 100644 Samples/Languages/Lua.lua create mode 100644 Samples/Languages/MSIL.msil create mode 100644 Samples/Languages/MarkDown.md create mode 100644 Samples/Languages/Pascal.pas create mode 100644 Samples/Languages/Perl.pl create mode 100644 Samples/Languages/PowerShell.ps create mode 100644 Samples/Languages/Python.py create mode 100644 Samples/Languages/Rtf.rtf create mode 100644 Samples/Languages/Ruby.rb create mode 100644 Samples/Languages/SQL.sql create mode 100644 Samples/Languages/Text.txt create mode 100644 Samples/Languages/VB.vb create mode 100644 Samples/Languages/VBScript.vbs create mode 100644 Samples/Languages/XAML.xaml create mode 100644 Samples/Languages/XML.xml create mode 100644 Samples/MBrace/Demo.fsx create mode 100644 Samples/MBrace/Setup.fsx create mode 100644 Samples/Machine Learning/Examples/Digits Nearest Neighbour.fsx create mode 100644 Samples/Machine Learning/Examples/Digits Random Forest.fsx create mode 100644 Samples/Machine Learning/Examples/Iris KMeans.fsx create mode 100644 Samples/Machine Learning/Examples/Synthetic LinearRegression.fsx create mode 100644 Samples/Machine Learning/KMeans.fsx create mode 100644 Samples/Machine Learning/KNearestNeighbours.fsx create mode 100644 Samples/Machine Learning/LinearRegression.fsx create mode 100644 Samples/Machine Learning/LogisticRegression.fsx create mode 100644 Samples/Machine Learning/MathNet.fsx create mode 100644 Samples/Machine Learning/NeuralNetwork.fsx create mode 100644 Samples/Machine Learning/RandomForest.fsx create mode 100644 Samples/PhoneGap/PhoneGap.fs create mode 100644 Samples/PhoneGap/www/config.xml create mode 100644 Samples/PhoneGap/www/css/index.css create mode 100644 Samples/PhoneGap/www/icon.png create mode 100644 Samples/PhoneGap/www/index.html create mode 100644 Samples/PhoneGap/www/js/index.js create mode 100644 Samples/PhoneGap/www/spec.html create mode 100644 Samples/PhoneGap/www/spec/helper.js create mode 100644 Samples/PhoneGap/www/spec/index.js create mode 100644 Samples/PhoneGap/www/spec/lib/jasmine-1.2.0/MIT.LICENSE create mode 100644 Samples/PhoneGap/www/spec/lib/jasmine-1.2.0/jasmine-html.js create mode 100644 Samples/PhoneGap/www/spec/lib/jasmine-1.2.0/jasmine.css create mode 100644 Samples/PhoneGap/www/spec/lib/jasmine-1.2.0/jasmine.js create mode 100644 Samples/Plugins/Fantomas/Main.fs rename Rhino3D/circles-on-sphere.fsx => Samples/Rhino 3D/Circles On Sphere.fsx (83%) rename {Rhino3D => Samples/Rhino 3D}/README.md (100%) rename Rhino3D/SierpinskiTriangle.fsx => Samples/Rhino 3D/Sierpinski Triangles.fsx (95%) create mode 100644 Samples/Turtle/TurtleExamples.fsx create mode 100644 Samples/Turtle/TurtleSVG.fsx create mode 100644 Samples/Type Providers/DocumentTypeProvider.fsx create mode 100644 Samples/Type Providers/FacebookTypeProvider.fsx create mode 100644 Samples/Type Providers/NuGetTypeProvider.fsx create mode 100644 Samples/Type Providers/S3TypeProvider.fsx create mode 100644 Samples/Utilities/MachineLearning.fsx create mode 100644 Samples/Utilities/Utilities.fsx create mode 100644 Samples/Utilities/WebData.fsx rename CompilerServices/iphone_client_beta.html => Samples/iPhone/iphone_client.html (100%) create mode 100644 Samples/samples.fsx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..771cc57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# Build Folders (you can keep bin if you'd like, to store dlls and pdbs) +[Bb]in/ +[Oo]bj/ + +# mstest test results +TestResults + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.log +*.vspscc +*.vssscc +*.dll +.builds + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# 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 +*.Publish.xml + +# NuGet Packages Directory +packages + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +[Bb]in +[Oo]bj +sql +TestResults +[Tt]est[Rr]esult* +*.Cache +ClientBin +[Ss]tyle[Cc]op.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# 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 +Samples/References/FMath.Excel.XML +Samples/References/Microsoft.Office.Interop.Excel.xml +Samples/References/office.xml +Samples/Samples.v2.zip diff --git a/Excel/README.md b/Excel/README.md deleted file mode 100644 index 04e3b6c..0000000 --- a/Excel/README.md +++ /dev/null @@ -1,30 +0,0 @@ -## Excel Examples - -####Prerequisites -VSTO Runtime for Excel - -`e.g. VSTO 2010 http://www.microsoft.com/en-gb/download/details.aspx?id=35594` - -####To install Tsunami for Excel - -Load the VSTO file at `C:\Program Files (x86)\Tsunami\ExcelFSharp.vsto` - - -####To execute - -Ribbon -> View -> F# -> Tsunami - -####Note - -Currently Tsunami is loads when Excel loads and adds a noticeable lag to opening Excel. In the future this lag will be delayed to the first time Tsunami is invoked within Excel. - -##Excel Charting - -##Excel F# UDFs -####Prerequisite -[F# PowerPack](http://fsharppowerpack.codeplex.com/) - -####Scripts -The script `StringToUdf.fsx` demonstrates the process for creating UDFs using inline strings. - -The script `ShellToUdf.fsx` demonstrates the process for creating UDFs using the in memory Shell.fsx and the Ribbon to invoke compilation and reload. \ No newline at end of file diff --git a/Excel/ShellToUdf.fsx b/Excel/ShellToUdf.fsx deleted file mode 100644 index 6f01302..0000000 --- a/Excel/ShellToUdf.fsx +++ /dev/null @@ -1,97 +0,0 @@ -(* - INSTRUCTIONS - - Run this script in Tsunami embedded in Excel - Clear the script and add the UDF code, e.g. - - """namespace FCellDemo - module MyUDF = - let fAdd2 x y = x + y + 2.""" - - Compile by clicking on Ribbon -> Excel -> Common -> Compile button - - Run the UDF function in an Excel cell, e.g. "=fadd2(1,2)" - -*) - -#r "Tsunami.IDEDesktop.exe" -#r "WindowsBase.dll" -#r "PresentationFramework.dll" -#r "PresentationCore.dll" -#r "Telerik.Windows.Controls.dll" -#r "Telerik.Windows.Controls.Navigation.dll" -#r "Telerik.Windows.Controls.Docking.dll" -#r "Telerik.Windows.Controls.RibbonView.dll" -#r "ActiproSoftware.SyntaxEditor.Wpf.dll" -#r "System.Xaml.dll" -#r "ActiproSoftware.Shared.Wpf.dll" -#r "ActiproSoftware.Text.Wpf.dll" -#r "ActiproUtilities.dll" -#r "FSharp.Compiler.dll" - -#r @"C:\Program Files (x86)\FSharpPowerPack-4.0.0.0\bin\FSharp.Compiler.CodeDom.dll" -#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Excel.dll" -#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Office.dll" -#r @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.dll" -#r @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.Rtd.dll" -#r @"C:\Program Files (x86)\Statfactory\FCell 1.0\log4net.dll" - - -open Tsunami.IDE -open Tsunami.Utilities -open System -open System.Windows -open System.Windows.Controls -open ActiproSoftware.Windows.Controls -open Telerik.Windows.Controls -open Telerik.Windows.Controls.RibbonView -open Tsunami.CC.SourceServices -open Tsunami.FS.FileSystem -open Microsoft.Office.Interop.Excel -open System.Runtime.InteropServices -open System.CodeDom.Compiler -open Microsoft.FSharp.Compiler.CodeDom -open Microsoft.FSharp.Control - - -let ui = Threading.DispatcherSynchronizationContext(Tsunami.IDE.UI.Instances.ApplicationMenu.Dispatcher) - -let compile() = - let ui = Threading.DispatcherSynchronizationContext(Tsunami.IDE.UI.Instances.ApplicationMenu.Dispatcher) - let reloadFcell() = - let xllPath = @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.xll" - let app = Marshal.GetActiveObject("Excel.Application"):?> Microsoft.Office.Interop.Excel.Application - app.RegisterXLL(xllPath) |> ignore - - async { - do! Async.SwitchToContext ui - let rawCode = Tsunami.FS.FileSystem.fileSystem.ReadAllText("ms:Shell.fsx") - let codeArr = rawCode.Split([|"\n"|], StringSplitOptions.RemoveEmptyEntries) - |> Array.filter (fun s -> not (s.Contains("#r"))) - let code = String.Join("\r\n", codeArr) - let provider = new FSharpCodeProvider() - let output = @"C:\Program Files (x86)\Statfactory\FCell 1.0\FSharpUdfs.dll" - let opt = new CompilerParameters([|"System.dll";@"C:\Program Files (x86)\Statfactory\FCell 1.0\log4net.dll";@"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.dll";@"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.Rtd.dll";@"C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp\3.0\Runtime\v4.0\FSharp.Core.dll"|], output) - let res = provider.CompileAssemblyFromSource(opt, code) - if res.Errors.Count = 0 then - try - reloadFcell() - with - | e -> MessageBox.Show(e.Message) |> ignore - else - MessageBox.Show(res.Errors.Item(0).ErrorText) |> ignore - - } |> Async.Start - -async { - do! Async.SwitchToContext ui - let button = RadRibbonButton(Text = "Compile", Size = ButtonSize.Large) - button.Click.Add(fun _ -> compile()) - let excelTab = - [| - [| button |] - |> addItems (RadRibbonGroup(Header = "Common")) - |] |> addItems (RadRibbonTab(Header = "Excel")) - Tsunami.IDE.UI.Instances.RibbonView.Items.Add(excelTab) |> ignore - -} |> Async.RunSynchronously \ No newline at end of file diff --git a/Excel/StringToUdf.fsx b/Excel/StringToUdf.fsx deleted file mode 100644 index 863d089..0000000 --- a/Excel/StringToUdf.fsx +++ /dev/null @@ -1,46 +0,0 @@ -#r @"C:\Program Files (x86)\FSharpPowerPack-4.0.0.0\bin\FSharp.Compiler.CodeDom.dll" -#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Excel.dll" -#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Office.dll" -#r @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.dll" -#r @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.Rtd.dll" -#r @"C:\Program Files (x86)\Statfactory\FCell 1.0\log4net.dll" - -open System -open System.Windows -open Microsoft.Office.Interop.Excel -open System.Runtime.InteropServices -open System.CodeDom.Compiler -open Microsoft.FSharp.Compiler.CodeDom -open Microsoft.FSharp.Control - -let rawCode = """namespace FCellDemo -module MyUDF = - let fAdd2 x y = x + y + 2. -""" - -let code = String.Join("\r\n", rawCode.Split([|"\n"|], StringSplitOptions.RemoveEmptyEntries)) -let provider = new FSharpCodeProvider() -let output = @"C:\Program Files (x86)\Statfactory\FCell 1.0\FSharpUdfs.dll" - -let opt = new CompilerParameters( - [| - "System.dll"; - @"C:\Program Files (x86)\Statfactory\FCell 1.0\log4net.dll"; - @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.dll"; - @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.ManagedXll.Rtd.dll"; - @"C:\Program Files (x86)\Statfactory\FCell 1.0\FSharp.Core.dll"|], output) - -let res = provider.CompileAssemblyFromSource(opt, code) - -if res.Errors.Count = 0 then - try - let xllPath = @"C:\Program Files (x86)\Statfactory\FCell 1.0\FCell.xll" - let app = Marshal.GetActiveObject("Excel.Application"):?> Microsoft.Office.Interop.Excel.Application - app.RegisterXLL(xllPath) |> ignore - with - | e -> printfn "%s" e.Message -else - printfn "%s" (res.Errors.Item(0).ErrorText) - - - \ No newline at end of file diff --git a/General/Charts.fsx b/General/Charts.fsx deleted file mode 100644 index f86ada6..0000000 --- a/General/Charts.fsx +++ /dev/null @@ -1,99 +0,0 @@ -#I @"C:\Program Files\Tsunami\" -#r "WindowsBase.dll" -#r "PresentationFramework.dll" -#r "PresentationCore.dll" -#r "System.Xaml.dll" -#r "Telerik.Windows.Data.dll" -#r "Telerik.Windows.Controls.dll" -#r "Telerik.Windows.Controls.Charting.dll" -#r "ActiproSoftware.SyntaxEditor.Wpf.dll" -#r "ActiproSoftware.Shared.Wpf.dll" -#r "ActiproSoftware.Docking.Wpf.dll" -#r "ActiproSoftware.Ribbon.Wpf.dll" -#r "ActiproSoftware.DataGrid.Contrib.Wpf.dll" -#r "Tsunami.IDEDesktop.exe" - -open System -open System.Windows -open Telerik.Windows.Controls -open Telerik.Windows.Controls.Charting -open Tsunami.IDE - -let ui = Threading.DispatcherSynchronizationContext UI.Instances.ApplicationMenu.Dispatcher - -let chart = - async { - do! Async.SwitchToContext ui - let chart = RadChart() - UI.Instances.VisualizationPane.Dock() - UI.Instances.VisualizationPane.Content <- chart - return chart - } |> Async.RunSynchronously - -type Candle = - struct - val date : DateTime - val high : float - val low : float - val ``open`` : float - val ``close`` : float - new(d: DateTime, h: float, l: float, o: float, c: float) = - { - date = d - high = h - low = l - ``open`` = o - ``close`` = c - } - end - -module Chart = - let lines (xss:(string*float[])[]) = - async { - do! Async.SwitchToContext ui - for (name,data) in xss do - let lineSeries = DataSeries(LegendLabel = name, Definition = new LineSeriesDefinition()) - lineSeries.Definition.Appearance.PointMark.Fill <- System.Windows.Media.Brushes.Transparent - lineSeries.Definition.Appearance.PointMark.Stroke <- System.Windows.Media.Brushes.Transparent - lineSeries.Definition.ShowItemLabels <- false - for x in data do - lineSeries.Add(DataPoint(x)) - chart.DefaultView.ChartArea.DataSeries.Add(lineSeries) - } |> Async.RunSynchronously - - let clear() = - async { - do! Async.SwitchToContext ui - chart.DefaultView.ChartArea.DataSeries.Clear() - } |> Async.RunSynchronously - - let candleStick (name:string, xs:Candle[]) = - async { - do! Async.SwitchToContext ui - chart.DefaultView.ChartArea.AxisX.IsDateTime <- true - chart.DefaultView.ChartArea.AxisX.LayoutMode <- AxisLayoutMode.Inside - chart.DefaultView.ChartArea.AxisX.LabelRotationAngle <- 45. - chart.DefaultView.ChartArea.AxisX.DefaultLabelFormat <- "dd-MMM" - - let candleStickSeries = DataSeries(LegendLabel = name, Definition = new CandleStickSeriesDefinition()) - candleStickSeries.AddRange(xs |> Array.map (fun x -> DataPoint(High = x.high, Low = x.low, Open = x.``open``, Close = x.close, XValue = x.date.ToOADate()))) - chart.DefaultView.ChartArea.DataSeries.Add(candleStickSeries) - } |> Async.RunSynchronously - -let random = new System.Random() -let randomWalk() = [|0..20|] |> Array.scan (fun state _ -> state + random.NextDouble() * 2. - 1.) 0. -let randomWalks = [| for i in 0..5 -> ("Random Walk " + string i, randomWalk()) |] - -Chart.lines randomWalks - -Chart.clear() - -[| - let now = DateTime.Now - for i in 0..20 -> - let o = random.NextDouble() - let c = random.NextDouble() - let h = (max o c) + random.NextDouble() / 4. - let l = (min o c) - random.NextDouble() / 4. - Candle(now.AddDays(float i),h,l,o,c) -|] |> fun xs -> Chart.candleStick("ASX200",xs) \ No newline at end of file diff --git a/General/EditRibbon.fsx b/General/EditRibbon.fsx deleted file mode 100644 index c5d2a6a..0000000 --- a/General/EditRibbon.fsx +++ /dev/null @@ -1,30 +0,0 @@ -#r "Tsunami.IDEDesktop.exe" -#r "WindowsBase.dll" -#r "PresentationFramework.dll" -#r "PresentationCore.dll" -#r "Telerik.Windows.Controls.dll" -#r "ActiproSoftware.Shared.Wpf.dll" -#r "ActiproSoftware.SyntaxEditor.Wpf.dll" -#r "ActiproSoftware.Docking.Wpf.dll" -#r "ActiproSoftware.Ribbon.Wpf.dll" -#r "ActiproSoftware.DataGrid.Contrib.Wpf.dll" -#r "System.Xaml.dll" - -open System.Windows -open System.Windows.Controls -open ActiproSoftware.Windows.Controls.Ribbon -open Tsunami.IDE - -let ui = Threading.DispatcherSynchronizationContext(UI.Instances.ApplicationMenu.Dispatcher) - -async { - do! Async.SwitchToContext ui - let button = Button(Width = 70.) - button.Content <- "Run" - button.Click.Add(fun _ -> MessageBox.Show("Hello world") |> ignore) - let tab = Controls.Tab(Label = "Tab") - let group = Controls.Group(Label = "Group") - group.ItemsSource <- [|button|] - tab.Items.Add group - UI.Instances.RibbonView.Tabs.Add tab -} |> Async.RunSynchronously \ No newline at end of file diff --git a/Licence.txt b/Licence.txt deleted file mode 100644 index 497413b..0000000 --- a/Licence.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2013, Earthquake Enterprises -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL Earthquake Enterprises BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 63c2a5d..4378770 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,2 @@ -#Welcome to Tsunami’s public repo. -###We’re using GitHub and StackOverflow to engage directly with developers. -####Questions / Issue Tracking: -* Public Q&A [StackOverflow](http://stackoverflow.com/questions/tagged/tsunami) -* Public enquiries to us [GitHub](https://github.com/Tsunami-ide/public/issues) -* Private enquiries to us at cases@tsunami.fogbugz.com - -###Example Code / SDK: -We provide code here to help users make the most out their experience using Tsunami. The licence for the code in this repository is *3-clase BSD* so you are free to use it in your own work. +Samples +======= diff --git a/Samples/3D/geometry.fsx b/Samples/3D/geometry.fsx new file mode 100644 index 0000000..0dfe61e --- /dev/null +++ b/Samples/3D/geometry.fsx @@ -0,0 +1,493 @@ +(* NOTE: Run in a seperate FSI Shell *) +#load "shared.fsx" +#r "WindowsBase" +#r "PresentationCore" +#r "PresentationFramework" +#r "System.Windows.Presentation" +#r "System.Xaml" + +open Shared +open System +open System.Collections.Generic +open System.IO +open System.Text +open System.Windows +open System.Windows.Shapes +open System.Windows.Controls +open System.Windows.Markup +open System.Windows.Input +open System.Windows.Media +open System.Windows.Media.Media3D +open System.Windows.Media.Imaging +open System.Windows.Threading +open System.Xml +open System.Diagnostics +open System.Threading +open System.Xaml +open Vector3D +open Matrix3D + +(* Helper functions *) +module Random = + let rand = new System.Random() + let int n = rand.Next(n) + let float x = x * rand.NextDouble() + + +(* 3D Primatives *) +module Shapes3D = + + /// Creats a two dimentional mesh from 2 parametric functions and 2 counters + let one_d ps f1 count1 = [|for i in 0 .. count1 -> f1 i ps |] + let two_d f1 count1 f2 count2 = [|for i in 0 .. count2 -> f2 i [|for j in 0 .. count1 -> f1 j |] |] + + /// Mesh3D Matrix Transform + let mesh3DTransform (m:Matrix3D) (m3d:MeshGeometry3D) = + let points = Point3DCollection() + for i in 0 .. m3d.Positions.Count - 1 do + points.Add(m.Transform(m3d.Positions.[i])) + let normals = Vector3DCollection() + for i in 0 .. m3d.Normals.Count - 1 do + normals.Add(m.Transform(m3d.Normals.[i])) + m3d.Positions <- points + m3d.Normals <- normals + m3d + + /// Builds a mesh out of a 2d point retangular array + let createHardMesh (mesh:Point3D array array) l w = + let length = l + 1 + let width = w + 1 + let positions, triangles = Point3DCollection(), Int32Collection() + + let addTriangle a b c = + let p = positions.Count + positions.Add(a) + triangles.Add(p + 2) + positions.Add(b) + triangles.Add(p+1) + positions.Add(c) + triangles.Add(p) + + let addSquare a b c d = + addTriangle a b c + addTriangle c d a + + for i in 0 .. width - 2 do + for j in 0 .. length - 2 do + addSquare mesh.[i].[j] mesh.[i].[j+1] mesh.[i+1].[j+1] mesh.[i+1].[j] + + MeshGeometry3D(Positions = positions, TriangleIndices = triangles) + + // Shared normals -> softer + let createSoftMesh (mesh:Point3D array array) l w = + let length = l + 1 + let width = w + 1 + let points = Point3DCollection() + for i in 0 .. width - 1 do + for j in 0 .. length - 1 do + points.Add(mesh.[i].[j]) + + // TextureCoords - equal distribution + let textureCoords = PointCollection() + for i in 0 .. width - 1 do + for j in 0 .. length - 1 do + textureCoords.Add(Point(float i/ float (width - 1), float j / float (length - 1))) + + // TriangleIndices + let indices = Media.Int32Collection() // add 3 times for each triangle + for i in 0 .. width - 2 do + for j in 0 .. length - 2 do + let a = i * length + j + let b = i * length + j + 1 + let c = (i + 1) * length + j + 1 + let d = (i + 1) * length + j + // add first triangle + indices.Add(a) + indices.Add(b) + indices.Add(c) + // add second triangle + indices.Add(a) + indices.Add(c) + indices.Add(d) + // Return a new mesh geometry + MeshGeometry3D(Positions = points, TextureCoordinates = textureCoords, TriangleIndices = indices) + + /// cone, horizontal count, rotational count + let cylinder h r = + let f1 = fun i -> + let x = float i / float h + Point3D(x,0.5,0.) // straight line + let f2 = fun i ps -> + let m = rotate (Quaternion(XAxis, ((float i / float r) * 360.))) + ps |> Array.map (fun (p:Point3D) -> m.Transform(p)) + let mesh = createSoftMesh (two_d f1 h f2 r) h r + let normals = Vector3DCollection() + for p in mesh.Positions do + normals.Add(Point3D(p.X,0.,0.) - p) + mesh.Normals <- normals + mesh + + let cone h r = + let f1 = fun i -> + let x = float i / float h + Point3D(x,1.0 - x,0.) // decending line + let f2 = fun i ps -> + let m = rotate (Quaternion(XAxis, ((float i / float r) * 360.))) + ps |> Array.map (fun (p:Point3D) -> m.Transform(p)) + createSoftMesh (two_d f1 h f2 r) h r + + + let circle h r = + let f1 = fun i -> + let x = float i / float h + Point3D(x,0.,0.) // straight line + let f2 = fun i ps -> + let m = rotate (Quaternion(YAxis, ((float i / float r) * 360.))) + ps |> Array.map (fun (p:Point3D) -> m.Transform(p)) + createSoftMesh (two_d f1 h f2 r) h r + + + /// helix + let helix coils height tes = + let f1 = fun i -> + let t = float i / float tes + Point3D(cos(2.*Math.PI* coils * t),sin(2.*Math.PI* coils * t),t * height) // straight line + let f2 = fun i ps -> + if i = 0 then + ps + else + ps |> Array.map (fun (p:Point3D) -> Point3D(0.,0.,p.Z)) + createSoftMesh (two_d f1 tes f2 2) tes 2 + + + /// Square with one texture for all 6 sides + let sphere t = + // rotate a semicircle around the x axis + let f1 = fun i -> + let x = float i / float t - 0.5 + let theta = x * Math.PI + Point3D(sin(theta), cos(theta),0.0) + + let f2 = fun i ps -> + let m = rotate (Quaternion(XAxis,((float i / float t) * 360.))) + ps |> Array.map (fun (p:Point3D) -> m.Transform(p)) + let mesh = createSoftMesh (two_d f1 t f2 t) t t + // manually specify normals + let normals = Vector3DCollection() + let c = Point3D(0.,0.,0.) + for p in mesh.Positions do + normals.Add(p - c) + mesh.Normals <- normals + mesh + + let inverse_sphere() t = + // rotate a semicircle around the x axis + let f1 = fun i -> + let x = float i / float t - 0.5 + let theta = x * Math.PI + Point3D(sin(theta), cos(theta),0.0) + + let f2 = fun i ps -> + let m = rotate (Quaternion(XAxis,((float i / float t) * 360. * -1.))) + ps |> Array.map (fun (p:Point3D) -> m.Transform(p)) + + let mesh = createSoftMesh (two_d f1 t f2 t) t t + // manually specify normals + let normals = Vector3DCollection() + let c = Point3D(0.,0.,0.) + for p in mesh.Positions do + normals.Add(c - p) + mesh.Normals <- normals + mesh + +module Geometry3D = + /// merge geometries + let private mergeGeom (g1:MeshGeometry3D) (g2:MeshGeometry3D) = + // create a new geometry with all the triangles/positions + let positions, textures, triangles, normals = Point3DCollection(), PointCollection(), Int32Collection(), Vector3DCollection() + // add g1 + for p in g1.Positions do positions.Add(p) + for t in g1.TextureCoordinates do textures.Add(t) + for t in g1.TriangleIndices do triangles.Add(t) + for n in g1.Normals do normals.Add(n) + // add g2 + for p in g2.Positions do positions.Add(p) + for t in g2.TextureCoordinates do textures.Add(t) + for t in g2.TriangleIndices do triangles.Add(t + g1.Positions.Count) + for n in g2.Normals do normals.Add(n) + // should eliminate non visible triangles + MeshGeometry3D(Positions = positions, TextureCoordinates = textures, TriangleIndices = triangles, Normals = normals) + + /// concave fill - clockwise (asumption) points depicting a shape + let private convexFill (ps:Point3D array) = + // setup positions, triangles, normals + let positions, triangles = Point3DCollection(), Int32Collection() + // Add the points to the array + ps |> Array.iter (fun p -> + positions.Add(p) + ) + // Add the triangles + for i in 0 .. ps.Length - 1 do + triangles.Add(0) + triangles.Add(i) + triangles.Add(i + 1) + MeshGeometry3D(Positions = positions, TriangleIndices = triangles) + + /// convert the convex array of points into an array of concave points + let private concaveFill(psa:Point3D array) = + let ps = Array.toList psa + let positions, triangles = Point3DCollection(), Int32Collection() + // function for finding out the direction of the turn + let turnRight a b c = (Vector3D.CrossProduct(b - a, c - a)).Z < 0. + // function for finding out if a point is in the triangle abc + let dinabc (a:Point3D) (b:Point3D) (c:Point3D) (d:Point3D) = // false + let dot a b c = Vector3D.DotProduct(b-a,c-a) + let acute a b c = (dot a b c) < 0. + // point maps to inside the triangle if + (acute a b d) && (acute b c d) && (acute c a d) && (acute a c d) && (acute c b d) && (acute b a d) + + let right_left = ref 0 + for i in 0 .. psa.Length - 3 do + let a,b,c = psa.[i],psa.[i+1],psa.[i+2] + if turnRight a b c then + right_left := !right_left + 1 + else + right_left := !right_left - 1 + + let turnCorrect a b c = + if !right_left > 0 + then turnRight a b c + else turnRight a b c |> not + + let tc = ref 0 // traingle counter + let skipCount = ref 0// the number of triangles skipped, if skipCount = list.Count then reverse list and try again + let rec getTriangles (ps:Point3D list) = + match ps with + | [a;b;c] -> // Add the triangle + positions.Add(a) + triangles.Add(!tc*3) + positions.Add(b) + triangles.Add(!tc*3 + 1) + positions.Add(c) + triangles.Add(!tc*3 + 2) + tc := !tc + 1 + | a::b::c::tail -> + if turnCorrect a b c then + if List.exists (fun d -> dinabc a b c d) tail then + // there exists a point in this triangle, don't remove, move on + getTriangles (b::c::tail@[a]) + //printf "%A" ps + else + // found a triangle to remove + getTriangles [a;b;c] + // continune without b + getTriangles (a::c::tail) + else + // move on + if !skipCount = tail.Length + 3 then + List.rev (b::c::tail@[a]) |> getTriangles + else + skipCount := !skipCount + 1 + getTriangles (b::c::tail@[a]) + | [] -> () + | [_] -> () + | [_;_] -> () + + getTriangles ps + MeshGeometry3D(Positions = positions, TriangleIndices = triangles) + + type Primitive3D = + | Point of Point3D + | Line of Point3D * Point3D + | Shape of Point3D array + | Geometry of MeshGeometry3D + with + member this.Reverse() = + match this with + | Point _ -> this + | Line (p1,p2) -> (p2,p1) |> Line + | Shape ps -> Array.rev ps |> Shape + | Geometry g -> + // reverse the triangle indicies + let triangles = Int32Collection() + for i in 0 .. 3 .. g.TriangleIndices.Count do + triangles.Add(g.TriangleIndices.[i + 2]) + triangles.Add(g.TriangleIndices.[i + 1]) + triangles.Add(g.TriangleIndices.[i]) + g.TriangleIndices <- triangles + g |> Geometry + + member this.Points() = + match this with + | Point p -> [|p|] + | Line (p1,p2) -> [|p1;p2|] + | Shape ps -> ps + | Geometry g -> [| for i in 0 .. (g.Positions.Count - 1) -> g.Positions.[i] |] + + member this.Transform(m:Matrix3D) = + match this with + | Point p -> Point(m.Transform(p)) + | Line (p1,p2) -> Line(m.Transform(p1),m.Transform(p2)) + | Shape ps -> Array.map (fun (p:Point3D) -> m.Transform(p)) ps |> Shape + | Geometry g -> + let positions = Point3DCollection() + for i in 0.. g.Positions.Count - 1 do + positions.Add(m.Transform(g.Positions.[i])) + g.Positions <- positions + Geometry(g) + + member this.AsGeomteryMesh() = + /// upgrade to a geometric object + let rec getMesh p = + // width + let width = 0.05 // 1/20 + match p with + | Point p -> + Shapes3D.sphere 30 + |> Shapes3D.mesh3DTransform ( (Matrix3D.translate(Vector3D(p.X,p.Y,p.Z)) * (Matrix3D.scale (Vector3D(width,width,width))) )) + | Line (p1,p2) -> + let v = p2 - p1 + let v1 = + v.Normalize() + let mutable tmp = Vector3D.CrossProduct(Vector3D(1.,0.,0.), v) + if tmp.LengthSquared < 0.01 then + tmp <- Vector3D.CrossProduct(Vector3D(0.,1.,0.), v) + if tmp.LengthSquared < 0.01 then + tmp <- Vector3D.CrossProduct(Vector3D(0.,0.,1.), v) + tmp.Normalize() + tmp * width + let v2 = + let tmp = Vector3D.CrossProduct(v,v1) + tmp.Normalize() + tmp * width + let vc = -(v1 + v2) / 2. + // get the sphere at the first end + //let sph = Shapes3D.sphere() 15 + //sph |> Shapes3D.mesh3DTransform ( (Math3D.tran_point p1.X p1.Y p1.Z) * (Math3D.scale_point width width width) |> Math3D.Matrix3D) + //sph |> mergeGeom + + Line(p1,p2) + .Transform(translate vc) + .Extrude(v2) + .Extrude(v1) + .AsGeomteryMesh() + | Shape ps -> concaveFill ps + | Geometry g -> g + getMesh this + member this.AsModelVisual(material:Material) = ModelVisual3D(Content = GeometryModel3D(Geometry = this.AsGeomteryMesh(), BackMaterial = material, Material = material)) + member this.AsPoints() = + match this with + | Point p -> [|Point(p)|] + | Line (p1,p2) -> [|Point(p1);Point(p2)|] + | Shape ps -> Array.map (fun p -> Point(p)) ps + | Geometry g -> [| for i in 0 .. (g.Positions.Count - 1) -> Point(g.Positions.[i]) |] + + member this.AsLines() = + match this with + | Point p -> [||] + | Line (p1,p2) -> [|Line(p1,p2)|] + | Shape ps -> Array.append [| for i in 0 .. ps.Length - 2 -> Line(ps.[i], ps.[i+1]) |] [|Line(ps.[ps.Length-1],ps.[0])|] + | Geometry g -> + [| for i in 0 .. g.TriangleIndices.Count - 2 -> + Line(g.Positions.[g.TriangleIndices.[i]],g.Positions.[g.TriangleIndices.[i+1]]) + |] + + member this.AsShapes() = + match this with + | Point p -> [||] + | Line (p1,p2) -> [||] + | Shape ps -> [| Shape(ps) |] + | Geometry g -> + [| for i in 0 .. 3 .. g.TriangleIndices.Count - 1 -> + Shape([|g.Positions.[g.TriangleIndices.[i+1]];g.Positions.[g.TriangleIndices.[i+2]];g.Positions.[g.TriangleIndices.[i]]|]) + |] + + member this.Extrude(v:Vector3D) : Primitive3D = + match this with + | Point p -> Line(p, (Matrix3D.translate(v).Transform(p))) + | Line (p1,p2) -> + let m = Matrix3D.translate(v) + Shape([| p1; p2; m.Transform(p2); m.Transform(p1) |]) + | Shape ps -> + let m = Matrix3D.translate(v) + // Add the first point onto the end + let ps1 = Array.append ps [|ps.[0]|] + let pss = Shapes3D.one_d ps1 (fun i ps1 -> if i = 0 then ps1 else ps1 |> Array.map (fun p -> m.Transform(p))) 1 + let sides = Shapes3D.createHardMesh pss (ps1.Length - 1) 1 + let front = ps1 |> concaveFill + let back = Array.rev (Array.map (fun (p:Point3D) -> m.Transform(p)) ps1 ) |> concaveFill + mergeGeom front back |> mergeGeom sides |> Geometry + | Geometry g -> Geometry(g) + + member this.Revolve(axis:Vector3D, angle:float, tes:int) = + let rev_p (p:Point3D) = [|for i in 0 .. tes -> (Matrix3D.rotate(Quaternion(axis, angle * (float i / float tes)))).Transform(p)|] + match this with + | Point p -> rev_p p |> Shape + | Line (p1,p2) -> + let top = rev_p p1 + let bottom = rev_p p2 + (Array.append top (Array.rev bottom)) |> Shape + | Shape ps -> + // Add the first point on to the end + let pss = Shapes3D.one_d (ps |> Array.append [|ps.[0]|]) (fun i ps -> ps |> Array.map (fun p -> (rotate(Quaternion(axis, -angle * (float i / float tes)))).Transform(p))) tes + let g = Shapes3D.createHardMesh pss (ps.Length - 1) tes + let top = convexFill(ps) + let bottom = convexFill(ps |> Array.map (fun p -> ((rotate(Quaternion(axis,-angle))).Transform(p)))) + top + |> mergeGeom bottom + |> mergeGeom g + |> Geometry + | Geometry g -> Geometry(g) + + +open Geometry3D + +let window = Viewer(Topmost = true) +window.Show() + +let add mv3d = window.Viewport.Children.Add(mv3d); mv3d +let remove mv3d = window.Viewport.Children.Remove(mv3d) |> ignore + +module Materials = + let Goldenrod = DiffuseMaterial(Brushes.Goldenrod) + +let point = Point(Point3D(0.,0.,0.)) +let line = point.Extrude(Vector3D(1.,0.,0.)) +let square = line.Extrude(Vector3D(0.,1.,0.)) +let cube = square.Extrude(Vector3D(0.,0.,1.)) + +(* This functionality is not finished yet *) +//let circle = line.Revolve(ZAxis,360.,10) +//let sphere = circle.Revolve(YAxis,180.,5) +//let cubeAsPoints = cube.AsPoints() +//let sphereAsPoints = sphere.AsPoints() + +open Shapes3D + +let toModelVisual (material:Material) (mesh:MeshGeometry3D) = + ModelVisual3D(Content = GeometryModel3D(Geometry = mesh, BackMaterial = material, Material = material)) + +let goldVisual = toModelVisual Materials.Goldenrod + + +let visuals = + [| + yield! + [|point; line; square; cube|] + |> Array.mapi (fun i shape -> shape.Transform(translate(Vector3D(float i,0.,0.))).AsModelVisual(Materials.Goldenrod) |> add) + yield! + [| + cylinder 5 30, (0.,2.) + cone 5 20, (2.,2.) + circle 10 30, (4.,2.) + helix 3. 3. 50, (6.,2.) + |] + |> Array.map ( + fun (mesh,pos) -> + let visual = goldVisual mesh + visual.Transform <- MatrixTransform3D(Matrix3D.translate(Vector3D(fst pos,snd pos,0.))) + visual |> add + ) + |] + +//window.Reset() \ No newline at end of file diff --git a/Samples/3D/scene.fsx b/Samples/3D/scene.fsx new file mode 100644 index 0000000..ab9e144 --- /dev/null +++ b/Samples/3D/scene.fsx @@ -0,0 +1,80 @@ +(* NOTE: Run in a seperate FSI Shell *) +#load "shared.fsx" +#load "..\Utilities\WebData.fsx" +open Tsunami.Public +#r "PresentationCore" +#r "PresentationFramework" +#r "WindowsBase" +#r "System.Xaml" +#r "UIAutomationTypes" +open System +open System.Collections.Generic +open System.IO +open System.Net +open System.Text +open System.Windows +open System.Windows.Shapes +open System.Windows.Controls +open System.Windows.Markup +open System.Windows.Input +open System.Windows.Media +open System.Windows.Media.Media3D +open System.Windows.Media.Imaging +open System.Windows.Threading +open System.Xml +open System.Diagnostics +open System.Threading +open System.Xaml +open Shared + +let window = Viewer(Topmost = true) +window.Show() + +let add mv3d = window.Viewport.Children.Add(mv3d); mv3d +let remove mv3d = window.Viewport.Children.Remove(mv3d) |> ignore + +let applyMatrix matrix (mv3D:Visual3D) = mv3D.Transform <- MatrixTransform3D(matrix); mv3D + +let updateMatrix (f:Matrix3D -> Matrix3D) (mv3D:Visual3D) = + match mv3D.Transform with + | :? MatrixTransform3D as mx -> mv3D.Transform <- MatrixTransform3D(f(mx.Matrix)) + | _ -> mv3D.Transform <- MatrixTransform3D(f(Matrix3D.Identity)) + mv3D + +let addMatrix (m:Matrix3D) mv3D = updateMatrix (fun x -> x * m) mv3D + +open Vector3D +open Matrix3D + +let translate x y z (mv3D:Visual3D) = addMatrix ((translate(Vector3D(x,y,z)))) mv3D +let scale x y z (mv3D:Visual3D) = addMatrix (scale(Vector3D(x,y,z))) mv3D +let reset (mv3D:Visual3D) = applyMatrix unitM mv3D +let rotate q = addMatrix(Matrix3D.rotate q) + +let imageModel url model = + let bi = BitmapImage() + bi.BeginInit() + bi.StreamSource <- new MemoryStream(WebData.ReadAllData(@"http://tsunami.io/assets/skypic_small.jpg")) + bi.EndInit() + let material = DiffuseMaterial(ImageBrush(bi)) + ModelVisual3D(Content = GeometryModel3D(Geometry = (model), Material = material, BackMaterial = material)) + +let brushModel brush model = ModelVisual3D(Content = GeometryModel3D(Geometry = (model), BackMaterial = (DiffuseMaterial(brush)), Material = (DiffuseMaterial(Brushes.Goldenrod)))) + + + +let terrain = + square() + |> imageModel @"http://tsunami.io/assets/skypic_small.jpg" + |> scale 200. 200. 200. + |> rotate (Quaternion(XAxis,90.)) + |> translate 1. -1. 1. + |> add + + +let walkerXaml = WebData.ReadAllText(@"http://tsunami.io/assets/walker.xaml") +let makeWalker() = walkerXaml |> parseXAML :?> ModelVisual3D + + +let walker = makeWalker() |> add +let cube = cube() |> brushModel Brushes.Goldenrod |> add diff --git a/Samples/3D/shared.fsx b/Samples/3D/shared.fsx new file mode 100644 index 0000000..968c12a --- /dev/null +++ b/Samples/3D/shared.fsx @@ -0,0 +1,250 @@ +(* NOTE: Run in a seperate FSI Shell *) +#r "PresentationCore" +#r "PresentationFramework" +#r "WindowsBase" +#r "System.Xaml" +#r "UIAutomationTypes" + +open System +open System.Collections.Generic +open System.IO +open System.Net +open System.Text +open System.Windows +open System.Windows.Shapes +open System.Windows.Controls +open System.Windows.Markup +open System.Windows.Input +open System.Windows.Media +open System.Windows.Media.Media3D +open System.Windows.Media.Imaging +open System.Windows.Threading +open System.Xml +open System.Diagnostics +open System.Threading +open System.Xaml + + +module Vector3D = + let cross (v1 : Vector3D) (v2 : Vector3D) = Vector3D.CrossProduct(v1,v2) + let dot (v1 : Vector3D) (v2 : Vector3D) = Vector3D.DotProduct(v1,v2) + let length (v : Vector3D) = v.Length + let lengthSquared (v : Vector3D) = v.LengthSquared + let angle_between (v1 : Vector3D) (v2 : Vector3D) = acos ((dot v1 v2) / ((v1 |> length) * (v2 |> length))) + let unitV = Vector3D(1.,1.,1.) + let XAxis = Vector3D(1.,0.,0.) + let YAxis = Vector3D(0.,1.,0.) + let ZAxis = Vector3D(0.,0.,1.) + +module Matrix3D = + open Vector3D + let unitM = Matrix3D.Identity + let scale(v:Vector3D) = + let mutable m = Matrix3D.Identity + m.Scale(Vector3D(v.X,v.Y,v.Z)) + m + + let translate(v:Vector3D) = + let mutable m = Matrix3D.Identity + m.Translate(Vector3D(v.X,v.Y,v.Z)) + m + + let rotate(x:Quaternion) = + let mutable m = Matrix3D.Identity + m.Rotate(x) + m + +(* Helper functions *) +let parseXAML (xaml : string) = + use ms = new MemoryStream(Encoding.ASCII.GetBytes(xaml)) + ms.Position <- int64 0 + XamlReader.Load(ms) + +let getUrlAsTxt (url:string) = + use sr = new StreamReader(WebRequest.Create(url).GetResponse().GetResponseStream()) + sr.ReadToEnd() + + +let square() = + let mg = MeshGeometry3D() + [|-1.,1.,0.;1.,1.,0.;-1.,-1.,0.;1.,-1.,0.|] + |> Array.iter (fun (x,y,z) -> mg.Positions.Add(Point3D(x,y,z))) + [|0.,0.,1.;0.,0.,1.;0.,0.,1.;0.,0.,1.|] + |> Array.iter (fun (x,y,z) -> mg.Normals.Add(Vector3D(x,y,z))) + [|0.,0.;1.,0.;0.,1.;1.,1.|] + |> Array.iter (fun (x,y) -> mg.TextureCoordinates.Add(Point(x,y))) + [|0;2;3;0;3;1|] |> Array.iter mg.TriangleIndices.Add + mg + +let cube() = + let mg = MeshGeometry3D() + [| + -0.5, 1.0, 0.5; 0.5, 1.0, 0.5 + -0.5, 0.0, 0.5; 0.5, 0.0, 0.5 + + 0.5 , 1.0,-0.5;-0.5, 1.0,-0.5 + 0.5 , 0.0,-0.5;-0.5, 0.0,-0.5 + + -0.5, 1.0,-0.5;-0.5, 1.0, 0.5 + -0.5, 0.0,-0.5;-0.5, 0.0, 0.5 + + 0.5 , 1.0, 0.5; 0.5, 1.0,-0.5 + 0.5 , 0.0, 0.5; 0.5, 0.0,-0.5 + + -0.5, 1.0,-0.5; 0.5, 1.0,-0.5 + -0.5, 1.0, 0.5; 0.5, 1.0, 0.5 + + 0.5 , 0.0,-0.5;-0.5, 0.0,-0.5 + 0.5 , 0.0, 0.5;-0.5, 0.0, 0.5 + |] |> Array.iter (fun (x,y,z) -> mg.Positions.Add(Point3D(x,y,z))) + + [| + 0 , 2, 1; 1, 2, 3 + 4 , 6, 5; 5, 6, 7 + 8 ,10, 9; 9,10,11 + 12,14,13;13,14,15 + 16,18,17;17,18,19 + 20,22,21;21,22,23 + |] + |> Array.collect (fun (a,b,c) -> [|a;b;c|]) + |> Array.iter mg.TriangleIndices.Add + + [|0., 0.; 1., 0.; 0., 1.; 1., 1.|] + |> Array.create 6 + |> Array.collect id + |> Array.iter (fun (x,y) -> mg.TextureCoordinates.Add(Point(x,y))) + mg + +type Viewer() as this = + inherit Window() + + let grid = Grid() + let viewport = Viewport3D() + let backPanel = Canvas(Background = Brushes.DarkBlue, IsHitTestVisible = true) + let frontPanel = Canvas(IsHitTestVisible = false) + + do + + + + grid.Children.Add(backPanel) |> ignore + grid.Children.Add(viewport) |> ignore + grid.Children.Add(frontPanel) |> ignore + this.Content <- grid + + // Add camera to viewport + let camera = PerspectiveCamera(Point3D(0.,0.,0.), Vector3D(0., 0., 1.), Vector3D(0., 1., 0.), 45.) + viewport.Camera <- camera + // Create the transforms + let zoom = TranslateTransform3D() + let tran = TranslateTransform3D() + let rotx = AxisAngleRotation3D(Vector3D(1.,0.,0.),0.) + let roty = AxisAngleRotation3D(Vector3D(0.,1.,0.),0.) + let rotz = AxisAngleRotation3D(Vector3D(0.,0.,1.),0.) + + zoom.OffsetZ <- -10. + roty.Angle <- 180. + + // Add the transform to the camera + let group = Transform3DGroup() + group.Children.Add(zoom) + group.Children.Add(RotateTransform3D(rotz)) + group.Children.Add(RotateTransform3D(rotx)) + group.Children.Add(RotateTransform3D(roty)) + group.Children.Add(tran) + camera.Transform <- group + + let addFront c = frontPanel.Children.Add(c) + + let removeFront c = frontPanel.Children.Remove(c) + + let getAspectRatio() = viewport.ActualWidth / viewport.ActualHeight + + let getProjMatrix (camera:PerspectiveCamera) aspectRatio = + let degToRad deg = deg * (Math.PI / 180.0) + let hFov = degToRad camera.FieldOfView + let zn = camera.NearPlaneDistance + let zf = camera.FarPlaneDistance + let xScale = 1.0 / tan(hFov / 2.0) + let yScale = aspectRatio * xScale + let m33 = + if zf = Double.PositiveInfinity then + -1. + else + zf / (zn - zf) + let m43 = zn * m33 + Matrix3D( + xScale, 0., 0., 0., + 0., yScale, 0., 0., + 0., 0., m33, -1., + 0., 0., m43, 0.) + + let getViewMatrix (camera:PerspectiveCamera) = + let zAxis = -camera.LookDirection + zAxis.Normalize() + let xAxis = Vector3D.CrossProduct(camera.UpDirection, zAxis) + xAxis.Normalize() + let yAxis = Vector3D.CrossProduct(zAxis,xAxis) + let position = Vector3D(X=camera.Position.X,Y=camera.Position.Y,Z=camera.Position.Z) + let offsetX = -Vector3D.DotProduct(xAxis,position) + let offsetY = -Vector3D.DotProduct(yAxis,position) + let offsetZ = -Vector3D.DotProduct(zAxis,position) + Matrix3D( + xAxis.X, yAxis.X, zAxis.X, 0., + xAxis.Y, yAxis.Y, zAxis.Y, 0., + xAxis.Z, yAxis.Z, zAxis.Z, 0., + offsetX, offsetY, offsetZ, 1.) + + + // Camrea Settings + let first = ref true // set to true when viewport3D focus is lost + let lastMouseMovePos = ref (Point()) + let lastMouseDownPos = ref (Point()) + + let mouse_move = fun (e:Input.MouseEventArgs) -> + let p = e.GetPosition(viewport) + if !first then + lastMouseMovePos := p + first := false + let d = !lastMouseMovePos - p + if e.RightButton = Input.MouseButtonState.Pressed then + if e.LeftButton = Input.MouseButtonState.Pressed then + // zoom + zoom.OffsetZ <- zoom.OffsetZ + zoom.OffsetZ * 10. * d.Y / viewport.ActualHeight + else + // rotation + rotx.Angle <- rotx.Angle + (d.Y / viewport.ActualHeight) * 180. // z is pitch + roty.Angle <- roty.Angle + (d.X / viewport.ActualWidth) * 180. + lastMouseMovePos := p + + backPanel.MouseMove.Add(mouse_move) + viewport.MouseMove.Add(mouse_move) + + backPanel.MouseWheel.Add(fun e -> if e.Delta < 0 then zoom.OffsetZ <- zoom.OffsetZ * 1.1 else zoom.OffsetZ <- zoom.OffsetZ / 1.1) + viewport.MouseWheel.Add(fun e -> if e.Delta < 0 then zoom.OffsetZ <- zoom.OffsetZ * 1.1 else zoom.OffsetZ <- zoom.OffsetZ / 1.1) + + backPanel.MouseUp.Add((fun _ -> first := true)) + viewport.MouseUp.Add((fun _ -> first := true)) + + backPanel.MouseLeave.Add(fun _ -> first := true) + viewport.MouseLeave.Add(fun _ -> first := true) + + let key_down = fun (e:KeyEventArgs) -> + if Keyboard.IsKeyDown(Key.A) then tran.OffsetX <- tran.OffsetX - 1. + if Keyboard.IsKeyDown(Key.D) then tran.OffsetX <- tran.OffsetX + 1. + if Keyboard.IsKeyDown(Key.W) then tran.OffsetZ <- tran.OffsetZ - 1. + if Keyboard.IsKeyDown(Key.S) then tran.OffsetZ <- tran.OffsetZ + 1. + if Keyboard.IsKeyDown(Key.Space) then tran.OffsetY <- tran.OffsetY + 1. + if Keyboard.IsKeyDown(Key.LeftCtrl) then tran.OffsetY <- tran.OffsetY - 1. + e.Handled <- false + this.KeyDown.Add(key_down) + this.Reset() + member this.Viewport = viewport + member this.Reset() = + viewport.Children.Clear() + let m3dgroup = Model3DGroup() + [| + AmbientLight(Color = Color.FromRgb(64uy,64uy,64uy)) :> Light + DirectionalLight(Color = Color.FromRgb(192uy,192uy,192uy), Direction = Vector3D(2.,-3.,-1.)) :> Light + |] |> Array.iter (fun light -> m3dgroup.Children.Add(light)) + viewport.Children.Add(ModelVisual3D(Content = m3dgroup)) \ No newline at end of file diff --git a/Samples/EventStore/EventStore.fsx b/Samples/EventStore/EventStore.fsx new file mode 100644 index 0000000..506f096 --- /dev/null +++ b/Samples/EventStore/EventStore.fsx @@ -0,0 +1,140 @@ +module Tsunami.EventStore +#r "Tsunami.IDEDesktop.exe" +#r "Newtonsoft.Json.dll" +#r "System.Reactive.Core.dll" +#r "System.Reactive.Linq.dll" +#r "System.Reactive.Interfaces.dll" +#r "WindowsBase.dll" +#r "PresentationCore.dll" +#r "PresentationFramework.dll" +#r "System.Xaml.dll" +#r "System.Core.dll" +#r "System.Xml.Linq.dll" +#r "UIAutomationTypes.dll" + +open System +open System.IO +open System.Net +open System.Text +open System.Collections.Generic +open Tsunami.Utilities +open Tsunami.SerDes.JS +open Newtonsoft.Json +open Newtonsoft.Json.Linq + +/// Should be private, ignore +type Wrapped<'a> = + { + wrapped: 'a + } + +/// Should be private, ignore +type Event<'a> = + { + eventId: string + eventType: string + data: Wrapped<'a> + } + +/// Should be private, ignore +let mkEvent (ty: string) (data: 'a) = + { + eventId = Guid.NewGuid().ToString() + eventType = ty + data = { wrapped = data } + } + +/// Adds element 'e' to the stream identified by 'url'. +/// It must be possible to serialize 'e' to JSON. +let postData (url: string) (e : 'T) = + let ev = mkEvent "null" e + let j = toJSON [|ev|] + let bs = Encoding.ASCII.GetBytes j + + let req = HttpWebRequest.Create(url) + req.Method <- WebRequestMethods.Http.Post + req.ContentType <- "application/json" + req.ContentLength <- bs.LongLength + let stream = req.GetRequestStream() + stream.Write(bs, 0, bs.Length) + stream.Close() + + try + req.GetResponse() + with + | :? System.Net.WebException as e -> e.Response + + +let private get (url: string) : string = + let req = HttpWebRequest.Create(url) :?> HttpWebRequest + req.Accept <- "application/json" + req.GetResponse().GetResponseStream().ToBytes() + |> Encoding.ASCII.GetString + +let private getUriByRelation (relation:string) (o: seq) : string = + Seq.pick (fun (o : JToken) -> if o.["relation"].ToString() = relation + then Some (o.["uri"].ToString()) else None) o + +let private eventUrls (j: JObject) = + j.["entries"].Children() + |> Seq.map (fun o -> (o.["updated"].Value(), o.["links"])) + |> Seq.map (fun (x,y) -> (x, y |> getUriByRelation "alternate")) + +/// Given a url of a stream, constructs a pair of objects, one being a +/// "hot" observable stream and the other being a disposable used to stop +/// polling of the underlying event store. +let observableOfStream<'T> (url: string) : IObservable * IDisposable = + + let cell = + Agent.Start (fun inbox -> + let observers = HashSet HashIdentity.Reference + + let rec loop url = + async { + let! command = inbox.TryReceive(250) + match command with + | Some (Choice1Of3 o) -> + observers.Add o |> ignore + return! loop url + | Some (Choice2Of3 o) -> + observers.Remove o |> ignore + return! loop url + | Some (Choice3Of3 o) -> () + + | None -> + let j = get url |> JObject.Parse + if j.["entries"].HasValues then + for (datetime,url) in eventUrls j |> Seq.toArray |> Array.rev do + let value = + let str = get url + //printfn "uri: %O\njson: \n%s" url str + str |> Tsunami.SerDes.JS.fromJSON> + for (o: IObserver) in observers do + o.OnNext(datetime,value.wrapped) + + return! loop (j.["links"].Children() |> getUriByRelation "previous") + else + return! loop url + } + loop url + ) + + cell.Error.Add(fun exn -> + printfn "Raised an exception: %A" exn) + + let o = + { new IObservable with + member __.Subscribe observer = + cell.Post(Choice1Of3 observer) + { new IDisposable with + member __.Dispose() = cell.Post(Choice2Of3 observer) + } + } + let d = + { + new IDisposable with + member __.Dispose() = cell.Post(Choice3Of3 ()) + } + + o,d + diff --git a/Samples/EventStore/EventStoreTest.fsx b/Samples/EventStore/EventStoreTest.fsx new file mode 100644 index 0000000..10e0b2c --- /dev/null +++ b/Samples/EventStore/EventStoreTest.fsx @@ -0,0 +1,139 @@ +// To run this sample you must run Tsunami as an administrator. +// Please download and install EventStore before you run this sample. +// You can find it here: http://geteventstore.com/ +// Make sure you update the value of the eventStoreDir binding to your install location. + +#r "Tsunami.IDEDesktop.exe" +#r "Newtonsoft.Json.dll" +#r "System.Reactive.Core.dll" +#r "System.Reactive.Linq.dll" +#r "System.Reactive.Interfaces.dll" +#r "WindowsBase.dll" +#r "PresentationCore.dll" +#r "PresentationFramework.dll" +#r "System.Xaml.dll" +#r "System.Core.dll" +#r "System.Xml.Linq.dll" +#r "UIAutomationTypes.dll" +#r "System.Drawing.dll" +#r "System.Windows.Forms.dll" +#r "WindowsFormsIntegration.dll" +#r "System.Windows.Forms.DataVisualization.dll" +#r "ActiproSoftware.Charts.Wpf.dll" +#r "ActiproSoftware.Shared.Wpf.dll" +#load "EventStore.fsx" + + +//let policyServer = Tsunami.Server.PolicyServer.server System.Net.IPAddress.Loopback 943 +let eventStoreDir = @"C:\bin\EventStore\eventstore-net-2.0.0" + +open Tsunami +open Tsunami.Utilities + +open System +open System.IO +open System.Net +open System.Text +open System.Collections.Generic +open Tsunami.Utilities +open Newtonsoft.Json +open Newtonsoft.Json.Linq +open System.Diagnostics +open ActiproSoftware.Windows.Controls.Charts +open System.Windows +open System.Windows.Controls +open System.Reactive.Linq + +let eventStoreExe = + let path = Path.Combine(eventStoreDir, "EventStore.SingleNode.exe") + if not (File.Exists path) then + failwith "Please install EventStore. See comment at top of page." + path + +let eventStore = Process.Start(eventStoreExe, "--http-port=4532") + +let stream = "http://127.0.0.1:4532/streams/floatstream" + +EventStore.postData stream 4.0 |> ignore + +let obs = EventStore.observableOfStream stream |> fst +let d = obs.Subscribe(fun (dt,v) -> printfn "Received %s %f" (dt.ToString()) v) + +d.Dispose() + +let chatStream = "http://127.0.0.1:4532/streams/chatstream" + +type Chat = { + sender : string + message : string +} + +EventStore.postData chatStream {sender = "Matt"; message = "Hello World"} |> ignore +let chatObs = EventStore.observableOfStream chatStream |> fst +let chatD = chatObs.Subscribe(fun (dt,v) -> printfn "%s: %s" v.sender v.message) +chatD.Dispose() + +type ViewModel private () = + let propertyChanged = Event() + let valueChanged = Event() + let notify obj s = propertyChanged.Trigger(obj, System.ComponentModel.PropertyChangedEventArgs(s)) + let mutable value = 0. + interface System.ComponentModel.INotifyPropertyChanged with + [] + member this.PropertyChanged = propertyChanged.Publish + + member this.Value with get() = value and set(x) = value <- x; notify this "Value"; valueChanged.Trigger(x) + member this.ValueChanged = valueChanged.Publish + member this.SilentUpdate(x) = value <- x; notify this "Value"; + static member val Instance = ViewModel() + +let trailing (timespan:TimeSpan) (obs:IObservable<'a>) = + obs.Timestamp() + |> Observable.scan (fun ys x -> + let now = DateTime.UtcNow + x :: (ys |> List.filter (fun x -> (now - x.Timestamp.UtcDateTime) < timespan))) [] + |> Observable.map (fun xs -> [| for x in xs -> (x.Timestamp.UtcDateTime,x.Value) |]) + + +Observable.Sample(ViewModel.Instance.ValueChanged, TimeSpan.FromSeconds(0.2)) +|> Observable.add (fun x -> EventStore.postData stream x |> ignore) + +(* Charting *) + +type DateValue = { + date : DateTime + value : float +} + +let f _ = + let dp = DockPanel() + let slider = Slider(Orientation = Orientation.Vertical, Minimum = 0., Maximum = 100.) + slider.DataContext <- ViewModel.Instance + slider.SetBinding(Slider.ValueProperty, Data.Binding("Value")) |> ignore + + + let chart = XYChart() + let xAxis = XYDateTimeAxis() + let yAxis = XYDoubleAxis(Minimum = 0., Maximum = 100.) + let lineSeries = LineSeries(LineKind = XYSeriesLineKind.Spline, + IsAggregationEnabled = false, + XPath = "date", XAxis = xAxis, YPath = "value", YAxis = yAxis) + chart.Series.Add(lineSeries) + chart.XAxes.Add(xAxis) + chart.YAxes.Add(yAxis) + + Observable.Interval(TimeSpan.FromSeconds(0.2)) + .CombineLatest(obs,fun _ x -> x) + .Sample(TimeSpan.FromSeconds(0.2)) + |> trailing (TimeSpan.FromMinutes(1.)) + |> Observable.add (fun xs -> Dispatcher.invoke(fun _ -> lineSeries.ItemsSource <- [|for x in xs -> { date = fst x; value = snd (snd x)}|])) + + DockPanel.SetDock(slider,Dock.Left) + DockPanel.SetDock(chart,Dock.Right) + dp.Children.Add(slider) |> ignore + dp.Children.Add(chart) |> ignore + dp :> UIElement + +Tsunami.IDE.SimpleUI.addControlToNewDocument("Event Stream", f) +Tsunami.IDE.SimpleUI.addControlToNewDocument("Event Stream", f) + diff --git a/Samples/EventStore/EventStoreTestSL.fsx b/Samples/EventStore/EventStoreTestSL.fsx new file mode 100644 index 0000000..e30dc0d --- /dev/null +++ b/Samples/EventStore/EventStoreTestSL.fsx @@ -0,0 +1,303 @@ +#r "System.Core.dll" +#r "System.dll" +#r "System.Net.dll" +#r "System.Runtime.Serialization.dll" +#r "System.ServiceModel.Web.dll" +#r "System.Windows.Browser.dll" +#r "System.Windows.dll" +#r "System.Xml.dll" +#r "System.Runtime.Serialization.Json.dll" +#r "System.Windows.Controls.dll" +#r "System.Windows.Data.dll" +#r "System.ComponentModel.DataAnnotations.dll" +#r "System.Xml.Linq.dll" +#r "ActiproSoftware.BarCode.Silverlight.dll" +#r "ActiproSoftware.Charts.Silverlight.dll" +#r "ActiproSoftware.MicroCharts.Silverlight.dll" +#r "ActiproSoftware.Shared.Silverlight.dll" +#r "ActiproSoftware.SyntaxEditor.Addons.DotNet.Silverlight.dll" +#r "ActiproSoftware.SyntaxEditor.Addons.Xml.Silverlight.dll" +#r "ActiproSoftware.SyntaxEditor.Silverlight.dll" +#r "ActiproSoftware.Text.Addons.DotNet.Silverlight.dll" +#r "ActiproSoftware.Text.Addons.Xml.Silverlight.dll" +#r "ActiproSoftware.Text.LLParser.Silverlight.dll" +#r "ActiproSoftware.Text.Silverlight.dll" +#r "ActiproSoftware.Themes.Office.Silverlight.dll" +#r "ActiproSoftware.Views.Silverlight.dll" +#r "ActiproSoftware.Wizard.Silverlight.dll" +#r "Telerik.Windows.Controls.Data.dll" +#r "Telerik.Windows.Controls.DataServices.dll" +#r "Telerik.Windows.Controls.Docking.dll" +#r "Telerik.Windows.Controls.Input.dll" +#r "Telerik.Windows.Controls.Navigation.dll" +#r "Telerik.Windows.Controls.RibbonView.dll" +#r "Telerik.Windows.Controls.dll" +#r "Telerik.Windows.Data.dll" +#r "ActiproUtilities.dll" +#r "Crystalbyte.Net.dll" +#r "FSharp.Compiler.Silverlight.dll" +#r "Ionic.Zip.dll" +#r "NewtonSoft.Json.dll" +#r "System.Reactive.Core.dll" +#r "System.Reactive.Interfaces.dll" +#r "System.Reactive.Linq.dll" +#r "System.Reactive.PlatformServices.dll" +//#r "System.Threading.Tasks.SL5.dll" +#r "Tsunami.IDESilverlight.dll" + + +open System +open System.IO +open System.Net +open System.Text +open System.Collections.Generic +open Tsunami.Utilities +open Tsunami.SerDes.JS +open Newtonsoft.Json +open Newtonsoft.Json.Linq + +module EventStore = + /// Should be private, ignore + type Wrapped<'a> = + { + wrapped: 'a + } + + /// Should be private, ignore + type Event<'a> = + { + eventId: string + eventType: string + data: Wrapped<'a> + } + + /// Should be private, ignore + let mkEvent (ty: string) (data: 'a) = + { + eventId = Guid.NewGuid().ToString() + eventType = ty + data = { wrapped = data } + } + + let private webClient = WebClient() + + /// Adds element 'e' to the stream identified by 'url'. + /// It must be possible to serialize 'e' to JSON. + let postData (url: string) (e : 'T) = + let ev = mkEvent "null" e + let j = toJSON [|ev|] + let bs = Encoding.UTF8.GetBytes j + + let req = HttpWebRequest.Create(url) + req.Method <-"POST" + req.ContentType <- "application/json" + req.ContentLength <- int64 bs.Length + let stream = Async.FromBeginEnd(req.BeginGetRequestStream, req.EndGetRequestStream) |> Async.RunSynchronously + stream.Write(bs, 0, bs.Length) + stream.Close() + + try + req.AsyncGetResponse() |> Async.RunSynchronously + with + | :? System.Net.WebException as e -> e.Response + + + let private get (hostUri:Uri) (url: string) : string = + let uri = UriBuilder(url) + uri.Port <- hostUri.Port + uri.Host <- hostUri.Host + let url2 = uri.Uri.ToString() + //printfn "url: %s" url2 + let req = HttpWebRequest.CreateHttp(url2) + req.Method <- "GET" + req.Accept <- "application/json" + //let stream = Async.FromBeginEnd(req.BeginGetRequestStream, req.EndGetRequestStream) |> Async.RunSynchronously + //stream.Close() + let resp = Async.FromBeginEnd(req.BeginGetResponse, req.EndGetResponse) |> Async.RunSynchronously + + //let resp = req.AsyncGetResponse() |> Async.RunSynchronously + let bytes = resp.GetResponseStream().ToBytes() + + let str = Encoding.UTF8.GetString(bytes,0,bytes.Length) + + //printfn "%O" uri.Uri + //let str = webClient.AsyncDownloadString(uri.Uri) |> Async.RunSynchronously + //printfn "%s" str + str + + + let private getUriByRelation (relation:string) (o: seq) : string = + Seq.pick (fun (o : JToken) -> if o.["relation"].ToString() = relation + then Some (o.["uri"].ToString()) else None) o + + let private eventUrls (j: JObject) = + j.["entries"].Children() + |> Seq.map (fun o -> (o.["updated"].Value(), o.["links"])) + |> Seq.map (fun (x,y) -> (x, y |> getUriByRelation "alternate")) + + /// Given a url of a stream, constructs a pair of objects, one being a + /// "hot" observable stream and the other being a disposable used to stop + /// polling of the underlying event store. + let observableOfStream<'T> (url: string) : IObservable * IDisposable = + let hostUri = Uri(url) + let cell = + Agent.Start (fun inbox -> + let observers = HashSet HashIdentity.Reference + + let rec loop url = + async { + let! command = inbox.TryReceive(250) + match command with + | Some (Choice1Of3 o) -> + observers.Add o |> ignore + return! loop url + | Some (Choice2Of3 o) -> + observers.Remove o |> ignore + return! loop url + | Some (Choice3Of3 o) -> () + + | None -> + let j = (get hostUri url) |> JObject.Parse + if j.["entries"].HasValues then + for (datetime,url) in eventUrls j |> Seq.toArray |> Array.rev do + let value = (get hostUri url) |> Tsunami.SerDes.JS.fromJSON> + for (o: IObserver) in observers do + o.OnNext(datetime,value.wrapped) + + return! loop (j.["links"].Children() |> getUriByRelation "previous") + else + return! loop url + } + loop url + ) + + cell.Error.Add(fun exn -> + printfn "Raised an exception: %A" exn) + + let o = + { new IObservable with + member __.Subscribe observer = + cell.Post(Choice1Of3 observer) + { new IDisposable with + member __.Dispose() = cell.Post(Choice2Of3 observer) + } + } + let d = + { + new IDisposable with + member __.Dispose() = cell.Post(Choice3Of3 ()) + } + + o,d + +open Tsunami +open Tsunami.Utilities + +open System +open System.IO +open System.Net +open System.Text +open System.Collections.Generic +open Tsunami.Utilities +open Newtonsoft.Json +open Newtonsoft.Json.Linq +open System.Diagnostics +open ActiproSoftware.Windows.Controls.Charts +open System.Windows +open System.Windows.Controls +open System.Reactive.Linq + +let stream = "http://127.0.0.1:4531/streams/floatstream" + +EventStore.postData stream 4.0 |> ignore + +let obs = EventStore.observableOfStream stream |> fst +let d = obs.Subscribe(fun (dt,v) -> printfn "Received %s %f" (dt.ToString()) v) +d.Dispose() + +let chatStream = "http://127.0.0.1:4531/streams/chatstream" + +type Chat = { + sender : string + message : string +} +let chatObs = EventStore.observableOfStream chatStream |> fst +let chatD = chatObs.Subscribe(fun (dt,v) -> printfn "%s: %s" v.sender v.message) +EventStore.postData chatStream {sender = "John"; message = "Hello Matt"} |> ignore +chatD.Dispose() + +let trailing (timespan:TimeSpan) (obs:IObservable<'a>) = + obs.Timestamp() + |> Observable.scan (fun ys x -> + let now = DateTime.UtcNow + x :: (ys |> List.filter (fun x -> (now - x.Timestamp.UtcDateTime) < timespan))) [] + |> Observable.map (fun xs -> [| for x in xs -> (x.Timestamp.UtcDateTime,x.Value) |]) + + + + +(* Charting *) +type DateValue = { + date : DateTime + value : float +} + +let mutable lastValue = 0. +let mutable newValue = 0. +async { + while true do + do! Async.Sleep(500) + if lastValue <> newValue then + EventStore.postData stream newValue |> ignore + lastValue <- newValue +} |> Async.StartImmediate + +let f _ = + let dp = Grid() + dp.ColumnDefinitions.Add(ColumnDefinition(Width = GridLength.Pixels 30)) + dp.ColumnDefinitions.Add(ColumnDefinition()) + let slider = Slider(Orientation = Orientation.Vertical, Minimum = 0., Maximum = 100.) + slider.ValueChanged.Add(fun x -> newValue <- x.NewValue) + + let chart = XYChart() + let xAxis = XYDateTimeAxis() + let yAxis = XYDoubleAxis(Minimum = 0., Maximum = 100.) + let lineSeries = LineSeries(LineKind = XYSeriesLineKind.Spline, + IsAggregationEnabled = false, + XPath = "date", XAxis = xAxis, YPath = "value", YAxis = yAxis) + chart.Series.Add(lineSeries) + chart.XAxes.Add(xAxis) + chart.YAxes.Add(yAxis) + + let xs = Observable.Interval(TimeSpan.FromSeconds(0.2)) + .CombineLatest(obs,fun _ x -> x) + .Sample(TimeSpan.FromSeconds(0.2)) + |> trailing (TimeSpan.FromMinutes(1.)) + + xs.Subscribe(fun xs -> Dispatcher.invoke(fun _ -> lineSeries.ItemsSource <- [|for x in xs -> { date = fst x; value = snd (snd x)}|])) |> ignore + + Grid.SetColumn(slider,0) + Grid.SetColumn(chart,1) + dp.Children.Add(slider) |> ignore + dp.Children.Add(chart) |> ignore + dp :> UIElement + + +open Telerik.Windows.Controls + +let addControlToNewDocument(name,f:unit -> UIElement) = + Dispatcher.invoke(fun () -> + // Find panes group + let window = Application.Current.RootVisual :?> Tsunami.IDESilverlight.MainWindow + let grid = window.Content :?> Grid + let docking = grid.Children |> Seq.pick (function :? RadDocking as x -> Some x | _ -> None) + let container = docking.Items |> Seq.pick (function :? RadSplitContainer as x -> Some x | _ -> None) + let group = container.Items |> Seq.pick (function :? RadPaneGroup as x -> Some x | _ -> None) + // Add pane + let pane = RadPane(Header=name) + pane.MakeFloatingDockable() + group.Items.Add(pane) + // Set content + pane.Content <- f()) + +addControlToNewDocument("Event Stream", f) diff --git a/Samples/Excel/ABTesting.fsx b/Samples/Excel/ABTesting.fsx new file mode 100644 index 0000000..57d0cb0 --- /dev/null +++ b/Samples/Excel/ABTesting.fsx @@ -0,0 +1,98 @@ +#r "cache:http://tsunami.io/assemblies/MathNet.Numerics.dll" +#r "cache:http://tsunami.io/assemblies/MathNet.Numerics.FSharp.dll" +#load "ExcelCharts.fsx" + +open System +open System.Net +open MathNet.Numerics.Random +open MathNet.Numerics.Distributions +open Microsoft.Office.Interop.Excel + +let now = System.DateTime.Now +let chart = ExcelCharts.NewChart().Value +let clear = ExcelCharts.clear + +chart.ChartType <- XlChartType.xlLine + +let monthOfYearAdjustment = + let adjustments = + [| + yield 1.3 // January + yield 0.8 // Feburary + yield 1.0 // March + yield 0.9 // April + yield 0.7 // May + yield 0.4 // June + yield 0.4 // July + yield 0.7 // August + yield 1.0 // September + yield 1.3 // October + yield 2.0 // November + yield 2.5 // December + |] + let average = (adjustments |> Array.sum) / float adjustments.Length + fun (time:System.DateTime) -> + + // TODO - find a better interpolation formulae + let current = adjustments.[time.Month - 1] / average + let previous = adjustments.[(time.Month - 2 + 12) % 12] / average + let numberOfDays = float (DateTime.DaysInMonth(time.Year,time.Month)) + let day = float time.Day + (previous * ((numberOfDays - day) / numberOfDays) + current * (day / numberOfDays)) / 2. + + + +let dayOfWeekAdjustment = + let adjustments = + [| + yield 1.5 // Sunday + yield 0.6 // Monday + yield 0.6 // Tuesday + yield 0.8 // Wednesday + yield 0.6 // Thursday + yield 0.8 // Friday + yield 2.0 // Saturday + |] + let average = (adjustments |> Array.sum) / float adjustments.Length + fun (time:System.DateTime) -> adjustments.[int time.DayOfWeek] / average + + +let normal = Normal.WithMeanVariance(0.0, 1.0) + +let weightedFunction(time:DateTime) = + monthOfYearAdjustment(time) * 10. + + dayOfWeekAdjustment(time) * 2. + + normal.Sample() * 0.4 + +let red = BitConverter.ToInt32([|255uy;0uy;0uy;0uy|],0) +let blue = BitConverter.ToInt32([|0uy;0uy;255uy;0uy|],0) +let green = BitConverter.ToInt32([|0uy;255uy;0uy;0uy|],0) + +let genData n = + let gen() = [| for x in 0..n -> now.AddDays(float -x) |] |> Array.map (weightedFunction) + let xs = + [| + // Controls + for i in 1..5 -> (gen(), red, sprintf "Cntrl%i" i) + // Experiments + for i in 1..4 -> (gen(), blue, sprintf "Exp%i" i) + // Successful Experiment + yield (gen() |> Array.map ((+) 2.), blue, "Exp5") + |] + [| + yield! xs + yield ([| for i in 0..n -> [| for j in 0..xs.Length-1 -> match xs.[j] with | (xs,color,name) -> xs.[i] |] |> Array.average |], green, "Average") + |] + +let plotData (chart:Chart) ys = + for (xs,color, name) in ys do + let seriesCollection = chart.SeriesCollection() :?> SeriesCollection + let series = seriesCollection.NewSeries() + series.Name <- name + series.Format.Line.ForeColor.RGB <- color + series.Values <- xs + +clear chart +genData 28 |> plotData chart +clear chart +genData 365 |> plotData chart diff --git a/Samples/Excel/BurndownChart.fsx b/Samples/Excel/BurndownChart.fsx new file mode 100644 index 0000000..a685fff --- /dev/null +++ b/Samples/Excel/BurndownChart.fsx @@ -0,0 +1,131 @@ +#r "cache:http://tsunami.io/assemblies/MathNet.Numerics.dll" +#r "cache:http://tsunami.io/assemblies/MathNet.Numerics.FSharp.dll" +#load "ExcelCharts.fsx" + +open System +open System.Net +open MathNet.Numerics.Random +open MathNet.Numerics.Distributions +open Microsoft.Office.Interop.Excel + +module Colors = + let red = BitConverter.ToInt32([|255uy;0uy;0uy;0uy|],0) + let blue = BitConverter.ToInt32([|0uy;0uy;255uy;0uy|],0) + let green = BitConverter.ToInt32([|0uy;255uy;0uy;0uy|],0) + +type BurnDownChart = { + title : string + xAxis : string + yAxis : string + days : int + storyPoints : int + remaining : int[] +} + +[] +module BurnDown = + let idealBurndown days storyPoints = Array.init (days + 1) (fun i -> max 0 (storyPoints - int (float i * float storyPoints / float days))) + + + let private genSinWave (width:float) (offset:float) = Seq.initInfinite (fun i -> sin (((float i + offset )/ width) * 2. * Math.PI)) + + /// slow, fast, slow + let private sfs days = genSinWave (float days * 2.) 0. |> Seq.map (fun x -> (x * 2.)) + // fast, slow, fast + let private fsf days = genSinWave (float days * 2.) (float days) |> Seq.map (fun x -> (x + 1.) * 2.) + // fast, slow, fast, slow + let private fsfs days = genSinWave (float days * 0.666) (float days * 0.2) |> Seq.map (fun x -> (x + 1.)) + + let private scurveBurndown days storyPoints (f:int -> float seq) (weight:float) = + [| + let avg = float storyPoints / float days + yield! + Normal.WithMeanVariance(avg, avg * 3.).Samples() + |> Seq.zip (f days) + |> Seq.map (fun (x,y) -> x * (max y 0.) * weight) + |> Seq.map int + |> Seq.map (max 4) + |> Seq.scan (fun state t -> state - t) storyPoints + |> Seq.takeWhile (fun x -> x > 0) + yield 0 + |] + + let slowFastSlow (chart:BurnDownChart) = {chart with remaining = scurveBurndown chart.days chart.storyPoints sfs 0.8} + let fastSlowFast (chart:BurnDownChart) = {chart with remaining = scurveBurndown chart.days chart.storyPoints fsf 1.3} + let fastSlowFastSlow (chart:BurnDownChart) = {chart with remaining = scurveBurndown chart.days chart.storyPoints fsfs 1.0} + let avereage (chart:BurnDownChart) = + {chart with + remaining = + [| + let avg = float chart.storyPoints / float chart.days + yield! + Normal.WithMeanVariance(avg, avg * 3.).Samples() + |> Seq.map int + |> Seq.map (max 4) + |> Seq.scan (fun state t -> state - t) chart.storyPoints + |> Seq.takeWhile (fun x -> x > 0) + yield 0 + |] + } + let mutable private excelChart = Option.None + let truncate (n:int) (chart:BurnDownChart) = {chart with remaining = chart.remaining |> Seq.truncate n |> Seq.toArray} + let display (chart:BurnDownChart) = + + let plotData (chart:Chart) ys = + for (xs,color, name) in ys do + let seriesCollection = chart.SeriesCollection() :?> SeriesCollection + let series = seriesCollection.NewSeries() + series.MarkerForegroundColor <- color + series.Name <- name + series.Format.Line.ForeColor.RGB <- color + series.Values <- xs + + let displayChart (bdc:BurnDownChart) (ec:Chart) = + ec.HasTitle <- true + ec.ChartTitle.Text <- bdc.title + let xAxis = ec.Axes(XlAxisType.xlCategory, XlAxisGroup.xlPrimary) :?> Axis + xAxis.HasTitle <- true + xAxis.AxisTitle.Text <- bdc.xAxis + let yAxis = ec.Axes(XlAxisType.xlValue, XlAxisGroup.xlPrimary) :?> Axis + yAxis.HasTitle <- true + yAxis.AxisTitle.Text <- bdc.yAxis + [| + idealBurndown bdc.days bdc.storyPoints, Colors.blue,"Ideal Burndown" + bdc.remaining, Colors.red,"Remaining" + |] + |> plotData ec + + match excelChart with + | Some(x) -> + ExcelCharts.clear x + displayChart chart x + | None -> + match ExcelCharts.NewChart() with + | Some(y) -> + excelChart <- Some(y) + y.ChartType <- XlChartType.xlLineMarkers + displayChart chart y + | None -> () // no-op + +module WidgetCo = + let moreHeadCountNeeded : BurnDownChart -> BurnDownChart = failwith "todo" + let everythingIsFine : BurnDownChart -> BurnDownChart = failwith "todo" + /// Demonstrate little and slowing progress + let thisWasNotMyIdea : BurnDownChart -> BurnDownChart = failwith "todo" + + + + +let Default = { + title = "Big Important Project Burn Down" + xAxis = "Iteration Timeline (days)" + yAxis = "Sum of Task Estimates (Story Points)" + days = 20 + storyPoints = 200 + remaining = [||] + } + +Default |> slowFastSlow |> display +Default |> fastSlowFast |> truncate 10 |> display +Default |> fastSlowFastSlow |> display +Default |> avereage |> display diff --git a/Excel/Charting/ExcelEnv.fsx b/Samples/Excel/ExcelCharts.fsx similarity index 89% rename from Excel/Charting/ExcelEnv.fsx rename to Samples/Excel/ExcelCharts.fsx index 835d18c..4381f8e 100644 --- a/Excel/Charting/ExcelEnv.fsx +++ b/Samples/Excel/ExcelCharts.fsx @@ -1,7 +1,7 @@ -module Excel +module ExcelCharts //Written by Mathias Brandewinder (Twitter: @brandewinder Blog: http://www.clear-lines.com/blog/) -#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\office.dll" -#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Excel.dll" +#r "cache:http://tsunami.io/assemblies/office.dll" +#r "cache:http://tsunami.io/assemblies/Microsoft.Office.Interop.Excel.dll" open Microsoft.Office.Interop.Excel open System.Runtime.InteropServices @@ -202,4 +202,19 @@ let labeledplot<'a when 'a: equality> (data: (float * float * 'a * string ) seq) let point = series.Points(i) :?> Point point.DataLabel.Text <- ls.[i-1] chart.ChartType <- XlChartType.xlXYScatter - xl.ScreenUpdating <- true \ No newline at end of file + xl.ScreenUpdating <- true + +type SeriesData = {Name : string; XValues : obj[]; Values : obj[] } + +let appendSeries (chartType : XlChartType, seriesData : SeriesData) (chart : Chart) = + chart.ChartType <- chartType + let seriesCollection = chart.SeriesCollection() :?> SeriesCollection + let series = seriesCollection.NewSeries() + series.Name <- seriesData.Name + series.Values <- seriesData.Values + series.XValues <- seriesData.XValues + chart + +let clear (c:Chart) = + let sc = c.SeriesCollection() :?> Microsoft.Office.Interop.Excel.SeriesCollection + for i in [sc.Count .. -1 .. 1] do sc.Item(i).Delete() |> ignore \ No newline at end of file diff --git a/Excel/Charting/README.md b/Samples/Excel/README.md similarity index 100% rename from Excel/Charting/README.md rename to Samples/Excel/README.md diff --git a/Excel/Charting/ChartingDemo.fsx b/Samples/Excel/Stocks/Demo.fsx similarity index 62% rename from Excel/Charting/ChartingDemo.fsx rename to Samples/Excel/Stocks/Demo.fsx index 6c6fa19..40d766a 100644 --- a/Excel/Charting/ChartingDemo.fsx +++ b/Samples/Excel/Stocks/Demo.fsx @@ -1,5 +1,5 @@ -#load @"C:\ExcelEnv.fsx" -#load @"C:\StocksScript.fsx" +//#load @"ExcelEnv.fsx" +#load "StocksScript.fsx" open StockScript open System open System.Net @@ -17,5 +17,6 @@ chart |> addMovingAverages msft chart |> clear for stock in ["AAPL";"MSFT";"GOOG"] do - chart |> addStockHistory (read (stock, now.AddMonths(-6), now)) - chart |> addMovingAverages (read (stock, now.AddMonths(-6), now)) + let data = (read (stock, now.AddMonths(-6), now)) + chart |> addStockHistory data + chart |> addMovingAverages data diff --git a/Excel/Charting/StocksScript.fsx b/Samples/Excel/Stocks/StocksScript.fsx similarity index 84% rename from Excel/Charting/StocksScript.fsx rename to Samples/Excel/Stocks/StocksScript.fsx index 690dd25..98086dc 100644 --- a/Excel/Charting/StocksScript.fsx +++ b/Samples/Excel/Stocks/StocksScript.fsx @@ -1,9 +1,10 @@ module StockScript -/// A translation by Matthew Moloney of http://vstostocks.codeplex.com/ by Mathias Brandewinder (Twitter: @brandewinder Blog: http://www.clear-lines.com/blog/) -#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Excel.dll" +/// A translation of http://vstostocks.codeplex.com/ (Twitter: @brandewinder Blog: http://www.clear-lines.com/blog/) by Matthew Moloney +#load "..\ExcelCharts.fsx" open Microsoft.Office.Interop.Excel open System open System.Net +open ExcelCharts type TradingDaySummary = {Day : DateTime; Volume : int64; Open : float; Close : float; High : float; Low : float} @@ -44,20 +45,7 @@ let run (history : StockHistory, horizon : int, runs : int) = |> Async.Parallel |> Async.RunSynchronously - - - -type SeriesData = {Name : string; XValues : obj[]; Values : obj[] } - -let appendSeries (chartType : XlChartType, seriesData : SeriesData) (chart : Chart) = - chart.ChartType <- chartType - let seriesCollection = chart.SeriesCollection() :?> SeriesCollection - let series = seriesCollection.NewSeries() - series.Name <- seriesData.Name - series.Values <- seriesData.Values - series.XValues <- seriesData.XValues - chart - + let addStockHistory (history : StockHistory) (chart : Chart) = let dataPoints = history.History |> Array.sortBy (fun x -> x.Day) let xValues = dataPoints |> Array.map (fun x -> box x.Day); @@ -137,6 +125,3 @@ let writeHistory(history : StockHistory, worksheet : Worksheet) = range.Value2 <- dataArray range.NumberFormat <- formatArray -let clear (c:Chart) = - let sc = c.SeriesCollection() :?> Microsoft.Office.Interop.Excel.SeriesCollection - for i in [sc.Count .. -1 .. 1] do sc.Item(i).Delete() |> ignore \ No newline at end of file diff --git a/Samples/Excel/UDFs/CSharp.cs b/Samples/Excel/UDFs/CSharp.cs new file mode 100644 index 0000000..9428848 --- /dev/null +++ b/Samples/Excel/UDFs/CSharp.cs @@ -0,0 +1,14 @@ +using System; + +namespace ClassLibrary1 +{ + public static class Class1 + { + public static Double fAdd3(Double x, Double y) { return x + y + 3.0; } + + public static Double fMult3(Double x) { return x * 3.0; } + + public static Double fSquare(Double x) { return x * x; } + + } +} diff --git a/Samples/Excel/UDFs/Nasdaq100.xlsx b/Samples/Excel/UDFs/Nasdaq100.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..16476ea9b0279913a6f590615878708f913f5754 GIT binary patch literal 14347 zcmeHuWmFwY(=P7r?(XjH8r&tgySux)ySoL~V8NZB0RjZ~K!8AiyUFpq=UeNp`{Vxk z-eIkpP3@{@o~rJi?&)2lC<_XP1_S{F1q1{{4CG^zfnfs-1XKVH1cV9%1)?KrZ|7oa z=kiw7)4|kPkHN#%hNuV(gsK1t1YrN)<9~4mCR6le2bs{qF2G+z$7H0)CWbahFultjSgJOd_b!&jj#QMT*p&c+NS}4r@zG|?3C!&Me9ArZx>XQa<7?Fxh$S}E;n|(be`~z68|B%op38xOU!-cwcUWW_g&8U846EMIh1mQh}@PC4zMR1(Af`-=9o+ ze-zk27xz7Dy&I|5WJenCqBUF!nmOcsm2X7w`5YNnd@Z- zhON!S`9qgM`!>S0Zj}O_Zt?NuIZ8nPdU*i_QvAmtZBb(){Ra3(4lo8`0fY3dlc|j} zBg3ow@8JAjJR<)VdQ8&Hn?WYH4;ND1qF)}5?WCXzOS|Nxx` zfCkAJug~2c%n0gip6^?#65<&#z8UPK5^iKQ@?N&`sLfYLEHEN5w?rGkiNiD0*x0mr z>rs-DZM6JY(G(qx)0y&v)QAmV0S)%*)TuO)Nom~BVlElG!t_(h9)DOLfg(NM$9^j) zNN>n0fd{_tO>J!Q_8yG;zDDV9VubQgc_-ZVM9p9;XVxpSL?)zz(XVqBkjEZLj6w4# z)^rtaURt>vNn|5ur9NNtZACc?oty+R zlAUP^Pfv_+z0MsFh0@OK5J%cArM+OXXs6H^Lm_4`*$-dA+486D@{1u%2&-JR1afMq z=F)2Ib*g)=9s#~!qNUdP+;CiXsOOTfHbLnK!7Xb|kRP zS!B4$GRDR^a`jw-ALo;AWBKthz^IHmE;Y!ZhJ)x$aQyU(byugItdHZKd3;*E=BPd9 zvV2Bp8uBfsaw5den;aKko5p>e30w8so z*)tr-91FySzV|A}mVfV7#aWV+6l0r;u28zuS1Afbj8_Ivz{cOT;JtVJytdz~hSRn{ zCPOtbL-7_rQ87wA9DR+q(O92v&u55PGggG+l3)s9C#x8PBV72l2hUD#y1xATh<-FGwM~Hl0xHLS zU7Y@G4;KqlTT{lLlKFK!pK2-CuXCXFA+ER+x!BuMw&Fs%My)hv*UIA#*@Y8WW~OPX z6ba2s`{J(c1^^Fs8m0qxoiW+a&VqEgPEu_j%dc zC3XF_793G1Nsxh&3_6N6b7a-yYErq=BsZ0fZ2EBSLMd>Z2L?0Nbr%3@MSf zH%p;_u&Dg}`gp`0R`&;)W8nPAKnnRBbO_$OB{q?1({yN;2(5Kc0b@TlGQ^Z{tTY&k zHT{o2y2qD3!feW6GPfAn8tQHRn0Gx2x^kJiMC+$A*FIU+(QtEmfOx;iQn!5K>fMr) z2W+qfDPKz`p7rfnqBePsF5q;R<;<={skZALd6y z-c$X!3B#Oem*1w{+SXkzAxy5O$bDX9s@dVh1Q|^M*`w%YG>Z1>;>>p^ z+<*7Q}PWjQKxqPsgI`1)iR|M#wL110}`p6C}in zbiP30UqZRChB=Z`k~;RH4H^C1v zDzkt-z%~2=bjM|m&maTuAbL%s>A@8p?6hyBK7?=JqxqpL+IXa;M+-!a6;@{Cb|Zny z=$sXg7B3-#s^s84B?0B^TKxGWdZvLf=t*0#n-r`fZaL+UzkF+0>tcb^ACLA1|E>VM4 zFqj6R5;}rNj?k9=HCv)kSqW$)3d44{MVPFmHQ&wicpeAt1D+9W_>fh6{MltE!E_uh zH#Vu7!E`frv8(*&n#ra55;5VnWkdA^k=#t3sR*;EqCt8Mt?ZSYiT*DgR!kW>s64n} zPe_^_&?A0RIMcitiH)sY-<0+-c0;3G65*~gN(AP;7#}Ola#DuJlSs4?ap4|LJ)%Cs zeA3Q3#dY9~PL4V~rd=`J^hRYgMee>tQRK~xqhp|;jGj|kw`O0$A^7Sze@o$@8a6@& zTrayJXN2+IH@Q9p{!t}))#Up?cC8#UC$yErqv2U^NrMU`3h_sTv_g1z*3>YdXm(24 z7j4i0#CSYZCUp)4%m*O(v2X@N0?}5vfHP{t#3Vzo2vgZxmP}xZHNrG$>&$J##D+K~STDuH3?$<;qwCo&(Y7 zW`Y+6cVFD^BMN`$o8p!oEMdl;iBU(1UL=ui#uvC%AdZI$>v~$O3ZFY%Rh8@OY-uSY7L( z$=v*A`S)T~RBx%eO9|R=f@&n>H!&jh?)s6bM9O(-SsaJ9<_J|WebXJI3;j8S@RBc# zWSk9>LTQbw)aE3xQCt4E-U1g7aCII*AU$#7f`75Y3Y)j zEK)^oO=0dwR<``sg*csg+|HyFaa0H^-`>rH&@q9rsVvR92G(IoBIc+?7|M9Hq`xG+ z0CNBT)GDn1ZI%Duts;6(Ob`!Pn&XQ9k7MQMsxKu+)_z?IBlHe*(jTeWUe+f$>`a1E z*w~Bty@9^BR+%lamj@a7(T_)Nx~3}4JK#e*kn(ftrR|S%=8@tL+slH8kHdG*KC8KL z@e`+ZJ-Rm|XGf;)KHj>|2U~qF^9-2jC;RtJtNC$HyI;-ywVoetmizdAaOL*tZ$3VE zzDJHrAK82S_Tbp3t=D?-Wa!+%>w6=qs;_V`xF7YBx22vQ@7(9aLg-zyUcBKx<(l5N&nlEr*r*g{~S!l zr<48l=ZBev_`YvlZFjU7b+aLbn0JhVeK+a!`e1av=^Fj_=~J%PHx2?%nxDZ#XC_9> zA|&13TCnC5vO=$TN4c0dl|!i`6X%Uy z&5s=x=OQ$~NO#|Vl0q#blSrso=VggdA+_Z?wiu!fN^~@^D}hx(A#E8RC3+KU!d9z~ zk`n{!Oqq$Y{K3c+36++uULU0<1~d|#ykU?rAbV>dI772A@>`ODDV|gfRU$r<+q+a@ zxtiHYFX;vr1TyuMS+jd_&o0bz3Ns74V2buk2^3x#7uEX)bF! zN{yZDOox-jAq04bSutL`jt-RVNie-~3O9?0V2ZWv&r_*b<`g$P?2V$q02hQ2`#SMe zu+Q6|KvM|OP0Ij}fes8))G$`W)qCgy1Et$6i}2+3*&-Ve$9-YmcY{Tz>~jd^j)mf@ zA-j`s!O23P=Cr#dTq8qJORjnbY>bW`Mb2j%(XZ-M2E zr~Yn|iH~7gAL+)GN=rXZL35DT&dN(Su8(>aL+T8tE|UTaV8Y{TuEsVljCv|Z8i`Ln z9vV6RCU>pU^0$lErBY#1(C`9M_!uQs17ezVD4HT;=$m5#Y0nG^?`yzk4m54rDe|fe{EoZ4)uz8w`S?oZC=&O~3MCC z^JYMU=z8r;pZ*b#S91E8<^u{s&xFWtHigl44X=`^fwE+ zJRSJTW_wPlLu*(kr|V{A6;Z|=VHC`j@va5sU|h_sye!NjFPI8v$6|U(rhpbTJ2wlb zC=#K_B4UtR$2yT?uH);M*oy|6DV8(wIiwNi)+G_!9p}AOUe=-3haiAoz;yFQ@3@#n zIR&HE2azcxetFqV(@YF2)MDD1EzfYxiXJMr2vMGiX2onOJx`Ds_(nxngMPW2JdD0`Pcw-~dy&|D(A=JyZ9VuHx=nwS+JJ(?BRmF1R|#G#9^k&AES;pY?~ zl}2aTl5muzHiTSx0hh%xS`Py7fR9E}wtfuy%ZiOGL3LhWFzYz0sf@!9g+J_ z>f)!rW&O95g=nu!{!{=!$sm+YUm+-ehrqCsIJVTDC$?q$hvJop`QM^_?GM|DBB0ok z+%00p_-e!wepKw@miz*4Tmr-%hcou)z{3+p-VYpn5?pw@N5nA-GvaPjPn0Ggw2d;+ zGPLSEs-J@6To9JpU)bo#@EHed`wf z_91~U!%w4cHHFATbKiH8syUS1Q;@t$8KPHd!|*B-=ze~td6g?vYL{NS3t6iDC$aJJ z%HaI(4=@Yo4G?o@?3UgwRtX^e+z#&o!v(*1w3;<~K zr&60ekvVmZ)i<`5%TOs^UTJxSX^npc@WrbP(l-_ruPT6C89|~!-#6&etw_;?_!f-*_|eSl@tW8(hmn9(~atBZ*cCSp`2-MaNO;r0Hiq8020WZ6Dk$O z*&#efk-7oFt|5&kBiK9lm5=R+GI-Ai{P(XCzUx)?ynB_lH9z&G0Ld_#bDx^IB(h%SfpO;bc4SnmUB_W%rbXUvFoDNJV! zrQWLyfAcC21YhL{`%gV(Da;!}L}nOtxkPh=pb|n5(pW@$c}u)<;ymy-1G$4VV&MK| zJI2>bFTT!x>-db(zAsUT@h7}K$z1XNB?C`B{J!lzoQ=JM zZ6B8xj*ee~ll4Odt-s#*|LX&9PNX@zGGN!X0s{yL?Y9SBXA46oQxg>zCrdkX=bw*N zn(I#M97w(waxcIiMIYxiEzPrznp;`B4+og3sljSxHw{qAm?(h3!EEQ=Zk6tk#bFpc zP{F{`xy|J9M^D_1bdtpDa*B*q&k|Y>T!X<-Or+m>Uj^ix8 zTNZqAS!D|gsI{q+@HR$xi5ZdaJz%$HSPYcCKhQtQ!oIJZaCDx{L7&QVR?*lM-0Jn^ ztjkOwO{!MUsz&y~PE?OlphxWW?dY```c~R;>qJT*enEUid-Y?|<%&1b#E+X}Ieph} zYNFEyztBFkb2r2-cH4CUm)ch`3tPV+wNOxaiva6hnkDa|v`UrQ!AWPOcv&r&A2G$;Wk0IFFkR z9-#Jx%p@!^Ro$Tx!s>m?FwEKIsLVHfMJYNf!@*UrR=&2|Oe*b1EkCr)qCF(c{7nAu zsUO4rBWHNYVXaMkx3-9_3*W3Vcx?3d{17TYZ!vt(21w63{ zxuWV$GD=mQhu9Q&amZ*4CsW}aKwP^x`#F5pGDh~WASUZF%Nirb45r&45!Dxl`d+X~ zmS@OH-$BqSqtpTJohO)(iZbsbKY%?i7YM_du|<>sQ8e}ulco89xd2$ioG1okgmA@F zC#tSmhB^=qRnrc2&QL_lz!*t!wFe}GkPZr!IIx!XR9j`Z3~S81bVBV+J%P5sx5|*-jegf- z9Bp$yo{2fVfe53U@T#f|ip>4EH5mGh(8rp0`sO>kCvpNUB2mTomdS|7Rn=ox?_vA# zAlA{XX7k}==6&t`@f}?iIBHfwLvhJ-A43lk!=Bif1PePbAa^77LyROcp2aaf@lij0 zX3Ba)nzBq?5yV@H7i$$|;Q@1hz@JBS07=p1d{IsicA?6qE4W6=6ian0ExcQecan}q z><6(UA%&ttWsyb^F=PQd&6ciJNyKZ*eB#rBV6UXg4e4|aqu<~(}o(Mv+EvzEb z)!jUQYr=xQ@FrAAl_8|Ygl_W=MwDJkIdiXFta3~+s(=9khUj~_6{ST=78(U(2}X)& z4tGcCrq~1^88HM97e<8o9s(S_B!f7wCri}tR*)m`NCFCB@DV+QLgZ+>WWdPdw9h~I zQF-IwcjFIE!Ec4^alX*qu&W~&k!u;0$#%k|mVxa8R3T}a;Ah1^N4}JTcjhz;GSR;+SYtdb5c^nzcO&EQhQr< zJ4C;LRhgEG38rrq`vc~gYvwx2RcHc1t>LSi6nahj5=a@X*~@=Mdhk0B5Hfp>cakxviGYfZS5&{qM^@y3J^zN z@%z*+_9DWF9op7bO6@6Qifo9YQpM3o@KcKy{oircgL&`Usg%=E9k!#`kU5pxBg zkM?kfU@vYMD9v*tu%MexZF;ByL#-n6;CWWaRm>LIj&cxF(}pyOa*`-~>%@C7^VBHsb8anBj?**uVAb} z+3RorJ_q$G@wDpMzXHZA^boK0~_J_B&D-PXhBlASdR1*-y8wkEs24J3W%K;F+Jl=Vq3rx;??F*5l5;^V+yIKoW-grR^Uz-GjBO zcj|`uHO$Oga?=yZOH8m)I}IvuN(0y886-zOE|WcxIv(-n)cSE?@A=~h_CT5Qm|3%Y zU7(tZ0htfL=1ul+RD3qt7vJWCJu;VNL?~|i+AL0)k@fLn2dQ^%3lPX2i{h|z_E${G_+|e?eC4^=bzgB`87C1R?*tZmv>d~SK=4( z)B*#`0H$?uG&*{Q{G1B!z&S=we7883l=nKus9@5-)$O#}y$bJ0^meyAwb54I7zz<307I|pE7nAKtjkM|c?7ZSwF$Z68h*y1gRhsv7!C}+1Ie;)lkLkt z!@*r}xXMwLbv`8OyLy7F60L88;rV`nmHzgLZ{c%5y19161J%KlaC~WySHKu=PicOB zky5Hx44xm7xL`%vi@O#g;YvqFOsg#E>X4nTPk$LWL&yBL?hQ#BM0LW6!Xmx^!4VuS z3;*`S?b_wJ^)`7vM3!{DaMPs~t+lW(k}4b@u6Dzzbjcmx-|A1I?0ctS1io!J8L}a~ z#mYZN7RZH>3Y}rUJtMs>Ol~4rUKv??HsC9WR(~92HGP!d2UQ>D?=MZTVxn8zMQPtot zbEiVE?kVQuR{e5UxW+JZUaY^IRfc>JlP+8Dv>GYY6q?b1P;M^Sz44*?D#>g+U$bzY zm8==ve%_cz{BWT$K6jOfHCB+t{t3^fF^az4%_hBlUh1nbli{P*rcd1-`{DUZqh*Uf z>v;@hzEFRaYE+(Gru_hvMv63-jIV$4Zq{xypOzM1Xeb+XBOT5x7+nl~x)E^UK#>!b zf$%rkMuXm=V`cLl&6UhPELc(1Q1Rf@fY88QWqk`}!?CqHw3ar+;Y9fpE=DJ<(DaDm zU8!UcU|5E?QGidPQ32rTjKe#QV4}biVTrPh9(a~R2i-O$>!q5mc(ybmc-7?6OVQY7H{KIi1H1k zG)_rFeOX=A{g~cOP2&xgtP%=e*O5I5Uqsftag9wUM8sQC4MTU6F2Ogi-lkh(ZT>KTW15g&YNZwqA|&vRsB_ zV$${G^J&-_MTTg`Fi#1isXZTH(lW-iR*?tx7QG9uhYP( zf8!o!F4G3A7hT>)oN_%R(FKX7S9XO*T_0Zdk88nIc`vE5V~wrBe9L~K4-_vyQqTRw z7(ejSP;tI>{-hM{Zk!xyd|h9D^xo6QskY0|o2UC-m+-~(kv!pr*R9RN{UMKSHUA@S z(%cqO`^fc;24C^aa~hC(IR2wR{3V&cI=4>ZOaDx){B}XfjGZTlEnR`zo93?%_IPs7 zfZDVF{Kaxc=tpxj;I~XRKzSn)puEw<-dNGe-ocsC(81yLks445{NJKRz)~+KSwUf! z32FF(_6nb5t8xLORz1}?CzjQcPM7y9oKjc5A7!MB3I@X`vweR4C%)E? zdv+)9Fi<+NV3>2ikOUhT9}Sct#h-Ltza30X;;0r=E`BJzmtVP zcx+c_Blsw-m5rfLot|F10Zk?BoUz`Pq6ss2b{nmTeJoQb=HqHuS-o#?%8j#Jy(TCU z%~+V>O1kkyyBnt0CT=?yyBjbl6l{5fwIso%LJM1@0C8S?q<2`L`+F@{p9SVEUQ5QQ z-ZY4LKmngP^A=~JL{MUUU`-a(SNk9iS1{6Xd|Gfhn+dG;5w+91q_WKc% z^!D{di{UNsKG8An>}FSgC|gJxZzm#6C7Vy#$D?&@$()}p+}aI)1fKy>hD5u6%_Raok=`!Fzi=I0b#gE!eC$A0d)J~NjlYxWIhOSV6n+R!7`?X0@d_#LSnhJv7c z$h_zr)b;1P9-U&NLWV4*>;|~#rR9>z$*97cLnVt#L*-5#(Pbjd z1dt43ZWY}F_8xz`eFB5f19l+){Z*$wlKsc|UtWAtl>H~bKVQT6!}0Z83b-f#?WK%g z9sgOG{&&X-0N4MyPW@M$U+a_qLV}0>k80&#oqsK+{mVHQ=3h%|e+Brpkm)Y~Bji7? z{vTyc|Mw{ODF2G`vkd1~lwS)){^Ak`*mC~8Zu{?CewK~=it_7D^+Jrk_djo@fA_X#`J4A2w^l`2a6nH3 Q0)l(}19a9Sw%7mu4}&ys?EnA( literal 0 HcmV?d00001 diff --git a/Samples/Excel/UDFs/VisualBasic.vb b/Samples/Excel/UDFs/VisualBasic.vb new file mode 100644 index 0000000..19f2b27 --- /dev/null +++ b/Samples/Excel/UDFs/VisualBasic.vb @@ -0,0 +1,18 @@ +Namespace ClassLibrary1 + Public NotInheritable Class Class1 + Private Sub New() + End Sub + Public Shared Function fAdd3(x As [Double], y As [Double]) As [String] + Return x + y + 3.0 + End Function + + Public Shared Function fMult3(x As [Double]) As [Double] + Return x * 3.0 + End Function + + Public Shared Function fSquare(x As [Double]) As [Double] + Return x * x + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Samples/Excel/Widget/Widget.xlsx b/Samples/Excel/Widget/Widget.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0fed1f22163309ae3a99ceede1b875ac6fb26cc5 GIT binary patch literal 90693 zcmeFYWl$uGnk|Z3V~sZM?(Pnad*klz?(XjHG}^d3H14j2Yvb+|JoY|k=FN?BW@7%| zTM?BN8JRzFeX>4TSqjqN5NIG!ATS^xAVeUo_#v-jpdcWEupl6)ATVHBB6haUCbrIc zKRoPBoOI~jZLA3kA;2i}LBKxm|9_wVi!Csqsw3Y|h~~p_Ex_8DnIem7G{H?!!e0^b z0*-v4iVF!=2IzTAby=ia@g1L_ z8rqEXI>4eYc{V-)-QAIii}#zrFWM)FqNZsH{6N0agLuDf&qv?s(K*%>|Crd27 zTCSPBovUQWBJe+qFl0~}(F_Jdx<4eUQ=h!5_E&#rlg{7wCeo19Q-T^o+eAC$zsbQj zs7g+OnTqQ*tthgW{rLF}Yxqf9(=T{Vl0{^-f$oGw2;D8}z*$Mqaag<608*+{T5Ky? zix;awq6gYS!NQbfROs8glK!HS9UkM3lfOb4YqLdP@#dfRz`P?$ir5EM_M+SrZ{)Me z{imMfWT%@nvhr&!YNfc!l9J27g(o_0unWEX>s22Y(R2)90#!Kg>G>Wg8>69fkX?RG z-eXV9E36K&F#EHrWWma`3zr&yP50%Fl%Wz1Io>#8{V$KUQyXco?_7^V&*Rzh+zc-S zhZAUdhvrB*&&@tzcJ@74R(HR6%zlXvyVdsi$MB`if=3B%2L`X@XGK?=iW+U^*$-P3 zbjHCTBdoJ-`jKCLM(@W5IEcdkK>e&F|#UJx|J6p;!(J_LgYN*MuWAC9I4 zv^FmeEq)N-8ZyZBw^It$u^4(STe?@}sUqbY5}R3|4P(dR8mMh-TI;zNCubQhzn0ZU zMPaumzmXWS;>n|Zxju9JnZT$xW?(*>gjHs;o_xR;+Jmn^$Gg~P2?Ol~UBUmt`&{3` zDr@J?aOh*0CKoN3i^?{Hbe-cegZnN7nespl=ZAhu*)<5;~*2If~Chc>)0ff(-%# z>Tb>Ok3?~^bF?zFv$OiERQ^ZOfPU7?&wKyx{&Xf!OARn04_yXcDmb=SR8|vbT?l=B zs5k`=c3w-iNlJ|HdwD=q(R9h1(#ucEMJ%v|mKhn$*I#?x32^#>?HN&y*E=I;qrBoVX?7azCa;e-Nt$4(b7Wmqia<)*ET1BLM zjC~b2sU%e#ofp~~3wv#`fzRfzM`#NB+?W)me_{ALKNYb;?efoW2T<;K5k@hYZ(@w^ zWo%IofGpzRqz@jss|r;nhnYA$*{wH6zY_%xTcaVbObhk7(nNWyT1XBitV1phPSfKj zkEAFod~K za7DCS)q-3K+`(!L!+@oscr(=<;PMO&yP5bV@y}G-7Vy$;c(2k%Bc5aCJ7vbqg@**} z$PWi1HCUYqzmLx10d;aNn&jZ%?w|s-q|2=#oS%An$)02|@#F<16d*slyCSOGjIANs7NvmKkn<(8w5}^#ucpf3JKR#9&4hta!2>4LnQ&@<1+Ip6p9Yp z7{}v|sdnP)TnvslAF63yzTFp^Z^KL#nt|fn!a5PH(ueF@Ce3V;X})(yg?_Vs8&k5+ z;vu`{cllzNq9xt~qv%K8SSLRvC!_dViDpxON>@8LC+NGC9k)k7Ehl((}5G z4eUjuSc799*Y?n#gx=2XJ^Uvh^;pcn3J#1>(RZQC@&qPPe<3>LPcqy~AeT%U&`r;Ui;30{apLP3uH*1{LpqXrM?a~SWdhNA`Q{EN3cpW!`PWk3=m1nx`ivY@6cCVa z|9|v2nVXn6J2CwI!~7RdC997+?n)qc+6}x13A=FseL4MMYuyZwYEf}s+sB1hg1;J) zt>S+GO)I~a^ws{^u7xN+`=O#{!nVgCFy?ZjxiDF=CZqJ-JQFReQ}Y*lT+-cTPxs~u ztHqji!^qNNNqX_6SdwqX``i3eRYhJ_X`wkPqjjd_%R(~%Fk3d77s+fT(L~4oWU8@f zvb5Xmi^~RRAHmoVIq)2yS}bDLvYG3(*V5}(q)V&pY^}LTt6ymhD5hW1_yU}qw*=f- zoA@H=WGqfI0J1u%Tk_r2tJKcD#52iFd|%HUlowa~u`hu^qqLW89a*R^jw#D3%5+*_ z?TW~rFH^Sr-}N(NJ2>BTDPUZ$`Dp5f^7ylv@nnai&^M<_Nz0=SNOS4{JhMKte0S3o z?pCf4@7&jI?qwBbez!JElI5igOJWyAiN;Tln(OGNZ0W7jM?C@P%j#B*N4%XE&pocs zK@(zi6S_<11dD*0!ISIqK~u%>Fr@MGU0}Dl<36}FYBjHwxMyC|sovT#*PR(Die3C`sp&ft;XK3>R9?5-v-=%fIoG>j*Aeu7{D+eXUnR6#;=?b; z>9r~z0kN5H4$CCo@JJ!s2i2Iw?P~2`MIFmISrRqqtD1Y(y*o{j(c&Z3>4Fcmxksa! zmGFs4@EpSt39gpQ38S$|a@b-W`pfOZ&V^|QK+i4{Zz{~;1~rWio}8U0mQp{lhv$`>WxA1&C@ zf(qqN@Sf_-E%yX6UdcRBgubfOWO7j^4b`FrFW4ONevGs=idp`OrVl$)H#+W*{e#H5 z97L&feHrYk?4$)HgGEKv2TmzSR}?j2jW3LVy~6BPB5duN08R0q`xDfI@%ei>U+ojO z&)byXngY4MVoHVdJyau!h2<9WZ;l^N#E47l@xV-ejQn=Or;HE_!4WB6_-J;2`s*zlkN2>HHUG2lz^S2^aL*ZP6Vz?ouo0>NYKO(KYEb z%Qy?)(#7THY-wR~p7)bF`sH^Aa2MYfZO`(e&Y!eTpjFs!|BQ#e!S1glJjNSI9|oP^ znD;2#-X&1^ExmcsK6Xd*o8=@5OOH-rvJogPu;h!(4i25QoQvNTz)||R2FKa4}Tyk?Qi2<3*RkwRUPAZ-%DhTHghbS~E z;_#g_y_iVm4E$bWT;yZnN{Rds0AxW_D@WPka|6mRfm zx>5V?;2CijMbu@7=)A3l#zJ9OlQ9J2yYq`El(o?#Y*VYbH+@#tk#fKC(<@Z=p#t@1 zN%C)8esQn`U!d>oQ#VcH2JJL}t!u2K?dq=3w$_*2*&|eaE?PD~ruBR~-XAtvDZmX> zUvDb=;u44$R}B>#zrBr{st+!+7lKcg{Z}3iV;7jE`ef+2&o92zF)&~>2Mx4|7?YtfskIwj91R%* z4E(hf;uN_NeuB_iSjt|Y3iAa9a?ICvGJ$F@P=tECAVIUmG&~7D(CP1^N(v*;<%QA z8@@LtI$XAYBz7ql+8;Sf@jk3A41<8EiQe>}E+M17b&oT zQpI(uo9&!XB@Dt5EIa6Bi&oa*MGOcEXAJR6iGCUQSI@SxDOSTV`E{fKu@cUunNl=Y zsk≠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 lj_I5~f%tinGjr+PO@s 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 RHAoP`3sxRsD=?0be*;R&p*;Wq literal 0 HcmV?d00001 diff --git a/Samples/Excel/Widget/WidgetDemo.fsx b/Samples/Excel/Widget/WidgetDemo.fsx new file mode 100644 index 0000000..109aeb8 --- /dev/null +++ b/Samples/Excel/Widget/WidgetDemo.fsx @@ -0,0 +1,84 @@ +#r "System.Data" +#r "System.Core" +#r "System.Data.Linq.dll" +#r "System.Data.Entity.dll" +#r "cache:http://tsunami.io/assemblies/Microsoft.Office.Interop.Excel.dll" +#r "cache:http://tsunami.io/assemblies/office.dll" +#r "cache:http://tsunami.io/assemblies/FSharp.Data.TypeProviders.dll" + +#r "cache:http://tsunami.io/assemblies/FCell.XlProvider.dll" +#r "cache:http://tsunami.io/assemblies/MathNet.Numerics.dll" +#r "cache:http://tsunami.io/assemblies/MathNet.Numerics.FSharp.dll" + +open System +open System.IO +open System.Data +open System.Data.Linq +open Microsoft.FSharp.Linq +open Microsoft.FSharp.Data.TypeProviders +open MathNet.Numerics.Random +open MathNet.Numerics.Distributions +open FCell.XlProvider +open FCell.TypeProviders.XlProvider +open Microsoft.Office.Interop.Excel + +type dbSchema = SqlEntityConnection<"Server=tcp:xxxx.database.windows.net,1433;Database=TsunamiAzure;User ID=xxxx@xxxxx;Password=xxxx;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;"> +let dc = dbSchema.GetDataContext() + +let widgetReport = new XlWorkbook< "">() + +let clearSQLAzure() = + for x in dc.EUDoDLift do dc.EUDoDLift.DeleteObject(x) + for x in dc.EUMoMLift do dc.EUMoMLift.DeleteObject(x) + dc.DataContext.SaveChanges() + +let uploadExcelToAzure() = + clearSQLAzure() |> ignore + + widgetReport.EUMoM + |> Seq.iteri (fun i x -> dc.EUMoMLift.AddObject(dbSchema.ServiceTypes.EUMoMLift(ID = i, Lift = x))) + + widgetReport.EUDoD + |> Seq.iteri (fun i x -> dc.EUDoDLift.AddObject(dbSchema.ServiceTypes.EUDoDLift(ID = i, Lift = x))) + + widgetReport.USAMoM + |> Seq.iteri (fun i x -> dc.USAMoMLift.AddObject(dbSchema.ServiceTypes.USAMoMLift(ID = i, Lift = x))) + + widgetReport.USADoD + |> Seq.iteri (fun i x -> dc.USADoDLift.AddObject(dbSchema.ServiceTypes.USADoDLift(ID = i, Lift = x))) + + dc.DataContext.SaveChanges() + +let downloadAzureToExcel() = + widgetReport.EUDoD <- [| for x in dc.EUDoDLift -> x |] |> Array.sortBy (fun x -> x.ID) |> Array.map (fun x -> x.Lift) + widgetReport.EUMoM <- [| for x in dc.EUMoMLift -> x |] |> Array.sortBy (fun x -> x.ID) |> Array.map (fun x -> x.Lift) + widgetReport.USADoD <- [| for x in dc.USADoDLift -> x |] |> Array.sortBy (fun x -> x.ID) |> Array.map (fun x -> x.Lift) + widgetReport.USAMoM <- [| for x in dc.USAMoMLift -> x |] |> Array.sortBy (fun x -> x.ID) |> Array.map (fun x -> x.Lift) + +let clearExcel() = + widgetReport.EUDoD <- Array.create 7 0. + widgetReport.EUMoM <- Array.create 12 0. + widgetReport.USADoD <- Array.create 7 0. + widgetReport.USAMoM <- Array.create 12 0. + +let genSynthetic() = + widgetReport.EUDoD <- Normal.WithMeanVariance(0.02, 0.003).Samples() |> Seq.take 7 |> Seq.toArray + widgetReport.EUMoM <- Normal.WithMeanVariance(0.02, 0.001).Samples() |> Seq.take 12 |> Seq.toArray + widgetReport.USADoD <- Normal.WithMeanVariance(0.05, 0.003).Samples() |> Seq.take 7 |> Seq.toArray + widgetReport.USAMoM <- Normal.WithMeanVariance(0.05, 0.001).Samples() |> Seq.take 12 |> Seq.toArray + +let demo() = + async { + clearExcel() + genSynthetic() + uploadExcelToAzure() |> ignore + do! Async.Sleep 500 + clearExcel() + do! Async.Sleep 500 + downloadAzureToExcel() + do! Async.Sleep 500 + genSynthetic() + do! Async.Sleep 500 + downloadAzureToExcel() + clearExcel() + } |> Async.RunSynchronously \ No newline at end of file diff --git a/Samples/Excel/Zero Install/ExcelDemo.cs b/Samples/Excel/Zero Install/ExcelDemo.cs new file mode 100644 index 0000000..560a4c5 --- /dev/null +++ b/Samples/Excel/Zero Install/ExcelDemo.cs @@ -0,0 +1,5 @@ +public static class ExcelUdfs +{ + public static string CSHelloWorld() { return "Hello World from C#!";} +} + diff --git a/Samples/Excel/Zero Install/ExcelDemo.fs b/Samples/Excel/Zero Install/ExcelDemo.fs new file mode 100644 index 0000000..8f005c0 --- /dev/null +++ b/Samples/Excel/Zero Install/ExcelDemo.fs @@ -0,0 +1,12 @@ +module ExcelUdfs +open System +open System.Net + +let ``FS.HelloWorld``() = "Hello World from F#!" +let ``FS.getLastTrade``() : Async = + async { + use client = new WebClient() + let url = sprintf "http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=l1&e=.csv" "MSFT" + let! res = client.AsyncDownloadString(Uri(url)) + return float res + } \ No newline at end of file diff --git a/Samples/Excel/Zero Install/ExcelDemo.vb b/Samples/Excel/Zero Install/ExcelDemo.vb new file mode 100644 index 0000000..ef32c74 --- /dev/null +++ b/Samples/Excel/Zero Install/ExcelDemo.vb @@ -0,0 +1,7 @@ +Public NotInheritable Class ExcelUdfs + Private Sub New() + End Sub + Public Shared Function VBHelloWorld() + Return "Hello World from VB!" + End Function +End Class \ No newline at end of file diff --git a/Samples/Excel/Zero Install/ExcelDemoSetup.fsx b/Samples/Excel/Zero Install/ExcelDemoSetup.fsx new file mode 100644 index 0000000..2b7697e --- /dev/null +++ b/Samples/Excel/Zero Install/ExcelDemoSetup.fsx @@ -0,0 +1,68 @@ +#r "Tsunami.IDEDesktop.exe" +open System +open System.IO +open Tsunami.IDE + +let demoDir = __SOURCE_DIRECTORY__ + +(* Zero Install Excel FCell components are not yet publically available *) +(* +#r @"NOT RELEASED YET\FCell.Packaging.dll" +open FCell.Packaging.Packager +let root = @"NOT RELEASED YET" +let generate ouput (dlls:string[]) = + File.Delete(output) + File.Copy(root + "Packaged.xlsm.bak", output) + + packageUDFs ([| + yield! + [| + "FCell.Bootstrap.xll" + "FCell.ManagedXll.dll" + "FCell.ManagedXll.Rtd.dll" + |] |> Array.map ((+) root) + yield! dlls |> Array.filter (fun x -> x <> demoDir + "VBExcelDemo.dll") + yield @"C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp\3.0\Runtime\v4.0\FSharp.Core.dll" + yield @"VBExcelDemo.dll" + |]) + output +*) + +#r @"C:\Program Files (x86)\Open XML SDK\V2.0\lib\DocumentFormat.OpenXml.dll" + +let output = demoDir + "Demo.xlsm" + +let buildCS = Build.csharpProject +let buildVB = Build.csharpProject + + + +let fsProject = { FSharpProject.DebugBuild with references = [| yield @"System.dll"; yield @"System.Net.dll"; yield! FSharpProject.DebugBuild.references |]; name = "Excel F#"; code = [| FS.File(demoDir + "ExcelDemo.fs") |]; out = Some(demoDir + "FSExcelDemo.dll") } +let cSharpProject = { CSharpProject.Empty with name = "Excel C#"; code = [| FS.File(demoDir + "ExcelDemo.cs") |]; out = Some(demoDir + "CSExcelDemo.dll") } +let vbProject = { VBProject.Empty with name = "Excel VB"; code = [| FS.File(demoDir + "ExcelDemo.vb") |]; out = Some(demoDir + "VBExcelDemo.dll") } + +(* Add projects to Project View *) +let addProjects() = + ProjectsViewModel.Instance.Add(fsProject) + [cSharpProject; vbProject] |> Seq.iter ProjectsViewModel.Instance.Add + +(* Build Projects *) +let buildAndDeploy() = + Build.fsharpProject(fsProject) + fsProject.build(fsProject) + buildCS(cSharpProject) + buildVB(vbProject) + + (* Embed into Excel Workbook *) + [| "FSExcelDemo.dll"; "CSExcelDemo.dll"; "VBExcelDemo.dll"|] + |> Array.map ((+) demoDir) + |> generate (demoDir + "Demo.xlsm") + + + + + + + + + diff --git a/Samples/FunScript/ChatClient.fsx b/Samples/FunScript/ChatClient.fsx new file mode 100644 index 0000000..3368df8 --- /dev/null +++ b/Samples/FunScript/ChatClient.fsx @@ -0,0 +1,372 @@ +#r "cache:http://tsunami.io/assemblies/FunScript.dll" +#r "cache:http://tsunami.io/assemblies/FunScript.TypeScript.dll" +#r "cache:http://tsunami.io/assemblies/FunScript.TypeScript.Interop.dll" +#load @"ChatServer.fsx" + +open System +open System.IO +open FunScript +open FunScript.TypeScript +open System.Threading + + + +[] +let ts = """http://tsunami.io/TypeScript/jquery.d.ts + http://tsunami.io/TypeScript/bootstrap.d.ts + """ + +type j = TypeScript.Api + +type Async = + static member AwaitJQueryEvent(f : ('T -> obj) -> j.JQuery) : Async<'T> = + Async.FromContinuations(fun (cont, econt, ccont) -> + let named = ref None + named := Some (f (fun v -> + (!named).Value.off() |> ignore + cont v + obj() ))) + +[] +module JQuery = + let prepend (x:j.JQuery) (y:j.JQuery) = y.prepend([|box x|]) + let append (xs:j.JQuery[]) (y:j.JQuery) = y.append([|for x in xs do yield box x|]) |> ignore; y + let nestedAppend (xs:j.JQuery[]) (y:j.JQuery) = + let nest (x:j.JQuery) (y:j.JQuery) = + y.append([|box x|]) |> ignore; x + xs |> Seq.fold (fun state x -> nest x state) y |> ignore + y + + let after (x:j.JQuery) (y:j.JQuery) = x.insertAfter(box y) |> ignore; y + let before (x:j.JQuery) (y:j.JQuery) = x.insertBefore(box y) |> ignore; y + + let addAttr (attr:string) (value:string) (x:j.JQuery) = x.attr(attr,value) |> ignore; x + let addClass (``class``:string) (x:j.JQuery) = x.addClass(``class``) |> ignore; x + let setId (id:string) (x:j.JQuery) = x.attr("id",id) |> ignore; x + let onClick (f:unit->unit) (x:j.JQuery) = + x.click(Func(fun _ -> f(); box null)) |> ignore + x + + let hide(x:j.JQuery) = x.hide() + let show(x:j.JQuery) = x.show() + + let onSubmit (f:unit->unit) (x:j.JQuery) = + x.submit(Func(fun _ -> f(); box null)) + + let html (s:string) (j:j.JQuery) = j.html s |> ignore; j + + let attrs (xs:(string*string)[]) (jobj:j.JQuery) = + xs |> Array.iter (fun (name,value) -> jobj.attr(name,value) |> ignore) + jobj + +[] +module Bootstrap = + let showModal (x:j.JQuery) = x.modal("show") + let hideModal (x:j.JQuery) = x.modal("hide") + +[] +module WebSocket = + type IWebSocket = + abstract send : string -> unit + abstract close : unit -> unit + + [] + let createImpl(host : string, onOpen : unit -> unit, onMessage : string -> unit, onClosed : unit -> unit) : IWebSocket = + failwith "never" + + let create(host, onMessage, onClosed) = + Async.FromContinuations (fun (callback, _, _) -> + let socket = ref Unchecked.defaultof<_> + socket := createImpl(host, (fun () -> callback !socket), onMessage, onClosed) + ) + +[] +[] +module Utilities = + [] + let field<'a> (y:string,x:obj) : 'a = failwith "never" + + let (?) (x:'a) (name:string) = field(name,x) + + +[] +module String = + [] + let IsNullOrWhiteSpace (s: string) : bool = failwith "never" + +[] +module TsunamiStateMachine = + let jQuery (command:string) = j.jQuery.Invoke(command) + let ignore _ = () + let (-->) (x:j.JQuery) (y:j.JQuery) = x.append([|box y|]) |> ignore; y + let (<--) (x:j.JQuery) (y:j.JQuery) = x.append([|box y|]) |> ignore; x + + type State = + | Login + | Messages of string * string // username, email + + type Message = + | LoginM of string * string // username, email + | LogoutM + + [] + let alert (s: string) : unit = failwith "never" + +// [] +// let animateScrollBottom(x:j.JQuery) : unit = failwith "never" + + [] + let scrollChatToBottom() : unit = failwith "never" + + type Model() = + + let loginLink = + jQuery("""
  • Login
  • """) + + let logoutLink = + jQuery("""
  • Logout
  • """) + |> JQuery.hide + + let email = jQuery("") |> JQuery.attrs [| "type", "text"; "placeholder", "Email" |] + + let username = jQuery("") |> JQuery.attrs [| "type", "text"; "placeholder", "Username" |] + + let login_button = jQuery("""Login""") + let close_login_button = jQuery("""Close """) + + let loginPanel = + jQuery("""