diff --git a/.gitignore b/.gitignore
index 4740b51..76d8cb3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,10 @@ build/
tensorboard_logs/
examples/_test*
.mypy_cache/
+
+# OS / editor cruft
+.DS_Store
+Thumbs.db
+*.swp
+*.swo
+.idea/
diff --git a/examples/bokeh_colab.ipynb b/examples/bokeh_colab.ipynb
new file mode 100644
index 0000000..128e703
--- /dev/null
+++ b/examples/bokeh_colab.ipynb
@@ -0,0 +1,76 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": "# livelossplot + Bokeh in Colab (issue #109 verification)\n\nOpen in Colab:
\n\nInstalls livelossplot from the `i109-bokeh-colab` branch on GitHub (not PyPI), so the in-progress Colab fix for `BokehPlot` is exercised."
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": "!pip install --quiet --upgrade git+https://github.com/stared/livelossplot.git@i109-bokeh-colab"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import sys\n",
+ "import livelossplot\n",
+ "print('livelossplot:', livelossplot.__version__)\n",
+ "print('running in Colab:', 'google.colab' in sys.modules)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from time import sleep\n",
+ "import numpy as np\n",
+ "\n",
+ "from livelossplot import PlotLosses\n",
+ "from livelossplot.outputs import BokehPlot\n",
+ "\n",
+ "plotlosses = PlotLosses(outputs=[BokehPlot()])\n",
+ "\n",
+ "for i in range(10):\n",
+ " plotlosses.update({\n",
+ " 'acc': 1 - np.random.rand() / (i + 2.),\n",
+ " 'val_acc': 1 - np.random.rand() / (i + 0.5),\n",
+ " 'loss': 1. / (i + 2.),\n",
+ " 'val_loss': 1. / (i + 0.5),\n",
+ " })\n",
+ " plotlosses.send()\n",
+ " sleep(0.5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## What you should see\n",
+ "\n",
+ "Two Bokeh panels (Accuracy, Loss) that **redraw with growing data lines** every ~0.5s. Before this fix, the panels rendered as empty skeletons and never updated (issue #109).\n",
+ "\n",
+ "If something still looks broken, please paste the failure into the issue."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
\ No newline at end of file
diff --git a/livelossplot/outputs/bokeh_plot.py b/livelossplot/outputs/bokeh_plot.py
index 9b8f12d..105a540 100644
--- a/livelossplot/outputs/bokeh_plot.py
+++ b/livelossplot/outputs/bokeh_plot.py
@@ -1,3 +1,4 @@
+import sys
from typing import List, Dict, Tuple
from livelossplot.main_logger import MainLogger, LogItem
@@ -28,11 +29,17 @@ def __init__(
self.skip_first = skip_first # think about it
self.figures = {}
self.is_notebook = False
+ self.is_colab = 'google.colab' in sys.modules
self.output_file = output_file
self.colors = palettes.Category10[10]
+ self._colab_handle = None
def send(self, logger: MainLogger) -> None:
"""Draw figures with metrics and show"""
+ if self.is_colab:
+ self._send_colab(logger)
+ return
+
log_groups = logger.grouped_log_history()
new_grid_plot = False
for idx, (group_name, group_logs) in enumerate(log_groups.items(), start=1):
@@ -48,6 +55,46 @@ def send(self, logger: MainLogger) -> None:
else:
self.plotting.save(self.grid)
+ def _send_colab(self, logger: MainLogger) -> None:
+ """Colab actively blocks Jupyter Comms, so push_notebook is dead there.
+ Render Bokeh into a complete HTML doc and embed it via