How to correctly display TCanvas in Jupyter notebook


ROOT Version: 6.36.02
Platform: WSL2 (unbuntu 22.04)
Python Version: 3.10.12


I’m using Jupyter Notebook to draw and adjust figures. But I find there is some problem to display TCanvas in the output region under input cell.

I create a global TCanvas,

c1=ROOT.TCanvas("c1","c1",800,600)

and draw objects on this canvas to get my figures.

I also define some functions to control the draw options

import ROOT
import math
from ctypes import c_double
import numpy as np
import pandas as pd
from IPython.display import clear_output

from ipytools import *

def draw_phaseshift(timedata, xtit="E_{c.m.} (MeV)", ytit="#delta_{0} (degree)", yrange=[],xrange=[],legshow=True,grid=True,ticks=True):
 c1.cd()
 if grid:
  ROOT.gPad.SetGrid()
 if ticks:
  ROOT.gPad.SetTicks()

 setcolors(timedata.mg,color,Alpha=timedata.Alpha)
 timedata.resetLeg()

 mg=timedata.mg.Clone()
 #mg=timedata.getiGraph(5)
 leg=timedata.leg.Clone()
 mg.GetYaxis().SetTitle(ytit)
 mg.GetYaxis().SetTitleOffset(1.)
 mg.GetYaxis().SetTitleSize(0.06)
 mg.GetYaxis().SetLabelSize(0.06)
 mg.GetYaxis().SetNdivisions(505)
 if len(yrange) == 2:
  mg.GetYaxis().SetRangeUser(yrange[0],yrange[1])
 mg.GetXaxis().SetTitle(xtit)
 mg.GetXaxis().SetTitleSize(0.06)
 mg.GetXaxis().SetLabelSize(0.06)
 mg.GetXaxis().SetTitleOffset(1.)
 if len(xrange) == 2:
  mg.GetXaxis().SetRangeUser(xrange[0],xrange[1])
 else:
  mg.GetXaxis().SetRangeUser(0,50)
 if (timedata.drawoption=="AC3"):
  mg.Draw("ACX")
  mg.Draw("3")
 else:
  mg.Draw(str(timedata.drawoption))
 if legshow:
  leg.Draw("same")
 c1.Draw()

Where timedata is a C++ class, used for store my data

when I run the following cell

%jsroot on
tstart=16
tend=18
t_list=[18,16,17]
ifmisnered=1
spin=2
if (ifmisnered==1):
 sub="_misnered"
else:
 sub=""
labels=["a", "b"]

timedata=ROOT.timedata(f"output/aveset/pot_{spin*2+1}s{spin}_cen_FconfNOmgccc_t%03d_B01_1600conf_080bin{sub}_observables.root",t_list,"phase_shift_nocoul",1/math.sqrt(2*m_red),180/3.1415926535897, "AC3")
#timedata=ROOT.timedata("output/set1/pot_3s1_cen_FconfNOmgccc_t%03d_1600conf_080bin_observables.root",16,18,"phase_shift_nocoul",1/math.sqrt(2*m_red),180/3.1415926535897, "AC4")
timedata.setFormula("x*x")
#timedata.leg.SetX1(0.63)
timedata.leg.SetY1(0.2)
#timedata.leg.SetX2(0.93)
timedata.leg.SetY2(0.5)
draw_phaseshift(timedata,yrange=[0,33],legshow=False)
leg2 = ROOT.TLegend(timedata.leg.GetX1(), timedata.leg.GetY1(), timedata.leg.GetX2(), timedata.leg.GetY2())
t_list_index=[[i,t_list[i]] for i,t in enumerate(t_list)]
for i,t in sorted(t_list_index, key=lambda x: x[1], reverse=True):
  leg2.AddEntry(timedata.mg.GetListOfGraphs().At(i), f"t/a = {t}", "fl")
if spin==1:
  leg2.Draw("same")
#timedata.mg.Draw("p same")

