Skip to content

Commit

Permalink
Switched to setup context
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsing1 committed Nov 18, 2023
1 parent cd157f1 commit 0398bf3
Showing 1 changed file with 7 additions and 279 deletions.
286 changes: 7 additions & 279 deletions docs/posts/quarto-webr/quarto_webr_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -377,32 +377,12 @@ <h1 class="title">Embedding R into Quarto documents with quarto-webr</h1>

</header>

<div id="qwebr-interactive-area-1" class="qwebr-interactive-area">
<button class="btn btn-default qwebr-button-run" disabled="" type="button" id="qwebr-button-run-1">🟡 Loading
webR...</button>
<div id="qwebr-console-area-1" class="qwebr-console-area">
<div id="qwebr-editor-1" class="qwebr-editor"></div>
<div id="qwebr-output-code-area-1" class="qwebr-output-code-area" aria-live="assertive">
<pre style="visibility: hidden"></pre>
</div>
</div>
<div id="qwebr-output-graph-area-1" class="qwebr-output-graph-area">
</div>
</div>
<script type="module">
// Retrieve webR code cell information
const runButton = document.getElementById("qwebr-button-run-1");
const outputCodeDiv = document.getElementById("qwebr-output-code-area-1");
const editorDiv = document.getElementById("qwebr-editor-1");
const outputGraphDiv = document.getElementById("qwebr-output-graph-area-1");
// Initialization WebR
await globalThis.webR.init();

