Skip to content

Commit

Permalink
#476: First try at no-deps interactive formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
kMutagene committed Jan 13, 2025
1 parent 5b1d95f commit ffe000b
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 0 deletions.
23 changes: 23 additions & 0 deletions src/Plotly.NET/ChartAPI/GenericChart.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ open System
open Newtonsoft.Json
open System.Runtime.CompilerServices
open Giraffe.ViewEngine
open System
open System.IO
open System.Collections.Generic

/// Figure is a domain transfer object that can be used to serialize a Chart to JSON. It is used internally and for most use cases should not be used directly.
///
Expand Down Expand Up @@ -55,6 +58,7 @@ type ChartDTO =
/// - `Config` is an object that configures high level properties of the chart like making all chart elements editable or the tool bar on top
///
/// - `DisplayOptions` is an object that contains meta information about how the html document that contains the chart.
[<TypeFormatterSourceAttribute(typeof<GenericChartFormatterSource>)>]
type GenericChart =
| Chart of data: Trace * layout: Layout * config: Config * displayOpts: DisplayOptions
| MultiChart of data: Trace list * layout: Layout * config: Config * displayOpts: DisplayOptions
Expand Down Expand Up @@ -584,3 +588,22 @@ type GenericChart =
match gChart with
| Chart(trace, _, _, _) -> [ TraceID.ofTrace trace ]
| MultiChart(traces, _, _, _) -> traces |> List.map TraceID.ofTrace

and GenericChartFormatter() =
let mutable mimeType = "text/html"
member this.MimeType
with get() = mimeType
and set(v) = mimeType <- v
member this.Format(instance:obj, writer:TextWriter) =
match instance with
| :? GenericChart as c ->
writer.Write("LOL!")
true
| _ -> false

and GenericChartFormatterSource =
member this.CreateTypeFormatters() : seq<obj> =
seq {
yield GenericChartFormatter()
}

11 changes: 11 additions & 0 deletions src/Plotly.NET/InternalUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ open DynamicObj
open System.Runtime.InteropServices
open System.IO
open System.Reflection
open System

let combineOptSeqs (first: seq<'A> option) (second: seq<'A> option) =
match first, second with
Expand Down Expand Up @@ -129,3 +130,13 @@ module internal ChartIO =
System.Diagnostics.Process.Start("open", path) |> ignore
else
invalidOp "Not supported OS platform"

[<AttributeUsage(AttributeTargets.Class)>]
type internal TypeFormatterSourceAttribute(formatterSourceType: Type) =
inherit Attribute()
let mutable preferredMimeTypes : string[] = [||]
member this.TypeFormatterSourceType = formatterSourceType
member this.PreferredMimeTypes
with get() = preferredMimeTypes
and set(v) = preferredMimeTypes <- v

7 changes: 7 additions & 0 deletions tests/InteractiveTests/Repack.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Clean up the previously-cached NuGet packages.
# Lower-case is intentional (that's how nuget stores those packages).
Remove-Item -Recurse ~\.nuget\packages\plotly.net* -Force

# build and pack Plotly.NET.Interactive
dotnet pack ../../src/Plotly.NET/Plotly.NET.fsproj -tl -c Release -p:PackageVersion=0.0.1-dev -o "./pkg"