#add a label
Label=ROOT.TLatex(0.17,0.78, f"({labels[spin-1]}) {{}}^{{{2*spin+1}}}S_{{{spin}}}")
Label.SetTextSize(0.06)
Label.SetNDC()
Label.Draw()

the canvas can display normally under the input cell

But when I run this cell for second time, the canvas disappared.

What should I do to correctly display canvas? Is my data too large?

Following are some defination of the class

class graphdata
{
public:
  TMultiGraph *mg = nullptr;
  int ngraphs = 0;
  TGraphErrors *aveGraph = nullptr;
  TLegend *leg = new TLegend(0.6, 0.4, 0.85, 0.65);

  double a_x = 1;
  double a_y = 1;
  TF1 *f_x = new TF1("f_x", "x", 0, 100);

  int Color = kAzure;
  int Pallete = EColorPalette::kLightTemperature;
  double Alpha = 0.3;
  int MarkerStyle = 20;
  double MarkerSize = 0.5;
  string drawoption = "AP";

  graphdata();
  graphdata(double a_x, double a_y);
  graphdata(double a_x, double a_y, string drawoption);
  graphdata(const graphdata &other);
  ~graphdata();

  TLegend *resetLeg(vector<double> Locate={0.6, 0.4, 0.85, 0.65}, double TextSize = 0.05, int TextFont = 42, string suffix = "");
...
}
class timedata : public graphdata
{
public:
  timedata(string filename_model, int time_start, int time_end, string plotname, string drawopt = "AP");
  timedata(string filename_model, int time_start, int time_end, string plotname, double a_x, double a_y, string drawopt = "AP");
  timedata(string filename_model, vector<int> t_list, string plotname, string drawopt = "AP");
  timedata(string filename_model, vector<int> t_list, string plotname, double a_x, double a_y, string drawopt = "AP");
  timedata(vector<jkdata> &datalist, vector<int> t_list, string plotname, string drawopt = "AP");
  timedata(vector<jkdata> &datalist, vector<int> t_list, string plotname, double a_x, double a_y, string drawopt = "AP");
  ~timedata();
....
}

Hello @asdw159632,

@linev will most likely know why the canvas disappears.

Hi,

Can you attach create notebook file with .ipynb extension - may be in zipped format?

Regards,
Sergey

Also for debug purposes you can add c1.ls() command at the end of your cell.
This should show list of objects which has to be drawn by the canvas.
In some cases Python overrules ROOT ownership and removes objects from the canvas list of primitives.

Hi,

Thank you for your quick reply.

Sure, Here is the minimum run example for my problem:

github: asdw159632/canvas_problem

P.S. It seems that I’m a new user and can’t attach a link, so I only put a repository path from my github.

Best wishes,

Liang

Hi linev,

There are c1.ls() output for the first run and second run

Can you store there already executed canvas cells.
They will include JSON data for painting and I will be able to reproduce problem - if it appears on JSROOT side.

Your c1.ls() data looks ok.

Sure,

Here is the first time I draw canvas