// Load the Monaco Editor and create an instance
let editor;
require(['vs/editor/editor.main'], function () {
editor = monaco.editor.create(editorDiv, {
value: `require("ggvenn", quietly = TRUE)
require("readr", quietly = TRUE)
require("huxtable", quietly = TRUE)
// Run R code without focusing on storing data.
await globalThis.webR.evalRVoid(`
kUrlRoot <- paste0(
"https://tomsing1.github.io/blog/posts/quarto-webr"
)
Expand All @@ -426,261 +406,9 @@ <h1 class="title">Embedding R into Quarto documents with quarto-webr</h1>
lapply(x, function(df){
df[df$adj.P.Val < fdr_cutoff, ]$symbol
})
}`,
language: 'r',
theme: 'vs-light',
automaticLayout: true, // TODO: Could be problematic for slide decks
scrollBeyondLastLine: false,
minimap: {
enabled: false
},
fontSize: '17.5pt', // Bootstrap is 1 rem
renderLineHighlight: "none", // Disable current line highlighting
hideCursorInOverviewRuler: true // Remove cursor indictor in right hand side scroll bar
});

// Dynamically modify the height of the editor window if new lines are added.
let ignoreEvent = false;
const updateHeight = () => {
const contentHeight = editor.getContentHeight();
// We're avoiding a width change
//editorDiv.style.width = `${width}px`;
editorDiv.style.height = `${contentHeight}px`;
try {
ignoreEvent = true;

// The key to resizing is this call
editor.layout();
} finally {
ignoreEvent = false;
}
};

// Helper function to check if selected text is empty
function isEmptyCodeText(selectedCodeText) {
return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === "");
}

// Registry of keyboard shortcuts that should be re-added to each editor window
// when focus changes.
const addWebRKeyboardShortCutCommands = () => {
// Add a keydown event listener for Shift+Enter to run all code in cell
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {

// Retrieve all text inside the editor
executeCode(editor.getValue());
});

// Add a keydown event listener for CMD/Ctrl+Enter to run selected code
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {

// Get the selected text from the editor
const selectedText = editor.getModel().getValueInRange(editor.getSelection());
// Check if no code is selected
if (isEmptyCodeText(selectedText)) {
// Obtain the current cursor position
let currentPosition = editor.getPosition();
// Retrieve the current line content
let currentLine = editor.getModel().getLineContent(currentPosition.lineNumber);

// Propose a new position to move the cursor to
let newPosition = new monaco.Position(currentPosition.lineNumber + 1, 1);

// Check if the new position is beyond the last line of the editor
if (newPosition.lineNumber > editor.getModel().getLineCount()) {
// Add a new line at the end of the editor
editor.executeEdits("addNewLine", [{
range: new monaco.Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1),
text: "\n",
forceMoveMarkers: true,
}]);
}

// Run the entire line of code.
executeCode(currentLine);

// Move cursor to new position
editor.setPosition(newPosition);
} else {
// Code to run when Ctrl+Enter is pressed with selected code
executeCode(selectedText);
}
});
}

// Register an on focus event handler for when a code cell is selected to update
// what keyboard shortcut commands should work.
// This is a workaround to fix a regression that happened with multiple
// editor windows since Monaco 0.32.0
// https://github.com/microsoft/monaco-editor/issues/2947
editor.onDidFocusEditorText(addWebRKeyboardShortCutCommands);

// Register an on change event for when new code is added to the editor window
editor.onDidContentSizeChange(updateHeight);

// Manually re-update height to account for the content we inserted into the call
updateHeight();
});

// Function to execute the code (accepts code as an argument)
async function executeCode(codeToRun) {

// Disallowing execution of other code cells
document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
btn.disabled = true;
});

// Emphasize the active code cell
runButton.innerHTML = '<i class="fa-solid fa-spinner fa-spin qwebr-icon-status-spinner"></i> <span>Run Code</span>';

// Create a canvas variable for graphics
let canvas = undefined;

// Create a pager variable for help/file contents
let pager = [];

// Process
async function parseTypePager(msg) {

// Split out the event data
const { path, title, deleteFile } = msg.data;

// Process the pager data by reading the information from disk
const paged_data = await webR.FS.readFile(path).then((data) => {
// Obtain the file content
let content = new TextDecoder().decode(data);

// Remove excessive backspace characters until none remain
while(content.match(/.[\b]/)){
content = content.replace(/.[\b]/g, '');
}

// Returned cleaned data
return content;
});

// Unlink file if needed
if (deleteFile) {
await webR.FS.unlink(path);
}

// Return extracted data with spaces
return paged_data;
}

// Initialize webR
await webR.init();

// Setup a webR canvas by making a namespace call into the {webr} package
await webR.evalRVoid("webr::canvas(width=504, height=360)");

// Capture output data from evaluating the code
const result = await webRCodeShelter.captureR(codeToRun, {
withAutoprint: true,
captureStreams: true,
captureConditions: false//,
// env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0
});

// Start attempting to parse the result data
try {

// Stop creating images
await webR.evalRVoid("dev.off()");

// Merge output streams of STDOUT and STDErr (messages and errors are combined.)
const out = result.output
.filter(evt => evt.type === "stdout" || evt.type === "stderr")
.map((evt, index) => {
const className = `qwebr-output-code-${evt.type}`;
return `<code id="${className}-editor-1-result-${index + 1}" class="${className}">${evt.data}</code>`;
})
.join("\n");


// Clean the state
const msgs = await webR.flush();

// Output each image event stored
msgs.forEach((msg) => {
// Determine if old canvas can be used or a new canvas is required.
if (msg.type === 'canvas'){
// Add image to the current canvas
if (msg.data.event === 'canvasImage') {
canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
} else if (msg.data.event === 'canvasNewPage') {
// Generate a new canvas element
canvas = document.createElement("canvas");
canvas.setAttribute("width", 2 * 504);
canvas.setAttribute("height", 2 * 360);
canvas.style.width = "700px";
canvas.style.display = "block";
canvas.style.margin = "auto";
}
}
});

// Use `map` to process the filtered "pager" events asynchronously
const pager = await Promise.all(
msgs.filter(msg => msg.type === 'pager').map(
async (msg) => {
return await parseTypePager(msg);
}
)
);

// Nullify the output area of content
outputCodeDiv.innerHTML = "";
outputGraphDiv.innerHTML = "";

// Design an output object for messages
const pre = document.createElement("pre");
if (/\S/.test(out)) {
// Display results as HTML elements to retain output styling
const div = document.createElement("div");
div.innerHTML = out;
pre.appendChild(div);
} else {
// If nothing is present, hide the element.
pre.style.visibility = "hidden";
}
outputCodeDiv.appendChild(pre);

// Place the graphics on the canvas
if (canvas) {
outputGraphDiv.appendChild(canvas);
}

// Display the pager data
if (pager) {
// Use the `pre` element to preserve whitespace.
pager.forEach((paged_data, index) => {
let pre_pager = document.createElement("pre");
pre_pager.innerText = paged_data;
pre_pager.classList.add("qwebr-output-code-pager");
pre_pager.setAttribute("id", "qwebr-output-code-pager-editor-1-result-" + (index + 1));
outputCodeDiv.appendChild(pre_pager);
});
}
} finally {
// Clean up the remaining code
webRCodeShelter.purge();
}

// Switch to allowing execution of code
document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
btn.disabled = false;
});

// Revert to the initial code cell state
runButton.innerHTML = '<i class="fa-solid fa-play qwebr-icon-run-code"></i> <span>Run Code</span>';
}

// Add a click event listener to the run button
runButton.onclick = function () {
executeCode(editor.getValue());
};
</script>
}
`)
</script>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>This interactive website allows users to intersect differential expression results of three different comparisons from an experiment published by <a href="https://www.biorxiv.org/content/10.1101/2021.01.19.426731v1">Xia et al</a></p>
Expand Down

0 comments on commit 0398bf3

Please sign in to comment.