Skip to content

Commit

Permalink
Hook up graphic output
Browse files Browse the repository at this point in the history
  • Loading branch information
coatless committed Feb 19, 2024
1 parent 9ba3fec commit 34a3f01
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 26 deletions.
118 changes: 104 additions & 14 deletions _extensions/pyodide/qpyodide-cell-classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ globalThis.qpyodideCreateCell = function(cellData) {
case 'output':
return new OutputCell(cellData);
case 'setup':
return new SetupContextCell(cellData);
return new SetupCell(cellData);
default:
return new InteractiveCell(cellData);
// throw new Error('Invalid cell type specified in options.');
Expand Down Expand Up @@ -74,6 +74,7 @@ class BaseCell {
this.id = cellData.id;
this.options = cellData.options;
this.insertionLocation = document.getElementById(`qpyodide-insertion-location-${this.id}`);
this.executionLock = false;
}

cellOptions() {
Expand Down Expand Up @@ -226,6 +227,8 @@ class InteractiveCell extends BaseCell {
this.resetButton = document.getElementById(`qpyodide-button-reset-${this.id}`);
this.copyButton = document.getElementById(`qpyodide-button-copy-${this.id}`);
this.editorDiv = document.getElementById(`qpyodide-editor-${this.id}`);
this.outputCodeDiv = document.getElementById(`qpyodide-output-code-area-${this.id}`);
this.outputGraphDiv = document.getElementById(`qpyodide-output-graph-area-${this.id}`);

// Store reference to the object
var thiz = this;
Expand Down Expand Up @@ -297,13 +300,13 @@ class InteractiveCell extends BaseCell {
// Add a keydown event listener for Shift+Enter to run all code in cell
thiz.editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {
// Retrieve all text inside the editor
thiz.executeCode(thiz.editor.getValue(), thiz.editor.__qpyodideCounter, thiz.editor.__qpyodideOptions);
thiz.runCode(thiz.editor.getValue());
});

// Add a keydown event listener for CMD/Ctrl+Enter to run selected code
thiz.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
// Get the selected text from the editor
const selectedText = editor.getModel().getValueInRange(editor.getSelection());
const selectedText = thiz.editor.getModel().getValueInRange(thiz.editor.getSelection());
// Check if no code is selected
if (isEmptyCodeText(selectedText)) {
// Obtain the current cursor position
Expand All @@ -325,13 +328,13 @@ class InteractiveCell extends BaseCell {
}

// Run the entire line of code.
thiz.executeCode(currentLine, thiz.editor.__qpyodideCounter, thiz.editor.__qpyodideOptions);
thiz.runCode(currentLine);

// Move cursor to new position
thiz.editor.setPosition(newPosition);
} else {
// Code to run when Ctrl+Enter is pressed with selected code
thiz.executeCode(selectedText, thiz.editor.__qpyodideCounter, thiz.editor.__qpyodideOptions);
thiz.runCode(selectedText);
}
});
}
Expand All @@ -353,11 +356,9 @@ class InteractiveCell extends BaseCell {


// Add a click event listener to the run button
this.runButton.onclick = function () {
this.executeCode(
this.editor.getValue(),
this.editor.__qpyodideCounter,
this.editor.__qpyodideOptions
thiz.runButton.onclick = function () {
thiz.runCode(
thiz.editor.getValue()
);
};

Expand All @@ -376,14 +377,103 @@ class InteractiveCell extends BaseCell {
};
}

disableInteractiveCells() {
// Enable locking of execution for the cell
this.executionLock = true;

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

enableInteractiveCells() {
// Remove locking of execution for the cell
this.executionLock = false;

// All execution of other code cells
document.querySelectorAll(".qpyodide-button-run").forEach((btn) => {
btn.disabled = false;
});
}

/**
* Execute the Python code inside the editor.
*/
async runCode() {
// Extract code
const code = this.editor.getValue();
async runCode(code) {

console.log("Hit! Log");

// Check if we have an execution lock
if (this.executeLock) return;

this.disableInteractiveCells();

// Force wait procedure
await mainPyodide;

// Clear the output stock
qpyodideResetOutputArray();

// Generate a new canvas element, avoid attaching until the end
let graphFigure = document.createElement("figure");
document.pyodideMplTarget = graphFigure;

console.log("Running code!");
// Obtain results from the base class
const result = this.executeCode(code);
try {
// Always check to see if the user adds new packages
await mainPyodide.loadPackagesFromImports(code);

// Process result
const output = await mainPyodide.runPythonAsync(code);

// Add output
qpyodideAddToOutputArray(output, "stdout");
} catch (err) {
// Add error message
qpyodideAddToOutputArray(err, "stderr");
// TODO: There has to be a way to remove the Pyodide portion of the errors...
}

const result = qpyodideRetrieveOutput();
console.log("Output of stdout: " + result);

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

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

// Add output under interactive div
this.outputCodeDiv.appendChild(pre);

// Place the graphics onto the page
if (graphFigure) {

if (options['fig-cap']) {
// Create figcaption element
const figcaptionElement = document.createElement('figcaption');
figcaptionElement.innerText = options['fig-cap'];
// Append figcaption to figure
graphFigure.appendChild(figcaptionElement);
}

this.outputGraphDiv.appendChild(graphFigure);
}

// Re-enable execution
this.enableInteractiveCells();
}
};

Expand Down
2 changes: 1 addition & 1 deletion _extensions/pyodide/qpyodide-cell-initialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ qpyodideCellDetails.map(
// Handle the creation of the element
qpyodideCreateCell(entry);
}
);
);
55 changes: 47 additions & 8 deletions _extensions/pyodide/qpyodide-document-engine-initialization.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,68 @@
// Create a logging setup
globalThis.qpyodideMessageArray = []

// Add messages to array
globalThis.qpyodideAddToOutputArray = function(message, type) {
qpyodideMessageArray.push({ message, type });
}

// Function to reset the output array
globalThis.qpyodideResetOutputArray = function() {
qpyodideMessageArray = [];
}

globalThis.qpyodideRetrieveOutput = function() {
return qpyodideMessageArray.map(entry => entry.message).join('\n');
}

// Start a timer
const initializeWebRTimerStart = performance.now();

const initializePyodideTimerStart = performance.now();

// Encase with a dynamic import statement
globalThis.qpyodideInstance = await import(
qpyodideCustomizedPyodideOptions.indexURL + "pyodide.mjs").then(
async({ loadPyodide }) => {

console.log("Start loading Pyodide");

// Populate Pyodide options with defaults or new values based on `pyodide`` meta
let loadedPyodide = await loadPyodide(
let mainPyodide = await loadPyodide(
qpyodideCustomizedPyodideOptions
);

loadedPyodide.runPython("globalScope = {}");
// Setup a namespace for global scoping
// await loadedPyodide.runPythonAsync("globalScope = {}");

// Add matplotlib
await mainPyodide.loadPackage("matplotlib");

// Set the backend for matplotlib to be interactive.
await mainPyodide.runPythonAsync(`
import matplotlib
matplotlib.use("module://matplotlib_pyodide.html5_canvas_backend")
from matplotlib import pyplot as plt
`);

// Unlock interactive buttons
qpyodideSetInteractiveButtonState(
`<i class="fa-solid fa-play qpyodide-icon-run-code"></i> <span>Run Code</span>`,
true
);

if (qpyodideShowStartupMessage) {
qpyodideStartupMessage.innerText = "🟢 Ready!"
}

// Assign Pyodide into the global environment
globalThis.mainPyodide = mainPyodide;

globalThis.mainPyodide = loadedPyodide;

console.log("Completed loading Pyodide");
return loadedPyodide;
return mainPyodide;
}
);

// Stop timer
const initializeWebRTimerEnd = performance.now();
const initializePyodideTimerEnd = performance.now();

// Create a function to retrieve the promise object.
globalThis._qpyodideGetInstance = function() {
Expand Down
7 changes: 5 additions & 2 deletions _extensions/pyodide/qpyodide-document-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ globalThis.qpyodideCustomizedPyodideOptions = {
"env": {
"HOME": "{{HOMEDIR}}",
},
};
stdout: (text) => {qpyodideAddToOutputArray(text, "out");},
stderr: (text) => {qpyodideAddToOutputArray(text, "error");}
}

// Store cell data
globalThis.qpyodideCellDetails = {{QPYODIDECELLDETAILS}};
globalThis.qpyodideCellDetails = {{QPYODIDECELLDETAILS}};

1 change: 0 additions & 1 deletion _extensions/pyodide/qpyodide-document-status.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Declare startupMessageqpyodide globally
globalThis.qpyodideStartupMessage = document.createElement("p");


// Function to set the button text
globalThis.qpyodideSetInteractiveButtonState = function(buttonText, enableCodeButton = true) {
document.querySelectorAll(".qpyodide-button-run").forEach((btn) => {
Expand Down

0 comments on commit 34a3f01

Please sign in to comment.