UNPKG

jsroot

Version:
343 lines (281 loc) 11.3 kB
import { createHistogram, setHistogramTitle, kNoStats, settings, gStyle, clTF2, clTH2F, isFunc } from '../core.mjs'; import { TH2Painter } from '../hist/TH2Painter.mjs'; import { proivdeEvalPar } from '../base/func.mjs'; import { produceTAxisLogScale, scanTF1Options } from '../hist/TF1Painter.mjs'; import { getElementPadPainter } from '../base/ObjectPainter.mjs'; import { DrawOptions, floatToString } from '../base/BasePainter.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; /** * @summary Painter for TF2 object * * @private */ class TF2Painter extends TH2Painter { #use_saved_points; // use saved points for drawing #func; // func object #fail_eval; // fail evaluation of function /** @summary Assign function */ setFunc(f) { this.#func = f; } /** @summary Returns drawn object name */ getObjectName() { return this.#func?.fName ?? 'func'; } /** @summary Returns drawn object class name */ getClassName() { return this.#func?._typename ?? clTF2; } /** @summary Returns true while function is drawn */ isTF1() { return true; } /** @summary Returns primary function which was then drawn as histogram */ getPrimaryObject() { return this.#func; } /** @summary Update histogram */ updateObject(obj /* , opt */) { if (!obj || (this.getClassName() !== obj._typename)) return false; delete obj.evalPar; const histo = this.getHisto(); if (this._webcanv_hist) { const h0 = this.getPadPainter()?.findInPrimitives('Func', clTH2F); if (h0) this.updateAxes(histo, h0, this.getFramePainter()); } this.setFunc(obj); this.createTF2Histogram(obj, histo); this.scanContent(); return true; } /** @summary Redraw TF2 * @private */ redraw(reason) { if (!this.#use_saved_points && (reason === 'logx' || reason === 'logy' || reason === 'zoom')) { this.createTF2Histogram(this.#func, this.getHisto()); this.scanContent(); } return super.redraw(reason); } /** @summary Create histogram for TF2 drawing * @private */ createTF2Histogram(func, hist) { let nsave = func.fSave.length - 6; if ((nsave > 0) && (nsave !== (func.fSave[nsave + 4] + 1) * (func.fSave[nsave + 5] + 1))) nsave = 0; this.#use_saved_points = (nsave > 0) && (settings.PreferSavedPoints || (this._use_saved > 1)); const fp = this.getFramePainter(), pad = this.getPadPainter()?.getRootPad(true), logx = pad?.fLogx, logy = pad?.fLogy, gr = fp?.getGrFuncs(this.second_x, this.second_y); let xmin = func.fXmin, xmax = func.fXmax, ymin = func.fYmin, ymax = func.fYmax, npx = Math.max(func.fNpx, 20), npy = Math.max(func.fNpy, 20); if (gr?.zoom_xmin !== gr?.zoom_xmax) { const dx = (xmax - xmin) / npx; if ((xmin < gr.zoom_xmin) && (gr.zoom_xmin < xmax)) xmin = Math.max(xmin, gr.zoom_xmin - dx); if ((xmin < gr.zoom_xmax) && (gr.zoom_xmax < xmax)) xmax = Math.min(xmax, gr.zoom_xmax + dx); } if (gr?.zoom_ymin !== gr?.zoom_ymax) { const dy = (ymax - ymin) / npy; if ((ymin < gr.zoom_ymin) && (gr.zoom_ymin < ymax)) ymin = Math.max(ymin, gr.zoom_ymin - dy); if ((ymin < gr.zoom_ymax) && (gr.zoom_ymax < ymax)) ymax = Math.min(ymax, gr.zoom_ymax + dy); } const ensureBins = (nx, ny) => { if (hist.fNcells !== (nx + 2) * (ny + 2)) { hist.fNcells = (nx + 2) * (ny + 2); hist.fArray = new Float32Array(hist.fNcells); } hist.fArray.fill(0); hist.fXaxis.fNbins = nx; hist.fXaxis.fXbins = []; hist.fYaxis.fNbins = ny; hist.fYaxis.fXbins = []; }; this.#fail_eval = undefined; if (!this.#use_saved_points) { let iserror = false; if (!func.evalPar && !proivdeEvalPar(func)) iserror = true; ensureBins(npx, npy); hist.fXaxis.fXmin = xmin; hist.fXaxis.fXmax = xmax; hist.fYaxis.fXmin = ymin; hist.fYaxis.fXmax = ymax; if (logx) produceTAxisLogScale(hist.fXaxis, npx, xmin, xmax); if (logy) produceTAxisLogScale(hist.fYaxis, npy, ymin, ymax); for (let j = 0; (j < npy) && !iserror; ++j) { for (let i = 0; (i < npx) && !iserror; ++i) { const x = hist.fXaxis.GetBinCenter(i + 1), y = hist.fYaxis.GetBinCenter(j + 1); let z = 0; try { z = func.evalPar(x, y); } catch { iserror = true; } if (!iserror) hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); } } if (iserror) this.#fail_eval = true; if (iserror && (nsave > 6)) this.#use_saved_points = true; } if (this.#use_saved_points) { npx = Math.round(func.fSave[nsave + 4]); npy = Math.round(func.fSave[nsave + 5]); xmin = func.fSave[nsave]; xmax = func.fSave[nsave + 1]; ymin = func.fSave[nsave + 2]; ymax = func.fSave[nsave + 3]; const dx = (xmax - xmin) / npx, dy = (ymax - ymin) / npy, getSave = (x, y) => { if (x < xmin || x > xmax || dx <= 0) return 0; if (y < ymin || y > ymax || dy <= 0) return 0; const ibin = Math.min(npx - 1, Math.floor((x - xmin) / dx)), jbin = Math.min(npy - 1, Math.floor((y - ymin) / dy)), xlow = xmin + ibin * dx, ylow = ymin + jbin * dy, t = (x - xlow) / dx, u = (y - ylow) / dy, k1 = jbin * (npx + 1) + ibin, k2 = jbin * (npx + 1) + ibin + 1, k3 = (jbin + 1) * (npx + 1) + ibin + 1, k4 = (jbin + 1) * (npx + 1) + ibin; return (1 - t) * (1 - u) * func.fSave[k1] + t * (1 - u) * func.fSave[k2] + t * u * func.fSave[k3] + (1 - t) * u * func.fSave[k4]; }; ensureBins(func.fNpx, func.fNpy); hist.fXaxis.fXmin = func.fXmin; hist.fXaxis.fXmax = func.fXmax; hist.fYaxis.fXmin = func.fYmin; hist.fYaxis.fXmax = func.fYmax; for (let j = 0; j < func.fNpy; ++j) { const y = hist.fYaxis.GetBinCenter(j + 1); for (let i = 0; i < func.fNpx; ++i) { const x = hist.fXaxis.GetBinCenter(i + 1), z = getSave(x, y); hist.setBinContent(hist.getBin(i + 1, j + 1), Number.isFinite(z) ? z : 0); } } } hist.fName = 'Func'; setHistogramTitle(hist, func.fTitle); hist.fMinimum = func.fMinimum; hist.fMaximum = func.fMaximum; // fHistogram->SetContour(fContour.fN, levels); hist.fLineColor = func.fLineColor; hist.fLineStyle = func.fLineStyle; hist.fLineWidth = func.fLineWidth; hist.fFillColor = func.fFillColor; hist.fFillStyle = func.fFillStyle; hist.fMarkerColor = func.fMarkerColor; hist.fMarkerStyle = func.fMarkerStyle; hist.fMarkerSize = func.fMarkerSize; hist.fBits |= kNoStats; return hist; } /** @summary Extract function ranges */ extractAxesProperties(ndim) { super.extractAxesProperties(ndim); const func = this.#func, nsave = func?.fSave.length ?? 0; if (nsave > 6 && this.#use_saved_points) { this.xmin = Math.min(this.xmin, func.fSave[nsave - 6]); this.xmax = Math.max(this.xmax, func.fSave[nsave - 5]); this.ymin = Math.min(this.ymin, func.fSave[nsave - 4]); this.ymax = Math.max(this.ymax, func.fSave[nsave - 3]); } if (func) { this.xmin = Math.min(this.xmin, func.fXmin); this.xmax = Math.max(this.xmax, func.fXmax); this.ymin = Math.min(this.ymin, func.fYmin); this.ymax = Math.max(this.ymax, func.fYmax); } } /** @summary return tooltips for TF2 */ getTF2Tooltips(pnt) { const lines = [this.getObjectHint()], o = this.getOptions(), funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); if (!funcs || !isFunc(this.#func?.evalPar)) { lines.push('grx = ' + pnt.x, 'gry = ' + pnt.y); return lines; } const x = funcs.revertAxis('x', pnt.x), y = funcs.revertAxis('y', pnt.y); let z = 0, iserror = false; try { z = this.#func.evalPar(x, y); } catch { iserror = true; } lines.push('x = ' + funcs.axisAsText('x', x), 'y = ' + funcs.axisAsText('y', y), 'value = ' + (iserror ? '<fail>' : floatToString(z, gStyle.fStatFormat))); return lines; } /** @summary process tooltip event for TF2 object */ processTooltipEvent(pnt) { if (this.#use_saved_points) return super.processTooltipEvent(pnt); let ttrect = this.getG()?.selectChild('.tooltip_bin'); if (!this.getG() || !pnt) { ttrect?.remove(); return null; } const res = { name: this.#func?.fName, title: this.#func?.fTitle, x: pnt.x, y: pnt.y, color1: this.lineatt?.color ?? 'green', color2: this.fillatt?.getFillColorAlt('blue') ?? 'blue', lines: this.getTF2Tooltips(pnt), exact: true, menu: true }; if (pnt.disabled) ttrect.remove(); else { if (ttrect.empty()) { ttrect = this.getG().append('svg:circle') .attr('class', 'tooltip_bin') .style('pointer-events', 'none') .style('fill', 'none') .attr('r', (this.lineatt?.width ?? 1) + 4); } ttrect.attr('cx', pnt.x) .attr('cy', pnt.y); if (this.lineatt) ttrect.call(this.lineatt.func); } return res; } /** @summary fill information for TWebCanvas * @desc Used to inform web canvas when evaluation failed * @private */ fillWebObjectOptions(opt) { opt.fcust = this.#fail_eval && !this._use_saved ? 'func_fail' : ''; } /** @summary draw TF2 object */ static async draw(dom, tf2, opt) { const web = scanTF1Options(opt); opt = web.opt; delete web.opt; const d = new DrawOptions(opt); if (d.empty()) opt = 'cont3'; else if (d.opt === 'SAME') opt = 'cont2 same'; let hist; if (web._webcanv_hist) hist = getElementPadPainter(dom)?.findInPrimitives('Func', clTH2F); if (!hist) { hist = createHistogram(clTH2F, 20, 20); hist.fBits |= kNoStats; } const painter = new TF2Painter(dom, hist); painter.setFunc(tf2); Object.assign(painter, web); painter.createTF2Histogram(tf2, hist); return THistPainter._drawHist(painter, opt); } } // class TF2Painter export { TF2Painter };