Skip to content

Instantly share code, notes, and snippets.

@maxandersen
Created November 4, 2023 13:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maxandersen/7015f659ed5b8ad26da1a98957434af4 to your computer and use it in GitHub Desktop.
Save maxandersen/7015f659ed5b8ad26da1a98957434af4 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en" class="overflow-hidden min-h-screen">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>jnotebook</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📓</text></svg>">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<link rel="stylesheet" type="text/css"
href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.18.2/tocbot.css">
<script>
function initTocBot() {
tocbot.init({
// Where to render the table of contents.
tocSelector: '#toc',
// Where to grab the headings to build the table of contents.
contentSelector: '#notebook',
// Which headings to grab inside of the contentSelector element.
headingSelector: 'h1, h2, h3',
// For headings inside relative or absolute positioned containers within content.
hasInnerContainers: true,
})
}
function renderLatex() {
var mathElems = document.getElementsByClassName("katex");
var elems = [];
for (const i in mathElems) {
if (mathElems.hasOwnProperty(i)) elems.push(mathElems[i]);
}
elems.forEach(elem => {
katex.render(elem.textContent, elem, {throwOnError: false, displayMode: elem.nodeName !== 'SPAN',});
});
}
function renderMermaid() {
mermaid.init({noteMargin: 10}, '.mermaid');
}
function renderCodeColor() {
document.querySelectorAll('div.cm-content').forEach(el => {
hljs.highlightElement(el, {language: 'java'});
});
}
function renderVega() {
document.querySelectorAll('div.vega-embed').forEach(el => {
vegaEmbed(el, JSON.parse(el.dataset.config))
});
}
function renderPlotly() {
document.querySelectorAll('div.js-plotly-plot').forEach(el => {
Plotly.newPlot(el, JSON.parse(el.dataset.data), JSON.parse(el.dataset.layout), JSON.parse(el.dataset.config));
});
}
function renderFlamegraphs() {
document.querySelectorAll(".flame").forEach(el => {
const chart = flamegraph().inverted(true).width(el.parentElement.offsetWidth);
d3.select(el).datum(JSON.parse(el.dataset.profile)).call(chart);
})
}
</script>
<script class="jnb-opti" src="https://cdn.tailwindcss.com?plugins=typography"></script>
<script class="jnb-opti" >tailwind.config = {
darkMode: "class",
content: ["./tw/viewer.js", "./tw/**/*.edn"],
safelist: ['dark'],
theme: {
extend: {},
fontFamily: {
sans: ["Fira Sans", "-apple-system", "BlinkMacSystemFont", "sans-serif"],
serif: ["PT Serif", "serif"],
mono: ["Fira Mono", "monospace"]
}
},
variants: {
extend: {},
},
plugins: [],
}</script>
<style class="jnb-opti" type="text/tailwindcss">@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
font-size: 18px;
}
@media (max-width: 600px) {
html {
font-size: 16px;
}
}
.font-condensed {
font-family: "Fira Sans Condensed", sans-serif;
}
.font-inter {
font-family: "Inter", sans-serif;
}
body {
@apply font-serif antialiased text-gray-900 sm:overscroll-y-none;
}
code, .code {
@apply font-mono text-sm text-gray-900 bg-slate-50 px-0.5 py-px rounded;
}
code::before, code::after {
@apply content-none !important;
}
h1, h3, h4, h5, h6 {
@apply font-condensed font-bold mt-8 first:mt-0;
}
h2 {
/*We cannot collapse margins due to nesting but we want to*/
/*keep the h2’s large margin visible*/
@apply font-condensed font-bold mt-8 first:mt-2;
}
h1 {
@apply text-4xl;
}
h2 {
@apply text-3xl;
}
h3 {
@apply text-2xl;
}
button {
@apply focus:outline-none;
}
strong {
@apply font-bold;
}
em {
@apply italic;
}
pre {
@apply m-0 font-mono;
}
}
/* Compatibility */
/* --------------------------------------------------------------- */
/* TODO: Verify which colors are in use and replace with Tailwind
colors accordingly. Move Nj-specific styles out of here. */
:root {
--teal-color: #31afd0;
--dark-teal-color: #095960;
--near-black-color: #2e2e2c;
--red-color: #d64242;
--dark-blue-color: #1f2937;
--dark-blue-60-color: rgba(28, 42, 56, 0.6);
--gray-panel-color: rgba(239, 241, 245, 1.000);
--brand-color: var(--dark-blue-color);
--link-color: #5046e4;
--command-bar-selected-color: var(--teal-color);
}
.serif {
@apply font-serif;
}
.sans-serif {
@apply font-sans;
}
.monospace {
@apply font-mono;
}
.inter {
@apply font-inter;
}
.border-color-teal {
border-color: var(--dark-teal-color);
}
.teal {
color: var(--teal-color);
}
.bg-dark-blue {
background: var(--dark-blue-color);
}
.bg-dark-blue-60 {
background: rgba(28, 42, 56, 0.6);
}
.bg-gray-panel {
background: var(--gray-panel-color);
}
.text-dark-blue {
color: var(--dark-blue-color);
}
.text-dark-blue-60 {
color: var(--dark-blue-60-color);
}
.border-dark-blue-30 {
border-color: rgba(28, 42, 56, 0.6);
}
.text-brand {
color: var(--dark-blue-color);
}
.bg-brand {
background: var(--dark-blue-color);
}
.text-selected {
color: white;
}
.red {
color: var(--red-color);
}
/* Disclose Button */
/* --------------------------------------------------------------- */
.disclose {
@apply content-none border-solid cursor-pointer inline-block relative mr-[3px] top-[-2px] transition-all;
border-color: var(--near-black-color) transparent;
border-width: 6px 4px 0;
}
.disclose:hover {
border-color: var(--near-black-color) transparent;
}
.dark .disclose,
.dark .disclose:hover {
border-color: white transparent;
}
.disclose.collapsed {
@apply rotate-[-90deg];
}
/* Layout */
/* --------------------------------------------------------------- */
.page {
@apply max-w-5xl mx-auto px-12 box-border flex-shrink-0;
}
.max-w-prose {
@apply max-w-[46rem] !important;
}
.max-w-wide {
@apply max-w-3xl !important;
}
/* List Styles */
/* --------------------------------------------------------------- */
.task-list-item + .task-list-item,
.viewer-markdown ul ul {
@apply mt-1 mb-0;
}
/* TOC related */
.toc-offset {
@apply ml-52;
}
/* compact TOC */
.viewer-markdown .toc ul {
list-style: none;
@apply my-1;
}
/* Code Viewer */
/* --------------------------------------------------------------- */
.viewer-code {
@apply font-mono bg-slate-100 rounded-sm text-sm overflow-x-auto;
}
.viewer-code .cm-content {
@apply py-4 px-8;
}
@media (min-width: 960px) {
.viewer-notebook .viewer-code .cm-content {
@apply py-4 pl-12;
}
}
/* Don’t show focus outline when double-clicking cell in Safari */
.cm-scroller {
@apply focus:outline-none;
}
/* Syntax Highlighting */
/* --------------------------------------------------------------- */
.inspected-value {
@apply text-xs font-mono leading-[1.25rem];
}
.result-data {
@apply font-mono text-sm overflow-x-auto whitespace-nowrap leading-normal;
}
.result-data::-webkit-scrollbar, .path-nav::-webkit-scrollbar {
@apply h-0;
}
.result-data-collapsed {
@apply whitespace-nowrap;
}
.result-data-field {
@apply ml-4 whitespace-nowrap;
}
.result-data-field-link {
@apply ml-4 whitespace-nowrap cursor-pointer;
}
.result-data-field-link:hover {
@apply text-black bg-black/5;
}
.result-text-empty {
color: rgba(0, 0, 0, .3);
}
.browsify-button:hover {
box-shadow: -2px 0 0 2px #edf2f7;
}
/* Prose */
/* --------------------------------------------------------------- */
.viewer-notebook,
.viewer-markdown {
@apply prose
prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline
prose-p:mt-4 prose-p:leading-snug
prose-ol:mt-4 prose-ol:mb-6 prose-ol:leading-snug
prose-ul:mt-4 prose-ul:mb-6 prose-ul:leading-snug
prose-blockquote:mt-4 prose-blockquote:leading-snug
prose-hr:mt-6 prose-hr:border-t-2 prose-hr:border-solid prose-hr:border-slate-200
prose-figure:mt-4
prose-figcaption:mt-2 prose-figcaption:text-xs
prose-headings:mb-4
prose-table:mt-0
prose-th:mb-0
prose-img:my-0
prose-code:font-medium prose-code:bg-slate-100
max-w-none;
}
.viewer-markdown blockquote p:first-of-type:before,
.viewer-markdown blockquote p:last-of-type:after {
@apply content-none;
}
/* Images */
/* --------------------------------------------------------------- */
/* Todo Lists */
/* --------------------------------------------------------------- */
.contains-task-list {
@apply pl-6 list-none;
}
.contains-task-list input[type="checkbox"] {
@apply appearance-none h-4 w-4 rounded border border-slate-200 relative mr-[0.3rem] ml-[-1.5rem] top-[0.15rem];
}
.contains-task-list input[type="checkbox"]:checked {
@apply border-indigo-600 bg-indigo-600 bg-no-repeat bg-contain;
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
/* Markdown TOC */
/* --------------------------------------------------------------- */
.viewer-markdown .toc {
@apply mt-4;
}
.viewer-markdown h1 + .toc {
@apply mt-8;
}
.viewer-markdown .toc h1,
.viewer-markdown .toc h2,
.viewer-markdown .toc h3,
.viewer-markdown .toc h4,
.viewer-markdown .toc h5,
.viewer-markdown .toc h6 {
@apply text-base text-indigo-600 font-sans my-0;
}
.viewer-markdown .toc a {
@apply text-indigo-600 font-normal no-underline hover:underline;
}
.viewer-markdown .toc li {
@apply m-0;
}
.viewer-markdown .toc ul ul {
@apply pl-4;
}
/* Notebook Spacing */
/* --------------------------------------------------------------- */
.viewer-notebook {
@apply py-16;
}
#clerk-static-app .viewer-notebook {
@apply pt-[0.8rem] pb-16;
}
.viewer-markdown *:first-child:not(.viewer-code):not(li):not(h2) {
@apply mt-0;
}
/*.viewer + .viewer { @apply mt-6; }*/
.viewer + .viewer-result {
@apply mt-0;
}
.viewer-code + .viewer-result {
@apply mt-3;
}
.viewer-markdown + .viewer-markdown {
@apply mt-0;
}
/* Sidenotes */
/* --------------------------------------------------------------- */
.sidenote-ref {
@apply top-[-3px] inline-flex justify-center items-center w-[18px] h-[18px]
rounded-full bg-slate-100 border border-slate-300 hover:bg-slate-200 hover:border-slate-300
m-0 ml-[4px] cursor-pointer;
}
.sidenote {
@apply hidden float-left clear-both mx-[2.5%] my-4 text-xs relative w-[95%];
}
.sidenote-ref.expanded + .sidenote {
@apply block;
}
@media (min-width: 860px) {
.sidenote-ref {
@apply top-[-0.5em] w-auto h-auto inline border-0 bg-transparent m-0 pointer-events-none;
}
.sidenote sup {
@apply inline;
}
.viewer-markdown .contains-sidenotes p {
@apply max-w-[65%];
}
.viewer-markdown p .sidenote {
@apply mr-[-54%] mt-[0.2rem] w-1/2 float-right clear-right relative block;
}
}
.viewer-code + .viewer:not(.viewer-markdown):not(.viewer-code):not(.viewer-code-folded),
.viewer-code-folded + .viewer:not(.viewer-markdown):not(.viewer-code):not(.viewer-code-folded),
.viewer-result + .viewer-result {
@apply mt-2;
}
.viewer-markdown code {
background-color: transparent;
}
.viewer-code + .viewer-code-folded {
@apply mt-4;
}
.viewer-result {
@apply leading-tight mb-6;
}
.viewer-result figure {
@apply mt-0 !important;
}
@media (min-width: 768px) {
.devcard-desc > div {
@apply max-w-full m-0;
}
}
/* Command Palette */
/* --------------------------------------------------------------- */
.nj-commands-input {
@apply bg-transparent text-white;
}
.nj-context-menu-item:hover:not([disabled]) {
@apply cursor-pointer;
background-color: rgba(255, 255, 255, .14);
}
/* Devdocs */
/* --------------------------------------------------------------- */
.logo, .logo-white {
@apply block indent-[-999em];
background: url(/images/nextjournal-logo.svg) center center no-repeat;
}
.devdocs-body {
@apply font-inter;
}
/* Workarounds */
/* --------------------------------------------------------------- */
/* Fixes vega viewer resizing into infinity */
.vega-embed .chart-wrapper {
@apply h-auto !important;
}
/* fixes fraction separators being overridden by tw’s border-color */
.katex * {
@apply border-black;
}
/* end of fork */
.hljs {
background-color: transparent !important;
}
.cm-success {
position: relative;
}
.cm-success::after {
content: '';
position: absolute;
bottom: 5px;
right: 5px;
width: 24px;
height: 24px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z'/%3E%3C/svg%3E");
}
.cm-failure {
position: relative;
}
.cm-failure::after {
content: '';
position: absolute;
bottom: 5px;
right: 5px;
width: 24px;
height: 24px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' style='width: 100%; height: 100%;'%3E%3Cpath fill-rule='evenodd' d='M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z' clip-rule='evenodd'/%3E%3C/svg%3E");
}
/* START: connection status header */
.status-header {
@apply z-10 fixed left-0 right-0 text-center font-sans text-xs bg-white;
}
.status-info {
@apply uppercase tracking-wider ml-1 font-bold text-[12px]
}
[data-status="COMPUTE"]::after {
content: 'Computing...';
color: green;
}
[data-status="TRANSFER"]::after {
content: "Transferring results...";
color: green;
}
[data-status="CONNECTED"]::after {
content: "Connected";
color: green;
}
[data-status="DISCONNECTED"]::after {
content: "Disconnected";
color: red;
}
/* END: connection status header */
.result-error {
background-color: #ffe4e4;
}
</style>
<script defer class="jnb-no-opti" src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.1/dist/cdn.min.js"></script>
<script defer class="jnb-opti" src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js", onload="renderCodeColor();"></script>
<script defer class="jnb-opti" src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.18.2/tocbot.min.js", onload="initTocBot();"></script>
<script defer class="jnb-opti" src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js"
integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"
onload="renderLatex();"></script>
<script defer class="jnb-opti" src="https://cdn.jsdelivr.net/npm/mermaid@10.2.4/dist/mermaid.min.js"
onload="renderMermaid();"></script>
<script defer class="jnb-no-opti" src="https://cdn.jsdelivr.net/npm/vega@5"></script>
<script defer class="jnb-no-opti" src="https://cdn.jsdelivr.net/npm/vega-lite@5"></script>
<script defer class="jnb-no-opti" src="https://cdn.jsdelivr.net/npm/vega-embed@6" onload="renderVega()"></script>
<script defer class="jnb-no-opti" type="text/javascript" src="https://d3js.org/d3.v7.js"></script>
<script defer class="jnb-no-opti" type="text/javascript"
src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.min.js" onload="renderFlamegraphs();"></script>
<script defer class="jnb-no-opti" src="https://cdn.plot.ly/plotly-latest.min.js" onload="renderPlotly();"></script>
</head>
<body>
<div x-data="{ tocOpen: window.innerWidth >= 768 ? true : false, notebookStatus: 'Connected'}" class="flex">
<button x-on:click="tocOpen = ! tocOpen"
class="toc-toggle z-20 fixed right-2 top-2 md:right-auto md:left-3 md:top-[7px] text-slate-400 font-sans text-xs hover:underline cursor-pointer flex items-center bg-white py-1 px-3 md:p-0 rounded-full md:rounded-none border md:border-0 border-slate-200 shadow md:shadow-none">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20"
height="20">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<span class="tracking-wider ml-1 font-bold text-[12px]">ToC</span>
</button>
<aside x-show="tocOpen" class="h-screen z-20 flex-shrink-0 fixed bg-slate-100 font-sans border-r border-t w-52">
<div class="px-3 mb-1 mt-1 md:mt-0 text-xs md:text-[12px] uppercase tracking-wider text-slate-500 font-medium px-3 mb-1 leading-none">
ToC
</div>
<div x-on:click="tocOpen = ! tocOpen"
class="toc-toggle text-slate-500 absolute right-2 top-[11px] cursor-pointer z-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2" class="w-4 w-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path>
</svg>
</div>
<div id="toc"></div>
</aside>
<div x-bind:class="tocOpen ? 'toc-offset' : ''" class="flex-auto h-screen overflow-y-auto scroll-container">
<div id="notebook" class="flex flex-col items-center viewer-notebook flex-auto"><div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h1 id="-book-of-jnotebook">📖 Book of jnotebook</h1>
<p><code>jnotebook</code> is a modern notebook system for Java<br />
<code>jnotebook</code> interprets Java JShell files and render them as notebooks.</p>
<h2 id="-rationale">⚖️ Rationale</h2>
<p>Computational notebooks allow arguing from evidence by mixing prose with executable code. For a good overview of problems users encounter in traditional notebooks like Jupyter, see <a href="https://www.youtube.com/watch?v=7jiPeIFXb6U">I don't like notebooks</a> and <a href="https://austinhenley.com/pubs/Chattopadhyay2020CHI_NotebookPainpoints.pdf">What’s Wrong with Computational Notebooks? Pain Points, Needs, and Design Opportunities</a>.</p>
<p><code>jnotebook</code> tries to address the following problems:</p>
<ul>
<li>notebook editors are less helpful than IDE editors</li>
<li>notebook code is hard to reuse</li>
<li>out-of-order execution causes reproducibility issues</li>
<li>notebook code is hard to version control</li>
<li>the Java ecosystem does not provide a great experience for visualization and document formatting</li>
</ul>
<p><code>jnotebook</code> is a notebook library for Java that address these problems by doing less, namely:</p>
<ul>
<li>no editing environment: you can keep the code editor you know and love</li>
<li>(almost) no new format: <code>jnotebook</code> interprets <a href="https://docs.oracle.com/en/java/javase/20/JShell/scripts.html#GUID-C3A41878-9A9A-4D31-BBDF-909729848A3E">JShell files</a> and renders them as notebook.<br />
Because <code>jnotebook</code> is not required to run JShell files, it does not introduce a dependency if you wish to run the JShell file in production.</li>
<li>no out-of-order execution: <code>jnotebook</code> always evaluates from top to bottom. <code>jnotebook</code> builds a dependency graph of Java statements and only recomputes the needed changes to keep the feedback loop fast.</li>
<li>cells outputs are interpreted as html. This gives access to great visualization libraries and standard html for formatting.</li>
</ul>
<h2 id="-getting-started">🚀 Getting Started</h2>
<h3 id="quickstart">Quickstart</h3>
<p><code>jnotebook</code> requires Java 17 or higher.<br />
<code>jnotebook</code> is distributed in a single portable binary. Download it.</p>
<pre><code>curl -Ls https://get.jnotebook.catheu.tech -o jnotebook
chmod +x jnotebook
</code></pre>
<p>Launch it.</p>
<pre><code>./jnotebook server
# or
java -jar jnotebook server
</code></pre>
<p>Then go to <a href="http://localhost:5002" target="_blank">http://localhost:5002</a>.<br />
By default, the notebook folder is <code>notebooks</code>. If it does not exist, it will be created with an example notebook.<br />
<code>jnotebook</code> automatically detects when a <code>.jsh</code> file in the <code>notebooks</code> folder is edited<br />
and renders it in the web app.<br />
Once your notebook is ready to be published, render it in a single html file with:</p>
<pre><code>./jnotebook render notebooks/my_notebook.jsh my_notebook.html
</code></pre>
<h3 id="install">Install</h3>
<p>See detailed installation instruction for different platforms in the <a href="https://github.com/cyrilou242/jnotebook/#install" target="_blank">github project</a>.</p>
<h3 id="-demo-notebooks">🤹 Demo notebooks</h3>
<p><em>Coming soon.</em></p>
<h3 id="-in-an-existing-project">🔌 In an Existing Project</h3>
<h4 id="maven">Maven</h4>
<p>When launched within a maven project, <code>jnotebook</code> automatically injects the project<br />
dependencies in the classpath. If launched in a submodule, only the submodule<br />
dependencies are injected.</p>
<h4 id="manual">Manual</h4>
<p>Dependencies can be injected manually with the <code>-cp=&lt;CLASSPATH&gt;</code> parameter.</p>
<h3 id="ide-integration">IDE integration</h3>
<h4 id="intellij">IntelliJ</h4>
<p>Enable IntelliSense highlighting and code utilities for JShell <code>.jsh</code> files:</p>
<ol>
<li>Go to <strong>Settings</strong> | <strong>Editor</strong> | <strong>File Types</strong></li>
<li>Click on <strong>JShell snippet</strong></li>
<li>In <strong>file name patterns</strong>, click <strong>+</strong> (<strong>add</strong>)</li>
<li>Add <code>*.jsh</code>.</li>
</ol>
<h2 id="-editor-principle">💡 Editor principle</h2>
<p>Cells are delimited by blank lines</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">String s1 = &quot;Hello&quot;;</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div>"Hello"</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"></div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">String s2 = &quot;World&quot;;</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div>"World"</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>for multi-statements cells, only the last value or method result is returned.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">String s3 = &quot;!&quot;;
String s4 = &quot;!&quot;;
String message = s1 + &quot; &quot; + s2 + s3 + s4;</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div>"Hello World!!"</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>but everything sent to System.out is returned.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">System.out.println(&quot;Hello John&quot;);
System.out.println(&quot;Hello Jane&quot;);
String s5 = &quot;Hello Alex&quot;;</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div>Hello John
</div> <div>Hello Jane
</div> <div>"Hello Alex"</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>html in return values is interpreted. (see <a href="#custom-html">custom html</a> for more html manipulation)</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">String sayHello() {
return &quot;&lt;b&gt;Hello John!&lt;/b&gt;&quot;;
}
sayHello();</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div>"<b>Hello John!</b>"</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>while System.out is not interpreted as html.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">System.out.println(&quot;&lt;b&gt;Hello Jane!&lt;/b&gt;&quot;);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div>&lt;b&gt;Hello Jane!&lt;/b&gt;
</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>By default, the java environment imports common classes. The exact list can be found <a href="https://github.com/cyrilou242/jnotebook/blob/360919e15414509af3ae1a7b9c246dcfe6c3421e/jnotebook-core/src/main/java/tech/catheu/jnotebook/JShell/PowerJShell.java#L28">here</a>.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">List.of(1,2,3);
Map.of(&quot;key&quot;, &quot;value&quot;);
Thread.sleep(1);</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>Mistakes happen! <code>jnotebook</code> tries its best to give helpful error messages.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-failure">invalidJava();</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8 result-error"><div class="relative"><div class="overflow-y-hidden"><pre>Error:
cannot find symbol
symbol: method invalidJava()
location: class
invalidJava();
^^^^^^^^^^^^
</pre></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>Exceptions happen too!</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-failure">throw new RuntimeException(&quot;Panic!&quot;);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8 result-error"><div class="relative"><div class="overflow-y-hidden"><div>jdk.jshell.EvalException: Panic!</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h3 id="markdown">Markdown</h3>
<p>Latex is supported inline: <pre>$`a^2+b^2=c^2`$</pre> → will render as <span class="katex">a^2+b^2=c^2</span>.<br />
and as block:</p>
<pre>
```math
a^2+b^2=c^2
```
</pre>
<div class="katex">
a^2+b^2=c^2
</div>
<h3 id="mermaid">Mermaid</h3>
<p>Mermaid graphs are supported</p>
<div class="mermaid">
sequenceDiagram
Alice-&gt;&gt;+John: Hello John, how are you?
Alice-&gt;&gt;+John: John, can you hear me?
John--&gt;&gt;-Alice: Hi Alice, I can hear you!
John--&gt;&gt;-Alice: I feel great!
</div>
<p>using</p>
<pre>```mermaid
[MERMAID GRAPH CODE]
```</pre>
<p>See <a href="https://mermaid.js.org/intro/">mermaid documentation</a> for examples.</p>
<h2 id="-viewers">🔍 Viewers</h2>
<p><code>jnotebook</code> provides viewers and utils for data, tables, plots, flamegraphs etc.<br />
These utils are packaged in a separate dependency <code>jnotebook-utils</code>. By default, <code>jnotebook-utils</code> is in the classpath.<br />
All utils are available as static method in <code>tech.catheu.jnotebook.Nb</code>.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">import tech.catheu.jnotebook.Nb;</div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h2 id="-tables">🔢 Tables</h2>
<p><em>coming soon</em></p>
<h2 id="-plotly">📊 Plotly</h2>
<p><code>jnotebook</code> has built-in support for Plotly's low-ceremony plotting. See Plotly's JavaScript <a href="https://plotly.com/javascript/">docs</a> for more examples and <a href="https://plotly.com/javascript/configuration-options/">options</a>.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">Nb.plotly(List.of(
Map.of(&quot;z&quot;, List.of(List.of(1, 2, 3), List.of(3, 2, 1)), &quot;type&quot;, &quot;surface&quot;)),
Map.of(),
Map.of());</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="overflow-x-auto"><div class="plotly js-plotly-plot" data-data="[{&quot;type&quot;:&quot;surface&quot;,&quot;z&quot;:[[1,2,3],[3,2,1]]}]" data-layout="{}" data-config="{}"></div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h2 id="-vega-lite">🗺 Vega Lite</h2>
<p><code>jnotebook</code> also supports <a href="https://vega.github.io/vega-lite/">Vega Lite</a>.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">Nb.vega(Map.of(
&quot;data&quot;, Map.of(&quot;url&quot;, &quot;https://vega.github.io/vega-lite/data/seattle-weather.csv&quot;),
&quot;mark&quot;, &quot;bar&quot;,
&quot;encoding&quot;, Map.of(
&quot;x&quot;, Map.of(&quot;timeUnit&quot;, &quot;month&quot;, &quot;field&quot;, &quot;date&quot;, &quot;type&quot;, &quot;ordinal&quot;),
&quot;y&quot;, Map.of(&quot;aggregate&quot;, &quot;mean&quot;, &quot;field&quot;, &quot;precipitation&quot;)
)
));</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="overflow-x-auto"><div class="vega-lite vega-embed has-actions" data-config="{&quot;encoding&quot;:{&quot;x&quot;:{&quot;field&quot;:&quot;date&quot;,&quot;type&quot;:&quot;ordinal&quot;,&quot;timeUnit&quot;:&quot;month&quot;},&quot;y&quot;:{&quot;field&quot;:&quot;precipitation&quot;,&quot;aggregate&quot;:&quot;mean&quot;}},&quot;data&quot;:{&quot;url&quot;:&quot;https://vega.github.io/vega-lite/data/seattle-weather.csv&quot;},&quot;mark&quot;:&quot;bar&quot;}"></div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h2 id="-images">🏞 Images</h2>
<p><em>coming soon</em></p>
<h2 id="-grid-layouts">🔠 Grid Layouts</h2>
<p>Layouts can be composed via rows, columns and grids</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">Nb.row(1, 2, 3, 4);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="grid grid-flow-row"><div>1</div><div>2</div><div>3</div><div>4</div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"></div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">Nb.col(1, 2, 3, 4);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="grid grid-cols-4"><div>1</div><div>2</div><div>3</div><div>4</div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"></div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">Nb.col(Nb.row(&quot;John&quot;, &quot;Jane&quot;, &quot;Alex&quot;), Nb.row(1, 2, 3), Nb.row(4, 5, 6));</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="grid grid-cols-3"><div class="grid grid-flow-row"><div>John</div><div>Jane</div><div>Alex</div></div><div class="grid grid-flow-row"><div>1</div><div>2</div><div>3</div></div><div class="grid grid-flow-row"><div>4</div><div>5</div><div>6</div></div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"></div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">int numCols = 4;
Nb.grid(numCols, 1, 2, 3, 4, 5, 6, 7);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="grid grid-cols-4"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h2 id="-custom-html">⚙️ Custom html</h2>
<p>You can use the <a href="https://j2html.com/">j2html</a> library directly to generate html easily.<br />
Values inheriting <code>j2html.tags.DomContent</code> are rendered as html.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">import static j2html.TagCreator.*; // import all html tags
b(&quot;Hello red!&quot;).withStyle(&quot;color: red&quot;);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><b style="color: red">Hello red!</b></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>This makes it easy to create custom viewers</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">import j2html.tags.DomContent;
DomContent title(String text) {
return p(text).withStyle(&quot;font-weight:bold; font-size: x-large; display: block;margin-left: auto; margin-right: auto&quot;);
};
title(&quot;A big title.&quot;);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><p style="font-weight:bold; font-size: x-large; display: block;margin-left: auto; margin-right: auto">A big title.</p></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><p>All <code>Nb</code> viewers output are of class <code>j2html.tags.DomContent</code>. This makes is easy to combine viewers</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">var graph1 = Nb.vega(Map.of(
&quot;data&quot;, Map.of(&quot;url&quot;, &quot;https://vega.github.io/vega-lite/data/seattle-weather.csv&quot;),
&quot;mark&quot;, &quot;bar&quot;,
&quot;encoding&quot;, Map.of(
&quot;x&quot;, Map.of(&quot;timeUnit&quot;, &quot;month&quot;, &quot;field&quot;, &quot;date&quot;, &quot;type&quot;, &quot;ordinal&quot;),
&quot;y&quot;, Map.of(&quot;aggregate&quot;, &quot;mean&quot;, &quot;field&quot;, &quot;precipitation&quot;)
)
));
var graph2 = Nb.vega(Map.of(
&quot;data&quot;, Map.of(&quot;url&quot;, &quot;https://vega.github.io/vega-lite/data/seattle-weather.csv&quot;),
&quot;mark&quot;, &quot;bar&quot;,
&quot;encoding&quot;, Map.of(
&quot;x&quot;, Map.of(&quot;timeUnit&quot;, &quot;month&quot;, &quot;field&quot;, &quot;date&quot;, &quot;type&quot;, &quot;ordinal&quot;),
&quot;y&quot;, Map.of(&quot;aggregate&quot;, &quot;mean&quot;, &quot;field&quot;, &quot;precipitation&quot;)
)
));
Nb.row(title(&quot;My awesome analysis&quot;), Nb.col(graph1, graph2));</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="grid grid-flow-row"><p style="font-weight:bold; font-size: x-large; display: block;margin-left: auto; margin-right: auto">My awesome analysis</p><div class="grid grid-cols-2"><div class="overflow-x-auto"><div class="vega-lite vega-embed has-actions" data-config="{&quot;encoding&quot;:{&quot;x&quot;:{&quot;field&quot;:&quot;date&quot;,&quot;type&quot;:&quot;ordinal&quot;,&quot;timeUnit&quot;:&quot;month&quot;},&quot;y&quot;:{&quot;field&quot;:&quot;precipitation&quot;,&quot;aggregate&quot;:&quot;mean&quot;}},&quot;data&quot;:{&quot;url&quot;:&quot;https://vega.github.io/vega-lite/data/seattle-weather.csv&quot;},&quot;mark&quot;:&quot;bar&quot;}"></div></div><div class="overflow-x-auto"><div class="vega-lite vega-embed has-actions" data-config="{&quot;encoding&quot;:{&quot;x&quot;:{&quot;field&quot;:&quot;date&quot;,&quot;type&quot;:&quot;ordinal&quot;,&quot;timeUnit&quot;:&quot;month&quot;},&quot;y&quot;:{&quot;field&quot;:&quot;precipitation&quot;,&quot;aggregate&quot;:&quot;mean&quot;}},&quot;data&quot;:{&quot;url&quot;:&quot;https://vega.github.io/vega-lite/data/seattle-weather.csv&quot;},&quot;mark&quot;:&quot;bar&quot;}"></div></div></div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h2 id="-flamegraphs">🔥 Flamegraphs</h2>
<p>You can profile methods and generate flamegraphs.</p>
</div></div> <div class="viewer viewer-code w-full max-w-wide"><div class="cm-editor"><div class="cm-scroller"><div class="cm-content whitespace-pre cm-success">Runnable arrayFilling = () -&gt; {
List&lt;Integer&gt; a = new ArrayList&lt;&gt;();
for (int i = 0; i &lt; 10000000; i++) {
a.add(i);
}
};
var profilePath = Nb.profile(arrayFilling);
Nb.flame(profilePath);</div></div></div></div> <div class="viewer viewer-result w-full max-w-prose px-8"><div class="relative"><div class="overflow-y-hidden"><div><div class="overflow-x-auto"><div class="flame" data-profile="{&quot;name&quot;:&quot;ALL&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;java.lang.Thread::run&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;jdk.jshell.execution.LocalExecutionControl$$Lambda$379+0x00000008002b4000.176408151::run&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;jdk.jshell.execution.LocalExecutionControl::lambda$invoke$1&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;java.lang.reflect.Method::invoke&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;jdk.internal.reflect.DelegatingMethodAccessorImpl::invoke&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;jdk.internal.reflect.NativeMethodAccessorImpl::invoke&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;jdk.internal.reflect.NativeMethodAccessorImpl::invoke0&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;::do_it$&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;::do_it$Aux&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;tech.catheu.jnotebook.Nb::profile&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;tech.catheu.jnotebook.Nb::profile&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;tech.catheu.jnotebook.Nb$ProfiledRunnable::run&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;$Lambda$487+0x000000080032dc70.2005752676::run&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;::lambda$do_it$$0&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;java.lang.Integer::valueOf&quot;,&quot;value&quot;:1},{&quot;name&quot;:&quot;java.util.ArrayList::add&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;java.util.ArrayList::add&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;java.util.ArrayList::grow&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;java.util.ArrayList::grow&quot;,&quot;children&quot;:[{&quot;name&quot;:&quot;java.util.Arrays::copyOf&quot;,&quot;value&quot;:1}],&quot;value&quot;:1}],&quot;value&quot;:1}],&quot;value&quot;:1}],&quot;value&quot;:1}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}],&quot;value&quot;:3}"></div></div></div></div></div></div> <div class="viewer viewer-html- w-full max-w-prose px-8"><div class="viewer-markdown"><h2 id="credits">Credits</h2>
<p>This documentation is directly copying some content from the <a href="https://book.clerk.vision/#rationale">book of Clerk</a>, a notebook system for Clojure.</p>
</div></div></div>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment