diff --git a/DotNetTry.sln b/DotNetTry.sln
index b895613f0..90abc5ec4 100644
--- a/DotNetTry.sln
+++ b/DotNetTry.sln
@@ -63,6 +63,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Interactiv
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Interactive", "Microsoft.DotNet.Interactive\Microsoft.DotNet.Interactive.csproj", "{2BB7CCD7-73D1-4B16-82EC-A5D0183F8CF5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XPlot.DotNet.Interactive.KernelExtensions", "XPlot.DotNet.Interactive.KernelExtensions\XPlot.DotNet.Interactive.KernelExtensions.csproj", "{90A9DF5F-CBEE-4B6B-8B58-BA94B0BDCF3C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -181,6 +183,10 @@ Global
{2BB7CCD7-73D1-4B16-82EC-A5D0183F8CF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BB7CCD7-73D1-4B16-82EC-A5D0183F8CF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BB7CCD7-73D1-4B16-82EC-A5D0183F8CF5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90A9DF5F-CBEE-4B6B-8B58-BA94B0BDCF3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90A9DF5F-CBEE-4B6B-8B58-BA94B0BDCF3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90A9DF5F-CBEE-4B6B-8B58-BA94B0BDCF3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90A9DF5F-CBEE-4B6B-8B58-BA94B0BDCF3C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -214,6 +220,7 @@ Global
{91902AAC-F4E9-4648-AC6B-4E4A722D3CC5} = {8192FEAD-BCE6-4E62-97E5-2E9EA884BD71}
{113A4166-5734-4F6E-B609-D6CF42679399} = {6EE8F484-DFA2-4F0F-939F-400CE78DFAC2}
{2BB7CCD7-73D1-4B16-82EC-A5D0183F8CF5} = {6EE8F484-DFA2-4F0F-939F-400CE78DFAC2}
+ {90A9DF5F-CBEE-4B6B-8B58-BA94B0BDCF3C} = {6EE8F484-DFA2-4F0F-939F-400CE78DFAC2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D6CD99BA-B16B-4570-8910-225CBDFFA3AD}
diff --git a/MLS.Agent.Tests/GetChartHtmlTests.cs b/MLS.Agent.Tests/GetChartHtmlTests.cs
new file mode 100644
index 000000000..38bb3017f
--- /dev/null
+++ b/MLS.Agent.Tests/GetChartHtmlTests.cs
@@ -0,0 +1,63 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using FluentAssertions;
+using HtmlAgilityPack;
+using System.Linq;
+using XPlot.DotNet.Interactive.KernelExtensions;
+using XPlot.Plotly;
+using Xunit;
+
+namespace MLS.Agent.Tests
+{
+ public partial class XplotKernelExtensionTests
+ {
+ public class GetChartHtmlTests
+ {
+ [Fact]
+ public void Returns_the_html_with_div()
+ {
+ var extension = new XPlotKernelExtension();
+ var html = extension.GetChartHtml(new PlotlyChart());
+ var document = new HtmlDocument();
+ document.LoadHtml(html);
+
+ document.DocumentNode.SelectSingleNode("//div").InnerHtml.Should().NotBeNull();
+ document.DocumentNode.SelectSingleNode("//div").Id.Should().NotBeNullOrEmpty();
+ }
+
+ [Fact]
+ public void Returns_the_html_with_script_containing_require_config()
+ {
+ var extension = new XPlotKernelExtension();
+ var html = extension.GetChartHtml(new PlotlyChart());
+ var document = new HtmlDocument();
+ document.LoadHtml(html);
+
+ document.DocumentNode.SelectSingleNode("//script").InnerHtml.Should().Contain("require.config({paths:{plotly:\'https://cdn.plot.ly/plotly-latest.min\'}});");
+ }
+
+ [Fact]
+ public void Returns_the_html_with_script_containing_require_plotly_and_call_to_new_plot_function()
+ {
+ var extension = new XPlotKernelExtension();
+ var html = extension.GetChartHtml(new PlotlyChart());
+ var document = new HtmlDocument();
+ document.LoadHtml(html);
+
+ var divId = document.DocumentNode.SelectSingleNode("//div").Id;
+ document.DocumentNode
+ .SelectSingleNode("//script")
+ .InnerHtml.Split("\n")
+ .Select(item => item.Trim())
+ .Where(item => !string.IsNullOrWhiteSpace(item))
+ .Should()
+ .ContainInOrder(@"require(['plotly'], function(Plotly) {",
+ "var data = null;",
+ @"var layout = """";",
+ $"Plotly.newPlot('{divId}', data, layout);");
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/MLS.Agent.Tests/MLS.Agent.Tests.csproj b/MLS.Agent.Tests/MLS.Agent.Tests.csproj
index 28021468e..b0b1e5e43 100644
--- a/MLS.Agent.Tests/MLS.Agent.Tests.csproj
+++ b/MLS.Agent.Tests/MLS.Agent.Tests.csproj
@@ -13,7 +13,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/MLS.Agent.Tests/XplotKernelExtensionTests.cs b/MLS.Agent.Tests/XplotKernelExtensionTests.cs
new file mode 100644
index 000000000..67d65e645
--- /dev/null
+++ b/MLS.Agent.Tests/XplotKernelExtensionTests.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using FluentAssertions;
+using Microsoft.DotNet.Interactive;
+using Microsoft.DotNet.Interactive.Commands;
+using Microsoft.DotNet.Interactive.Events;
+using System.Linq;
+using System.Threading.Tasks;
+using WorkspaceServer.Tests.Kernel;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace MLS.Agent.Tests
+{
+ public partial class XplotKernelExtensionTests : CSharpKernelTestBase
+ {
+
+ public XplotKernelExtensionTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task When_a_chart_is_returned_the_value_produced_has_html_with_the_require_config_call()
+ {
+ var kernel = CreateKernel();
+ kernel.UseXplot();
+
+ await kernel.SendAsync(new SubmitCode("using XPlot.Plotly;"));
+ await kernel.SendAsync(new SubmitCode("new PlotlyChart()"));
+
+ KernelEvents
+ .ValuesOnly()
+ .OfType()
+ .Should().
+ ContainSingle(valueProduced =>
+ valueProduced.FormattedValues.Any(formattedValue =>
+ formattedValue.MimeType == "text/html" &&
+ formattedValue.Value.ToString().Contains("require([\'plotly\'], function(Plotly)")
+ && formattedValue.Value.ToString().Contains("require.config({paths:{plotly:\'https://cdn.plot.ly/plotly-latest.min\'}});")
+ ));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MLS.Agent/CommandLine/CommandLineParser.cs b/MLS.Agent/CommandLine/CommandLineParser.cs
index 7c02d6ebf..702313ac8 100644
--- a/MLS.Agent/CommandLine/CommandLineParser.cs
+++ b/MLS.Agent/CommandLine/CommandLineParser.cs
@@ -494,6 +494,7 @@ private static CompositeKernel CreateKernel()
.UseNugetDirective()
.UseExtendDirective()
.UseKernelHelpers()
+ .UseXplot()
};
}
}
diff --git a/MLS.Agent/ExceptionExtensions.cs b/MLS.Agent/ExceptionExtensions.cs
index 23ca15749..4edff89c1 100644
--- a/MLS.Agent/ExceptionExtensions.cs
+++ b/MLS.Agent/ExceptionExtensions.cs
@@ -30,4 +30,4 @@ public static int ToHttpStatusCode(this Exception exception)
}
}
}
-}
+}
\ No newline at end of file
diff --git a/MLS.Agent/KernelExtensions.cs b/MLS.Agent/KernelExtensions.cs
new file mode 100644
index 000000000..73675aafb
--- /dev/null
+++ b/MLS.Agent/KernelExtensions.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Clockwise;
+using Microsoft.DotNet.Interactive;
+using XPlot.DotNet.Interactive.KernelExtensions;
+
+namespace MLS.Agent
+{
+ public static class KernelExtensions
+ {
+ public static T UseXplot(this T kernel)
+ where T : KernelBase
+ {
+ var extension = new XPlotKernelExtension();
+ extension.OnLoadAsync(kernel).Wait();
+ return kernel;
+ }
+ }
+}
diff --git a/MLS.Agent/MLS.Agent.csproj b/MLS.Agent/MLS.Agent.csproj
index 55bc9f44d..5e835a73f 100644
--- a/MLS.Agent/MLS.Agent.csproj
+++ b/MLS.Agent/MLS.Agent.csproj
@@ -109,6 +109,7 @@
+
diff --git a/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj b/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj
index 28875454a..eada3649f 100644
--- a/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj
+++ b/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj
@@ -8,7 +8,7 @@
-
+
all
diff --git a/Microsoft.DotNet.Try.Markdown.Tests/Microsoft.DotNet.Try.Markdown.Tests.csproj b/Microsoft.DotNet.Try.Markdown.Tests/Microsoft.DotNet.Try.Markdown.Tests.csproj
index 9d1c3aa51..78817e1c6 100644
--- a/Microsoft.DotNet.Try.Markdown.Tests/Microsoft.DotNet.Try.Markdown.Tests.csproj
+++ b/Microsoft.DotNet.Try.Markdown.Tests/Microsoft.DotNet.Try.Markdown.Tests.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/WorkspaceServer.Tests/Kernel/TimestampedExtensions.cs b/WorkspaceServer.Tests/Kernel/TimestampedExtensions.cs
index 83ba5a54e..9a9d4bea5 100644
--- a/WorkspaceServer.Tests/Kernel/TimestampedExtensions.cs
+++ b/WorkspaceServer.Tests/Kernel/TimestampedExtensions.cs
@@ -8,7 +8,7 @@
namespace WorkspaceServer.Tests.Kernel
{
- internal static class TimestampedExtensions
+ public static class TimestampedExtensions
{
public static IEnumerable ValuesOnly(this IEnumerable> source)
{
diff --git a/WorkspaceServer/Kernel/CSharpKernel.cs b/WorkspaceServer/Kernel/CSharpKernel.cs
index d7bb9e817..7182d72bf 100644
--- a/WorkspaceServer/Kernel/CSharpKernel.cs
+++ b/WorkspaceServer/Kernel/CSharpKernel.cs
@@ -66,7 +66,8 @@ private void SetupScriptOptions()
typeof(Task<>).Assembly,
typeof(IKernel).Assembly,
typeof(CSharpKernel).Assembly,
- typeof(PocketView).Assembly);
+ typeof(PocketView).Assembly,
+ typeof(XPlot.Plotly.PlotlyChart).Assembly);
}
private (bool shouldExecute, string completeSubmission) IsBufferACompleteSubmission(string input)
diff --git a/WorkspaceServer/WorkspaceServer.csproj b/WorkspaceServer/WorkspaceServer.csproj
index 8c52aad93..0513697d7 100644
--- a/WorkspaceServer/WorkspaceServer.csproj
+++ b/WorkspaceServer/WorkspaceServer.csproj
@@ -92,6 +92,7 @@
all
runtime; build; native; contentfiles; analyzers
+
diff --git a/XPlot.DotNet.Interactive.KernelExtensions/XPlot.DotNet.Interactive.KernelExtensions.csproj b/XPlot.DotNet.Interactive.KernelExtensions/XPlot.DotNet.Interactive.KernelExtensions.csproj
new file mode 100644
index 000000000..0441a0e5c
--- /dev/null
+++ b/XPlot.DotNet.Interactive.KernelExtensions/XPlot.DotNet.Interactive.KernelExtensions.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/XPlot.DotNet.Interactive.KernelExtensions/XPlotKernelExtension.cs b/XPlot.DotNet.Interactive.KernelExtensions/XPlotKernelExtension.cs
new file mode 100644
index 000000000..b713610ae
--- /dev/null
+++ b/XPlot.DotNet.Interactive.KernelExtensions/XPlotKernelExtension.cs
@@ -0,0 +1,55 @@
+using HtmlAgilityPack;
+using Microsoft.DotNet.Interactive;
+using Microsoft.DotNet.Interactive.Rendering;
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using XPlot.Plotly;
+using static Microsoft.DotNet.Interactive.Rendering.PocketViewTags;
+
+namespace XPlot.DotNet.Interactive.KernelExtensions
+{
+ public class XPlotKernelExtension : IKernelExtension
+ {
+ public Task OnLoadAsync(IKernel kernel)
+ {
+ Formatter.Register((chart, writer) =>
+ {
+ writer.Write(GetChartHtml(chart));
+ }, "text/html");
+
+ return Task.CompletedTask;
+ }
+
+ public string GetChartHtml(PlotlyChart chart)
+ {
+ var document = new HtmlDocument();
+ document.LoadHtml(chart.GetInlineHtml());
+
+ var divNode = document.DocumentNode.SelectSingleNode("//div");
+ var scriptNode = document.DocumentNode.SelectSingleNode("//script");
+
+ var newHtmlDocument = new HtmlDocument();
+ newHtmlDocument.DocumentNode.ChildNodes.Add(divNode);
+ newHtmlDocument.DocumentNode.ChildNodes.Add(GetScriptNodeWithRequire(scriptNode));
+
+ return newHtmlDocument.DocumentNode.WriteContentTo();
+ }
+
+ private static HtmlNode GetScriptNodeWithRequire(HtmlNode scriptNode)
+ {
+ var newScript = new StringBuilder();
+
+ newScript.AppendLine("");
+ return HtmlNode.CreateNode(newScript.ToString());
+ }
+ }
+}