import { gStyle, settings, isObject, isFunc, isStr, nsREX, getPromise } from '../core.mjs'; import { RObjectPainter } from '../base/RObjectPainter.mjs'; /** @summary assign methods for the RAxis objects * @private */ function assignRAxisMethods(axis) { if ((axis._typename === `${nsREX}RAxisEquidistant`) || (axis._typename === `${nsREX}RAxisLabels`)) { if (axis.fInvBinWidth === 0) { axis.$dummy = true; axis.fInvBinWidth = 1; axis.fNBinsNoOver = 0; axis.fLow = 0; } axis.min = axis.fLow; axis.max = axis.fLow + axis.fNBinsNoOver/axis.fInvBinWidth; axis.GetNumBins = function() { return this.fNBinsNoOver; }; axis.GetBinCoord = function(bin) { return this.fLow + bin/this.fInvBinWidth; }; axis.FindBin = function(x, add) { return Math.floor((x - this.fLow)*this.fInvBinWidth + add); }; } else if (axis._typename === `${nsREX}RAxisIrregular`) { axis.min = axis.fBinBorders[0]; axis.max = axis.fBinBorders[axis.fBinBorders.length - 1]; axis.GetNumBins = function() { return this.fBinBorders.length; }; axis.GetBinCoord = function(bin) { const indx = Math.round(bin); if (indx <= 0) return this.fBinBorders[0]; if (indx >= this.fBinBorders.length) return this.fBinBorders[this.fBinBorders.length - 1]; if (indx === bin) return this.fBinBorders[indx]; const indx2 = (bin < indx) ? indx - 1 : indx + 1; return this.fBinBorders[indx] * Math.abs(bin-indx2) + this.fBinBorders[indx2] * Math.abs(bin-indx); }; axis.FindBin = function(x, add) { for (let k = 1; k < this.fBinBorders.length; ++k) if (x < this.fBinBorders[k]) return Math.floor(k-1+add); return this.fBinBorders.length - 1; }; } // to support some code from ROOT6 drawing axis.GetBinCenter = function(bin) { return this.GetBinCoord(bin-0.5); }; axis.GetBinLowEdge = function(bin) { return this.GetBinCoord(bin-1); }; } /** @summary Returns real histogram impl * @private */ function getHImpl(obj) { return obj?.fHistImpl?.fIO || null; } /** @summary Base painter class for RHist objects * * @private */ class RHistPainter extends RObjectPainter { /** @summary Constructor * @param {object|string} dom - DOM element for drawing or element id * @param {object} histo - RHist object */ constructor(dom, histo) { super(dom, histo); this.csstype = 'hist'; this.draw_content = true; this.nbinsx = 0; this.nbinsy = 0; this.accept_drops = true; // indicate that one can drop other objects like doing Draw('same') this.mode3d = false; // initialize histogram methods this.getHisto(true); } /** @summary Returns true if RHistDisplayItem is used */ isDisplayItem() { return this.getObject()?.fAxes; } /** @summary get histogram */ getHisto(force) { const obj = this.getObject(); let histo = getHImpl(obj); if (histo && (!histo.getBinContent || force)) { if (histo.fAxes._2) { assignRAxisMethods(histo.fAxes._0); assignRAxisMethods(histo.fAxes._1); assignRAxisMethods(histo.fAxes._2); histo.getBin = function(x, y, z) { return (x-1) + this.fAxes._0.GetNumBins()*(y-1) + this.fAxes._0.GetNumBins()*this.fAxes._1.GetNumBins()*(z-1); }; // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now histo.getBinContent = function(x, y, z) { return this.fStatistics.fBinContent[this.getBin(x, y, z)]; }; histo.getBinError = function(x, y, z) { const bin = this.getBin(x, y, z); if (this.fStatistics.fSumWeightsSquared) return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]); return Math.sqrt(Math.abs(this.fStatistics.fBinContent[bin])); }; } else if (histo.fAxes._1) { assignRAxisMethods(histo.fAxes._0); assignRAxisMethods(histo.fAxes._1); histo.getBin = function(x, y) { return (x-1) + this.fAxes._0.GetNumBins()*(y-1); }; // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now histo.getBinContent = function(x, y) { return this.fStatistics.fBinContent[this.getBin(x, y)]; }; histo.getBinError = function(x, y) { const bin = this.getBin(x, y); if (this.fStatistics.fSumWeightsSquared) return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]); return Math.sqrt(Math.abs(this.fStatistics.fBinContent[bin])); }; } else { assignRAxisMethods(histo.fAxes._0); histo.getBin = function(x) { return x-1; }; // all normal ROOT methods uses indx+1 logic, but RHist has no underflow/overflow bins now histo.getBinContent = function(x) { return this.fStatistics.fBinContent[x-1]; }; histo.getBinError = function(x) { if (this.fStatistics.fSumWeightsSquared) return Math.sqrt(this.fStatistics.fSumWeightsSquared[x-1]); return Math.sqrt(Math.abs(this.fStatistics.fBinContent[x-1])); }; } } else if (!histo && obj?.fAxes) { // case of RHistDisplayItem histo = obj; if (!histo.getBinContent || force) { if (histo.fAxes.length === 3) { assignRAxisMethods(histo.fAxes[0]); assignRAxisMethods(histo.fAxes[1]); assignRAxisMethods(histo.fAxes[2]); histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; histo.dx = histo.fIndicies[0] + 1; histo.stepx = histo.fIndicies[2]; histo.ny = histo.fIndicies[4] - histo.fIndicies[3]; histo.dy = histo.fIndicies[3] + 1; histo.stepy = histo.fIndicies[5]; histo.nz = histo.fIndicies[7] - histo.fIndicies[6]; histo.dz = histo.fIndicies[6] + 1; histo.stepz = histo.fIndicies[8]; // this is index in original histogram histo.getBin = function(x, y, z) { return (x-1) + this.fAxes[0].GetNumBins()*(y-1) + this.fAxes[0].GetNumBins()*this.fAxes[1].GetNumBins()*(z-1); }; // this is index in current available data if ((histo.stepx > 1) || (histo.stepy > 1) || (histo.stepz > 1)) histo.getBin0 = function(x, y, z) { return Math.floor((x-this.dx)/this.stepx) + this.nx/this.stepx*Math.floor((y-this.dy)/this.stepy) + this.nx/this.stepx*this.ny/this.stepy*Math.floor((z-this.dz)/this.stepz); }; else histo.getBin0 = function(x, y, z) { return (x-this.dx) + this.nx*(y-this.dy) + this.nx*this.ny*(z-this.dz); }; histo.getBinContent = function(x, y, z) { return this.fBinContent[this.getBin0(x, y, z)]; }; histo.getBinError = function(x, y, z) { return Math.sqrt(Math.abs(this.getBinContent(x, y, z))); }; } else if (histo.fAxes.length === 2) { assignRAxisMethods(histo.fAxes[0]); assignRAxisMethods(histo.fAxes[1]); histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; histo.dx = histo.fIndicies[0] + 1; histo.stepx = histo.fIndicies[2]; histo.ny = histo.fIndicies[4] - histo.fIndicies[3]; histo.dy = histo.fIndicies[3] + 1; histo.stepy = histo.fIndicies[5]; // this is index in original histogram histo.getBin = function(x, y) { return (x-1) + this.fAxes[0].GetNumBins()*(y-1); }; // this is index in current available data if ((histo.stepx > 1) || (histo.stepy > 1)) histo.getBin0 = function(x, y) { return Math.floor((x-this.dx)/this.stepx) + this.nx/this.stepx*Math.floor((y-this.dy)/this.stepy); }; else histo.getBin0 = function(x, y) { return (x-this.dx) + this.nx*(y-this.dy); }; histo.getBinContent = function(x, y) { return this.fBinContent[this.getBin0(x, y)]; }; histo.getBinError = function(x, y) { return Math.sqrt(Math.abs(this.getBinContent(x, y))); }; } else { assignRAxisMethods(histo.fAxes[0]); histo.nx = histo.fIndicies[1] - histo.fIndicies[0]; histo.dx = histo.fIndicies[0] + 1; histo.stepx = histo.fIndicies[2]; histo.getBin = function(x) { return x-1; }; if (histo.stepx > 1) histo.getBin0 = function(x) { return Math.floor((x-this.dx)/this.stepx); }; else histo.getBin0 = function(x) { return x-this.dx; }; histo.getBinContent = function(x) { return this.fBinContent[this.getBin0(x)]; }; histo.getBinError = function(x) { return Math.sqrt(Math.abs(this.getBinContent(x))); }; } } } return histo; } /** @summary Decode options */ decodeOptions(/* opt */) { if (!this.options) this.options = { Hist: 1, System: 1 }; } /** @summary Copy draw options from other painter */ copyOptionsFrom(src) { if (src === this) return; const o = this.options, o0 = src.options; o.Mode3D = o0.Mode3D; } /** @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 Clear 3d drawings - if any */ clear3DScene() { const fp = this.getFramePainter(); if (isFunc(fp?.create3DScene)) fp.create3DScene(-1); this.mode3d = false; } /** @summary Cleanup hist painter */ cleanup() { this.clear3DScene(); delete this.options; super.cleanup(); } /** @summary Returns histogram dimension */ getDimension() { return 1; } /** @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,nbins, maxcontent values // if when_axis_changed === true specified, content will be scanned after axis zoom changed } /** @summary Draw axes */ async drawFrameAxes() { // return true when axes was drawn const main = this.getFramePainter(); if (!main) return false; if (!this.draw_content) return true; if (!this.isMainPainter()) { if (!this.options.second_x && !this.options.second_y) return true; main.setAxes2Ranges(this.options.second_x, this.getAxis('x'), this.xmin, this.xmax, this.options.second_y, this.getAxis('y'), this.ymin, this.ymax); return main.drawAxes2(this.options.second_x, this.options.second_y); } main.cleanupAxes(); main.xmin = main.xmax = 0; main.ymin = main.ymax = 0; main.zmin = main.zmax = 0; main.setAxesRanges(this.getAxis('x'), this.xmin, this.xmax, this.getAxis('y'), this.ymin, this.ymax, this.getAxis('z'), this.zmin, this.zmax); return main.drawAxes(); } /** @summary create attributes */ createHistDrawAttributes() { this.createv7AttFill(); this.createv7AttLine(); } /** @summary update display item */ updateDisplayItem(obj, src) { if (!obj || !src) return false; obj.fAxes = src.fAxes; obj.fIndicies = src.fIndicies; obj.fBinContent = src.fBinContent; obj.fContMin = src.fContMin; obj.fContMinPos = src.fContMinPos; obj.fContMax = src.fContMax; // update histogram attributes this.getHisto(true); return true; } /** @summary update histogram object */ updateObject(obj /* , opt */) { const origin = this.getObject(); if (obj !== origin) { if (!this.matchObjectType(obj)) return false; if (this.isDisplayItem()) this.updateDisplayItem(origin, obj); else { const horigin = getHImpl(origin), hobj = getHImpl(obj); if (!horigin || !hobj) return false; // make it easy - copy statistics without axes horigin.fStatistics = hobj.fStatistics; origin.fTitle = obj.fTitle; } } this.scanContent(); this.histogram_updated = true; // indicate that object updated return true; } /** @summary Get axis object */ getAxis(name) { const histo = this.getHisto(), obj = this.getObject(); let axis; if (obj?.fAxes) { switch (name) { case 'x': axis = obj.fAxes[0]; break; case 'y': axis = obj.fAxes[1]; break; case 'z': axis = obj.fAxes[2]; break; default: axis = obj.fAxes[0]; break; } } else if (histo?.fAxes) { switch (name) { case 'x': axis = histo.fAxes._0; break; case 'y': axis = histo.fAxes._1; break; case 'z': axis = histo.fAxes._2; break; default: axis = histo.fAxes._0; break; } } if (axis && !axis.GetBinCoord) assignRAxisMethods(axis); return axis; } /** @summary Get tip text for axis bin */ getAxisBinTip(name, bin, step) { const pmain = this.getFramePainter(), handle = pmain[`${name}_handle`], axis = this.getAxis(name), x1 = axis.GetBinCoord(bin); if (handle.kind === 'labels') return pmain.axisAsText(name, x1); const x2 = axis.GetBinCoord(bin+(step || 1)); if (handle.kind === 'time') return pmain.axisAsText(name, (x1+x2)/2); return `[${pmain.axisAsText(name, x1)}, ${pmain.axisAsText(name, x2)})`; } /** @summary Extract axes ranges and bins numbers * @desc Also here ensured that all axes objects got their necessary methods */ extractAxesProperties(ndim) { const histo = this.getHisto(); if (!histo) return; this.nbinsx = this.nbinsy = this.nbinsz = 0; let axis = this.getAxis('x'); this.nbinsx = axis.GetNumBins(); this.xmin = axis.min; this.xmax = axis.max; if (ndim < 2) return; axis = this.getAxis('y'); this.nbinsy = axis.GetNumBins(); this.ymin = axis.min; this.ymax = axis.max; if (ndim < 3) return; axis = this.getAxis('z'); this.nbinsz = axis.GetNumBins(); this.zmin = axis.min; this.zmax = axis.max; } /** @summary Add interactive features, only main painter does it */ addInteractivity() { // only first painter in list allowed to add interactive functionality to the frame const ismain = this.isMainPainter(), second_axis = this.options.second_x || this.options.second_y, fp = ismain || second_axis ? this.getFramePainter() : null; return fp?.addInteractivity(!ismain && second_axis) ?? true; } /** @summary Process item reply */ processItemReply(reply, req) { if (!this.isDisplayItem()) return console.error('Get item when display normal histogram'); if (req.reqid === this.current_item_reqid) { if (reply !== null) this.updateDisplayItem(this.getObject(), reply.item); req.resolveFunc(true); } } /** @summary Special method to request bins from server if existing data insufficient * @return {Promise} when ready */ async drawingBins(reason) { let is_axes_zoomed = false; if (reason && isStr(reason) && (reason.indexOf('zoom') === 0)) { if (reason.indexOf('0') > 0) is_axes_zoomed = true; if ((this.getDimension() > 1) && (reason.indexOf('1') > 0)) is_axes_zoomed = true; if ((this.getDimension() > 2) && (reason.indexOf('2') > 0)) is_axes_zoomed = true; } if (this.isDisplayItem() && is_axes_zoomed && this.v7NormalMode()) { const handle = this.prepareDraw({ only_indexes: true }); // submit request if histogram data not enough for display if (handle.incomplete) { return new Promise(resolveFunc => { // use empty kind to always submit request const req = this.v7SubmitRequest('', { _typename: `${nsREX}RHistDrawableBase::RRequest` }, this.processItemReply.bind(this)); if (req) { this.current_item_reqid = req.reqid; // ignore all previous requests, only this one will be processed req.resolveFunc = resolveFunc; setTimeout(this.processItemReply.bind(this, null, req), 1000); // after 1 s draw something that we can } else resolveFunc(true); }); } } return true; } /** @summary Toggle statbox drawing * @desc Not yet implemented */ toggleStat(/* arg */) {} /** @summary get selected index for axis */ getSelectIndex(axis, size, add) { // be aware - here indexes starts from 0 const taxis = this.getAxis(axis), nbins = this['nbins'+axis] || 0; let indx = 0; if (this.options.second_x && axis === 'x') axis = 'x2'; if (this.options.second_y && axis === 'y') axis = 'y2'; const main = this.getFramePainter(), min = main ? main[`zoom_${axis}min`] : 0, max = main ? main[`zoom_${axis}max`] : 0; if ((min !== max) && taxis) { if (size === 'left') indx = taxis.FindBin(min, add || 0); else indx = taxis.FindBin(max, (add || 0) + 0.5); if (indx < 0) indx = 0; else if (indx > nbins) indx = nbins; } else indx = (size === 'left') ? 0 : nbins; return indx; } /** @summary Auto zoom into histogram non-empty range * @abstract */ autoZoom() {} /** @summary Process click on histogram-defined buttons */ clickButton(funcname) { const fp = this.getFramePainter(); if (!fp) return false; switch (funcname) { case 'ToggleZoom': if ((this.zoom_xmin !== this.zoom_xmax) || (this.zoom_ymin !== this.zoom_ymax) || (this.zoom_zmin !== this.zoom_zmax)) { const res = this.unzoom(); fp.zoomChangedInteractive('reset'); return res; } if (this.draw_content) return this.autoZoom(); break; case 'ToggleLogX': return fp.toggleAxisLog('x'); case 'ToggleLogY': return fp.toggleAxisLog('y'); case 'ToggleLogZ': return fp.toggleAxisLog('z'); case 'ToggleStatBox': return getPromise(this.toggleStat()); } return false; } /** @summary Fill pad toolbar with hist-related functions */ fillToolbar(not_shown) { const pp = this.getPadPainter(); if (!pp) return; pp.addPadButton('auto_zoom', 'Toggle between unzoom and autozoom-in', 'ToggleZoom', 'Ctrl *'); pp.addPadButton('arrow_right', 'Toggle log x', 'ToggleLogX', 'PageDown'); pp.addPadButton('arrow_up', 'Toggle log y', 'ToggleLogY', 'PageUp'); if (this.getDimension() > 1) pp.addPadButton('arrow_diag', 'Toggle log z', 'ToggleLogZ'); if (this.draw_content) pp.addPadButton('statbox', 'Toggle stat box', 'ToggleStatBox'); if (!not_shown) pp.showPadButtons(); } /** @summary get tool tips used in 3d mode */ get3DToolTip(indx) { const histo = this.getHisto(), tip = { bin: indx, name: histo.fName || 'histo', title: histo.fTitle }; switch (this.getDimension()) { case 1: tip.ix = indx + 1; tip.iy = 1; tip.value = histo.getBinContent(tip.ix); tip.error = histo.getBinError(tip.ix); tip.lines = this.getBinTooltips(indx-1); break; case 2: tip.ix = (indx % this.nbinsx) + 1; tip.iy = (indx - (tip.ix - 1)) / this.nbinsx + 1; tip.value = histo.getBinContent(tip.ix, tip.iy); tip.error = histo.getBinError(tip.ix, tip.iy); tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1); break; case 3: tip.ix = indx % this.nbinsx + 1; tip.iy = ((indx - (tip.ix - 1)) / this.nbinsx) % this.nbinsy + 1; tip.iz = (indx - (tip.ix - 1) - (tip.iy - 1) * this.nbinsx) / this.nbinsx / this.nbinsy + 1; tip.value = histo.getBinContent(tip.ix, tip.iy, tip.iz); tip.error = histo.getBinError(tip.ix, tip.iy, tip.iz); tip.lines = this.getBinTooltips(tip.ix-1, tip.iy-1, tip.iz-1); break; } return tip; } /** @summary Create contour levels for currently selected Z range */ createContour(main, palette, args) { if (!main || !palette) return; if (!args) args = {}; let nlevels = gStyle.fNumberContours, zmin = this.minbin, zmax = this.maxbin, zminpos = this.minposbin; if (args.scatter_plot) { if (nlevels > 50) nlevels = 50; zmin = this.minposbin; } if (zmin === zmax) { zmin = this.gminbin; zmax = this.gmaxbin; zminpos = this.gminposbin; } if (this.getDimension() < 3) { if (main.zoom_zmin !== main.zoom_zmax) { zmin = main.zoom_zmin; zmax = main.zoom_zmax; } else if (args.full_z_range) { zmin = main.zmin; zmax = main.zmax; } } palette.setFullRange(main.zmin, main.zmax); palette.createContour(main.logz, nlevels, zmin, zmax, zminpos); if (this.getDimension() < 3) { main.scale_zmin = palette.colzmin; main.scale_zmax = palette.colzmax; } } /** @summary Start dialog to modify range of axis where histogram values are displayed */ changeValuesRange(menu, arg) { const pmain = this.getFramePainter(); if (!pmain) return; const prefix = pmain.isAxisZoomed(arg) ? 'zoom_' + arg : arg, curr = '[' + pmain[`${prefix}min`] + ',' + pmain[`${prefix}max`] + ']'; menu.input('Enter values range for axis ' + arg + ' like [0,100] or empty string to unzoom', curr).then(res => { res = res ? JSON.parse(res) : []; if (!isObject(res) || (res.length !== 2) || !Number.isFinite(res[0]) || !Number.isFinite(res[1])) pmain.unzoom(arg); else pmain.zoom(arg, res[0], res[1]); }); } /** @summary Fill histogram context menu */ fillContextMenuItems(menu) { if (this.draw_content) { menu.addchk(this.toggleStat('only-check'), 'Show statbox', () => this.toggleStat()); if (this.getDimension() === 2) menu.add('Values range', () => this.changeValuesRange(menu, 'z')); if (isFunc(this.fillHistContextMenu)) this.fillHistContextMenu(menu); } const fp = this.getFramePainter(); if (this.options.Mode3D) { // menu for 3D drawings if (menu.size() > 0) menu.add('separator'); const main = this.getMainPainter() || this; menu.addchk(main.isTooltipAllowed(), 'Show tooltips', () => main.setTooltipAllowed('toggle')); menu.addchk(fp.enable_highlight, 'Highlight bins', () => { fp.enable_highlight = !fp.enable_highlight; if (!fp.enable_highlight && main.mode3d && isFunc(main.highlightBin3D)) main.highlightBin3D(null); }); if (isFunc(fp?.render3D)) { menu.addchk(main.options.FrontBox, 'Front box', () => { main.options.FrontBox = !main.options.FrontBox; fp.render3D(); }); menu.addchk(main.options.BackBox, 'Back box', () => { main.options.BackBox = !main.options.BackBox; fp.render3D(); }); } if (this.draw_content) { menu.addchk(!this.options.Zero, 'Suppress zeros', () => { this.options.Zero = !this.options.Zero; this.redrawPad(); }); if ((this.options.Lego === 12) || (this.options.Lego === 14)) this.fillPaletteMenu(menu); } if (isFunc(main.control?.reset)) menu.add('Reset camera', () => main.control.reset()); } if (this.histogram_updated && fp.zoomChangedInteractive()) menu.add('Let update zoom', () => fp.zoomChangedInteractive('reset')); } /** @summary Update palette drawing */ updatePaletteDraw() { if (this.isMainPainter()) this.getPadPainter().findPainterFor(undefined, undefined, `${nsREX}RPaletteDrawable`)?.drawPalette(); } /** @summary Fill menu entries for palette */ fillPaletteMenu(menu) { menu.addPaletteMenu(this.options.Palette || settings.Palette, arg => { // TODO: rewrite for RPalette functionality this.options.Palette = parseInt(arg); this.redraw(); // redraw histogram }); } /** @summary Toggle 3D drawing mode */ toggleMode3D() { this.options.Mode3D = !this.options.Mode3D; if (this.options.Mode3D) { if (!this.options.Surf && !this.options.Lego && !this.options.Error) { if ((this.nbinsx >= 50) || (this.nbinsy >= 50)) this.options.Lego = this.options.Color ? 14 : 13; else this.options.Lego = this.options.Color ? 12 : 1; this.options.Zero = false; // do not show zeros by default } } this.copyOptionsToOthers(); return this.interactiveRedraw('pad', 'drawopt'); } /** @summary Calculate histogram inidicies and axes values for each visible bin */ prepareDraw(args) { if (!args) args = { rounding: true, extra: 0, middle: 0 }; if (args.extra === undefined) args.extra = 0; if (args.right_extra === undefined) args.right_extra = args.extra; if (args.middle === undefined) args.middle = 0; const histo = this.getHisto(), xaxis = this.getAxis('x'), yaxis = this.getAxis('y'), pmain = this.getFramePainter(), hdim = this.getDimension(), res = { i1: this.getSelectIndex('x', 'left', 0 - args.extra), i2: this.getSelectIndex('x', 'right', 1 + args.right_extra), j1: (hdim < 2) ? 0 : this.getSelectIndex('y', 'left', 0 - args.extra), j2: (hdim < 2) ? 1 : this.getSelectIndex('y', 'right', 1 + args.right_extra), k1: (hdim < 3) ? 0 : this.getSelectIndex('z', 'left', 0 - args.extra), k2: (hdim < 3) ? 1 : this.getSelectIndex('z', 'right', 1 + args.right_extra), stepi: 1, stepj: 1, stepk: 1, min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1 }; let i, j, x, y, binz, binarea; if (this.isDisplayItem() && histo.fIndicies) { if (res.i1 < histo.fIndicies[0]) { res.i1 = histo.fIndicies[0]; res.incomplete = true; } if (res.i2 > histo.fIndicies[1]) { res.i2 = histo.fIndicies[1]; res.incomplete = true; } res.stepi = histo.fIndicies[2]; if (res.stepi > 1) res.incomplete = true; if ((hdim > 1) && (histo.fIndicies.length > 5)) { if (res.j1 < histo.fIndicies[3]) { res.j1 = histo.fIndicies[3]; res.incomplete = true; } if (res.j2 > histo.fIndicies[4]) { res.j2 = histo.fIndicies[4]; res.incomplete = true; } res.stepj = histo.fIndicies[5]; if (res.stepj > 1) res.incomplete = true; } if ((hdim > 2) && (histo.fIndicies.length > 8)) { if (res.k1 < histo.fIndicies[6]) { res.k1 = histo.fIndicies[6]; res.incomplete = true; } if (res.k2 > histo.fIndicies[7]) { res.k2 = histo.fIndicies[7]; res.incomplete = true; } res.stepk = histo.fIndicies[8]; if (res.stepk > 1) res.incomplete = true; } } if (args.only_indexes) return res; // no need for Float32Array, plain Array is 10% faster // reserve more places to avoid complex boundary checks res.grx = new Array(res.i2+res.stepi+1); res.gry = new Array(res.j2+res.stepj+1); if (args.original) { res.original = true; res.origx = new Array(res.i2+1); res.origy = new Array(res.j2+1); } if (args.pixel_density) args.rounding = true; const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y); // calculate graphical coordinates in advance for (i = res.i1; i <= res.i2; ++i) { x = xaxis.GetBinCoord(i + args.middle); if (funcs.logx && (x <= 0)) { res.i1 = i+1; continue; } if (res.origx) res.origx[i] = x; res.grx[i] = funcs.grx(x); if (args.rounding) res.grx[i] = Math.round(res.grx[i]); if (args.use3d) { if (res.grx[i] < -pmain.size_x3d) { res.i1 = i; res.grx[i] = -pmain.size_x3d; } if (res.grx[i] > pmain.size_x3d) { res.i2 = i; res.grx[i] = pmain.size_x3d; } } } if (args.use3d) { if ((res.i1 < res.i2-2) && (res.grx[res.i1] === res.grx[res.i1+1])) res.i1++; if ((res.i1 < res.i2-2) && (res.grx[res.i2-1] === res.grx[res.i2])) res.i2--; } // copy last valid value to higher indicies while (i < res.i2 + res.stepi + 1) res.grx[i++] = res.grx[res.i2]; if (hdim === 1) { res.gry[0] = funcs.gry(0); res.gry[1] = funcs.gry(1); } else { for (j = res.j1; j <= res.j2; ++j) { y = yaxis.GetBinCoord(j + args.middle); if (funcs.logy && (y <= 0)) { res.j1 = j+1; continue; } if (res.origy) res.origy[j] = y; res.gry[j] = funcs.gry(y); if (args.rounding) res.gry[j] = Math.round(res.gry[j]); if (args.use3d) { if (res.gry[j] < -pmain.size_y3d) { res.j1 = j; res.gry[j] = -pmain.size_y3d; } if (res.gry[j] > pmain.size_y3d) { res.j2 = j; res.gry[j] = pmain.size_y3d; } } } } if (args.use3d && (hdim > 1)) { if ((res.j1 < res.j2-2) && (res.gry[res.j1] === res.gry[res.j1+1])) res.j1++; if ((res.j1 < res.j2-2) && (res.gry[res.j2-1] === res.gry[res.j2])) res.j2--; } // copy last valid value to higher indicies if (hdim > 1) { while (j < res.j2 + res.stepj + 1) res.gry[j++] = res.gry[res.j2]; } // find min/max values in selected range this.maxbin = this.minbin = this.minposbin = null; for (i = res.i1; i < res.i2; i += res.stepi) { for (j = res.j1; j < res.j2; j += res.stepj) { binz = histo.getBinContent(i + 1, j + 1); if (!Number.isFinite(binz)) continue; res.sumz += binz; if (args.pixel_density) { binarea = (res.grx[i+res.stepi]-res.grx[i])*(res.gry[j]-res.gry[j+res.stepj]); if (binarea <= 0) continue; res.max = Math.max(res.max, binz); if ((binz > 0) && ((binz < res.min) || (res.min === 0))) res.min = binz; binz = binz/binarea; } if (this.maxbin === null) this.maxbin = this.minbin = binz; else { this.maxbin = Math.max(this.maxbin, binz); this.minbin = Math.min(this.minbin, binz); } if (binz > 0) if ((this.minposbin === null) || (binz < this.minposbin)) this.minposbin = binz; } } res.palette = pmain.getHistPalette(); if (res.palette) this.createContour(pmain, res.palette, args); return res; } } // class RHistPainter export { RHistPainter };