I have some javascript plots I would like to revive:
To do so, I need to pass data to a javascript function and cause it to update its plots.
(Note I want more than one such plot at a time)
A way that works is as follows:
import panel as pn
import numpy as np
import uuid
def create_vector_app(nm):
# Generate a unique ID for the container and JavaScript function
unique_id = f"app_{uuid.uuid4().hex[:8]}"
# Create a slider to adjust the size of the vector
size_slider = pn.widgets.IntSlider(name=f"{nm}: Vector Size", start=1, end=10, value=5)
# Create a button to generate a random vector
generate_button = pn.widgets.Button(name=f"{nm} Generate Vector", button_type="primary")
# Create a pane to include the JavaScript logic
js_pane = pn.pane.HTML(f"""
<div id="{unique_id}">Waiting for vector...</div>
<script>
// Ensure the function and variable are immediately available globally
(function() {{
let vector = []; // Local vector for this instance
let container = document.getElementById("{unique_id}");
function updateVector(newVector) {{
vector = newVector; // Update the local vector
console.log("Instance {unique_id} vector:", vector); // Log to console
if (container) {{
container.innerHTML = "Vector: " + JSON.stringify(vector); // Display vector in the container
}}
}}
// Expose the updateVector function to the global window immediately
window["updateVector_{unique_id}"] = updateVector;
}})();
</script>
""", height=50)
# Define a callback to update the vector in JavaScript
def update_vector(event):
# Generate a random vector based on the slider value
vector = np.random.rand(size_slider.value).tolist()
# Generate JavaScript code to call the local updateVector function
js_code = f"""
window["updateVector_{unique_id}"]({vector});
"""
js_pane.object = f"<script>{js_code}</script>"
# Attach the callback to the button click event
generate_button.on_click(update_vector)
# Layout the widgets and the display pane
return pn.Column(
size_slider,
generate_button,
js_pane,
f"Open the browser console (F12 -> Console) to see the vector for instance {unique_id}."
)
# Create two independent instances
vector_app1 = create_vector_app("1")
vector_app2 = create_vector_app("2")
# Serve the apps
pn.Column( vector_app1, vector_app2 ).servable()
Might there be an easier solution?
Is there a way to do the opposite? Have a javascript function pass data back to python and trigger some action in python? (I have not yet figured out any way to do that… )
import param
import panel as pn
from panel.custom import JSComponent
pn.extension()
class RandomArrayGenerator(JSComponent):
"""
A custom JSComponent that generates two arrays of random floats and sends them to Python.
"""
# Parameter to store the generated array
value = param.String(default="")
_esm = """
export function render({model, el}) {
console.log("RandomArrayGenerator initialized.");
// Create a container to display generated arrays
const output = document.createElement('div');
output.style.padding = "10px";
output.style.border = "1px solid #ddd";
output.style.marginTop = "10px";
output.style.fontFamily = "monospace";
output.style.background = "#f9f9f9";
output.textContent = "Waiting for data...";
el.appendChild(output);
// Track the number of updates
let count = 0;
// Function to generate a random array of floats
const generateArray = () => {
if (count >= 2) {
clearInterval(interval); // Stop after sending two arrays
console.log("Stopped after sending two arrays.");
return;
}
count += 1;
const randomArray = Array.from({length: 10}, () => Math.random().toFixed(2));
console.log("Generated Array:", randomArray);
// Update the display
output.textContent = "Generated Array: " + JSON.stringify(randomArray);
// Notify Python by updating the `value` parameter
model.value = JSON.stringify(randomArray);
};
// Start generating random arrays every 3 seconds
const interval = setInterval(generateArray, 3000);
// Cleanup when the component is removed
model.on('change', (changed) => {
if (changed.value === undefined) {
clearInterval(interval);
console.log("RandomArrayGenerator stopped.");
}
});
}
"""
# Instantiate the custom JSComponent
random_array_generator = RandomArrayGenerator()
# Widget to display the received array in Python
data_display = pn.widgets.StaticText(name="Received Array", value="Waiting for data...")
# Python callback to process the data when `value` changes
@pn.depends(random_array_generator.param.value, watch=True)
def update_display(value):
import json
try:
array = json.loads(value)
formatted = ", ".join(f"{float(x):.2f}" for x in array)
data_display.value = f"Received Array: {formatted}"
except (json.JSONDecodeError, ValueError):
data_display.value = "Invalid data received"
# Layout of the Panel app
layout = pn.Column(
pn.pane.Markdown("### Random Float Array Generator (Sends Two Arrays Only)"),
pn.Row(
pn.Column(
"# JavaScript generates two random arrays and stops.",
random_array_generator
),
pn.Column(
"# Python receives and displays the array:",
data_display
)
)
)
# Serve the app
layout.servable()