{
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Canvas Name=c Title=c Option=\n",
      " TCanvas fXlowNDC=0 fYlowNDC=0 fWNDC=1 fHNDC=1 Name= c Title= c Option=\n",
      "  OBJ: TList\tTList\tDoubly linked list : 0\n",
      "   TFrame  X1= -2.410596 Y1=0.000000 X2=50.846663 Y2=33.000000\n",
      "   OBJ: TMultiGraph\toutput-for-test/pot_5s2_cen_FconfNOmgccc_t%03d_B01_1600conf_080bin_misnered_observables.root_phase_shift_nocoul_mg_timedata\t : 0 at: 0x64e639eef520\n",
      "   OBJ: TMultiGraph\toutput-for-test/pot_5s2_cen_FconfNOmgccc_t%03d_B01_1600conf_080bin_misnered_observables.root_phase_shift_nocoul_mg_timedata\t : 0 at: 0x64e639eef520\n",
      "   Text  X=0.170000 Y=0.780000 Text=(b) {}^{5}S_{2}\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "\n",
       "<div id=\"root_plot_1758545678617\" style=\"width: 800px; height: 600px; position: relative\">\n",
       "</div>\n",
       "\n",
       "<script>\n",
       "\n",
       "function display_root_plot_1758545678617(Core) {\n",
       "   let obj = Core.parse({\"_typename\":\"TCanvasWebSnapshot\",\"fUniqueID\":0..."
       "   Core.settings.HandleKeys = false;\n",
       "   Core.draw(\"root_plot_1758545678617\", obj, \"\");\n",
       "}\n",
       "\n",
       "function script_load_root_plot_1758545678617(src, on_error) {\n",
       "    let script = document.createElement('script');\n",
       "    script.src = src;\n",
       "    script.onload = function() { display_root_plot_1758545678617(JSROOT); };\n",
       "    script.onerror = function() { script.remove(); on_error(); };\n",
       "    document.head.appendChild(script);\n",
       "}\n",
       "\n",
       "if (typeof requirejs !== 'undefined') {\n",
       "\n",
       "    // We are in jupyter notebooks, use require.js which should be configured already\n",
       "    requirejs.config({\n",
       "       paths: { 'JSRootCore' : [ 'build/jsroot', 'https://root.cern/js/7.9.0/build/jsroot', 'https://jsroot.gsi.de/7.9.0/build/jsroot' ] }\n",
       "    })(['JSRootCore'],  function(Core) {\n",
       "       display_root_plot_1758545678617(Core);\n",
       "    });\n",
       "\n",
       "} else if (typeof JSROOT !== 'undefined') {\n",
       "\n",
       "   // JSROOT already loaded, just use it\n",
       "   display_root_plot_1758545678617(JSROOT);\n",
       "\n",
       "} else {\n",
       "\n",
       "    // We are in jupyterlab without require.js, directly loading jsroot\n",
       "    // Jupyterlab might be installed in a different base_url so we need to know it.\n",
       "    try {\n",
       "        var base_url = JSON.parse(document.getElementById('jupyter-config-data').innerHTML).baseUrl;\n",
       "    } catch(_) {\n",
       "        var base_url = '/';\n",
       "    }\n",
       "\n",
       "    // Try loading a local version of requirejs and fallback to cdn if not possible.\n",
       "    script_load_root_plot_1758545678617(base_url + 'static/build/jsroot.js', function(){\n",
       "        console.error('Fail to load JSROOT locally, please check your jupyter_notebook_config.py file');\n",
       "        script_load_root_plot_1758545678617('https://root.cern/js/7.9.0/build/jsroot.js', function(){\n",
       "            document.getElementById(\"root_plot_1758545678617\").innerHTML = \"Failed to load JSROOT\";\n",
       "        });\n",
       "    });\n",
       "}\n",
       "\n",
       "</script>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
...
]
}

and the second time I call the draw function again and also print c1.ls() for detail info:

{
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Canvas Name=c Title=c Option=\n",
      " TCanvas fXlowNDC=0 fYlowNDC=0 fWNDC=1 fHNDC=1 Name= c Title= c Option=\n",
      "  OBJ: TList\tTList\tDoubly linked list : 0\n",
      "   TFrame  X1= -2.410596 Y1=0.000000 X2=50.846663 Y2=33.000000\n",
      "   OBJ: TMultiGraph\toutput-for-test/pot_5s2_cen_FconfNOmgccc_t%03d_B01_1600conf_080bin_misnered_observables.root_phase_shift_nocoul_mg_timedata\t : 0 at: 0x64e63aa33cd0\n",
      "   OBJ: TMultiGraph\toutput-for-test/pot_5s2_cen_FconfNOmgccc_t%03d_B01_1600conf_080bin_misnered_observables.root_phase_shift_nocoul_mg_timedata\t : 0 at: 0x64e63aa33cd0\n"
     ]
    }
   ],
   "source": [
    "draw_phaseshift(timedata,yrange=[0,33],legshow=False)\n",
    "c1.ls()"
   ]
  }

