Permalink
Browse files

Dynamic Angara.Chart via AsyncSeq (#190)

* Dynamic Angara charts by Async printer for AsyncSeq<Chart>

* Angara live chart demo notebook

* Workaround Angara.Serialization.dll load Mono issue. Moving example into main demo notebook
  • Loading branch information...
dgrechka authored and cgravill committed Sep 12, 2018
1 parent 2bffd07 commit 9b307e8ecd10040d53802aca618eaec839359550
@@ -2374,6 +2374,151 @@
"AsyncSeq.initAsync 501L (delayedGenerator 100) |> Display"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Live Angara.Chart \n",
"There is an ability to dynamically update data displayed by Angara.Charting library.\n",
"This can be done by returning `AsyncSeq<Chart>` object."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"// To enable the feature we need to have these scripts loaded\n",
"\n",
"// First, paket installations\n",
"#load \"Angara.Charting.Paket.fsx\"\n",
"#load \"AsyncDisplay.Paket.fsx\"\n",
"#load \".paket/load/Itis.Angara.Html.fsx\"\n",
"\n",
"// Then activations\n",
"#load \"AsyncDisplay.fsx\"\n",
"#load \"Angara.Charting.fsx\"\n",
"#load \"Angara.Charting.Dynamic.fsx\""
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" <section>\n",
" <div id='chart776567e778f64fbfb1604211eac09bba' style='height:auto'></div>\n",
" <script src='https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.js' type='text/javascript'></script>\n",
" <script type='text/javascript'>\n",
" require.config({ \n",
" paths: {\n",
" 'idd': 'https://cdn.rawgit.com/predictionmachines/InteractiveDataDisplay/v1.5.13/dist/idd',\n",
" 'idd.umd': 'https://cdn.rawgit.com/predictionmachines/InteractiveDataDisplay/v1.5.13/dist/idd.umd',\n",
" 'idd-css': 'https://cdn.rawgit.com/predictionmachines/InteractiveDataDisplay/v1.5.13/dist/idd.umd',\n",
" 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min',\n",
" 'jquery-ui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui',\n",
" 'css': 'https://cdnjs.cloudflare.com/ajax/libs/require-css/0.1.8/css.min',\n",
" 'rx': 'https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.lite.min',\n",
" 'svg': 'https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.4.0/svg.min',\n",
" 'filesaver': 'https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min',\n",
" 'jquery-mousewheel': 'https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min',\n",
" 'angara-serialization': 'https://cdn.rawgit.com/predictionmachines/Angara.Serialization/v0.3.0/dist/Angara.Serialization.umd'\n",
" }\n",
" });\n",
" require(['angara-serialization','idd.umd'], function (Serialization,Charting) {\n",
" //console.log('callback called');\n",
"\n",
" var viewerControl776567e778f64fbfb1604211eac09bba = Charting.InteractiveDataDisplay.show(document.getElementById('chart776567e778f64fbfb1604211eac09bba'), {}); \n",
"\n",
" window.UpdateAngaraChart776567e778f64fbfb1604211eac09bba = function(encodedData) {\n",
" var decodedData = atob(encodedData);\n",
" //console.log(decodedData);\n",
" var parsed = JSON.parse(decodedData)\n",
" //console.log(parsed);\n",
" var infoset = Serialization.InfoSet.Unmarshal(parsed);\n",
" //console.log(infoset);\n",
" var chartInfo = Serialization.InfoSet.Deserialize(infoset);\n",
" //console.log(chartInfo);\n",
" \n",
" var plotMap = [];\n",
" for (var i = 0; i < chartInfo.plots.length; i++) {\n",
" var pi = chartInfo.plots[i];\n",
" var props = $.extend(true, {}, pi.properties);\n",
" props['kind'] = pi.kind;\n",
" props['displayName'] = pi.displayName;\n",
" props['titles'] = pi.titles;\n",
" plotMap[i] = props;\n",
" }\n",
"\n",
" viewerControl776567e778f64fbfb1604211eac09bba.update(plotMap);\n",
" }\n",
"\n",
" \n",
" });\n",
" \n",
" </script>\n",
" </section>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div style='visibility=hidden'> \n",
" <script type='text/javascript'>\n",
" //console.log('probe');\n",
" if(window.UpdateAngaraChart776567e778f64fbfb1604211eac09bba) {\n",
" window.UpdateAngaraChart776567e778f64fbfb1604211eac09bba('ewogICI6Q2hhcnQiOiB7CiAgICAibGF5b3V0IjogIkNodWJieSIsCiAgICAicGxvdHMiOiBbCiAgICAgIHsKICAgICAgICAiZGlzcGxheU5hbWUiOiAibWFya2VycyIsCiAgICAgICAgImtpbmQiOiAibWFya2VycyIsCiAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAiYm9yZGVyIjogIm5vbmUiLAogICAgICAgICAgImNvbG9yIjogIiM2MDYwNjAiLAogICAgICAgICAgInNoYXBlIjogImJveCIsCiAgICAgICAgICAic2l6ZSI6IDguMCwKICAgICAgICAgICJ4OmRvdWJsZSBhcnJheSI6ICJBQUFBQUFBQUFBQWFKeGVTdnhXd1B4b25GNUsvRmNBL3Byb2lXNThneUQ4YUp4ZVN2eFhRUCtEd25IWXZHOVEvcHJvaVc1OGcyRDl1aEtnL0R5YmNQeG9uRjVLL0ZlQS8vUXRhaEhjWTRqL2c4SngyTHh2a1A4UFYzMmpuSGVZL3Byb2lXNThnNkQrS24yVk5WeVBxUDI2RXFEOFBKdXcvVUduck1jY283ajhhSnhlU3Z4WHdQNHlaT0lzYkYvRS8vUXRhaEhjWThqOXVmbnQ5MHhuelArRHduSFl2Ry9RL1VXTytiNHNjOVQvRDFkOW81eDMyUHpWSUFXSkRIL2MvcHJvaVc1OGcrRDhZTFVSVSt5SDVQNHFmWlUxWEkvby8vQkdIUnJNayt6OXVoS2cvRHliOFA5NzJ5VGhySi8wL1VHbnJNY2NvL2ovQzJ3d3JJeXIvUHhvbkY1Sy9GUUJBVStDbmptMldBRUNNbVRpTEd4Y0JRTVJTeVlmSmx3RkEvUXRhaEhjWUFrQTJ4ZXFBSlprQ1FHNStlMzNUR1FOQXB6Y01lb0dhQTBEZzhKeDJMeHNFUUJpcUxYUGRtd1JBVVdPK2I0c2NCVUNLSEU5c09aMEZRTVBWMzJqbkhRWkEvSTV3WlpXZUJrQTFTQUZpUXg4SFFHMEJrbDd4bndkQXByb2lXNThnQ0VEZmM3TlhUYUVJUUJndFJGVDdJUWxBVWViVVVLbWlDVUNLbjJWTlZ5TUtRTU5ZOWtrRnBBcEEvQkdIUnJNa0MwQTF5eGREWWFVTFFHNkVxRDhQSmd4QXBUMDVQTDJtREVEZTlzazRheWNOUUJld1dqVVpxQTFBVUduck1jY29Ea0NKSW53dWRha09RTUxiRENzaktnOUErcFNkSjlHcUQwQWFKeGVTdnhVUVFMYURYNUFXVmhCQVUrQ25qbTJXRUVEdlBQQ014TllRUUl5Wk9Jc2JGeEZBSi9hQWlYSlhFVURFVXNtSHlaY1JRR0N2RVlZZzJCRkEvUXRhaEhjWUVrQ1phS0tDemxnU1FEYkY2b0FsbVJKQTBpRXpmM3paRWtCdWZudDkweGtUUUF2Ynczc3FXaE5BcHpjTWVvR2FFMEJFbEZSNDJOb1RRT0R3bkhZdkd4UkFmVTNsZElaYkZFQVlxaTF6M1pzVVFMVUdkbkUwM0JSQVVXTytiNHNjRlVEdXZ3WnU0bHdWUUlvY1QydzVuUlZBSjNtWGFwRGRGVUREMWQ5bzV4MFdRR0F5S0djK1hoWkEvSTV3WlpXZUZrQ1o2N2hqN040V1FEVklBV0pESHhkQTBxUkpZSnBmRjBCdEFaSmU4WjhYUUFwZTJseEk0QmRBcHJvaVc1OGdHRUJERjJ0WjltQVlRTjl6czFkTm9SaEFmTkQ3VmFUaEdFQT0iLAogICAgICAgICAgInk6ZG91YmxlIGFycmF5IjogIm1VdFk2SHEyNnorUnloOXlTcWZxUCtWZ2hrOHNmZWsvSFVEb3NVMDU2RC8wRHgzUDlkem1QelVLNHBhRWFlVS9iT1pJVDNIZzR6OFIwSklaU1VQaVB6cDM5bUN0aytBL3h5L05hYVNtM1QvcEZnWWgrUWZhUDJ6OTRpRUJUOVkvQVZqWU9IOS8wajlCcjVIdm1Uck5QOXoxa2FTdFdNVS82MFk4bTJEQ3VqLzQwSWZHdVhDbFAwVlkvZ0h1Y1pXL3FDcURRT2xydGIvMWxSWkQyYkxDdjFGaFFxelpuTXEvQkgreWV2czEwYitOTlIreEpnelZ2MWNsb0N3T3pkaS9zUWxRSE9kMDNML25BUUFBQUFEZ3Y5WkN2REZpdGVHL0Fjb3dQdUJZNDcvWmNwVlQwdWprdi9QaXRsK2tZK2EvTU1BenFOZkg1Ny9adUNsTkJCVHB2NUt6eTdUYVJ1cS8yZEJ4M2lSZjY3L0xyc3lieDF2c3YyMkEvNjdETysyL011eC96RGIrN2IrU0pyZC9YS0x1djVoT2ZmR09KKysveTRLbWowZU43NzhBYVBpVUg5UHZ2ekxGL25EUStPKy9zVTFXRHpUKzc3OTdraVgrUk9QdnYyRThuWE1lcU8rL0N3QjZNdnhNNzcrNUVxUk5PdEx1djJRZktjdFVPTzYvVHJqdUp1ZC83Yjg1M1p1MXE2bnN2NWxMV09oNnR1dS9rc29mY2txbjZyL21ZSVpQTEgzcHZ4NUE2TEZOT2VpLzN3OGR6L1hjNXI4MkN1S1doR25sdjIzbVNFOXg0T08vRXRDU0dVbEQ0cjg3ZC9aZ3JaUGd2OGt2eldta3B0Mi82eFlHSWZrSDJyOXUvZUloQVUvV3Z3TlkyRGgvZjlLL1JhK1I3NWs2emIvZzlaR2tyVmpGdi9aRlBKdGd3cnEvQ2RHSHhybHdwYjhpV1A0QjduR1ZQNThxZzBEcGE3VS84SlVXUTlteXdqOU5ZVUtzMlp6S1B3Si9zbnI3TmRFL2l6VWZzU1lNMVQ5VkphQXNEczNZUCtnSlVCem5kTncveXdFQUFBQUE0RC9WUXJ3eFlyWGhQd0RLTUQ3Z1dPTS8ySEtWVTlMbzVEL3k0clpmcEdQbVB5L0FNNmpYeCtjLzJMZ3BUUVFVNlQrU3M4dTAya2JxUDluUWNkNGtYK3Mvdks3TW04ZGI3RDk2Z1ArdXd6dnRQeWZzZjh3Mi91MC9raWEzZjF5aTdqK1lUbjN4amlmdlA4dUNwbzlIamU4Ly8yZjRsQi9UN3o4eXhmNXcwUGp2UDdGTlZnODAvdTgvZTVJbC9rVGo3ejloUEoxekhxanZQeElBZWpMOFRPOC91UktrVFRyUzdqOWtIeW5MVkRqdVAwNjQ3aWJuZiswL090MmJ0YXVwN0Q4PSIKICAgICAgICB9LAogICAgICAgICJ0aXRsZXMiOiB7fQogICAgICB9CiAgICBdLAogICAgInhBeGlzIjoge30sCiAgICAieUF4aXMiOiB7fQogIH0KfQ==');\n",
" }\n",
" </script>\n",
" </div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"open System\n",
"open FSharp.Control\n",
"open Angara.Charting\n",
"\n",
"let phi_0 = 0.0 // initial phase\n",
"let N = 100 // number of chart points\n",
"\n",
"let x = Array.init N (fun i -> float(i)/float(N) * 2.0*Math.PI)\n",
"\n",
"/// the generator produces the next chart state using the previous state\n",
"/// the state is a phase value\n",
"let generator recent_phase =\n",
" async {\n",
" let new_phase = recent_phase + Math.PI/30.0\n",
" \n",
" // generating values\n",
" let y = Array.map (fun x -> Math.Sin(x+new_phase)) x\n",
" \n",
" let chart = \n",
" [\n",
" Angara.Charting.Plot.markers(x,y)\n",
" ] |> Chart.ofList\n",
" do! Async.Sleep 100 // slowing down the sequence generation process\n",
" return Some (chart,new_phase)\n",
" }\n",
" \n",
"let live_chart = //this is infinitly updated chart\n",
" AsyncSeq.unfoldAsync generator phi_0\n",
" \n",
"//returning only 200 first iterations\n",
"AsyncSeq.take 200 live_chart"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -19,7 +19,7 @@ type IAsyncPrinter =
module Printers =
let mutable internal displayPrinters : list<Type * (obj -> BinaryOutput)> = []
let mutable internal asyncPrinters : list<IAsyncPrinter> = []
let mutable internal asyncPrinters : list<IAsyncPrinter*int> = [] //(printer * its priority). Capable printer with higher priority is selected for printing
/// Convenience method for encoding a string within HTML
let internal htmlEncode(str) = HttpUtility.HtmlEncode(str)
@@ -28,8 +28,16 @@ module Printers =
let addDisplayPrinter(printer : 'T -> BinaryOutput) =
displayPrinters <- (typeof<'T>, (fun (x:obj) -> printer (unbox x))) :: displayPrinters
let addAsyncDisplayPrinter(printer:IAsyncPrinter) =
asyncPrinters <- printer :: asyncPrinters
/// Adds a custom async display printer for extensibility
/// Priority affects the cases where several printers are capabale of printing the save value.
/// The printer with higher priority is selected
let addAsyncDisplayPrinter(printer:IAsyncPrinter, priority: int) =
let printers = List.filter (fun entry -> let p,_ = entry in p <> printer) asyncPrinters //unregister if previously registered
asyncPrinters <- (printer,priority) :: printers
/// Removes all registered async diaplay printers
let clearDisplayPrinters() =
asyncPrinters <- List.empty
/// Default display printer
let defaultDisplayPrinter(x) =
@@ -78,7 +86,11 @@ module Printers =
{ ContentType = "text/plain"; Data = sprintf "%A : %s" func funcArguments }
let tryFindAsyncPrinter(objToPrint:obj)=
Seq.tryFind (fun (printer:IAsyncPrinter) -> printer.CanPrint objToPrint) asyncPrinters
asyncPrinters
|> Seq.filter (fun entry -> let (p:IAsyncPrinter),_ = entry in p.CanPrint objToPrint) //only capable
|> Seq.sortByDescending (fun entry -> let _,priority = entry in priority) //sorted by priority desc
|> Seq.map (fun entry -> let printer,_ = entry in printer) // filtering out priority data
|> Seq.tryHead
/// Finds a display printer based off of the type
let findDisplayPrinter(findType) =
@@ -0,0 +1,104 @@
#load ".paket/load/Itis.Angara.Html.fsx"
open System
open IfSharp.Kernel
open IfSharp.Kernel.Globals
open FSharp.Control
open Angara.Charting
open Angara.Serialization
open Newtonsoft.Json
type DynamicChartPrinter private () =
static let instance = DynamicChartPrinter()
static member Instance : IfSharp.Kernel.IAsyncPrinter = upcast instance
interface IfSharp.Kernel.IAsyncPrinter with
member __.CanPrint value =
let t = value.GetType()
match AsyncDisplay.getAsyncSeqType(t) with
| Some iface ->
iface.GetGenericArguments().[0] = typedefof<Chart>
| None -> false
member __.Print value isExecutionResult sendExecutionResult sendDisplayData =
let chart_display_id = Guid.NewGuid().ToString()
let data_display_id = Guid.NewGuid().ToString()
let chart_id = Guid.NewGuid().ToString("N")
let aSeq: FSharp.Control.AsyncSeq<Angara.Charting.Chart> = value :?> FSharp.Control.AsyncSeq<Angara.Charting.Chart>
let displayDiv = @"
<section>
<div id='chart"+chart_id+"' style='height:auto'></div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.js' type='text/javascript'></script>
<script type='text/javascript'>
require.config({
paths: {
'idd': 'https://cdn.rawgit.com/predictionmachines/InteractiveDataDisplay/v1.5.13/dist/idd',
'idd.umd': 'https://cdn.rawgit.com/predictionmachines/InteractiveDataDisplay/v1.5.13/dist/idd.umd',
'idd-css': 'https://cdn.rawgit.com/predictionmachines/InteractiveDataDisplay/v1.5.13/dist/idd.umd',
'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min',
'jquery-ui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui',
'css': 'https://cdnjs.cloudflare.com/ajax/libs/require-css/0.1.8/css.min',
'rx': 'https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.lite.min',
'svg': 'https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.4.0/svg.min',
'filesaver': 'https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min',
'jquery-mousewheel': 'https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min',
'angara-serialization': 'https://cdn.rawgit.com/predictionmachines/Angara.Serialization/v0.3.0/dist/Angara.Serialization.umd'
}
});
require(['angara-serialization','idd.umd'], function (Serialization,Charting) {
//console.log('callback called');
var viewerControl"+chart_id+" = Charting.InteractiveDataDisplay.show(document.getElementById('chart"+chart_id+"'), {});
window.UpdateAngaraChart"+chart_id+" = function(encodedData) {
var decodedData = atob(encodedData);
//console.log(decodedData);
var parsed = JSON.parse(decodedData)
//console.log(parsed);
var infoset = Serialization.InfoSet.Unmarshal(parsed);
//console.log(infoset);
var chartInfo = Serialization.InfoSet.Deserialize(infoset);
//console.log(chartInfo);
var plotMap = [];
for (var i = 0; i < chartInfo.plots.length; i++) {
var pi = chartInfo.plots[i];
var props = $.extend(true, {}, pi.properties);
props['kind'] = pi.kind;
props['displayName'] = pi.displayName;
props['titles'] = pi.titles;
plotMap[i] = props;
}
viewerControl"+chart_id+".update(plotMap);
}
});
</script>
</section>"
sendDisplayData "text/html" displayDiv "display_data" chart_display_id
let resolver = SerializerCompositeResolver([CoreSerializerResolver.Instance; Angara.Html.Serializers])
let sendUpdatedData (chart:Angara.Charting.Chart) =
let dataJToken = Angara.Serialization.Json.FromObject(resolver,chart)
let dataString = dataJToken.ToString(Formatting.Indented)
let bytes = System.Text.Encoding.UTF8.GetBytes(dataString)
let base64 = System.Convert.ToBase64String(bytes)
// printfn "%s" dataSerialized
let updateDiv = @"<div style='visibility=hidden'>
<script type='text/javascript'>
//console.log('probe');
if(window.UpdateAngaraChart"+chart_id+") {
window.UpdateAngaraChart"+chart_id+"('"+base64+"');
}
</script>
</div>"
sendDisplayData "text/html" updateDiv "update_display_data" data_display_id
sendDisplayData "text/html" "<div style='visibility=hidden'></div>" "display_data" data_display_id
AsyncSeq.iter sendUpdatedData aSeq |> Async.StartImmediate
IfSharp.Kernel.Printers.addAsyncDisplayPrinter(DynamicChartPrinter.Instance, 20)
@@ -1,11 +1,11 @@
#load "Paket.fsx"
Paket.Version
[ "Angara.Base", "~> 0.2.5"
"Angara.Html", "~> 0.2.5"
[ "Itis.Angara.Base", "~> 0.3.3"
"Itis.Angara.Html", "~> 0.3.3"
"Angara.Chart", ""
"Angara.Reinstate", "~> 0.2.5"
"Angara.Serialization", ""
"Angara.Serialization.Json", ""
"Itis.Angara.Reinstate", "~> 0.3.3"
"Angara.Serialization", "~> 0.3.0"
"Angara.Serialization.Json", "~> 0.3.0"
"Suave", "~> 1.1.3"
]
@@ -1,13 +1,12 @@
#r "IfSharp.Kernel.dll"
#r "packages/Angara.Base/lib/net452/Angara.Base.dll"
#r "packages/Angara.Html/lib/net452/Angara.Html.dll"
#r "packages/Angara.Chart/lib/net452/Angara.Chart.dll"
#r "packages/Angara.Table/lib/net452/Angara.Table.dll"
#r "packages/Angara.Reinstate/lib/net452/Angara.Reinstate.dll"
#r "packages/Angara.Serialization/lib/net452/Angara.Serialization.dll"
#r "packages/Angara.Serialization.Json/lib/net452/Angara.Serialization.Json.dll"
#r "packages/Suave/lib/net40/Suave.dll"
#load ".paket/load/Itis.Angara.Base.fsx"
#load ".paket/load/Itis.Angara.Html.fsx"
#load ".paket/load/Angara.Chart.fsx"
#load ".paket/load/Itis.Angara.Table.fsx"
#load ".paket/load/Itis.Angara.Reinstate.fsx"
#load ".paket/load/Angara.Serialization.fsx"
#load ".paket/load/Angara.Serialization.Json.fsx"
open IfSharp.Kernel
open IfSharp.Kernel.Globals
Oops, something went wrong.

0 comments on commit 9b307e8

Please sign in to comment.