121 changes: 121 additions & 0 deletions tests/InteractiveTests/TestNotebook.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"To reproduce the package, run `./Repack.ps1` in powershell. It will clean your cache in `~/.nuget/packages` and pack the library to `Plotly.NET/pkg` folder, which you should specify below (absolute paths only) in `#i` line.\n",
"\n",
"The version of the package is always `0.0.0-dev`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div><strong>Restore sources</strong><ul><li><span> C:/Users/schne/source/repos/plotly/Plotly.NET/pkg</span></li></ul></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Plotly.NET, 2.0.0-alpha2</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"// be advised, that you always should set absolute paths for local nuget packages - change this to reflect your own setup\n",
"#i \"nuget: C:/Users/schne/source/repos/plotly/Plotly.NET/tests/Interactivetests/pkg\"\n",
"#r \"nuget: Plotly.NET, 0.0.1-dev\""
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div id=\"4fc61fc7-1f1e-48f0-9aca-5da1084da281\"><!-- Plotly chart will be drawn inside this DIV --></div><script type=\"text/javascript\">\n",
"var renderPlotly_4fc61fc71f1e48f09aca5da1084da281 = function() {\n",
" var fsharpPlotlyRequire = requirejs.config({context:'fsharp-plotly',paths:{plotly:'https://cdn.plot.ly/plotly-2.18.1.min'}}) || require;\n",
" fsharpPlotlyRequire(['plotly'], function(Plotly) {\n",
" var data = [{\"type\":\"scatter\",\"mode\":\"markers\",\"x\":[1,3],\"y\":[2,4],\"marker\":{},\"line\":{}}];\n",
" var layout = {\"width\":600,\"height\":600,\"template\":{\"layout\":{\"title\":{\"x\":0.05},\"font\":{\"color\":\"rgba(42, 63, 95, 1.0)\"},\"paper_bgcolor\":\"rgba(255, 255, 255, 1.0)\",\"plot_bgcolor\":\"rgba(229, 236, 246, 1.0)\",\"autotypenumbers\":\"strict\",\"colorscale\":{\"diverging\":[[0.0,\"#8e0152\"],[0.1,\"#c51b7d\"],[0.2,\"#de77ae\"],[0.3,\"#f1b6da\"],[0.4,\"#fde0ef\"],[0.5,\"#f7f7f7\"],[0.6,\"#e6f5d0\"],[0.7,\"#b8e186\"],[0.8,\"#7fbc41\"],[0.9,\"#4d9221\"],[1.0,\"#276419\"]],\"sequential\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"sequentialminus\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]},\"hovermode\":\"closest\",\"hoverlabel\":{\"align\":\"left\"},\"coloraxis\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}},\"geo\":{\"showland\":true,\"landcolor\":\"rgba(229, 236, 246, 1.0)\",\"showlakes\":true,\"lakecolor\":\"rgba(255, 255, 255, 1.0)\",\"subunitcolor\":\"rgba(255, 255, 255, 1.0)\",\"bgcolor\":\"rgba(255, 255, 255, 1.0)\"},\"mapbox\":{\"style\":\"light\"},\"polar\":{\"bgcolor\":\"rgba(229, 236, 246, 1.0)\",\"radialaxis\":{\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"ticks\":\"\"},\"angularaxis\":{\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"ticks\":\"\"}},\"scene\":{\"xaxis\":{\"ticks\":\"\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"gridwidth\":2.0,\"zerolinecolor\":\"rgba(255, 255, 255, 1.0)\",\"backgroundcolor\":\"rgba(229, 236, 246, 1.0)\",\"showbackground\":true},\"yaxis\":{\"ticks\":\"\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"gridwidth\":2.0,\"zerolinecolor\":\"rgba(255, 255, 255, 1.0)\",\"backgroundcolor\":\"rgba(229, 236, 246, 1.0)\",\"showbackground\":true},\"zaxis\":{\"ticks\":\"\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"gridwidth\":2.0,\"zerolinecolor\":\"rgba(255, 255, 255, 1.0)\",\"backgroundcolor\":\"rgba(229, 236, 246, 1.0)\",\"showbackground\":true}},\"ternary\":{\"aaxis\":{\"ticks\":\"\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\"},\"baxis\":{\"ticks\":\"\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\"},\"caxis\":{\"ticks\":\"\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\"},\"bgcolor\":\"rgba(229, 236, 246, 1.0)\"},\"xaxis\":{\"title\":{\"standoff\":15},\"ticks\":\"\",\"automargin\":\"height+width+left+right+top+bottom\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"zerolinecolor\":\"rgba(255, 255, 255, 1.0)\",\"zerolinewidth\":2.0},\"yaxis\":{\"title\":{\"standoff\":15},\"ticks\":\"\",\"automargin\":\"height+width+left+right+top+bottom\",\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"zerolinecolor\":\"rgba(255, 255, 255, 1.0)\",\"zerolinewidth\":2.0},\"annotationdefaults\":{\"arrowcolor\":\"#2a3f5f\",\"arrowhead\":0,\"arrowwidth\":1},\"shapedefaults\":{\"line\":{\"color\":\"rgba(42, 63, 95, 1.0)\"}},\"colorway\":[\"rgba(99, 110, 250, 1.0)\",\"rgba(239, 85, 59, 1.0)\",\"rgba(0, 204, 150, 1.0)\",\"rgba(171, 99, 250, 1.0)\",\"rgba(255, 161, 90, 1.0)\",\"rgba(25, 211, 243, 1.0)\",\"rgba(255, 102, 146, 1.0)\",\"rgba(182, 232, 128, 1.0)\",\"rgba(255, 151, 255, 1.0)\",\"rgba(254, 203, 82, 1.0)\"]},\"data\":{\"bar\":[{\"marker\":{\"line\":{\"color\":\"rgba(229, 236, 246, 1.0)\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"error_x\":{\"color\":\"rgba(42, 63, 95, 1.0)\"},\"error_y\":{\"color\":\"rgba(42, 63, 95, 1.0)\"}}],\"barpolar\":[{\"marker\":{\"line\":{\"color\":\"rgba(229, 236, 246, 1.0)\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}}}],\"carpet\":[{\"aaxis\":{\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"endlinecolor\":\"rgba(42, 63, 95, 1.0)\",\"minorgridcolor\":\"rgba(255, 255, 255, 1.0)\",\"startlinecolor\":\"rgba(42, 63, 95, 1.0)\"},\"baxis\":{\"linecolor\":\"rgba(255, 255, 255, 1.0)\",\"gridcolor\":\"rgba(255, 255, 255, 1.0)\",\"endlinecolor\":\"rgba(42, 63, 95, 1.0)\",\"minorgridcolor\":\"rgba(255, 255, 255, 1.0)\",\"startlinecolor\":\"rgba(42, 63, 95, 1.0)\"}}],\"choropleth\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"contour\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"contourcarpet\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}],\"heatmap\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"heatmapgl\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"histogram\":[{\"marker\":{\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}}}],\"histogram2d\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"histogram2dcontour\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"mesh3d\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}],\"parcoords\":[{\"line\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"pie\":[{\"automargin\":true}],\"scatter\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scatter3d\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}},\"line\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scattercarpet\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scattergeo\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scattergl\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scattermapbox\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scatterpolar\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scatterpolargl\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"scatterternary\":[{\"marker\":{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"}}}],\"surface\":[{\"colorbar\":{\"outlinewidth\":0.0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"table\":[{\"cells\":{\"fill\":{\"color\":\"rgba(235, 240, 248, 1.0)\"},\"line\":{\"color\":\"rgba(255, 255, 255, 1.0)\"}},\"header\":{\"fill\":{\"color\":\"rgba(200, 212, 227, 1.0)\"},\"line\":{\"color\":\"rgba(255, 255, 255, 1.0)\"}}}]}}};\n",
" var config = {\"responsive\":true};\n",
" Plotly.newPlot('4fc61fc7-1f1e-48f0-9aca-5da1084da281', data, layout, config);\n",
" });\n",
"};\n",
"if ((typeof(requirejs) !== typeof(Function)) || (typeof(requirejs.config) !== typeof(Function))) {\n",
" var script = document.createElement(\"script\");\n",
" script.setAttribute(\"src\", \"https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js\");\n",
" script.onload = function(){\n",
" renderPlotly_4fc61fc71f1e48f09aca5da1084da281();\n",
" };\n",
" document.getElementsByTagName(\"head\")[0].appendChild(script);\n",
"}\n",
"else {\n",
" renderPlotly_4fc61fc71f1e48f09aca5da1084da281();\n",
"}\n",
"</script><h1>lel!</h1></div>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"open Plotly.NET\n",
"open Giraffe.ViewEngine\n",
"\n",
"Chart.Point([1,2; 3,4])\n",
"|> Chart.withDescription [\n",
" h1 [] [str \"lel!\"]\n",
"]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
{
"aliases": [],
"name": "csharp"
},
{
"aliases": [
"frontend"
],
"name": "vscode"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}

0 comments on commit ffe000b

Please sign in to comment.