UNPKG

jsroot

Version:
739 lines (653 loc) 37.6 kB
import { select as d3_select } from './d3.mjs'; import { loadScript, loadModules, findFunction, internals, settings, 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, clTLink, clTLine, clTBox, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, clTH2, clTF1, clTF12, 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, nsSVG, atob_func } from './core.mjs'; import { clTStreamerInfoList, kBaseClass } 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'; import { makePDF } from './base/makepdf.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'); } let import_v7 = null, import_geo = null; 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: 'TInspectCanvas', icon: 'img_canvas', sameas: clTCanvas }, { 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: clTLink, sameas: clTText }, { 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;projx;projxb;projxc=e;projxw', 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), opt: ';COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;COL_POL;COL_ARR;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;PROJXY1;PROJXY2;PROJXY3;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;CHORD;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, opt2: ';projxyb;projxyc=e;projxyw' }, { 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;COL;LEGO;E;PFC;PLC;PADS' }, { 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;A4P;B' }, { name: clTCutG, sameas: clTGraph }, { name: /^RooHist/, sameas: clTGraph }, { name: /^RooCurve/, sameas: clTGraph }, { name: /^RooEllipse/, sameas: clTGraph }, { name: 'TScatter', icon: 'img_graph', class: () => import('./hist2d/TScatterPainter.mjs').then(h => h.TScatterPainter), opt: ';A' }, { name: 'RooPlot', icon: 'img_canvas', draw: () => import('./hist/TGraphTimePainter.mjs').then(h => h.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: ';ac;l;p;3d;pads', 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: clTF12, sameas: clTF1 }, { 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', class: () => import('./draw/TPolyLinePainter.mjs').then(h => h.TPolyLinePainter), opt: ';F' }, { 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', class: () => import('./draw/TBoxPainter.mjs').then(h => h.TBoxPainter), opt: ';L' }, { 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: () => import_v7().then(h => h.RCanvasPainter), opt: '', expand_item: fPrimitives }, { name: nsREX+'RCanvasDisplayItem', icon: 'img_canvas', draw: () => import_v7().then(h => h.drawRPadSnapshot), opt: '', expand_item: fPrimitives }, { name: nsREX+'RHist1Drawable', icon: 'img_histo1d', class: () => import_v7('rh1').then(h => h.RH1Painter), opt: '' }, { name: nsREX+'RHist2Drawable', icon: 'img_histo2d', class: () => import_v7('rh2').then(h => h.RH2Painter), opt: '' }, { name: nsREX+'RHist3Drawable', icon: 'img_histo3d', class: () => import_v7('rh3').then(h => h.RH3Painter), opt: '' }, { name: nsREX+'RHistDisplayItem', icon: 'img_histo1d', draw: () => import_v7('rh3').then(h => h.drawHistDisplayItem), opt: '' }, { name: nsREX+'RText', icon: 'img_text', draw: () => import_v7('more').then(h => h.drawText), opt: '', direct: 'v7', csstype: 'text' }, { name: nsREX+'RFrameTitle', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFrameTitle), opt: '', direct: 'v7', csstype: 'title' }, { name: nsREX+'RPaletteDrawable', icon: 'img_text', class: () => import_v7('more').then(h => h.RPalettePainter), opt: '' }, { name: nsREX+'RDisplayHistStat', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RHistStatsPainter), opt: '' }, { name: nsREX+'RLine', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawLine), opt: '', direct: 'v7', csstype: 'line' }, { name: nsREX+'RBox', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawBox), opt: '', direct: 'v7', csstype: 'box' }, { name: nsREX+'RMarker', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawMarker), opt: '', direct: 'v7', csstype: 'marker' }, { name: nsREX+'RPave', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPavePainter), opt: '' }, { name: nsREX+'RLegend', icon: 'img_graph', class: () => import_v7('pave').then(h => h.RLegendPainter), opt: '' }, { name: nsREX+'RPaveText', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPaveTextPainter), opt: '' }, { name: nsREX+'RFrame', icon: 'img_frame', draw: () => import_v7().then(h => h.drawRFrame), opt: '' }, { name: nsREX+'RFont', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFont), opt: '', direct: 'v7', csstype: 'font' }, { name: nsREX+'RAxisDrawable', icon: 'img_frame', draw: () => import_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 draw option 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) { if (opts[j].toLowerCase() === selector.toLowerCase()) 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 (h.opt === undefined) continue; let opt = h.opt; if (isStr(h.opt2)) opt += h.opt2; const opts = 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 = Boolean(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) { if (!classname) return; if ((opt === undefined) && isStr(classname) && (classname.indexOf(':') > 0)) { // special usage to set list of options like TH2:lego2;TH3:glbox2 opt.split(';').forEach(part => { const arr = part.split(':'); if (arr.length >= 1) setDefaultDrawOpt(arr[0], arr[1] || ''); }); } else { 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 { let func = findFunction(handle.func); if (isFunc(func)) { handle.func = func; return performDraw(); } let modules = null; if (isStr(handle.script)) { if (handle.script.indexOf('modules:') === 0) modules = handle.script.slice(8); else if (handle.script.indexOf('.mjs') > 0) modules = handle.script; } if (!modules && !handle.prereq && !handle.script) return Promise.reject(Error(`Prerequicities to load ${handle.func} are not specified`)); let init_promise = Promise.resolve(true); if (modules) init_promise = loadModules(modules); else if (!internals.ignore_v6) { init_promise = _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(() => { 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 !== kBaseClass) || (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 = settings.CanvasWidth; if (!args.height) args.height = settings.CanvasHeight; 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_use_canvsize', args.use_canvas_size ?? false) .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; } } const mainsvg = main.select('svg'); mainsvg.attr('xmlns', nsSVG) .attr('style', null).attr('class', null).attr('x', null).attr('y', null); if (!mainsvg.attr('width') && !mainsvg.attr('height')) mainsvg.attr('width', args.width).attr('height', args.height); 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: mainsvg.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).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); } function assignPadPainterDraw(PadPainterClass) { PadPainterClass.prototype.drawObject = draw; PadPainterClass.prototype.getObjectDrawSettings = getDrawSettings; } // only now one can draw primitives in the canvas assignPadPainterDraw(TPadPainter); import_geo = async function() { return import('./geom/TGeoPainter.mjs').then(geo => { const handle = getDrawHandle(prROOT + 'TGeoVolumeAssembly'); if (handle) handle.icon = 'img_geoassembly'; return geo; }); }; // load v7 only on demand import_v7 = async function(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-dependency between modules Object.assign(internals, { addStreamerInfosForPainter, addDrawFunc, setDefaultDrawOpt, makePDF }); Object.assign(internals.jsroot, { draw, redraw, makeSVG, makeImage, addDrawFunc }); export { addDrawFunc, getDrawHandle, canDrawHandle, getDrawSettings, setDefaultDrawOpt, draw, redraw, cleanup, makeSVG, makeImage, assignPadPainterDraw };