import { select as d3_select } from './d3.mjs';
import { loadScript, findFunction, internals, getPromise, isNodeJs, isObject, isFunc, isStr, _ensureJSROOT,
prROOT,
clTObject, clTNamed, clTString, clTAttLine, clTAttFill, clTAttMarker, clTAttText,
clTObjString, clTFile, clTList, clTHashList, clTMap, clTObjArray, clTClonesArray,
clTPave, clTPaveText, clTPavesText, clTPaveStats, clTPaveLabel, clTPaveClass, clTDiamond, clTLegend, clTPaletteAxis,
clTText, clTLine, clTBox, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, clTH2, clTF1, clTF2, clTF3, clTH3,
clTProfile, clTProfile2D, clTProfile3D, clTFrame,
clTColor, clTHStack, clTGraph, clTGraph2DErrors, clTGraph2DAsymmErrors,
clTGraphPolar, clTGraphPolargram, clTGraphTime, clTCutG, clTPolyLine, clTPolyLine3D, clTPolyMarker3D,
clTPad, clTStyle, clTCanvas, clTGaxis, clTGeoVolume, kInspect, nsREX, atob_func } from './core.mjs';
import { clTStreamerInfoList } from './io.mjs';
import { clTBranchFunc } from './tree.mjs';
import { BasePainter, compressSVG, svgToImage, _loadJSDOM } from './base/BasePainter.mjs';
import { ObjectPainter, cleanup, drawRawText, getElementCanvPainter, getElementMainPainter } from './base/ObjectPainter.mjs';
import { TPadPainter, clTButton } from './gpad/TPadPainter.mjs';
async function import_more() { return import('./draw/more.mjs'); }
async function import_canvas() { return import('./gpad/TCanvasPainter.mjs'); }
async function import_tree() { return import('./draw/TTree.mjs'); }
async function import_h() { return import('./gui/HierarchyPainter.mjs'); }
async function import_geo() {
return import('./geom/TGeoPainter.mjs').then(geo => {
const handle = getDrawHandle(prROOT + 'TGeoVolumeAssembly');
if (handle) handle.icon = 'img_geoassembly';
return geo;
});
}
const clTGraph2D = 'TGraph2D', clTH2Poly = 'TH2Poly', clTEllipse = 'TEllipse',
clTSpline3 = 'TSpline3', clTTree = 'TTree', clTCanvasWebSnapshot = 'TCanvasWebSnapshot',
fPrimitives = 'fPrimitives', fFunctions = 'fFunctions',
/** @summary list of registered draw functions
* @private */
drawFuncs = { lst: [
{ name: clTCanvas, icon: 'img_canvas', class: () => import_canvas().then(h => h.TCanvasPainter), opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true },
{ name: clTPad, icon: 'img_canvas', func: TPadPainter.draw, opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true },
{ name: 'TSlider', icon: 'img_canvas', func: TPadPainter.draw },
{ name: clTButton, icon: 'img_canvas', func: TPadPainter.draw },
{ name: clTFrame, icon: 'img_frame', draw: () => import_canvas().then(h => h.drawTFrame) },
{ name: clTPave, icon: 'img_pavetext', class: () => import('./hist/TPavePainter.mjs').then(h => h.TPavePainter) },
{ name: clTPaveText, sameas: clTPave },
{ name: clTPavesText, sameas: clTPave },
{ name: clTPaveStats, sameas: clTPave },
{ name: clTPaveLabel, sameas: clTPave },
{ name: clTPaveClass, sameas: clTPave },
{ name: clTDiamond, sameas: clTPave },
{ name: clTLegend, icon: 'img_pavelabel', sameas: clTPave },
{ name: clTPaletteAxis, icon: 'img_colz', sameas: clTPave },
{ name: clTLatex, icon: 'img_text', draw: () => import_more().then(h => h.drawText), direct: true },
{ name: clTMathText, sameas: clTLatex },
{ name: clTText, sameas: clTLatex },
{ name: clTAnnotation, sameas: clTLatex },
{ name: /^TH1/, icon: 'img_histo1d', class: () => import('./hist/TH1Painter.mjs').then(h => h.TH1Painter), opt: ';hist;P;P0;E;E1;E2;E3;E4;E1X0;L;LF2;C;B;B1;A;TEXT;LEGO;same', ctrl: 'l', expand_item: fFunctions, for_derived: true },
{ name: clTProfile, icon: 'img_profile', class: () => import('./hist/TH1Painter.mjs').then(h => h.TH1Painter), opt: ';E0;E1;E2;p;AH;hist', expand_item: fFunctions },
{ name: clTH2Poly, icon: 'img_histo2d', class: () => import('./hist/TH2Painter.mjs').then(h => h.TH2Painter), opt: ';COL;COL0;COLZ;LCOL;LCOL0;LCOLZ;LEGO;TEXT;same', expand_item: 'fBins', theonly: true },
{ name: 'TProfile2Poly', sameas: clTH2Poly },
{ name: 'TH2PolyBin', icon: 'img_histo2d', draw_field: 'fPoly', draw_field_opt: 'L' },
{ name: /^TH2/, icon: 'img_histo2d', class: () => import('./hist/TH2Painter.mjs').then(h => h.TH2Painter), dflt: 'col', opt: ';COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;SCAT;TEXT;TEXTE;TEXTE0;CANDLE;CANDLE1;CANDLE2;CANDLE3;CANDLE4;CANDLE5;CANDLE6;CANDLEY1;CANDLEY2;CANDLEY3;CANDLEY4;CANDLEY5;CANDLEY6;VIOLIN;VIOLIN1;VIOLIN2;VIOLINY1;VIOLINY2;CONT;CONT1;CONT2;CONT3;CONT4;ARR;SURF;SURF1;SURF2;SURF4;SURF6;E;A;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same', ctrl: 'lego', expand_item: fFunctions, for_derived: true },
{ name: clTProfile2D, sameas: clTH2 },
{ name: /^TH3/, icon: 'img_histo3d', class: () => import('./hist/TH3Painter.mjs').then(h => h.TH3Painter), opt: ';SCAT;BOX;BOX2;BOX3;GLBOX1;GLBOX2;GLCOL', expand_item: fFunctions, for_derived: true },
{ name: clTProfile3D, sameas: clTH3 },
{ name: clTHStack, icon: 'img_histo1d', class: () => import('./hist/THStackPainter.mjs').then(h => h.THStackPainter), expand_item: 'fHists', opt: 'NOSTACK;HIST;E;PFC;PLC' },
{ name: clTPolyMarker3D, icon: 'img_histo3d', draw: () => import('./draw/draw3d.mjs').then(h => h.drawPolyMarker3D), direct: true, frame: '3d' },
{ name: clTPolyLine3D, icon: 'img_graph', draw: () => import('./draw/draw3d.mjs').then(h => h.drawPolyLine3D), direct: true, frame: '3d' },
{ name: 'TGraphStruct' },
{ name: 'TGraphNode' },
{ name: 'TGraphEdge' },
{ name: clTGraphTime, icon: 'img_graph', class: () => import('./hist/TGraphTimePainter.mjs').then(h => h.TGraphTimePainter), opt: 'once;repeat;first', theonly: true },
{ name: clTGraph2D, icon: 'img_graph', class: () => import('./hist/TGraph2DPainter.mjs').then(h => h.TGraph2DPainter), opt: ';P;PCOL', theonly: true },
{ name: clTGraph2DErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true },
{ name: clTGraph2DAsymmErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true },
{ name: clTGraphPolargram, icon: 'img_graph', class: () => import('./draw/TGraphPolarPainter.mjs').then(h => h.TGraphPolargramPainter), theonly: true },
{ name: clTGraphPolar, icon: 'img_graph', class: () => import('./draw/TGraphPolarPainter.mjs').then(h => h.TGraphPolarPainter), opt: ';F;L;P;PE', theonly: true },
{ name: /^TGraph/, icon: 'img_graph', class: () => import('./hist2d/TGraphPainter.mjs').then(h => h.TGraphPainter), opt: ';L;P' },
{ name: 'TEfficiency', icon: 'img_graph', class: () => import('./hist/TEfficiencyPainter.mjs').then(h => h.TEfficiencyPainter), opt: ';AP' },
{ name: clTCutG, sameas: clTGraph },
{ name: /^RooHist/, sameas: clTGraph },
{ name: /^RooCurve/, sameas: clTGraph },
{ name: 'TScatter', icon: 'img_graph', class: () => import('./hist2d/TScatterPainter.mjs').then(h => h.TScatterPainter), opt: ';A' },
{ name: 'RooPlot', icon: 'img_canvas', func: drawRooPlot },
{ name: 'TRatioPlot', icon: 'img_mgraph', class: () => import('./draw/TRatioPlotPainter.mjs').then(h => h.TRatioPlotPainter), opt: '' },
{ name: clTMultiGraph, icon: 'img_mgraph', class: () => import('./hist/TMultiGraphPainter.mjs').then(h => h.TMultiGraphPainter), opt: ';l;p;3d', expand_item: 'fGraphs' },
{ name: clTStreamerInfoList, icon: 'img_question', draw: () => import_h().then(h => h.drawStreamerInfo) },
{ name: 'TWebPainting', icon: 'img_graph', class: () => import('./draw/TWebPaintingPainter.mjs').then(h => h.TWebPaintingPainter) },
{ name: clTCanvasWebSnapshot, icon: 'img_canvas', draw: () => import_canvas().then(h => h.drawTPadSnapshot) },
{ name: 'TPadWebSnapshot', sameas: clTCanvasWebSnapshot },
{ name: 'kind:Text', icon: 'img_text', func: drawRawText },
{ name: clTObjString, icon: 'img_text', func: drawRawText },
{ name: clTF1, icon: 'img_tf1', class: () => import('./hist/TF1Painter.mjs').then(h => h.TF1Painter), opt: ';L;C;FC;FL' },
{ name: clTF2, icon: 'img_tf2', class: () => import('./hist/TF2Painter.mjs').then(h => h.TF2Painter), opt: ';BOX;ARR;SURF;SURF1;SURF2;SURF4;SURF6;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same' },
{ name: clTF3, icon: 'img_histo3d', class: () => import('./hist/TF3Painter.mjs').then(h => h.TF3Painter), opt: ';SURF' },
{ name: clTSpline3, icon: 'img_tf1', class: () => import('./draw/TSplinePainter.mjs').then(h => h.TSplinePainter) },
{ name: 'TSpline5', sameas: clTSpline3 },
{ name: clTEllipse, icon: 'img_graph', draw: () => import_more().then(h => h.drawEllipse), direct: true },
{ name: 'TArc', sameas: clTEllipse },
{ name: 'TCrown', sameas: clTEllipse },
{ name: 'TPie', icon: 'img_graph', draw: () => import_more().then(h => h.drawPie), direct: true },
{ name: 'TPieSlice', icon: 'img_graph', dummy: true },
{ name: 'TExec', icon: 'img_graph', dummy: true },
{ name: clTLine, icon: 'img_graph', class: () => import('./draw/TLinePainter.mjs').then(h => h.TLinePainter) },
{ name: 'TArrow', icon: 'img_graph', class: () => import('./draw/TArrowPainter.mjs').then(h => h.TArrowPainter) },
{ name: clTPolyLine, icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyLine), direct: true },
{ name: 'TCurlyLine', sameas: clTPolyLine },
{ name: 'TCurlyArc', sameas: clTPolyLine },
{ name: 'TParallelCoord', icon: 'img_graph', dummy: true },
{ name: clTGaxis, icon: 'img_graph', class: () => import('./draw/TGaxisPainter.mjs').then(h => h.TGaxisPainter) },
{ name: clTBox, icon: 'img_graph', draw: () => import_more().then(h => h.drawBox), direct: true },
{ name: 'TWbox', sameas: clTBox },
{ name: 'TSliderBox', sameas: clTBox },
{ name: 'TMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawMarker), direct: true },
{ name: 'TPolyMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyMarker), direct: true },
{ name: 'TASImage', icon: 'img_mgraph', class: () => import('./draw/TASImagePainter.mjs').then(h => h.TASImagePainter), opt: ';z' },
{ name: 'TJSImage', icon: 'img_mgraph', draw: () => import_more().then(h => h.drawJSImage), opt: ';scale;center' },
{ name: clTGeoVolume, icon: 'img_histo3d', class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;count;projx;projz;wire;no_screen;dflt', ctrl: 'dflt' },
{ name: 'TEveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' },
{ name: nsREX+'REveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' },
{ name: 'TGeoOverlap', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt', dflt: 'dflt', ctrl: 'expand' },
{ name: 'TGeoManager', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;tracks;no_screen;dflt', dflt: 'expand', ctrl: 'dflt', noappend: true, exapnd_after_draw: true },
{ name: 'TGeoVolumeAssembly', sameas: clTGeoVolume, /* icon: 'img_geoassembly', */ opt: ';more;all;count' },
{ name: /^TGeo/, class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;axis;compa;count;projx;projz;wire;no_screen;dflt', dflt: 'dflt', ctrl: 'expand' },
{ name: 'TAxis3D', icon: 'img_graph', draw: () => import_geo().then(h => h.drawAxis3D), direct: true },
// these are not draw functions, but provide extra info about correspondent classes
{ name: 'kind:Command', icon: 'img_execute', execute: true },
{ name: 'TFolder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true, get_expand: () => import_h().then(h => h.folderHierarchy) },
{ name: 'TTask', icon: 'img_task', get_expand: () => import_h().then(h => h.taskHierarchy), for_derived: true },
{ name: clTTree, icon: 'img_tree', get_expand: () => import('./tree.mjs').then(h => h.treeHierarchy), draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: 'player;testio', shift: kInspect },
{ name: 'TNtuple', sameas: clTTree },
{ name: 'TNtupleD', sameas: clTTree },
{ name: clTBranchFunc, icon: 'img_leaf_method', draw: () => import_tree().then(h => h.drawTree), opt: ';dump', noinspect: true },
{ name: /^TBranch/, icon: 'img_branch', draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: ';dump', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true },
{ name: /^TLeaf/, icon: 'img_leaf', noexpand: true, draw: () => import_tree().then(h => h.drawTree), opt: ';dump', ctrl: 'dump', ignore_online: true, always_draw: true },
{ name: clTList, icon: 'img_list', draw: () => import_h().then(h => h.drawList), get_expand: () => import_h().then(h => h.listHierarchy), dflt: 'expand' },
{ name: clTHashList, sameas: clTList },
{ name: clTObjArray, sameas: clTList },
{ name: clTClonesArray, sameas: clTList },
{ name: clTMap, sameas: clTList },
{ name: clTColor, icon: 'img_color' },
{ name: clTFile, icon: 'img_file', noinspect: true },
{ name: 'TMemFile', icon: 'img_file', noinspect: true },
{ name: clTStyle, icon: 'img_question', noexpand: true },
{ name: 'Session', icon: 'img_globe' },
{ name: 'kind:TopFolder', icon: 'img_base' },
{ name: 'kind:Folder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true },
{ name: nsREX+'RCanvas', icon: 'img_canvas', class: () => init_v7().then(h => h.RCanvasPainter), opt: '', expand_item: fPrimitives },
{ name: nsREX+'RCanvasDisplayItem', icon: 'img_canvas', draw: () => init_v7().then(h => h.drawRPadSnapshot), opt: '', expand_item: fPrimitives },
{ name: nsREX+'RHist1Drawable', icon: 'img_histo1d', class: () => init_v7('rh1').then(h => h.RH1Painter), opt: '' },
{ name: nsREX+'RHist2Drawable', icon: 'img_histo2d', class: () => init_v7('rh2').then(h => h.RH2Painter), opt: '' },
{ name: nsREX+'RHist3Drawable', icon: 'img_histo3d', class: () => init_v7('rh3').then(h => h.RH3Painter), opt: '' },
{ name: nsREX+'RHistDisplayItem', icon: 'img_histo1d', draw: () => init_v7('rh3').then(h => h.drawHistDisplayItem), opt: '' },
{ name: nsREX+'RText', icon: 'img_text', draw: () => init_v7('more').then(h => h.drawText), opt: '', direct: 'v7', csstype: 'text' },
{ name: nsREX+'RFrameTitle', icon: 'img_text', draw: () => init_v7().then(h => h.drawRFrameTitle), opt: '', direct: 'v7', csstype: 'title' },
{ name: nsREX+'RPaletteDrawable', icon: 'img_text', class: () => init_v7('more').then(h => h.RPalettePainter), opt: '' },
{ name: nsREX+'RDisplayHistStat', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RHistStatsPainter), opt: '' },
{ name: nsREX+'RLine', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawLine), opt: '', direct: 'v7', csstype: 'line' },
{ name: nsREX+'RBox', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawBox), opt: '', direct: 'v7', csstype: 'box' },
{ name: nsREX+'RMarker', icon: 'img_graph', draw: () => init_v7('more').then(h => h.drawMarker), opt: '', direct: 'v7', csstype: 'marker' },
{ name: nsREX+'RPave', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RPavePainter), opt: '' },
{ name: nsREX+'RLegend', icon: 'img_graph', class: () => init_v7('pave').then(h => h.RLegendPainter), opt: '' },
{ name: nsREX+'RPaveText', icon: 'img_pavetext', class: () => init_v7('pave').then(h => h.RPaveTextPainter), opt: '' },
{ name: nsREX+'RFrame', icon: 'img_frame', draw: () => init_v7().then(h => h.drawRFrame), opt: '' },
{ name: nsREX+'RFont', icon: 'img_text', draw: () => init_v7().then(h => h.drawRFont), opt: '', direct: 'v7', csstype: 'font' },
{ name: nsREX+'RAxisDrawable', icon: 'img_frame', draw: () => init_v7().then(h => h.drawRAxis), opt: '' }
], cache: {} };
/** @summary Register draw function for the class
* @desc List of supported draw options could be provided, separated with ';'
* @param {object} args - arguments
* @param {string|regexp} args.name - class name or regexp pattern
* @param {function} [args.func] - draw function
* @param {function} [args.draw] - async function to load draw function
* @param {function} [args.class] - async function to load painter class with static draw function
* @param {boolean} [args.direct] - if true, function is just Redraw() method of ObjectPainter
* @param {string} [args.opt] - list of supported draw options (separated with semicolon) like 'col;scat;'
* @param {string} [args.icon] - icon name shown for the class in hierarchy browser
* @param {string} [args.draw_field] - draw only data member from object, like fHistogram
* @protected */
function addDrawFunc(args) {
drawFuncs.lst.push(args);
return args;
}
/** @summary return draw handle for specified item kind
* @desc kind could be ROOT.TH1I for ROOT classes or just
* kind string like 'Command' or 'Text'
* selector can be used to search for draw handle with specified option (string)
* or just sequence id
* @private */
function getDrawHandle(kind, selector) {
if (!isStr(kind)) return null;
if (selector === '') selector = null;
let first = null;
if ((selector === null) && (kind in drawFuncs.cache))
return drawFuncs.cache[kind];
const search = (kind.indexOf(prROOT) === 0) ? kind.slice(5) : `kind:${kind}`;
let counter = 0;
for (let i = 0; i < drawFuncs.lst.length; ++i) {
const h = drawFuncs.lst[i];
if (isStr(h.name)) {
if (h.name !== search) continue;
} else if (!search.match(h.name))
continue;
if (h.sameas) {
const hs = getDrawHandle(prROOT + h.sameas, selector);
if (hs) {
for (const key in hs) {
if (h[key] === undefined)
h[key] = hs[key];
}
delete h.sameas;
}
return h;
}
if ((selector === null) || (selector === undefined)) {
// store found handle in cache, can reuse later
if (!(kind in drawFuncs.cache)) drawFuncs.cache[kind] = h;
return h;
} else if (isStr(selector)) {
if (!first) first = h;
// if drawoption specified, check it present in the list
if (selector === '::expand') {
if (('expand' in h) || ('expand_item' in h)) return h;
} else if ('opt' in h) {
const opts = h.opt.split(';');
for (let j = 0; j < opts.length; ++j)
opts[j] = opts[j].toLowerCase();
if (opts.indexOf(selector.toLowerCase()) >= 0) return h;
}
} else if (selector === counter)
return h;
++counter;
}
return first;
}
/** @summary Returns true if handle can be potentially drawn
* @private */
function canDrawHandle(h) {
if (isStr(h))
h = getDrawHandle(h);
if (!isObject(h)) return false;
return h.func || h.class || h.draw || h.draw_field;
}
/** @summary Provide draw settings for specified class or kind
* @private */
function getDrawSettings(kind, selector) {
const res = { opts: null, inspect: false, expand: false, draw: false, handle: null };
if (!isStr(kind)) return res;
let isany = false, noinspect = false, canexpand = false;
if (!isStr(selector)) selector = '';
for (let cnt = 0; cnt < 1000; ++cnt) {
const h = getDrawHandle(kind, cnt);
if (!h) break;
if (!res.handle) res.handle = h;
if (h.noinspect) noinspect = true;
if (h.noappend) res.noappend = true;
if (h.expand || h.get_expand || h.expand_item || h.can_expand) canexpand = true;
if (!h.func && !h.class && !h.draw) break;
isany = true;
if (!('opt' in h)) continue;
const opts = h.opt.split(';');
for (let i = 0; i < opts.length; ++i) {
opts[i] = opts[i].toLowerCase();
if (opts[i].indexOf('same') === 0) {
res.has_same = true;
if (selector.indexOf('nosame') >= 0) continue;
}
if (res.opts === null) res.opts = [];
if (res.opts.indexOf(opts[i]) < 0) res.opts.push(opts[i]);
}
if (h.theonly) break;
}
if (selector.indexOf('noinspect') >= 0) noinspect = true;
if (isany && (res.opts === null)) res.opts = [''];
// if no any handle found, let inspect ROOT-based objects
if (!isany && (kind.indexOf(prROOT) === 0) && !noinspect) res.opts = [];
if (!noinspect && res.opts)
res.opts.push(kInspect);
res.inspect = !noinspect;
res.expand = canexpand;
res.draw = !!res.opts;
return res;
}
/** @summary Set default draw option for provided class
* @example
import { setDefaultDrawOpt } from 'https://root.cern/js/latest/modules/draw.mjs';
setDefaultDrawOpt('TH1', 'text');
setDefaultDrawOpt('TH2', 'col'); */
function setDefaultDrawOpt(classname, opt) {
const handle = getDrawHandle(prROOT + classname, 0);
if (handle)
handle.dflt = opt;
}
/** @summary Draw object in specified HTML element with given draw options.
* @param {string|object} dom - id of div element to draw or directly DOMElement
* @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc}
* @param {string} opt - draw options separated by space, comma or semicolon
* @return {Promise} with painter object
* @public
* @desc An extensive list of support draw options can be found on [examples page]{@link https://root.cern/js/latest/examples.htm}
* @example
* import { openFile } from 'https://root.cern/js/latest/modules/io.mjs';
* import { draw } from 'https://root.cern/js/latest/modules/draw.mjs';
* let file = await openFile('https://root.cern/js/files/hsimple.root');
* let obj = await file.readObject('hpxpy;1');
* await draw('drawing', obj, 'colz;logx;gridx;gridy'); */
async function draw(dom, obj, opt) {
if (!isObject(obj))
return Promise.reject(Error('not an object in draw call'));
if (isStr(opt) && (opt.indexOf(kInspect) === 0))
return import_h().then(h => h.drawInspector(dom, obj, opt));
let handle, type_info;
if ('_typename' in obj) {
type_info = 'type ' + obj._typename;
handle = getDrawHandle(prROOT + obj._typename, opt);
} else if ('_kind' in obj) {
type_info = 'kind ' + obj._kind;
handle = getDrawHandle(obj._kind, opt);
} else
return import_h().then(h => h.drawInspector(dom, obj, opt));
// this is case of unsupported class, close it normally
if (!handle)
return Promise.reject(Error(`Object of ${type_info} cannot be shown with draw`));
if (handle.dummy)
return null;
if (handle.draw_field && obj[handle.draw_field])
return draw(dom, obj[handle.draw_field], opt || handle.draw_field_opt);
if (!canDrawHandle(handle)) {
if (opt && (opt.indexOf('same') >= 0)) {
const main_painter = getElementMainPainter(dom);
if (isFunc(main_painter?.performDrop))
return main_painter.performDrop(obj, '', null, opt);
}
return Promise.reject(Error(`Function not specified to draw object ${type_info}`));
}
function performDraw() {
let promise, painter;
if (handle.direct === 'v7') {
promise = import('./gpad/RCanvasPainter.mjs').then(v7h => {
painter = new v7h.RObjectPainter(dom, obj, opt, handle.csstype);
painter.redraw = handle.func;
return v7h.ensureRCanvas(painter, handle.frame || false);
}).then(() => painter.redraw());
} else if (handle.direct) {
painter = new ObjectPainter(dom, obj, opt);
painter.redraw = handle.func;
promise = import_canvas().then(v6h => v6h.ensureTCanvas(painter, handle.frame || false))
.then(() => painter.redraw());
} else
promise = getPromise(handle.func(dom, obj, opt));
return promise.then(p => {
if (!painter)
painter = p;
if (painter === false)
return null;
if (!painter)
throw Error(`Fail to draw object ${type_info}`);
if (isObject(painter) && !painter.options)
painter.options = { original: opt || '' }; // keep original draw options
return painter;
});
}
if (isFunc(handle.func))
return performDraw();
let promise;
if (isFunc(handle.class)) {
// class coded as async function which returns class handle
// simple extract class and access class.draw method
promise = handle.class().then(cl => { handle.func = cl.draw; });
} else if (isFunc(handle.draw)) {
// draw function without special class
promise = handle.draw().then(h => { handle.func = h; });
} else if (!handle.func || !isStr(handle.func))
return Promise.reject(Error(`Draw function or class not specified to draw ${type_info}`));
else if (!handle.prereq && !handle.script)
return Promise.reject(Error(`Prerequicities to load ${handle.func} are not specified`));
else {
const init_promise = internals.ignore_v6
? Promise.resolve(true)
: _ensureJSROOT().then(v6 => {
const pr = handle.prereq ? v6.require(handle.prereq) : Promise.resolve(true);
return pr.then(() => {
if (handle.script)
return loadScript(handle.script);
}).then(() => v6._complete_loading());
});
promise = init_promise.then(() => {
const func = findFunction(handle.func);
if (!isFunc(func))
return Promise.reject(Error(`Fail to find function ${handle.func} after loading ${handle.prereq || handle.script}`));
handle.func = func;
});
}
return promise.then(() => performDraw());
}
/** @summary Redraw object in specified HTML element with given draw options.
* @param {string|object} dom - id of div element to draw or directly DOMElement
* @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc}
* @param {string} opt - draw options
* @return {Promise} with painter object
* @desc If drawing was not done before, it will be performed with {@link draw}.
* Otherwise drawing content will be updated
* @public
* @example
* import { openFile } from 'https://root.cern/js/latest/modules/io.mjs';
* import { draw, redraw } from 'https://root.cern/js/latest/modules/draw.mjs';
* let file = await openFile('https://root.cern/js/files/hsimple.root');
* let obj = await file.readObject('hpxpy;1');
* await draw('drawing', obj, 'colz');
* let cnt = 0;
* setInterval(() => {
* obj.fTitle = `Next iteration ${cnt++}`;
* redraw('drawing', obj, 'colz');
* }, 1000); */
async function redraw(dom, obj, opt) {
if (!isObject(obj))
return Promise.reject(Error('not an object in redraw'));
const can_painter = getElementCanvPainter(dom);
let handle, res_painter = null, redraw_res;
if (obj._typename)
handle = getDrawHandle(prROOT + obj._typename);
if (handle?.draw_field && obj[handle.draw_field])
obj = obj[handle.draw_field];
if (can_painter) {
if (can_painter.matchObjectType(obj._typename)) {
redraw_res = can_painter.redrawObject(obj, opt);
if (redraw_res) res_painter = can_painter;
} else {
for (let i = 0; i < can_painter.painters.length; ++i) {
const painter = can_painter.painters[i];
if (painter.matchObjectType(obj._typename)) {
redraw_res = painter.redrawObject(obj, opt);
if (redraw_res) {
res_painter = painter;
break;
}
}
}
}
} else {
const top = new BasePainter(dom).getTopPainter();
// base painter do not have this method, if it there use it
// it can be object painter here or can be specially introduce method to handling redraw!
if (isFunc(top?.redrawObject)) {
redraw_res = top.redrawObject(obj, opt);
if (redraw_res) res_painter = top;
}
}
if (res_painter)
return getPromise(redraw_res).then(() => res_painter);
cleanup(dom);
return draw(dom, obj, opt);
}
/** @summary Scan streamer infos for derived classes
* @desc Assign draw functions for such derived classes
* @private */
function addStreamerInfosForPainter(lst) {
if (!lst) return;
const basics = [clTObject, clTNamed, clTString, 'TCollection', clTAttLine, clTAttFill, clTAttMarker, clTAttText];
function checkBaseClasses(si, lvl) {
const element = si.fElements?.arr[0];
if ((element?.fTypeName !== 'BASE') || (lvl > 4))
return null;
// exclude very basic classes
if (basics.indexOf(element.fName) >= 0)
return null;
let handle = getDrawHandle(prROOT + element.fName);
if (handle && !handle.for_derived)
handle = null;
// now try find that base class of base in the list
if (handle === null) {
for (let k = 0; k < lst.arr.length; ++k) {
if (lst.arr[k].fName === element.fName) {
handle = checkBaseClasses(lst.arr[k], lvl + 1);
break;
}
}
}
return handle?.for_derived ? handle : null;
}
lst.arr.forEach(si => {
if (getDrawHandle(prROOT + si.fName) !== null) return;
const handle = checkBaseClasses(si, 0);
if (handle) {
const newhandle = Object.assign({}, handle);
delete newhandle.for_derived; // should we disable?
newhandle.name = si.fName;
addDrawFunc(newhandle);
}
});
}
/** @summary Create SVG/PNG/JPEG image for provided object.
* @desc Function especially useful in Node.js environment to generate images for
* supported ROOT classes, but also can be used from web browser
* @param {object} args - function settings
* @param {object} args.object - object for the drawing
* @param {string} [args.format = 'svg'] - image format like 'svg' (default), 'png' or 'jpeg'
* @param {string} [args.option = ''] - draw options
* @param {number} [args.width = 1200] - image width
* @param {number} [args.height = 800] - image height
* @param {boolean} [args.as_buffer = false] - returns image as Buffer instance, can store directly to file
* @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object
* @return {Promise} with image code - svg as is, png/jpeg as base64 string or buffer (if as_buffer) specified
* @example
* // how makeImage can be used in node.js
* import { openFile, makeImage } from 'jsroot';
* let file = await openFile('https://root.cern/js/files/hsimple.root');
* let object = await file.readObject('hpxpy;1');
* let png64 = await makeImage({ format: 'png', object, option: 'colz', width: 1200, height: 800 });
* let pngbuf = await makeImage({ format: 'png', as_buffer: true, object, option: 'colz', width: 1200, height: 800 }); */
async function makeImage(args) {
if (!args) args = {};
if (!isObject(args.object))
return Promise.reject(Error('No object specified to generate SVG'));
if (!args.format)
args.format = 'svg';
if (!args.width)
args.width = 1200;
if (!args.height)
args.height = 800;
if (args.use_canvas_size && (args.object?._typename === clTCanvas) && args.object.fCw && args.object.fCh) {
args.width = args.object?.fCw;
args.height = args.object?.fCh;
}
async function build(main) {
main.attr('width', args.width).attr('height', args.height)
.style('width', args.width + 'px').style('height', args.height + 'px')
.property('_batch_mode', true)
.property('_batch_format', args.format !== 'svg' ? args.format : null);
function complete(res) {
cleanup(main.node());
main.remove();
return res;
}
return draw(main.node(), args.object, args.option || '').then(() => {
if (args.format !== 'svg') {
const only_img = main.select('svg').selectChild('image');
if (!only_img.empty()) {
const href = only_img.attr('href');
if (args.as_buffer) {
const p = href.indexOf('base64,'),
str = atob_func(href.slice(p + 7)),
buf = new ArrayBuffer(str.length),
bufView = new Uint8Array(buf);
for (let i = 0; i < str.length; i++)
bufView[i] = str.charCodeAt(i);
return isNodeJs() ? Buffer.from(buf) : buf;
}
return href;
}
}
main.select('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('width', args.width)
.attr('height', args.height)
.attr('style', null).attr('class', null).attr('x', null).attr('y', null);
function clear_element() {
const elem = d3_select(this);
if (elem.style('display') === 'none') elem.remove();
}
main.selectAll('g.root_frame').each(clear_element);
main.selectAll('svg').each(clear_element);
let svg;
if (args.format === 'pdf')
svg = { node: main.select('svg').node(), width: args.width, height: args.height, can_modify: true };
else {
svg = compressSVG(main.html());
if (args.format === 'svg')
return complete(svg);
}
return svgToImage(svg, args.format, args.as_buffer).then(complete);
});
}
return isNodeJs()
? _loadJSDOM().then(handle => build(handle.body.append('div')))
: build(d3_select('body').append('div').style('display', 'none'));
}
/** @summary Create SVG image for provided object.
* @desc Function especially useful in Node.js environment to generate images for
* supported ROOT classes
* @param {object} args - function settings
* @param {object} args.object - object for the drawing
* @param {string} [args.option] - draw options
* @param {number} [args.width = 1200] - image width
* @param {number} [args.height = 800] - image height
* @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object
* @return {Promise} with svg code
* @example
* // how makeSVG can be used in node.js
* import { openFile, makeSVG } from 'jsroot';
* let file = await openFile('https://root.cern/js/files/hsimple.root');
* let object = await file.readObject('hpxpy;1');
* let svg = await makeSVG({ object, option: 'lego2,pal50', width: 1200, height: 800 }); */
async function makeSVG(args) {
if (!args) args = {};
args.format = 'svg';
return makeImage(args);
}
internals.addDrawFunc = addDrawFunc;
function assignPadPainterDraw(PadPainterClass) {
PadPainterClass.prototype.drawObject = (...args) =>
draw(...args).catch(err => { console.log(`Error ${err?.message ?? err} at ${err?.stack ?? 'uncknown place'}`); return null; });
PadPainterClass.prototype.getObjectDrawSettings = getDrawSettings;
}
// only now one can draw primitives in the canvas
assignPadPainterDraw(TPadPainter);
// load v7 only by demand
async function init_v7(arg) {
return import('./gpad/RCanvasPainter.mjs').then(h => {
// only now one can draw primitives in the canvas
assignPadPainterDraw(h.RPadPainter);
switch (arg) {
case 'more': return import('./draw/v7more.mjs');
case 'pave': return import('./hist/RPavePainter.mjs');
case 'rh1': return import('./hist/RH1Painter.mjs');
case 'rh2': return import('./hist/RH2Painter.mjs');
case 'rh3': return import('./hist/RH3Painter.mjs');
}
return h;
});
}
// to avoid cross-dependnecy between io.mjs and draw.mjs
internals.addStreamerInfosForPainter = addStreamerInfosForPainter;
/** @summary Draw TRooPlot
* @private */
async function drawRooPlot(dom, plot) {
return draw(dom, plot._hist, 'hist').then(async hp => {
const arr = [];
for (let i = 0; i < plot._items.arr.length; ++i)
arr.push(draw(dom, plot._items.arr[i], plot._items.opt[i]));
return Promise.all(arr).then(() => hp);
});
}
export { addDrawFunc, getDrawHandle, canDrawHandle, getDrawSettings, setDefaultDrawOpt,
draw, redraw, cleanup, makeSVG, makeImage, drawRooPlot, assignPadPainterDraw };