jsroot
Version:
JavaScript ROOT
1,376 lines (1,145 loc) • 95.4 kB
JavaScript
import { gStyle, BIT, settings, constants, create, isObject, isFunc, isStr, getPromise,
clTList, clTPaveText, clTPaveStats, clTPaletteAxis, clTProfile, clTProfile2D, clTProfile3D, clTPad,
clTAxis, clTF1, clTF2, kNoZoom, clTCutG, kNoStats, kTitle, setHistogramTitle } from '../core.mjs';
import { getColor, getColorPalette } from '../base/colors.mjs';
import { DrawOptions } from '../base/BasePainter.mjs';
import { ObjectPainter, EAxisBits, kAxisTime, kAxisLabels } from '../base/ObjectPainter.mjs';
import { TPavePainter, kPosTitle } from '../hist/TPavePainter.mjs';
import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs';
import { gamma_quantile, gamma_quantile_c } from '../base/math.mjs';
const kCARTESIAN = 1, kPOLAR = 2, kCYLINDRICAL = 3, kSPHERICAL = 4, kRAPIDITY = 5,
kNormal = 0, kPoisson = 1, kPoisson2 = 2;
/**
* @summary Class to decode histograms draw options
* @desc All options started from capital letter are major drawing options
* any other draw options are internal settings.
* @private
*/
class THistDrawOptions {
constructor() { this.reset(); }
/** @summary Reset hist draw options */
reset() {
Object.assign(this,
{ Axis: 0, RevX: false, RevY: false, SymlogX: 0, SymlogY: 0,
Bar: false, BarStyle: 0, Curve: false,
Hist: 1, Line: false, Fill: false,
Error: 0, ErrorKind: -1, errorX: gStyle.fErrorX,
Mark: false, Same: false, Scat: false, ScatCoef: 1.0, Func: true, AllFunc: false,
Arrow: false, Box: false, BoxStyle: 0,
Text: false, TextAngle: 0, TextKind: '', Char: 0, Color: false, Contour: 0, Cjust: false,
Lego: 0, Surf: 0, Off: 0, Tri: 0, Proj: 0, AxisPos: 0, Ortho: gStyle.fOrthoCamera,
Spec: false, Pie: false, List: false, Zscale: false, Zvert: true, PadPalette: false,
Candle: '', Violin: '', Scaled: null, Circular: 0, Poisson: kNormal,
GLBox: 0, GLColor: false, Project: '', ProfileProj: '', Profile2DProj: '', System: kCARTESIAN,
AutoColor: false, NoStat: false, ForceStat: false, PadStats: false, PadTitle: false, AutoZoom: false,
HighRes: 0, Zero: 1, Palette: 0, BaseLine: false, ShowEmpty: false,
Optimize: settings.OptimizeDraw,
Mode3D: false, x3dscale: 1, y3dscale: 1, SwapXY: false,
Render3D: constants.Render3D.Default,
FrontBox: true, BackBox: true,
need_fillcol: false,
minimum: kNoZoom, maximum: kNoZoom, ymin: 0, ymax: 0, cutg: null,
IgnoreMainScale: false, IgnorePalette: false });
}
isCartesian() { return this.System === kCARTESIAN; }
is3d() { return this.Lego || this.Surf; }
/** @summary Base on sumw2 values (re)set some basic draw options, only for 1dim hist */
decodeSumw2(histo, force) {
const len = histo.fSumw2?.length ?? 0;
let isany = false;
for (let n = 0; n < len; ++n)
if (histo.fSumw2[n] > 0) { isany = true; break; }
if (Number.isInteger(this.Error) || force)
this.Error = isany ? 1 : 0;
if (Number.isInteger(this.Hist) || force)
this.Hist = isany ? 0 : 1;
if (Number.isInteger(this.Zero) || force)
this.Zero = isany ? 0 : 1;
}
/** @summary Is palette can be used with current draw options */
canHavePalette() {
if (this.ndim === 3)
return this.BoxStyle === 12 || this.BoxStyle === 13 || this.GLBox === 12;
if (this.ndim === 1)
return this.Lego === 12 || this.Lego === 14;
if (this.Mode3D)
return this.Lego === 12 || this.Lego === 14 || this.Surf === 11 || this.Surf === 12;
if (this.Color || this.Contour || this.Hist || this.Axis)
return true;
return !this.Scat && !this.Box && !this.Arrow && !this.Proj && !this.Candle && !this.Violin && !this.Text;
}
/** @summary Decode histogram draw options */
decode(opt, hdim, histo, pp, pad, painter) {
this.orginal = opt; // will be overwritten by storeDrawOpt call
this.cutg_name = '';
if (isStr(opt) && (hdim === 2)) {
const p1 = opt.lastIndexOf('['), p2 = opt.lastIndexOf(']');
if ((p1 >= 0) && (p2 > p1+1)) {
this.cutg_name = opt.slice(p1+1, p2);
opt = opt.slice(0, p1) + opt.slice(p2+1);
this.cutg = pp?.findInPrimitives(this.cutg_name, clTCutG);
if (this.cutg) this.cutg.$redraw_pad = true;
}
}
const d = new DrawOptions(opt);
if (hdim === 1)
this.decodeSumw2(histo, true);
this.ndim = hdim || 1; // keep dimensions, used for now in GED
// for old web canvas json
// TODO: remove in version 8
d.check('USE_PAD_TITLE');
d.check('USE_PAD_PALETTE');
d.check('USE_PAD_STATS');
if (d.check('IGNORE_PALETTE'))
this.IgnorePalette = true;
if (d.check('PAL', true))
this.Palette = d.partAsInt();
// this is zooming of histogram content
if (d.check('MINIMUM:', true)) {
this.ominimum = true;
this.minimum = parseFloat(d.part);
} else {
this.ominimum = false;
this.minimum = histo.fMinimum;
}
if (d.check('MAXIMUM:', true)) {
this.omaximum = true;
this.maximum = parseFloat(d.part);
} else {
this.omaximum = false;
this.maximum = histo.fMaximum;
}
if (!this.ominimum && !this.omaximum && this.minimum === this.maximum)
this.minimum = this.maximum = kNoZoom;
if (d.check('HMIN:', true)) {
this.ohmin = true;
this.hmin = parseFloat(d.part);
} else {
this.ohmin = false;
delete this.hmin;
}
if (d.check('HMAX:', true)) {
this.ohmax = true;
this.hmax = parseFloat(d.part);
} else {
this.ohmax = false;
delete this.hmax;
}
this.zoom_min_max = d.check('ZOOM_MIN_MAX');
// let configure histogram titles - only for debug purposes
if (d.check('HTITLE:', true)) histo.fTitle = decodeURIComponent(d.part.toLowerCase());
if (d.check('XTITLE:', true)) histo.fXaxis.fTitle = decodeURIComponent(d.part.toLowerCase());
if (d.check('YTITLE:', true)) histo.fYaxis.fTitle = decodeURIComponent(d.part.toLowerCase());
if (d.check('ZTITLE:', true)) histo.fZaxis.fTitle = decodeURIComponent(d.part.toLowerCase());
if (d.check('POISSON2')) this.Poisson = kPoisson2;
if (d.check('POISSON')) this.Poisson = kPoisson;
if (d.check('SHOWEMPTY')) this.ShowEmpty = true;
if (d.check('NOOPTIMIZE')) this.Optimize = 0;
if (d.check('OPTIMIZE')) this.Optimize = 2;
if (d.check('AUTOCOL')) this.AutoColor = true;
if (d.check('AUTOZOOM')) this.AutoZoom = true;
if (d.check('OPTSTAT', true)) this.optstat = d.partAsInt();
if (d.check('OPTFIT', true)) this.optfit = d.partAsInt();
if (this.optstat || this.optfit)
histo?.SetBit(kNoStats, false);
if (d.check('ALLBINS') && histo) {
histo.fXaxis.fFirst = 0;
histo.fXaxis.fLast = histo.fXaxis.fNbins + 1;
histo.fXaxis.SetBit(EAxisBits.kAxisRange);
if (this.ndim > 1) {
histo.fYaxis.fFirst = 0;
histo.fYaxis.fLast = histo.fYaxis.fNbins + 1;
histo.fYaxis.SetBit(EAxisBits.kAxisRange);
}
if (this.ndim > 2) {
histo.fZaxis.fFirst = 0;
histo.fZaxis.fLast = histo.fZaxis.fNbins + 1;
histo.fZaxis.SetBit(EAxisBits.kAxisRange);
}
}
if (d.check('NOSTAT')) this.NoStat = true;
if (d.check('STAT')) this.ForceStat = true;
if (d.check('NOTOOLTIP'))
painter?.setTooltipAllowed(false);
if (d.check('TOOLTIP'))
painter?.setTooltipAllowed(true);
if (d.check('SYMLOGX', true)) this.SymlogX = d.partAsInt(0, 3);
if (d.check('SYMLOGY', true)) this.SymlogY = d.partAsInt(0, 3);
if (d.check('X3DSC', true)) this.x3dscale = d.partAsInt(0, 100) / 100;
if (d.check('Y3DSC', true)) this.y3dscale = d.partAsInt(0, 100) / 100;
if (d.check('PERSPECTIVE') || d.check('PERSP')) this.Ortho = false;
if (d.check('ORTHO')) this.Ortho = true;
let lx = 0, ly = 0, check3dbox = '';
if (d.check('LOG2XY')) lx = ly = 2;
if (d.check('LOGXY')) lx = ly = 1;
if (d.check('LOG2X')) lx = 2;
if (d.check('LOGX')) lx = 1;
if (d.check('LOG2Y')) ly = 2;
if (d.check('LOGY')) ly = 1;
if (lx && pad) { pad.fLogx = lx; pad.fUxmin = 0; pad.fUxmax = 1; pad.fX1 = 0; pad.fX2 = 1; }
if (ly && pad) { pad.fLogy = ly; pad.fUymin = 0; pad.fUymax = 1; pad.fY1 = 0; pad.fY2 = 1; }
if (d.check('LOG2Z') && pad) pad.fLogz = 2;
if (d.check('LOGZ') && pad) pad.fLogz = 1;
if (d.check('LOGV') && pad) pad.fLogv = 1; // fictional member, can be introduced in ROOT
if (d.check('GRIDXY') && pad) pad.fGridx = pad.fGridy = 1;
if (d.check('GRIDX') && pad) pad.fGridx = 1;
if (d.check('GRIDY') && pad) pad.fGridy = 1;
if (d.check('TICKXY') && pad) pad.fTickx = pad.fTicky = 1;
if (d.check('TICKX') && pad) pad.fTickx = 1;
if (d.check('TICKY') && pad) pad.fTicky = 1;
if (d.check('TICKZ') && pad) pad.fTickz = 1;
if (d.check('GRAYSCALE'))
pp?.setGrayscale(true);
if (d.check('FILL_', 'color')) {
this.histoFillColor = d.color;
this.histoFillPattern = 1001;
}
if (d.check('LINE_', 'color'))
this.histoLineColor = getColor(d.color);
if (d.check('WIDTH_', true))
this.histoLineWidth = d.partAsInt();
if (d.check('XAXIS_', 'color'))
histo.fXaxis.fAxisColor = histo.fXaxis.fLabelColor = histo.fXaxis.fTitleColor = d.color;
if (d.check('YAXIS_', 'color'))
histo.fYaxis.fAxisColor = histo.fYaxis.fLabelColor = histo.fYaxis.fTitleColor = d.color;
if (d.check('X+')) { this.AxisPos = 10; this.second_x = Boolean(painter?.getMainPainter()); }
if (d.check('Y+')) { this.AxisPos += 1; this.second_y = Boolean(painter?.getMainPainter()); }
if (d.check('SAME0')) { this.Same = true; this.IgnoreMainScale = true; }
if (d.check('SAMES')) { this.Same = true; this.ForceStat = true; }
if (d.check('SAME')) { this.Same = true; this.Func = true; }
if (d.check('SPEC')) this.Spec = true; // not used
if (d.check('BASE0') || d.check('MIN0'))
this.BaseLine = 0;
else if (gStyle.fHistMinimumZero)
this.BaseLine = 0;
if (d.check('PIE')) this.Pie = true; // not used
if (d.check('CANDLE', true)) this.Candle = d.part || '1';
if (d.check('VIOLIN', true)) { this.Violin = d.part || '1'; delete this.Candle; }
if (d.check('NOSCALED')) this.Scaled = false;
if (d.check('SCALED')) this.Scaled = true;
if (d.check('GLBOX', true)) this.GLBox = 10 + d.partAsInt();
if (d.check('GLCOL')) this.GLColor = true;
d.check('GL'); // suppress GL
if (d.check('CIRCULAR', true) || d.check('CIRC', true)) {
this.Circular = 11;
if (d.part.indexOf('0') >= 0) this.Circular = 10; // black and white
if (d.part.indexOf('1') >= 0) this.Circular = 11; // color
if (d.part.indexOf('2') >= 0) this.Circular = 12; // color and width
}
this.Chord = d.check('CHORD');
if (d.check('LEGO', true)) {
this.Lego = 1;
if (d.part.indexOf('0') >= 0) this.Zero = false;
if (d.part.indexOf('1') >= 0) this.Lego = 11;
if (d.part.indexOf('2') >= 0) this.Lego = 12;
if (d.part.indexOf('3') >= 0) this.Lego = 13;
if (d.part.indexOf('4') >= 0) this.Lego = 14;
check3dbox = d.part;
if (d.part.indexOf('Z') >= 0) this.Zscale = true;
if (d.part.indexOf('H') >= 0) this.Zvert = false;
}
if (d.check('R3D_', true))
this.Render3D = constants.Render3D.fromString(d.part.toLowerCase());
if (d.check('POL')) this.System = kPOLAR;
if (d.check('CYL')) this.System = kCYLINDRICAL;
if (d.check('SPH')) this.System = kSPHERICAL;
if (d.check('PSR')) this.System = kRAPIDITY;
if (d.check('SURF', true)) {
this.Surf = d.partAsInt(10, 1);
check3dbox = d.part;
if (d.part.indexOf('Z') >= 0) this.Zscale = true;
if (d.part.indexOf('H') >= 0) this.Zvert = false;
}
if (d.check('TF3', true)) check3dbox = d.part;
if (d.check('ISO', true)) check3dbox = d.part;
if (d.check('LIST')) this.List = true; // not used
if (d.check('CONT', true) && (hdim > 1)) {
this.Contour = 1;
if (d.part.indexOf('Z') >= 0) this.Zscale = true;
if (d.part.indexOf('H') >= 0) this.Zvert = false;
if (d.part.indexOf('1') >= 0) this.Contour = 11; else
if (d.part.indexOf('2') >= 0) this.Contour = 12; else
if (d.part.indexOf('3') >= 0) this.Contour = 13; else
if (d.part.indexOf('4') >= 0) this.Contour = 14;
}
// decode bar/hbar option
if (d.check('HBAR', true))
this.BarStyle = 20;
else if (d.check('BAR', true))
this.BarStyle = 10;
if (this.BarStyle > 0) {
this.Hist = false;
this.need_fillcol = true;
this.BarStyle += d.partAsInt();
}
if (d.check('ARR'))
this.Arrow = true;
if (d.check('BOX', true)) {
this.BoxStyle = 10;
if (d.part.indexOf('1') >= 0) this.BoxStyle = 11; else
if (d.part.indexOf('2') >= 0) this.BoxStyle = 12; else
if (d.part.indexOf('3') >= 0) this.BoxStyle = 13;
if (d.part.indexOf('Z') >= 0) this.Zscale = true;
if (d.part.indexOf('H') >= 0) this.Zvert = false;
}
this.Box = this.BoxStyle > 0;
if (d.check('CJUST')) this.Cjust = true;
if (d.check('COL7')) this.Color = 7; // special color mode with use of bar offset
if (d.check('COL')) this.Color = true;
if (d.check('CHAR')) this.Char = 1;
if (d.check('ALLFUNC')) this.AllFunc = true;
if (d.check('FUNC')) { this.Func = true; this.Hist = false; }
if (d.check('HAXISG')) { this.Axis = 3; this.SwapXY = 1; }
if (d.check('HAXIS')) { this.Axis = 1; this.SwapXY = 1; }
if (d.check('HAXIG')) { this.Axis = 2; this.SwapXY = 1; }
if (d.check('AXISG')) this.Axis = 3;
if (d.check('AXIS')) this.Axis = 1;
if (d.check('AXIG')) this.Axis = 2;
if (d.check('TEXT', true)) {
this.Text = true;
this.Hist = false;
this.TextAngle = Math.min(d.partAsInt(), 90);
if (d.part.indexOf('N') >= 0) this.TextKind = 'N';
if (d.part.indexOf('E0') >= 0) this.TextLine = true;
if (d.part.indexOf('E') >= 0) this.TextKind = 'E';
}
if (d.check('SCAT=', true)) {
this.Scat = true;
this.ScatCoef = parseFloat(d.part);
if (!Number.isFinite(this.ScatCoef) || (this.ScatCoef <= 0)) this.ScatCoef = 1.0;
}
if (d.check('SCAT')) this.Scat = true;
if (d.check('TRI', true)) {
this.Color = false;
this.Tri = 1;
check3dbox = d.part;
if (d.part.indexOf('ERR') >= 0) this.Error = true;
}
if (d.check('AITOFF')) this.Proj = 1;
if (d.check('MERCATOR')) this.Proj = 2;
if (d.check('SINUSOIDAL')) this.Proj = 3;
if (d.check('PARABOLIC')) this.Proj = 4;
if (d.check('MOLLWEIDE')) this.Proj = 5;
if (this.Proj > 0) this.Contour = 14;
if (d.check('PROJXY', true)) {
let flag = true;
if ((histo?._typename === clTProfile2D) && d.part && !Number.isInteger(Number.parseInt(d.part))) {
this.Profile2DProj = d.part;
flag = d.check('PROJXY', true); // allow projxy with projected profile2d
}
if (flag)
this.Project = 'XY' + d.partAsInt(0, 1);
}
if (d.check('PROJX', true)) {
if (histo?._typename === clTProfile)
this.ProfileProj = d.part || 'B';
else
this.Project = 'X' + d.part;
}
if (d.check('PROJY', true)) this.Project = 'Y' + d.part;
if (d.check('PROJ')) this.Project = 'Y1';
if (check3dbox) {
if (check3dbox.indexOf('FB') >= 0) this.FrontBox = false;
if (check3dbox.indexOf('BB') >= 0) this.BackBox = false;
}
if ((hdim === 3) && d.check('FB')) this.FrontBox = false;
if ((hdim === 3) && d.check('BB')) this.BackBox = false;
if (d.check('PFC') && !this._pfc)
this._pfc = 2;
if ((d.check('PLC') || this.AutoColor) && !this._plc)
this._plc = 2;
if (d.check('PMC') && !this._pmc)
this._pmc = 2;
const check_axis_bit = (aopt, axis, bit) => {
// ignore Z scale options for 2D plots
if ((axis === 'fZaxis') && (hdim < 3) && !this.Lego && !this.Surf)
return;
let flag = d.check(aopt);
if (pad && pad['$'+aopt]) {
flag = true;
pad['$'+aopt] = undefined;
}
if (flag && histo)
histo[axis].SetBit(bit, true);
};
check_axis_bit('OTX', 'fXaxis', EAxisBits.kOppositeTitle);
check_axis_bit('OTY', 'fYaxis', EAxisBits.kOppositeTitle);
check_axis_bit('OTZ', 'fZaxis', EAxisBits.kOppositeTitle);
check_axis_bit('CTX', 'fXaxis', EAxisBits.kCenterTitle);
check_axis_bit('CTY', 'fYaxis', EAxisBits.kCenterTitle);
check_axis_bit('CTZ', 'fZaxis', EAxisBits.kCenterTitle);
check_axis_bit('MLX', 'fXaxis', EAxisBits.kMoreLogLabels);
check_axis_bit('MLY', 'fYaxis', EAxisBits.kMoreLogLabels);
check_axis_bit('MLZ', 'fZaxis', EAxisBits.kMoreLogLabels);
check_axis_bit('NOEX', 'fXaxis', EAxisBits.kNoExponent);
check_axis_bit('NOEY', 'fYaxis', EAxisBits.kNoExponent);
check_axis_bit('NOEZ', 'fZaxis', EAxisBits.kNoExponent);
if (d.check('RX') || pad?.$RX) this.RevX = true;
if (d.check('RY') || pad?.$RY) this.RevY = true;
if (d.check('L')) { this.Line = true; this.Hist = false; }
if (d.check('F')) { this.Fill = true; this.need_fillcol = true; }
if (d.check('A')) this.Axis = -1;
if (pad?.$ratio_pad === 'up') {
if (!this.Same) this.Axis = 0; // draw both axes
histo.fXaxis.fLabelSize = 0;
histo.fXaxis.fTitle = '';
histo.fYaxis.$use_top_pad = true;
} else if (pad?.$ratio_pad === 'low') {
if (!this.Same) this.Axis = 0; // draw both axes
histo.fXaxis.$use_top_pad = true;
histo.fYaxis.$use_top_pad = true;
histo.fXaxis.fTitle = 'x';
const fp = painter?.getCanvPainter().findPainterFor(null, 'upper_pad', clTPad)?.getFramePainter();
if (fp) {
painter.zoom_xmin = fp.scale_xmin;
painter.zoom_xmax = fp.scale_xmax;
}
}
if (d.check('B1')) { this.BarStyle = 1; this.BaseLine = 0; this.Hist = false; this.need_fillcol = true; }
if (d.check('B')) { this.BarStyle = 1; this.Hist = false; this.need_fillcol = true; }
if (d.check('C')) { this.Curve = true; this.Hist = false; }
if (d.check('][')) { this.Off = 1; this.Hist = true; }
if (d.check('HIST')) { this.Hist = true; this.Func = true; this.Error = false; }
this.Bar = (this.BarStyle > 0);
delete this.MarkStyle; // remove mark style if any
if (d.check('P0')) { this.Mark = true; this.Hist = false; this.Zero = true; }
if (d.check('P')) { this.Mark = true; this.Hist = false; this.Zero = false; }
if (d.check('HZ')) { this.Zscale = true; this.Zvert = false; }
if (d.check('Z')) this.Zscale = true;
if (d.check('*')) { this.Mark = true; this.MarkStyle = 3; this.Hist = false; }
if (d.check('H')) this.Hist = true;
if (d.check('E', true)) {
this.Error = true;
if (hdim === 1) {
this.Zero = false; // do not draw empty bins with errors
if (this.Hist === 1) this.Hist = false;
if (Number.isInteger(parseInt(d.part[0])))
this.ErrorKind = parseInt(d.part[0]);
if ((this.ErrorKind === 3) || (this.ErrorKind === 4)) this.need_fillcol = true;
if (this.ErrorKind === 0) this.Zero = true; // enable drawing of empty bins
if (d.part.indexOf('X0') >= 0) this.errorX = 0;
}
}
if (d.check('9')) this.HighRes = 1;
if (d.check('0')) this.Zero = false;
if (this.Color && d.check('1')) this.Zero = false;
// flag identifies 3D drawing mode for histogram
if ((this.Lego > 0) || (hdim === 3) ||
(((this.Surf > 0) || this.Error) && (hdim === 2))) this.Mode3D = true;
// default draw options for TF1 is line and fill
if (painter?.isTF1() && (hdim === 1) && (this.Hist === 1) && !this.Line && !this.Fill && !this.Curve && !this.Mark) {
this.Hist = false;
this.Curve = settings.FuncAsCurve;
this.Line = !this.Curve;
this.Fill = true;
}
if ((this.Surf === 15) && (this.System === kPOLAR || this.System === kCARTESIAN))
this.Surf = 13;
}
/** @summary Is X/Y swap is configured */
swap_xy() {
return this.BarStyle >= 20 || this.SwapXY;
}
/** @summary Tries to reconstruct string with hist draw options */
asString(is_main_hist, pad) {
let res = '', zopt = '';
if (this.Zscale)
zopt = this.Zvert ? 'Z' : 'HZ';
if (this.Mode3D) {
if (this.Lego) {
res = 'LEGO';
if (!this.Zero) res += '0';
if (this.Lego > 10) res += (this.Lego-10);
res += zopt;
} else if (this.Surf) {
res = 'SURF' + (this.Surf-10);
res += zopt;
}
if (!this.FrontBox) res += 'FB';
if (!this.BackBox) res += 'BB';
if (this.x3dscale !== 1) res += `_X3DSC${Math.round(this.x3dscale * 100)}`;
if (this.y3dscale !== 1) res += `_Y3DSC${Math.round(this.y3dscale * 100)}`;
} else {
if (this.Candle)
res = 'CANDLE' + this.Candle;
else if (this.Violin)
res = 'VIOLIN' + this.Violin;
else if (this.Scat)
res = 'SCAT';
else if (this.Color) {
res = 'COL';
if (!this.Zero) res += '0';
res += zopt;
if (this.Axis < 0) res += 'A';
} else if (this.Contour) {
res = 'CONT';
if (this.Contour > 10) res += (this.Contour-10);
res += zopt;
} else if (this.Bar)
res = (this.BaseLine === false) ? 'B' : 'B1';
else if (this.Mark)
res = this.Zero ? 'P0' : 'P'; // here invert logic with 0
else if (this.Line) {
res += 'L';
if (this.Fill) res += 'F';
} else if (this.Off)
res = '][';
if (this.Error) {
res += 'E';
if (this.ErrorKind >= 0)
res += this.ErrorKind;
if (this.errorX === 0)
res += 'X0';
}
if (this.Cjust)
res += ' CJUST';
if (this.Hist === true)
res += 'HIST';
if (this.Text) {
res += 'TEXT';
if (this.TextAngle) res += this.TextAngle;
res += this.TextKind;
}
}
if (this.Palette && this.canHavePalette())
res += `_PAL${this.Palette}`;
if (this.is3d() && this.Ortho && is_main_hist)
res += '_ORTHO';
if (this.ProfileProj)
res += '_PROJX' + this.ProfileProj;
if (this.Profile2DProj)
res += '_PROJXY' + this.Profile2DProj;
if (this.Proj)
res += '_PROJ' + this.Proj;
if (this.ShowEmpty)
res += '_SHOWEMPTY';
if (this.Same)
res += this.ForceStat ? 'SAMES' : 'SAME';
else if (is_main_hist && res) {
if (this.ForceStat || (this.StatEnabled === true))
res += '_STAT';
else if (this.NoStat || (this.StatEnabled === false))
res += '_NOSTAT';
}
if (is_main_hist && pad && res) {
if (pad.fLogx === 2)
res += '_LOG2X';
else if (pad.fLogx)
res += '_LOGX';
if (pad.fLogy === 2)
res += '_LOG2Y';
else if (pad.fLogy)
res += '_LOGY';
if (pad.fLogz === 2)
res += '_LOG2Z';
else if (pad.fLogz)
res += '_LOGZ';
if (pad.fGridx) res += '_GRIDX';
if (pad.fGridy) res += '_GRIDY';
if (pad.fTickx) res += '_TICKX';
if (pad.fTicky) res += '_TICKY';
if (pad.fTickz) res += '_TICKZ';
}
if (this.cutg_name)
res += ` [${this.cutg_name}]`;
return res;
}
} // class THistDrawOptions
/**
* @summary Handle for histogram contour
*
* @private
*/
class HistContour {
constructor(zmin, zmax) {
this.arr = [];
this.colzmin = zmin;
this.colzmax = zmax;
this.below_min_indx = -1;
this.exact_min_indx = 0;
}
/** @summary Returns contour levels */
getLevels() { return this.arr; }
/** @summary Create normal contour levels */
createNormal(nlevels, log_scale, zminpositive) {
if (log_scale) {
if (this.colzmax <= 0)
this.colzmax = 1.0;
if (this.colzmin <= 0) {
if ((zminpositive === undefined) || (zminpositive <= 0))
this.colzmin = 0.0001*this.colzmax;
else
this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3*zminpositive : 1;
}
if (this.colzmin >= this.colzmax)
this.colzmin = 0.0001*this.colzmax;
const logmin = Math.log(this.colzmin)/Math.log(10),
logmax = Math.log(this.colzmax)/Math.log(10),
dz = (logmax-logmin)/nlevels;
this.arr.push(this.colzmin);
for (let level = 1; level < nlevels; level++)
this.arr.push(Math.exp((logmin + dz*level)*Math.log(10)));
this.arr.push(this.colzmax);
this.custom = true;
} else {
if ((this.colzmin === this.colzmax) && (this.colzmin !== 0)) {
this.colzmax += 0.01*Math.abs(this.colzmax);
this.colzmin -= 0.01*Math.abs(this.colzmin);
}
const dz = (this.colzmax-this.colzmin)/nlevels;
for (let level = 0; level <= nlevels; level++)
this.arr.push(this.colzmin + dz*level);
}
}
/** @summary Create custom contour levels */
createCustom(levels) {
this.custom = true;
for (let n = 0; n < levels.length; ++n)
this.arr.push(levels[n]);
if (this.colzmax > this.arr.at(-1))
this.arr.push(this.colzmax);
}
/** @summary Configure indices */
configIndicies(below_min, exact_min) {
this.below_min_indx = below_min;
this.exact_min_indx = exact_min;
}
/** @summary Get index based on z value */
getContourIndex(zc) {
// bins less than zmin not drawn
if (zc < this.colzmin)
return this.below_min_indx;
// if bin content exactly zmin, draw it when col0 specified or when content is positive
if (zc === this.colzmin)
return this.exact_min_indx;
if (!this.custom)
return Math.floor(0.01 + (zc - this.colzmin) * (this.arr.length - 1) / (this.colzmax - this.colzmin));
let l = 0, r = this.arr.length - 1;
if (zc < this.arr[0]) return -1;
if (zc >= this.arr[r]) return r;
while (l < r-1) {
const mid = Math.round((l+r)/2);
if (this.arr[mid] > zc) r = mid; else l = mid;
}
return l;
}
/** @summary Get palette color */
getPaletteColor(palette, zc) {
const zindx = this.getContourIndex(zc);
if (zindx < 0) return null;
const pindx = palette.calcColorIndex(zindx, this.arr.length);
return palette.getColor(pindx);
}
/** @summary Get palette index */
getPaletteIndex(palette, zc) {
const zindx = this.getContourIndex(zc);
return (zindx < 0) ? null : palette.calcColorIndex(zindx, this.arr.length);
}
} // class HistContour
/**
* @summary Handle for updating of secondary functions
*
* @private
*/
class FunctionsHandler {
constructor(painter, pp, funcs, statpainter) {
this.painter = painter;
this.pp = pp;
const painters = [], update_painters = [],
only_draw = (statpainter === true);
this.newfuncs = [];
this.newopts = [];
// find painters associated with histogram/graph/...
if (!only_draw) {
pp?.forEachPainterInPad(objp => {
if (objp.isSecondary(painter) && objp.getSecondaryId()?.match(/^func_|^indx_/))
painters.push(objp);
}, 'objects');
}
for (let n = 0; n < funcs?.arr.length; ++n) {
const func = funcs.arr[n], fopt = funcs.opt[n];
if (!func?._typename) continue;
if (isFunc(painter.needDrawFunc) && !painter.needDrawFunc(painter.getObject(), func)) continue;
let funcpainter = null, func_indx = -1;
if (!only_draw) {
// try to find matching object in associated list of painters
for (let i = 0; i < painters.length; ++i) {
if (painters[i].matchObjectType(func._typename) && (painters[i].getObjectName() === func.fName)) {
funcpainter = painters[i];
func_indx = i;
break;
}
}
// or just in generic list of painted objects
if (!funcpainter && func.fName)
funcpainter = pp?.findPainterFor(null, func.fName, func._typename);
}
if (funcpainter) {
funcpainter.updateObject(func, fopt);
if (func_indx >= 0) {
painters.splice(func_indx, 1);
update_painters.push(funcpainter);
}
} else {
// use arrays index while index is important
this.newfuncs[n] = func;
this.newopts[n] = fopt;
}
}
// stat painter has to be kept even when no object exists in the list
if (isObject(statpainter)) {
const indx = painters.indexOf(statpainter);
if (indx >= 0) painters.splice(indx, 1);
}
// remove all function which are not found in new list of functions
if (painters.length > 0)
pp?.cleanPrimitives(p => painters.indexOf(p) >= 0);
if (update_painters.length > 0)
this._extraPainters = update_painters;
}
/** @summary Draw/update functions selected before */
drawNext(indx) {
if (this._extraPainters) {
const p = this._extraPainters.shift();
if (this._extraPainters.length === 0)
delete this._extraPainters;
return getPromise(p.redraw()).then(() => this.drawNext(0));
}
if (!this.newfuncs || (indx >= this.newfuncs.length)) {
delete this.newfuncs;
delete this.newopts;
return Promise.resolve(this.painter); // simplify drawing
}
const func = this.newfuncs[indx], fopt = this.newopts[indx];
if (!func || this.pp?.findPainterFor(func))
return this.drawNext(indx+1);
const func_id = func?.fName ? `func_${func.fName}` : `indx_${indx}`;
// Required to correctly draw multiple stats boxes
// TODO: set reference via weak pointer
func.$main_painter = this.painter;
const promise = TPavePainter.canDraw(func)
? TPavePainter.draw(this.pp, func, fopt)
: this.pp.drawObject(this.pp, func, fopt);
return promise.then(fpainter => {
fpainter.setSecondaryId(this.painter, func_id);
return this.drawNext(indx+1);
});
}
} // class FunctionsHandler
// TH1 bits
// kNoStats = BIT(9), don't draw stats box
const kUserContour = BIT(10), // user specified contour levels
// kCanRebin = BIT(11), // can rebin axis
// kLogX = BIT(15), // X-axis in log scale
kIsZoomed = BIT(16), // bit set when zooming on Y axis
kNoTitle = BIT(17); // don't draw the histogram title
// kIsAverage = BIT(18); // Bin contents are average (used by Add)
/**
* @summary Basic painter for histogram classes
* @private
*/
class THistPainter extends ObjectPainter {
/** @summary Constructor
* @param {object|string} dom - DOM element for drawing or element id
* @param {object} histo - TH1 derived histogram object */
constructor(dom, histo) {
super(dom, histo);
this.draw_content = true;
this.nbinsx = this.nbinsy = 0;
this.mode3d = false;
}
/** @summary Returns histogram object */
getHisto() {
return this.getObject();
}
/** @summary Returns histogram axis */
getAxis(name) {
const histo = this.getObject();
switch (name) {
case 'x': return histo?.fXaxis;
case 'y': return histo?.fYaxis;
case 'z': return histo?.fZaxis;
}
return null;
}
/** @summary Returns true if TProfile */
isTProfile() {
return this.matchObjectType(clTProfile);
}
/** @summary Returns true if histogram drawn instead of TF1/TF2 object */
isTF1() { return false; }
/** @summary Returns true if TH1K */
isTH1K() { return this.matchObjectType('TH1K'); }
/** @summary Returns true if TH2Poly */
isTH2Poly() {
return this.matchObjectType(/^TH2Poly/) || this.matchObjectType(/^TProfile2Poly/);
}
/** @summary Clear 3d drawings - if any */
clear3DScene() {
const fp = this.getFramePainter();
if (isFunc(fp?.create3DScene))
fp.create3DScene(-1);
this.mode3d = false;
}
/** @summary Cleanup histogram painter */
cleanup() {
this.clear3DScene();
delete this._color_palette;
delete this.fContour;
delete this.options;
super.cleanup();
}
/** @summary Returns number of histogram dimensions */
getDimension() {
const histo = this.getHisto();
if (!histo) return 0;
if (histo._typename.match(/^TH2/)) return 2;
if (histo._typename === clTProfile2D) return 2;
if (histo._typename.match(/^TH3/)) return 3;
if (histo._typename === clTProfile3D) return 3;
if (this.isTH2Poly()) return 2;
return 1;
}
/** @summary Decode options string opt and fill the option structure */
decodeOptions(opt) {
const histo = this.getHisto(),
hdim = this.getDimension(),
pp = this.getPadPainter(),
pad = pp?.getRootPad(true);
if (!this.options)
this.options = new THistDrawOptions();
else
this.options.reset();
// when changing draw option, reset attributes usage
this.lineatt?.setUsed(false);
this.fillatt?.setUsed(false);
this.markeratt?.setUsed(false);
this.options.decode(opt || histo.fOption, hdim, histo, pp, pad, this);
this.storeDrawOpt(opt); // opt will be return as default draw option, used in web canvas
}
/** @summary Copy draw options from other painter */
copyOptionsFrom(src) {
if (src === this) return;
const o = this.options, o0 = src.options;
o.Mode3D = o0.Mode3D;
o.Zero = o0.Zero;
if (o0.Mode3D) {
o.Lego = o0.Lego;
o.Surf = o0.Surf;
} else {
o.Color = o0.Color;
o.Contour = o0.Contour;
}
}
/** @summary copy draw options to all other histograms in the pad */
copyOptionsToOthers() {
this.forEachPainter(painter => {
if ((painter !== this) && isFunc(painter.copyOptionsFrom))
painter.copyOptionsFrom(this);
}, 'objects');
}
/** @summary Scan histogram content
* @abstract */
scanContent(/* when_axis_changed */) {
// function will be called once new histogram or
// new histogram content is assigned
// one should find min, max, bins number, content min/max values
// if when_axis_changed === true specified, content will be scanned after axis zoom changed
}
/** @summary Check pad ranges when drawing of frame axes will be performed
* @desc Only if histogram is main painter and drawn with SAME option, pad range can be used
* In all other cases configured range must be derived from histogram itself */
checkPadRange() {
if (this.isMainPainter())
this.check_pad_range = this.options.Same ? 'pad_range' : true;
}
/** @summary Create necessary histogram draw attributes */
createHistDrawAttributes(only_check_auto) {
const histo = this.getHisto(), o = this.options;
if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) {
const pp = this.getPadPainter();
if (isFunc(pp?.getAutoColor)) {
const icolor = pp.getAutoColor(histo.$num_histos);
this._auto_exec = ''; // can be reused when sending option back to server
if (o._pfc > 1) { o._pfc = 1; histo.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; }
if (o._plc > 1) { o._plc = 1; histo.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; }
if (o._pmc > 1) { o._pmc = 1; histo.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; }
}
}
if (only_check_auto)
this.deleteAttr();
else {
this.createAttFill({ attr: histo, color: this.options.histoFillColor, pattern: this.options.histoFillPattern, kind: 1 });
this.createAttLine({ attr: histo, color0: this.options.histoLineColor, width: this.options.histoLineWidth });
}
}
/** @summary Update axes attributes in target histogram
* @private */
updateAxes(tgt_histo, src_histo, fp) {
const copyTAxisMembers = (tgt, src, copy_zoom) => {
tgt.fTitle = src.fTitle;
tgt.fLabels = src.fLabels;
tgt.fXmin = src.fXmin;
tgt.fXmax = src.fXmax;
tgt.fTimeDisplay = src.fTimeDisplay;
tgt.fTimeFormat = src.fTimeFormat;
tgt.fAxisColor = src.fAxisColor;
tgt.fLabelColor = src.fLabelColor;
tgt.fLabelFont = src.fLabelFont;
tgt.fLabelOffset = src.fLabelOffset;
tgt.fLabelSize = src.fLabelSize;
tgt.fNdivisions = src.fNdivisions;
tgt.fTickLength = src.fTickLength;
tgt.fTitleColor = src.fTitleColor;
tgt.fTitleFont = src.fTitleFont;
tgt.fTitleOffset = src.fTitleOffset;
tgt.fTitleSize = src.fTitleSize;
if (copy_zoom) {
tgt.fFirst = src.fFirst;
tgt.fLast = src.fLast;
tgt.fBits = src.fBits;
}
};
copyTAxisMembers(tgt_histo.fXaxis, src_histo.fXaxis, this.snapid && !fp?.zoomChangedInteractive('x'));
copyTAxisMembers(tgt_histo.fYaxis, src_histo.fYaxis, this.snapid && !fp?.zoomChangedInteractive('y'));
copyTAxisMembers(tgt_histo.fZaxis, src_histo.fZaxis, this.snapid && !fp?.zoomChangedInteractive('z'));
}
/** @summary Update histogram object
* @param obj - new histogram instance
* @param opt - new drawing option (optional)
* @return {Boolean} - true if histogram was successfully updated */
updateObject(obj, opt) {
const histo = this.getHisto(),
fp = this.getFramePainter(),
pp = this.getPadPainter(),
o = this.options;
if (obj !== histo) {
if (!this.matchObjectType(obj)) return false;
// simple replace of object does not help - one can have different
// complex relations between histogram and stat box, histogram and colz axis,
// one could have THStack or TMultiGraph object
// The only that could be done is update of content
const statpainter = pp?.findPainterFor(this.findStat());
// copy histogram bits
if (histo.TestBit(kNoStats) !== obj.TestBit(kNoStats)) {
histo.SetBit(kNoStats, obj.TestBit(kNoStats));
// here check only stats bit
if (statpainter) {
statpainter.Enabled = !histo.TestBit(kNoStats) && !this.options.NoStat; // && (!this.options.Same || this.options.ForceStat)
// remove immediately when redraw not called for disabled stats
if (!statpainter.Enabled)
statpainter.removeG();
}
}
histo.SetBit(kIsZoomed, obj.TestBit(kIsZoomed));
// special treatment for web canvas - also name can be changed
if (this.snapid !== undefined) {
histo.fName = obj.fName;
o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas
}
if (!o._pfc)
histo.fFillColor = obj.fFillColor;
histo.fFillStyle = obj.fFillStyle;
if (!o._plc)
histo.fLineColor = obj.fLineColor;
histo.fLineStyle = obj.fLineStyle;
histo.fLineWidth = obj.fLineWidth;
if (!o._pmc)
histo.fMarkerColor = obj.fMarkerColor;
histo.fMarkerSize = obj.fMarkerSize;
histo.fMarkerStyle = obj.fMarkerStyle;
histo.fEntries = obj.fEntries;
histo.fTsumw = obj.fTsumw;
histo.fTsumwx = obj.fTsumwx;
histo.fTsumwx2 = obj.fTsumwx2;
histo.fXaxis.fNbins = obj.fXaxis.fNbins;
if (this.getDimension() > 1) {
histo.fTsumwy = obj.fTsumwy;
histo.fTsumwy2 = obj.fTsumwy2;
histo.fTsumwxy = obj.fTsumwxy;
histo.fYaxis.fNbins = obj.fYaxis.fNbins;
if (this.getDimension() > 2) {
histo.fTsumwz = obj.fTsumwz;
histo.fTsumwz2 = obj.fTsumwz2;
histo.fTsumwxz = obj.fTsumwxz;
histo.fTsumwyz = obj.fTsumwyz;
histo.fZaxis.fNbins = obj.fZaxis.fNbins;
}
}
this.updateAxes(histo, obj, fp);
histo.fArray = obj.fArray;
histo.fNcells = obj.fNcells;
histo.fTitle = obj.fTitle;
histo.fMinimum = obj.fMinimum;
histo.fMaximum = obj.fMaximum;
histo.fSumw2 = obj.fSumw2;
if (!o.ominimum)
o.minimum = histo.fMinimum;
if (!o.omaximum)
o.maximum = histo.fMaximum;
if (this.getDimension() === 1)
o.decodeSumw2(histo);
if (this.isTProfile())
histo.fBinEntries = obj.fBinEntries;
else if (this.isTH1K()) {
histo.fNIn = obj.fNIn;
histo.fReady = 0;
} else if (this.isTH2Poly())
histo.fBins = obj.fBins;
// remove old functions, update existing, prepare to draw new one
this._funcHandler = new FunctionsHandler(this, pp, obj.fFunctions, statpainter);
const changed_opt = (histo.fOption !== obj.fOption);
histo.fOption = obj.fOption;
if (((opt !== undefined) && (o.original !== opt)) || changed_opt)
this.decodeOptions(opt || histo.fOption);
}
if (!o.ominimum)
o.minimum = histo.fMinimum;
if (!o.omaximum)
o.maximum = histo.fMaximum;
if (!o.ominimum && !o.omaximum && o.minimum === o.maximum)
o.minimum = o.maximum = kNoZoom;
if (!fp || !fp.zoomChangedInteractive())
this.checkPadRange();
this.scanContent();
this.histogram_updated = true; // indicate that object updated
return true;
}
/** @summary Access or modify histogram min/max
* @private */
accessMM(ismin, v) {
const name = ismin ? 'minimum' : 'maximum';
if (v === undefined)
return this.options[name];
this.options[name] = v;
this.interactiveRedraw('pad', ismin ? `exec:SetMinimum(${v})` : `exec:SetMaximum(${v})`);
}
/** @summary Extract axes bins and ranges
* @desc here functions are defined to convert index to axis value and back
* was introduced to support non-equidistant bins */
extractAxesProperties(ndim) {
const assignTAxisFuncs = axis => {
if (axis.fXbins.length >= axis.fNbins) {
axis.GetBinCoord = function(bin) {
const indx = Math.round(bin);
if (indx <= 0) return this.fXmin;
if (indx > this.fNbins) return this.fXmax;
if (indx === bin) return this.fXbins[indx];
const indx2 = (bin < indx) ? indx - 1 : indx + 1;
return this.fXbins[indx] * Math.abs(bin-indx2) + this.fXbins[indx2] * Math.abs(bin-indx);
};
axis.FindBin = function(x, add) {
for (let k = 1; k < this.fXbins.length; ++k)
if (x < this.fXbins[k]) return Math.floor(k-1+add);
return this.fNbins;
};
} else {
axis.$binwidth = (axis.fXmax - axis.fXmin) / (axis.fNbins || 1);
axis.GetBinCoord = function(bin) { return this.fXmin + bin*this.$binwidth; };
axis.FindBin = function(x, add) { return Math.floor((x - this.fXmin) / this.$binwidth + add); };
}
};
this.nbinsx = this.nbinsy = this.nbinsz = 0;
const histo = this.getHisto();
this.nbinsx = histo.fXaxis.fNbins;
this.xmin = histo.fXaxis.fXmin;
this.xmax = histo.fXaxis.fXmax;
if (histo.fXaxis.TestBit(EAxisBits.kAxisRange) && (histo.fXaxis.fFirst !== histo.fXaxis.fLast)) {
if (histo.fXaxis.fFirst === 0)
this.xmin = histo.fXaxis.GetBinLowEdge(0);
if (histo.fXaxis.fLast === this.nbinsx + 1)
this.xmax = histo.fXaxis.GetBinLowEdge(this.nbinsx + 2);
}
assignTAxisFuncs(histo.fXaxis);
this.ymin = histo.fYaxis.fXmin;
this.ymax = histo.fYaxis.fXmax;
this._exact_y_range = (ndim === 1) && this.options.ohmin && this.options.ohmax;
if (this._exact_y_range) {
this.ymin = this.options.hmin;
this.ymax = this.options.hmax;
}
if (ndim > 1) {
this.nbinsy = histo.fYaxis.fNbins;
if (histo.fYaxis.TestBit(EAxisBits.kAxisRange) && (histo.fYaxis.fFirst !== histo.fYaxis.fLast)) {
if (histo.fYaxis.fFirst === 0)
this.ymin = histo.fYaxis.GetBinLowEdge(0);
if (histo.fYaxis.fLast === this.nbinsy + 1)
this.ymax = histo.fYaxis.GetBinLowEdge(this.nbinsy + 2);
}
assignTAxisFuncs(histo.fYaxis);
this.zmin = histo.fZaxis.fXmin;
this.zmax = histo.fZaxis.fXmax;
if ((ndim === 2) && this.options.ohmin && this.options.ohmax) {
this.zmin = this.options.hmin;
this.zmax = this.options.hmax;
}
}
if (ndim > 2) {
this.nbinsz = histo.fZaxis.fNbins;
if (histo.fZaxis.TestBit(EAxisBits.kAxisRange) && (histo.fZaxis.fFirst !== histo.fZaxis.fLast)) {
if (histo.fZaxis.fFirst === 0)
this.zmin = histo.fZaxis.GetBinLowEdge(0);
if (histo.fZaxis.fLast === this.nbinsz + 1)
this.zmax = histo.fZaxis.GetBinLowEdge(this.nbinsz + 2);
}
assignTAxisFuncs(histo.fZaxis);
}
}
/** @summary Draw axes for histogram
* @desc axes can be drawn only for main histogram */
async drawAxes() {
const fp = this.getFramePainter();
if (!fp) return false;
const histo = this.getHisto();
// artificially add y range to display axes
if (this.ymin === this.ymax)
this.ymax += 1;
if (!this.isMainPainter()) {
const opts = {
second_x: (this.options.AxisPos >= 10),
second_y: (this.options.AxisPos % 10) === 1,
hist_painter: this
};
if ((!opts.second_x && !opts.second_y) || fp.hasDrawnAxes(opts.second_x, opts.second_y))
return false;
fp.setAxes2Ranges(opts.second_x, histo.fXaxis, this.xmin, this.xmax, opts.second_y, histo.fYaxis, this.ymin, this.ymax);
fp.createXY2(opts);
return fp.drawAxes2(opts.second_x, opts.second_y);
}
fp.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, 0, 0);
fp.createXY({ ndim: this.getDimension(),
check_pad_range: this.check_pad_range,
zoom_xmin: this.zoom_xmin,
zoom_xmax: this.zoom_xmax,
zoom_ymin: this.zoom_ymin,
zoom_ymax: this.zoom_ymax,
xmin_nz: histo.$xmin_nz,
ymin_nz: this.ymin_nz ?? histo.$ymin_nz,
swap_xy: this.options.swap_xy(),
reverse_x: this.options.RevX,
reverse_y: this.options.RevY,
symlog_x: this.options.SymlogX,
symlog_y: this.options.SymlogY,
Proj: this.options.Proj,
extra_y_space: this.options.Text && (this.options.BarStyle > 0),
hist_painter: this });
delete this.check_pad_range;
delete this.zoom_xmin;
delete this.zoom_xmax;
delete this.zoom_ymin;
delete this.zoom_ymax;
if (this.options.Same)
return false;
const disable_axis_draw = (this.options.Axis < 0) || (this.options.Axis === 2);
return fp.drawAxes(false, disable_axis_draw, disable_axis_draw,
this.options.AxisPos, this.options.Zscale && this.options.Zvert,
this.options.Zscale && !this.options.Zvert, this.options.Axis !== 1);
}
/** @summary Inform web canvas that something changed in the histogram */
processOnlineChange(kind) {
const cp = this.getCanvPainter();
if (isFunc(cp?.processChanges))
cp.processChanges(kind, this);
}
/** @summary Fill option object used in TWebCanvas */
fillW