| File: DataFrameKernelExtension.cs | Web Access |
| Project: src\src\Microsoft.Data.Analysis.Interactive\Microsoft.Data.Analysis.Interactive.csproj (Microsoft.Data.Analysis.Interactive) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Html; using Microsoft.DotNet.Interactive; using Microsoft.DotNet.Interactive.Commands; using Microsoft.DotNet.Interactive.Formatting; using Microsoft.DotNet.Interactive.Formatting.TabularData; using static Microsoft.DotNet.Interactive.Formatting.PocketViewTags; namespace Microsoft.Data.Analysis.Interactive { public class DataFrameKernelExtension : IKernelExtension { public async Task OnLoadAsync(Kernel kernel) { RegisterDataFrame(); if (Kernel.Root?.FindKernelByName("csharp") is { } csKernel) { await LoadExtensionApiAsync(csKernel); } } private static async Task LoadExtensionApiAsync(Kernel cSharpKernel) { await cSharpKernel.SendAsync(new SubmitCode($@"#r ""{typeof(DataFrameKernelExtension).Assembly.Location}"" using {typeof(TabularDataResource).Namespace};")); } public static void RegisterDataFrame() { Formatter.Register<DataFrame>((df, writer) => { const int maxRowCount = 10000; const int rowsPerPage = 25; var uniqueId = DateTime.Now.Ticks; var header = new List<IHtmlContent> { th(i("index")) }; header.AddRange(df.Columns.Select(c => (IHtmlContent)th(c.Name))); if (df.Rows.Count > rowsPerPage) { var maxMessage = df.Rows.Count > maxRowCount ? $" (showing a max of {maxRowCount} rows)" : string.Empty; var title = h3[style: "text-align: center;"]($"DataFrame - {df.Rows.Count} rows {maxMessage}"); // table body var rowCount = Math.Min(maxRowCount, df.Rows.Count); var rows = new List<List<IHtmlContent>>(); for (var index = 0; index < rowCount; index++) { var cells = new List<IHtmlContent> { td(i((index))) }; foreach (var obj in df.Rows[index]) { cells.Add(td(obj)); } rows.Add(cells); } //navigator var footer = new List<IHtmlContent>(); BuildHideRowsScript(uniqueId); var paginateScriptFirst = BuildHideRowsScript(uniqueId) + GotoPageIndex(uniqueId, 0) + BuildPageScript(uniqueId, rowsPerPage); footer.Add(button[style: "margin: 2px;", onclick: paginateScriptFirst]("⏮")); var paginateScriptPrevTen = BuildHideRowsScript(uniqueId) + UpdatePageIndex(uniqueId, -10, (rowCount - 1) / rowsPerPage) + BuildPageScript(uniqueId, rowsPerPage); footer.Add(button[style: "margin: 2px;", onclick: paginateScriptPrevTen]("⏪")); var paginateScriptPrev = BuildHideRowsScript(uniqueId) + UpdatePageIndex(uniqueId, -1, (rowCount - 1) / rowsPerPage) + BuildPageScript(uniqueId, rowsPerPage); footer.Add(button[style: "margin: 2px;", onclick: paginateScriptPrev]("◀️")); footer.Add(b[style: "margin: 2px;"]("Page")); footer.Add(b[id: $"page_{uniqueId}", style: "margin: 2px;"]("1")); var paginateScriptNext = BuildHideRowsScript(uniqueId) + UpdatePageIndex(uniqueId, 1, (rowCount - 1) / rowsPerPage) + BuildPageScript(uniqueId, rowsPerPage); footer.Add(button[style: "margin: 2px;", onclick: paginateScriptNext]("▶️")); var paginateScriptNextTen = BuildHideRowsScript(uniqueId) + UpdatePageIndex(uniqueId, 10, (rowCount - 1) / rowsPerPage) + BuildPageScript(uniqueId, rowsPerPage); footer.Add(button[style: "margin: 2px;", onclick: paginateScriptNextTen]("⏩")); var paginateScriptLast = BuildHideRowsScript(uniqueId) + GotoPageIndex(uniqueId, (rowCount - 1) / rowsPerPage) + BuildPageScript(uniqueId, rowsPerPage); footer.Add(button[style: "margin: 2px;", onclick: paginateScriptLast]("⏭️")); //table var t = table[id: $"table_{uniqueId}"]( caption(title), thead(tr(header)), tbody(rows.Select(r => tr[style: "display: none"](r))), tfoot(tr(td[colspan: df.Columns.Count + 1, style: "text-align: center;"](footer))) ); writer.Write(t); //show first page writer.Write($"<script>{BuildPageScript(uniqueId, rowsPerPage)}</script>"); } else { var rows = new List<List<IHtmlContent>>(); for (var index = 0; index < df.Rows.Count; index++) { var cells = new List<IHtmlContent> { td(i((index))) }; foreach (var obj in df.Rows[index]) { cells.Add(td(obj)); } rows.Add(cells); } //table var t = table[id: $"table_{uniqueId}"]( thead(tr(header)), tbody(rows.Select(r => tr(r))) ); writer.Write(t); } }, "text/html"); } private static string BuildHideRowsScript(long uniqueId) { var script = $"var allRows = document.querySelectorAll('#table_{uniqueId} tbody tr:nth-child(n)'); "; script += "for (let i = 0; i < allRows.length; i++) { allRows[i].style.display='none'; } "; return script; } private static string BuildPageScript(long uniqueId, int size) { var script = $"var page = parseInt(document.querySelector('#page_{uniqueId}').innerHTML) - 1; "; script += $"var pageRows = document.querySelectorAll(`#table_{uniqueId} tbody tr:nth-child(n + ${{page * {size} + 1 }})`); "; script += $"for (let j = 0; j < {size}; j++) {{ pageRows[j].style.display='table-row'; }} "; return script; } private static string GotoPageIndex(long uniqueId, long page) { var script = $"document.querySelector('#page_{uniqueId}').innerHTML = {page + 1}; "; return script; } private static string UpdatePageIndex(long uniqueId, int step, long maxPage) { var script = $"var page = parseInt(document.querySelector('#page_{uniqueId}').innerHTML) - 1; "; script += $"page = parseInt(page) + parseInt({step}); "; script += $"page = page < 0 ? 0 : page; "; script += $"page = page > {maxPage} ? {maxPage} : page; "; script += $"document.querySelector('#page_{uniqueId}').innerHTML = page + 1; "; return script; } } }