Best wishes,

Liang

let obj = Core.parse({\"_typename\":\"TCanvasWebSnapshot\",\"fUniqueID\":0..."

This line is important.
Can you add complete file to your repository?
At the best two variants - after first cell execution where drawings are ok and then after second execution when drawing disappear.

Yes, current repository include the full executed JSON data.

Actually data is fine - it displayed normally when extracted into independent HTML page.
I trying to understand why it does not work inside notebook,

Moreover - your stored notebook does display data in my Visual Studio Code:

But does not work in jupyter lab and github viewer.

Since I’m always confused with WSL port, I prefer to display notebook in VS code.

Hi Liang,

I have some questions.

Unfortunately, I cannot run your notebook - while I missing some components in my jupyter/python installation. And I do not have data files you are using.

But with my simple notebook I can reproduce some problems.
If I store notebook with several simple drawings - not all of them displayed automatically when notebook opened again.

Did you see same behavior? Or problem already happens when you draw canvas again - and first drawing disappears? Can you try to create two different canvas for such case?

Funny fact - using Visual Studio Code I always get all drawing displayed.
And github viewer does not work at all.

Regards,
Sergey

Hi linev,

I can still see the drawing when I open the notebook again in VS code.

When I run cell 2 for first time, the canvas shows perfectly below the cell 2. But if I run cell 2 again, the canvas disappears, I need to run a new cell with c1.Draw() to get canvas back, and this cell don’t have any problem.

I find an interesting thing:

If I set plot ranges, both c1.DrawFrame(…) and mg.GetXaxis().SetRangeUser(…), in the same cell with c1.Darw(), the canvas will not show. If I remove these setting, the canvas will display normally. I updated this with cell 4 and 5 in github.

P.S. I atteched the data with output-for-test/.

Best wishes,

Liang

I see both canvases in your updated Canvas.ipynb notebook - when using Visual Studio Code.
But there are rendering problems when loading this notebook into jupyter lab.
Seems to be jupyter improved/optimized rendering of produced HTML pages - making difficult for JSROOT correctly render drawing inside.

I will try improve it on the level of ROOT-jupyter interface

I made several improvements in jupyter drawing with jsroot - see PR. This includes better HTML layout detection and also better loading of jsroot code in notebooks.

As separate PR JSON zipping will be implemented which should reduce size of produced .ipynb files.

But I still do not understand problem with your original Canvas.ipynb file.
It works fine in Visual Studio Code and also in online viewers:

But not inside regular jupyter session.
So I not sure if my changes will solve your problems.

And separate question - can you point out your code where you create files from output-for-test/ directory. They missing some important data and I want to understand why

Great!

Oh! Sorry for misleading name, output-for-test/ is actully input for Canvas.ipynb. It is a output fold for my analysis program and .ipynb is for drawing and adjusting figures. And it already have some TGraph for drawing in root files. So I think you can run the Canvas.ipynb and find that

  1. cell 2 work normal
  2. cell 3 seems update in cell 2’s output without drawing a new canvas in its output.
  3. Then if you run cell 2 again, the canvas disappears in cell 2’s output.
  4. And there will be no c1 displaying in the notebook unless you create a new cell with c1.Draw()

I tried to run your notebook now.

First, I remove c1.ls() from first cell.
And also remove multiple invocation of c1.DrawFrame - keeping only first one.
Then I can produce plots normally for all cells.

In my PC, the third cell still have no display after removing c1.DrawFrame

And what happend if you run the sconed cell again

As I mentioned, if I don’t set any axis range, the display will be normal. In my draw_phaseshift function, I set the both X and Y axis ranges. And only the first time you call draw_phaseshift in a cell, the canvas will display in the output of the cell. If you run this cell again, the display will disappear.

But If you don’t change axis range as removing c1.DrawFrame(…) in the forth cell, the canvas will always display no matter how many times you run it.