diff --git a/bindings/pyroot/pythonizations/python/CMakeLists.txt b/bindings/pyroot/pythonizations/python/CMakeLists.txt
index 6dcbf3e7584e1..0bddff3c3216b 100644
--- a/bindings/pyroot/pythonizations/python/CMakeLists.txt
+++ b/bindings/pyroot/pythonizations/python/CMakeLists.txt
@@ -110,9 +110,11 @@ set(py_sources
ROOT/_jupyroot/kernel/magics/__init__.py
ROOT/_jupyroot/kernel/magics/cppmagic.py
ROOT/_jupyroot/kernel/magics/jsrootmagic.py
+ ROOT/_jupyroot/kernel/magics/rootbrowsemagic.py
ROOT/_jupyroot/magics/__init__.py
ROOT/_jupyroot/magics/cppmagic.py
ROOT/_jupyroot/magics/jsrootmagic.py
+ ROOT/_jupyroot/magics/rootbrowsemagic.py
ROOT/_numbadeclare.py
ROOT/_pythonization/__init__.py
ROOT/_pythonization/_cppinstance.py
diff --git a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/handlers.py b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/handlers.py
index 1401695ca8c23..98f40c7386eeb 100644
--- a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/handlers.py
+++ b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/handlers.py
@@ -169,8 +169,7 @@ def __init__(self, poller):
def display_drawables(displayFunction):
drawers = helpers.utils.GetDrawers()
for drawer in drawers:
- for dobj in drawer.GetDrawableObjects():
- displayFunction(dobj)
+ drawer.Draw(displayFunction)
class JupyROOTDisplayer(Runner):
diff --git a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/utils.py b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/utils.py
index 72918f4edd1e7..66587a23fb1e5 100644
--- a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/utils.py
+++ b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/utils.py
@@ -21,6 +21,7 @@
import sys
import tempfile
import time
+import ctypes
from contextlib import contextmanager
from datetime import datetime
from hashlib import sha1
@@ -37,9 +38,13 @@
cppMIME = "text/x-c++src"
+_enableJSVis = True
+
# Keep display handle for canvases to be able update them
_canvasHandles = {}
+_visualObjects = []
+
_jsMagicHighlight = """
Jupyter.CodeCell.options_default.highlight_modes['magic_{cppMIME}'] = {{'reg':[/^%%cpp/]}};
console.log("JupyROOT - %%cpp magic configured");
@@ -50,25 +55,54 @@
_jsCanvasWidth = 800
_jsCanvasHeight = 600
-_jsCode = """
+_jsFixedSizeDiv = """
+"""
+
+_jsFullWidthDiv = """
+
+"""
+
+_jsDrawJsonCode = """
+Core.unzipJSON({jsonLength},'{jsonZip}').then(json => {{
+ const obj = Core.parse(json);
+ Core.draw('{jsDivId}', obj, '{jsDrawOptions}');
+}});
+"""
+
+_jsBrowseUrlCode = """
+Core.buildGUI('{jsDivId}','notebook').then(h => h.openRootFile('{fileUrl}'));
+"""
+
+
+_jsBrowseFileCode = """
+const binaryString = atob('{fileBase64}');
+const bytes = new Uint8Array(binaryString.length);
+for (let i = 0; i < binaryString.length; i++)
+ bytes[i] = binaryString.charCodeAt(i);
+Core.buildGUI('{jsDivId}','notebook').then(h => h.openRootFile(bytes.buffer));
+"""
+
+_jsCode = """
+{jsDivHtml}
+
"""
+
+_jsFileCode = """
+
+
+"""
+
+
+
TBufferJSONErrorMessage = "The TBufferJSON class is necessary for JS visualisation to work and cannot be found. Did you enable the http module (-D http=ON for CMake)?"
@@ -113,7 +188,7 @@ def RCanvasAvailable():
return True
-def _initializeJSVis():
+def initializeJSVis():
global _enableJSVis
jupyter_jsroot = ROOT.gEnv.GetValue("Jupyter.JSRoot", "on").lower()
if jupyter_jsroot not in {"on", "off"}:
@@ -122,42 +197,49 @@ def _initializeJSVis():
_enableJSVis = jupyter_jsroot == "on"
-_initializeJSVis()
-_enableJSVisDebug = False
-
-
-def enableJSVis():
- if not TBufferJSONAvailable():
- return
+def enableJSVis(flag=True):
global _enableJSVis
- _enableJSVis = True
+ _enableJSVis = flag and TBufferJSONAvailable()
-def disableJSVis():
- global _enableJSVis
- _enableJSVis = False
-
+def addVisualObject(object, kind="none", option=""):
+ global _visualObjects
+ if kind == "url":
+ _visualObjects.append(NotebookDrawerUrl(object))
+ elif kind == "tfile":
+ _visualObjects.append(NotebookDrawerFile(object, option))
+ else:
+ _visualObjects.append(NotebookDrawerJson(object))
-def enableJSVisDebug():
- if not TBufferJSONAvailable():
- return
- global _enableJSVis
- global _enableJSVisDebug
- _enableJSVis = True
- _enableJSVisDebug = True
+def browseRootFile(fname, force=False):
+ if not fname:
+ return False
-def disableJSVisDebug():
- global _enableJSVis
- global _enableJSVisDebug
- _enableJSVis = False
- _enableJSVisDebug = False
+ if not force and (fname.startswith("https://") or fname.startswith("http://")):
+ addVisualObject(fname, "url")
+ return True
+ addVisualObject(fname, "tfile", force)
+ return False
def _getPlatform():
return sys.platform
+def _getUniqueDivId():
+ """
+ Every DIV containing a JavaScript snippet must be unique in the
+ notebook. This method provides a unique identifier.
+ With the introduction of JupyterLab, multiple Notebooks can exist
+ simultaneously on the same HTML page. In order to ensure a unique
+ identifier with the UID throughout all open Notebooks the UID is
+ generated as a timestamp.
+ """
+ return "root_plot_" + str(int(round(time.time() * 1000)))
+
+
+
def _getLibExtension(thePlatform):
"""Return appropriate file extension for a shared library
>>> _getLibExtension('darwin')
@@ -306,61 +388,6 @@ def invokeAclic(cell):
processCppCode(".L %s+" % fileName)
-def produceCanvasJson(canvas):
- if canvas.IsUpdated() and not canvas.IsDrawn():
- canvas.Draw()
-
- if TWebCanvasAvailable():
- return ROOT.TWebCanvas.CreateCanvasJSON(canvas, 23, True)
-
- # Add extra primitives to canvas with custom colors, palette, gStyle
-
- prim = canvas.GetListOfPrimitives()
-
- style = ROOT.gStyle
- colors = ROOT.gROOT.GetListOfColors()
- palette = None
-
- # always provide gStyle object
- if prim.FindObject(style):
- style = None
- else:
- prim.Add(style)
-
- cnt = 0
- for n in range(colors.GetLast() + 1):
- if colors.At(n):
- cnt = cnt + 1
-
- # add all colors if there are more than 598 colors defined
- if cnt < 599 or prim.FindObject(colors):
- colors = None
- else:
- prim.Add(colors)
-
- if colors:
- pal = ROOT.TColor.GetPalette()
- palette = ROOT.TObjArray()
- palette.SetName("CurrentColorPalette")
- for i in range(pal.GetSize()):
- palette.Add(colors.At(pal[i]))
- prim.Add(palette)
-
- ROOT.TColor.DefinedColors()
-
- canvas_json = ROOT.TBufferJSON.ConvertToJSON(canvas, 23)
-
- # Cleanup primitives after conversion
- if style is not None:
- prim.Remove(style)
- if colors is not None:
- prim.Remove(colors)
- if palette is not None:
- prim.Remove(palette)
-
- return canvas_json
-
-
transformers = []
@@ -439,61 +466,43 @@ def __del__(self):
def GetCanvasDrawers():
lOfC = ROOT.gROOT.GetListOfCanvases()
- return [NotebookDrawer(can) for can in lOfC if can.IsDrawn() or can.IsUpdated()]
+ return [NotebookDrawerTCanvas(can) for can in lOfC if can.IsDrawn() or can.IsUpdated()]
def GetRCanvasDrawers():
if not RCanvasAvailable():
return []
lOfC = ROOT.Experimental.RCanvas.GetCanvases()
- return [NotebookDrawer(can.__smartptr__().get()) for can in lOfC if can.IsShown() or can.IsUpdated()]
+ return [NotebookDrawerRCanvas(can.__smartptr__().get()) for can in lOfC if can.IsShown() or can.IsUpdated()]
+def GetVisualDrawers():
+ global _visualObjects
+ res = [obj for obj in _visualObjects]
+ _visualObjects.clear()
+ return res
-def GetGeometryDrawer():
+def GetGeometryDrawers():
if not hasattr(ROOT, "gGeoManager"):
- return
+ return []
if not ROOT.gGeoManager:
- return
- if not ROOT.gGeoManager.GetUserPaintVolume():
- return
- vol = ROOT.gGeoManager.GetTopVolume()
- if vol:
- return NotebookDrawer(vol)
+ return []
+ vol = ROOT.gGeoManager.GetUserPaintVolume()
+ if not vol:
+ return []
+ return [NotebookDrawerGeometry(vol)]
def GetDrawers():
- drawers = GetCanvasDrawers() + GetRCanvasDrawers()
- geometryDrawer = GetGeometryDrawer()
- if geometryDrawer:
- drawers.append(geometryDrawer)
- return drawers
-
-
-def DrawGeometry():
- drawer = GetGeometryDrawer()
- if drawer:
- drawer.Draw()
-
-
-def DrawCanvases():
- drawers = GetCanvasDrawers()
- for drawer in drawers:
- drawer.Draw()
-
-
-def DrawRCanvases():
- rdrawers = GetRCanvasDrawers()
- for drawer in rdrawers:
- drawer.Draw()
+ return GetCanvasDrawers() + GetRCanvasDrawers() + GetVisualDrawers() + GetGeometryDrawers()
def NotebookDraw():
- DrawGeometry()
- DrawCanvases()
- DrawRCanvases()
+ drawers = GetDrawers()
+ for drawer in drawers:
+ drawer.Draw(display.display)
-class CaptureDrawnPrimitives(object):
+class CaptureDrawnPrimitives:
"""
Capture the canvas which is drawn to display it.
"""
@@ -508,68 +517,266 @@ def register(self):
self.shell.events.register("post_execute", self._post_execute)
-class NotebookDrawer(object):
+
+class NotebookDrawerUrl:
+ """
+ Special drawer for file URL - JSROOT uses url to load file directly, without root
+ """
+
+ def __init__(self, theUrl):
+ self.drawUrl = theUrl
+
+ def _getUrlJsCode(self):
+
+ id = _getUniqueDivId()
+
+ drawHtml = _jsFullWidthDiv.format(
+ jsDivId=id,
+ jsCanvasHeight=_jsCanvasHeight
+ )
+
+ browseUrlCode = _jsBrowseUrlCode.format(
+ jsDivId=id,
+ fileUrl=self.drawUrl
+ )
+
+ thisJsCode = _jsCode.format(
+ jsDivId=id,
+ jsDivHtml=drawHtml,
+ jsDrawCode=browseUrlCode
+ )
+
+ return thisJsCode
+
+ def Draw(self, displayFunction):
+ code = self._getUrlJsCode()
+ displayFunction(display.HTML(code))
+
+
+
+class NotebookDrawerFile:
+ """
+ Special drawer for TFile - shows files hierarchy with possibility to draw objects
+ """
+
+ def __init__(self, theFileName, theForce=False):
+ self.drawFileName = theFileName
+ self.drawForce = theForce
+
+ def _getFileJsCode(self):
+ base64 = ""
+
+ with ROOT.TDirectory.TContext(), ROOT.TFile.Open(self.drawFileName) as f:
+ if not f:
+ return f"Fail to open file {self.drawFileName}"
+
+ if f.GetVersion() < 30000:
+ return f"File version {f.GetVersion()} is too old and not supported by JSROOT"
+
+ sz = f.GetSize()
+ if sz > 300000000:
+ return f"File size {sz} is too large for JSROOT display."
+ if sz > 10000000 and not self.drawForce:
+ return f"File size {sz} is large for JSROOT display. Use '-f' flag like '%rootbrowse {self.drawFileName} -f' to show file nevertheless"
+
+ # create plain buffer and get pointer on it
+ u_buffer = (ctypes.c_ubyte * sz)(*range(sz))
+ addrc = ctypes.cast(ctypes.pointer(u_buffer), ctypes.c_char_p)
+
+ if f.ReadBuffer(addrc, 0, sz):
+ return f"Fail to read file {self.drawFileName} buffer of size {sz}"
+
+ base64 = ROOT.TBase64.Encode(addrc, sz)
+
+ id = _getUniqueDivId()
+
+ drawHtml = _jsFullWidthDiv.format(
+ jsDivId=id,
+ jsCanvasHeight=_jsCanvasHeight
+ )
+
+ browseFileCode = _jsBrowseFileCode.format(
+ jsDivId=id,
+ fileBase64=base64
+ )
+
+ thisJsCode = _jsCode.format(
+ jsDivId=id,
+ jsDivHtml=drawHtml,
+ jsDrawCode=browseFileCode
+ )
+
+ return thisJsCode
+
+ def Draw(self, displayFunction):
+ code = self._getFileJsCode()
+ displayFunction(display.HTML(code))
+
+
+
+class NotebookDrawerJson:
"""
- Capture the canvas which is drawn and decide if it should be displayed using
- jsROOT.
+ Generic class to create JSROOT drawing for the arbitrary object based on json conversion.
"""
def __init__(self, theObject):
- self.drawableObject = theObject
- self.isRCanvas = False
- self.isCanvas = False
- self.drawableId = str(ROOT.AddressOf(theObject)[0])
- if hasattr(self.drawableObject, "ResolveSharedPtrs"):
- self.isRCanvas = True
+ self.drawObject = theObject
+
+ def _canJsDisplay(self):
+ return TBufferJSONAvailable()
+
+ def _canPngDisplay(self):
+ return True
+
+ def _getWidth(self):
+ return _jsCanvasWidth
+
+ def _getHeight(self):
+ return _jsCanvasHeight
+
+ def _getJson(self):
+ return ROOT.TBufferJSON.ConvertToJSON(self.drawObject, 23).Data()
+
+ def _getJsOptions(self):
+ return ""
+
+ def _getJsCode(self):
+ width = self._getWidth()
+ height = self._getHeight()
+ json = self._getJson()
+ options = self._getJsOptions()
+
+ if not json:
+ return f"Class {self.drawObject.ClassName()} not supported yet"
+
+ zip = ROOT.TBufferJSON.zipJSON(json)
+
+ id = _getUniqueDivId()
+
+ drawHtml = _jsFixedSizeDiv.format(
+ jsDivId=id,
+ jsCanvasWidth=width,
+ jsCanvasHeight=height
+ )
+
+ drawJsonCode = _jsDrawJsonCode.format(
+ jsDivId=id,
+ jsonLength=len(json),
+ jsonZip=zip,
+ jsDrawOptions=options
+ )
+
+ thisJsCode = _jsCode.format(
+ jsDivId=id,
+ jsDivHtml=drawHtml,
+ jsDrawCode=drawJsonCode
+ )
+ return thisJsCode
+
+ def _getCanvas(self):
+ gPadSave = ROOT.gPad
+ c1 = ROOT.TCanvas("__tmp_draw_image_canvas__", "", self._getWidth(), self._getHeight())
+ c1.Add(self.drawObject)
+ ROOT.gPad = gPadSave
+ return c1
+
+ def _getPngImage(self):
+ ofile = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
+ canv = self._getCanvas()
+ with _setIgnoreLevel(ROOT.kError):
+ canv.SaveAs(ofile.name)
+ img = display.Image(filename=ofile.name, format="png", embed=True)
+ ofile.close()
+ os.unlink(ofile.name)
+ return img
+
+
+ def Draw(self, displayFunction):
+ global _enableJSVis
+ if _enableJSVis and self._canJsDisplay():
+ code = self._getJsCode()
+ displayFunction(display.HTML(code))
+ elif self._canPngDisplay():
+ displayFunction(self._getPngImage())
else:
- self.isCanvas = self.drawableObject.ClassName() == "TCanvas"
+ displayFunction(display.HTML(f"Neither JSROOT nor plain drawing of {self.drawObject.ClassName()} class is not implemented"))
+
+
+
+class NotebookDrawerGeometry(NotebookDrawerJson):
+ """
+ Drawer for geometry - png output is not supported.
+ """
def __del__(self):
- if self.isRCanvas:
- self.drawableObject.ClearShown()
- self.drawableObject.ClearUpdated()
- elif self.isCanvas:
- self.drawableObject.ResetDrawn()
- self.drawableObject.ResetUpdated()
+ self.drawObject.GetGeoManager().SetUserPaintVolume(ROOT.nullptr)
+
+ def _getJsOptions(self):
+ return "all"
+
+ def _canPngDisplay(self):
+ return False
+
+
+
+class NotebookDrawerCanvBase(NotebookDrawerJson):
+ """
+ Base class for TCanvas/RCanvas drawing.
+ Implements a specific Draw function where canvas updating is handled
+ """
+
+ def _getCanvasId(self):
+ return ""
+
+ def _getUpdated(self):
+ return False
+
+ def _getCanvas(self):
+ return self.drawObject
+
+ def Draw(self, displayFunction):
+ global _enableJSVis, _canvasHandles
+ code = ""
+ if _enableJSVis and self._canJsDisplay():
+ code = display.HTML(self._getJsCode())
+ elif self._canPngDisplay():
+ code = self._getPngImage()
+ else:
+ code = display.HTML(f"Neither JSROOT nor plain drawing of {self.drawObject.ClassName()} is implemented")
+
+ name = self._getCanvasId()
+ updated = self._getUpdated()
+ if updated and name and (name in _canvasHandles):
+ _canvasHandles[name].update(code)
+ elif name:
+ _canvasHandles[name] = displayFunction(code, display_id=True)
else:
- ROOT.gGeoManager.SetUserPaintVolume(None)
-
- def _getListOfPrimitivesNamesAndTypes(self):
- """
- Get the list of primitives in the pad, recursively descending into
- histograms and graphs looking for fitted functions.
- """
- primitives = self.drawableObject.GetListOfPrimitives()
- primitivesNames = map(lambda p: p.ClassName(), primitives)
- return sorted(primitivesNames)
-
- def _getUniqueDivId(self):
- """
- Every DIV containing a JavaScript snippet must be unique in the
- notebook. This method provides a unique identifier.
- With the introduction of JupyterLab, multiple Notebooks can exist
- simultaneously on the same HTML page. In order to ensure a unique
- identifier with the UID throughout all open Notebooks the UID is
- generated as a timestamp.
- """
- return "root_plot_" + str(int(round(time.time() * 1000)))
+ displayFunction(code)
+
+
+
+class NotebookDrawerTCanvas(NotebookDrawerCanvBase):
+ """
+ Drawer of TCanvas.
+ """
+
+ def __del__(self):
+ self.drawObject.ResetDrawn()
+ self.drawObject.ResetUpdated()
+
+ def _getCanvasId(self):
+ return self.drawObject.GetName() + str(ROOT.AddressOf(self.drawObject)[0])
+
+ def _getUpdated(self):
+ return self.drawObject.IsUpdated()
def _canJsDisplay(self):
- # returns true if js-based drawing should be used
if not TBufferJSONAvailable():
return False
- # RCanvas clways displayed with jsroot
- if self.isRCanvas:
- return True
- # check if jsroot was disabled
- if not _enableJSVis:
- return False
- # geometry can be drawn, TWebCanvas also can be used
- if not self.isCanvas or TWebCanvasAvailable():
+ if TWebCanvasAvailable():
return True
-
- # to be optimised
- primitivesTypesNames = self._getListOfPrimitivesNamesAndTypes()
+ primitives = self.drawObject.GetListOfPrimitives()
+ primitivesTypesNames = sorted(map(lambda p: p.ClassName(), primitives))
for unsupportedPattern in _jsNotDrawableClassesPatterns:
for primitiveTypeName in primitivesTypesNames:
if fnmatch.fnmatch(primitiveTypeName, unsupportedPattern):
@@ -581,118 +788,100 @@ def _canJsDisplay(self):
return False
return True
- def _getJsCode(self):
- # produce JSON for the canvas
- if self.isRCanvas:
- json = self.drawableObject.CreateJSON()
- else:
- json = produceCanvasJson(self.drawableObject).Data()
+ def _getWidth(self):
+ if self.drawObject.GetWindowWidth() > 0:
+ return self.drawObject.GetWindowWidth()
+ return _jsCanvasWidth
- divId = self._getUniqueDivId()
+ def _getHeight(self):
+ if self.drawObject.GetWindowHeight() > 0:
+ return self.drawObject.GetWindowHeight()
+ return _jsCanvasHeight
- width = _jsCanvasWidth
- height = _jsCanvasHeight
- jsonzip = ROOT.TBufferJSON.zipJSON(json)
- options = "all"
+ def _getJson(self):
+ if self.drawObject.IsUpdated() and not self.drawObject.IsDrawn():
+ self.drawObject.Draw()
- if self.isCanvas:
- if self.drawableObject.GetWindowWidth() > 0:
- width = self.drawableObject.GetWindowWidth()
- if self.drawableObject.GetWindowHeight() > 0:
- height = self.drawableObject.GetWindowHeight()
- options = ""
+ if TWebCanvasAvailable():
+ return ROOT.TWebCanvas.CreateCanvasJSON(self.drawObject, 23, True).Data()
- if self.isRCanvas:
- if self.drawableObject.GetWidth() > 0:
- width = self.drawableObject.GetWidth()
- if self.drawableObject.GetHeight() > 0:
- height = self.drawableObject.GetHeight()
- options = ""
+ # Add extra primitives to canvas with custom colors, palette, gStyle
- thisJsCode = _jsCode.format(
- jsCanvasWidth=width,
- jsCanvasHeight=height,
- jsonLength=len(json),
- jsonZip=jsonzip,
- jsDrawOptions=options,
- jsDivId=divId,
- )
- return thisJsCode
+ prim = self.drawObject.GetListOfPrimitives()
- def _getJsDiv(self):
- return display.HTML(self._getJsCode())
+ style = ROOT.gStyle
+ colors = ROOT.gROOT.GetListOfColors()
+ palette = None
- def _getDrawId(self):
- if self.isCanvas:
- return self.drawableObject.GetName() + self.drawableId
- if self.isRCanvas:
- return self.drawableObject.GetUID()
- # all other objects do not support update and can be ignored
- return ""
+ # always provide gStyle object
+ if prim.FindObject(style):
+ style = None
+ else:
+ prim.Add(style)
- def _getUpdated(self):
- if self.isCanvas:
- return self.drawableObject.IsUpdated()
- if self.isRCanvas:
- return self.drawableObject.IsUpdated()
- return False
+ cnt = 0
+ for n in range(colors.GetLast() + 1):
+ if colors.At(n):
+ cnt = cnt + 1
- def _jsDisplay(self):
- global _canvasHandles
- name = self._getDrawId()
- updated = self._getUpdated()
- jsdiv = self._getJsDiv()
- if name and (name in _canvasHandles) and updated:
- _canvasHandles[name].update(jsdiv)
- elif name:
- _canvasHandles[name] = display.display(jsdiv, display_id=True)
+ # add all colors if there are more than 598 colors defined
+ if cnt < 599 or prim.FindObject(colors):
+ colors = None
else:
- display.display(jsdiv)
- return 0
+ prim.Add(colors)
- def _getPngImage(self):
- ofile = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
- with _setIgnoreLevel(ROOT.kError):
- self.drawableObject.SaveAs(ofile.name)
- img = display.Image(filename=ofile.name, format="png", embed=True)
- ofile.close()
- os.unlink(ofile.name)
- return img
+ if colors:
+ pal = ROOT.TColor.GetPalette()
+ palette = ROOT.TObjArray()
+ palette.SetName("CurrentColorPalette")
+ for i in range(pal.GetSize()):
+ palette.Add(colors.At(pal[i]))
+ prim.Add(palette)
- def _pngDisplay(self):
- global _canvasHandles
- name = self._getDrawId()
- updated = self._getUpdated()
- img = self._getPngImage()
- if updated and name and (name in _canvasHandles):
- _canvasHandles[name].update(img)
- elif name:
- _canvasHandles[name] = display.display(img, display_id=True)
- else:
- display.display(img)
+ # reset global flag to avoid automatic colors storage by canvas
+ ROOT.TColor.DefinedColors()
- def _display(self):
- if _enableJSVisDebug:
- self._pngDisplay()
- self._jsDisplay()
- else:
- if self._canJsDisplay():
- self._jsDisplay()
- else:
- self._pngDisplay()
+ canvas_json = ROOT.TBufferJSON.ConvertToJSON(self.drawObject, 23)
- def GetDrawableObjects(self):
- if _enableJSVisDebug:
- return [self._getJsDiv(), self._getPngImage()]
+ # Cleanup primitives after conversion
+ if style is not None:
+ prim.Remove(style)
+ if colors is not None:
+ prim.Remove(colors)
+ if palette is not None:
+ prim.Remove(palette)
- if self._canJsDisplay():
- return [self._getJsDiv()]
- else:
- return [self._getPngImage()]
+ return canvas_json.Data()
+
+
+class NotebookDrawerRCanvas(NotebookDrawerCanvBase):
+ """
+ Drawer of RCanvas.
+ """
+
+ def __del__(self):
+ self.drawObject.ClearShown()
+ self.drawObject.ClearUpdated()
+
+ def _getCanvasId(self):
+ return self.drawObject.GetUID()
+
+ def _getUpdated(self):
+ return self.drawObject.IsUpdated()
+
+ def _getWidth(self):
+ if self.drawObject.GetWidth() > 0:
+ return self.drawObject.GetWidth()
+ return _jsCanvasWidth
+
+ def _getHeight(self):
+ if self.drawObject.GetHeight() > 0:
+ return self.drawObject.GetHeight()
+ return _jsCanvasHeight
+
+ def _getJson(self):
+ return self.drawObject.CreateJSON()
- def Draw(self):
- self._display()
- return 0
def setStyle():
@@ -705,7 +894,7 @@ def setStyle():
def loadMagicsAndCapturers():
global captures
- extNames = ["ROOT._jupyroot.magics." + name for name in ["cppmagic", "jsrootmagic"]]
+ extNames = ["ROOT._jupyroot.magics." + name for name in ["cppmagic", "jsrootmagic", "rootbrowsemagic"]]
ip = get_ipython()
extMgr = ExtensionManager(ip)
for extName in extNames:
@@ -729,9 +918,6 @@ def declareProcessLineWrapper():
def enhanceROOTModule():
ROOT.enableJSVis = enableJSVis
- ROOT.disableJSVis = disableJSVis
- ROOT.enableJSVisDebug = enableJSVisDebug
- ROOT.disableJSVisDebug = disableJSVisDebug
def enableCppHighlighting():
@@ -742,6 +928,7 @@ def enableCppHighlighting():
def iPythonize():
setStyle()
+ initializeJSVis()
loadMagicsAndCapturers()
declareProcessLineWrapper()
# enableCppHighlighting()
diff --git a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/magics/jsrootmagic.py b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/magics/jsrootmagic.py
index d4e172f979bfa..d2db11722b921 100644
--- a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/magics/jsrootmagic.py
+++ b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/magics/jsrootmagic.py
@@ -17,9 +17,7 @@
from ROOT._jupyroot.helpers.utils import (
TBufferJSONAvailable,
TBufferJSONErrorMessage,
- disableJSVis,
- enableJSVis,
- enableJSVisDebug,
+ enableJSVis
)
@@ -32,12 +30,9 @@ def line_jsroot(self, args):
"""Change the visualisation of plots from images to interactive JavaScript objects."""
if args == "on" or args == "":
self.printErrorIfNeeded()
- enableJSVis()
+ enableJSVis(True)
elif args == "off":
- disableJSVis()
- elif args == "debug":
- self.printErrorIfNeeded()
- enableJSVisDebug()
+ enableJSVis(False)
def printErrorIfNeeded(self):
if not TBufferJSONAvailable():
diff --git a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/magics/rootbrowsemagic.py b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/magics/rootbrowsemagic.py
new file mode 100644
index 0000000000000..37e040bab1b84
--- /dev/null
+++ b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/magics/rootbrowsemagic.py
@@ -0,0 +1,40 @@
+# -*- coding:utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2016, ROOT Team.
+# Authors: Danilo Piparo CERN
+# -----------------------------------------------------------------------------
+
+################################################################################
+# Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. #
+# All rights reserved. #
+# #
+# For the licensing terms see $ROOTSYS/LICENSE. #
+# For the list of contributors see $ROOTSYS/README/CREDITS. #
+################################################################################
+
+from metakernel import Magic, option
+
+from ROOT._jupyroot.helpers.utils import browseRootFile
+
+
+class RootBrowseMagics(Magic):
+ def __init__(self, kernel):
+ super(RootBrowseMagics, self).__init__(kernel)
+
+ @option("arg", default="", help="Show JSROOT browser with file content")
+ @option(
+ "-f", "--force",
+ action="store_true",
+ default=False,
+ help="Force opening of large files"
+ )
+ def line_rootbrowse(self, arg, force):
+ """Open file and start browser."""
+ if not browseRootFile(arg, force):
+ self.kernel.Error(f"Not able to open file {arg}")
+ else:
+ self.kernel.do_display()
+
+
+def register_magics(kernel):
+ kernel.register_magics(RootBrowseMagics)
diff --git a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/rootkernel.py b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/rootkernel.py
index 274fe27042def..6b3b9deee9280 100644
--- a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/rootkernel.py
+++ b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/kernel/rootkernel.py
@@ -80,6 +80,9 @@ def print_output(self, handler):
for streamDict in filter(lambda d: d is not None, streamDicts):
self.send_response(self.iopub_socket, "stream", streamDict)
+ def do_display(self):
+ Display(self.Displayer, self.Display)
+
def do_execute_direct(self, code, silent=False):
if not code.strip():
return
@@ -88,7 +91,7 @@ def do_execute_direct(self, code, silent=False):
try:
RunAsyncAndPrint(self.Executor, code, self.ioHandler, self.print_output, silent, 0.1)
- Display(self.Displayer, self.Display)
+ self.do_display()
except KeyboardInterrupt:
ROOT.gROOT.SetInterrupt()
diff --git a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/magics/jsrootmagic.py b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/magics/jsrootmagic.py
index 05851b03b90b5..9058c9c059d0d 100644
--- a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/magics/jsrootmagic.py
+++ b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/magics/jsrootmagic.py
@@ -15,7 +15,7 @@
from IPython.core.magic import Magics, line_magic, magics_class
from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
-from ROOT._jupyroot.helpers.utils import disableJSVis, enableJSVis, enableJSVisDebug
+from ROOT._jupyroot.helpers.utils import enableJSVis
@magics_class
@@ -35,11 +35,9 @@ def jsroot(self, line):
"""Change the visualisation of plots from images to interactive JavaScript objects."""
args = parse_argstring(self.jsroot, line)
if args.arg == "on":
- enableJSVis()
+ enableJSVis(True)
elif args.arg == "off":
- disableJSVis()
- elif args.arg == "debug":
- enableJSVisDebug()
+ enableJSVis(False)
def load_ipython_extension(ipython):
diff --git a/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/magics/rootbrowsemagic.py b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/magics/rootbrowsemagic.py
new file mode 100644
index 0000000000000..a11ae7c212ac9
--- /dev/null
+++ b/bindings/pyroot/pythonizations/python/ROOT/_jupyroot/magics/rootbrowsemagic.py
@@ -0,0 +1,51 @@
+# -*- coding:utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2026, ROOT Team.
+# Authors: Sergey Linev GSI
+# -----------------------------------------------------------------------------
+
+################################################################################
+# Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. #
+# All rights reserved. #
+# #
+# For the licensing terms see $ROOTSYS/LICENSE. #
+# For the list of contributors see $ROOTSYS/README/CREDITS. #
+################################################################################
+
+from IPython.core.magic import Magics, line_magic, magics_class
+from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
+
+from ROOT._jupyroot.helpers.utils import browseRootFile
+
+
+@magics_class
+class RootBrowseMagics(Magics):
+ def __init__(self, shell):
+ super(RootBrowseMagics, self).__init__(shell)
+
+ @line_magic
+ @magic_arguments()
+ @argument(
+ "arg",
+ nargs="?",
+ default="",
+ help="Open and browse ROOT file",
+ )
+ @argument(
+ "--force",
+ "-f",
+ action="store_true",
+ help="Force opening of large files"
+ )
+ def rootbrowse(self, line):
+ """start root browser."""
+ args = parse_argstring(self.rootbrowse, line)
+ if not args:
+ print("Provide ROOT file name")
+ else:
+ browseRootFile(args.arg, args.force)
+
+
+
+def load_ipython_extension(ipython):
+ ipython.register_magics(RootBrowseMagics)