From 1360793f76af3df88cddcdd8354f6fd36e1e4d2d Mon Sep 17 00:00:00 2001 From: Frank Sauerburger <frank@sauerburger.com> Date: Wed, 23 Dec 2020 23:35:25 +0100 Subject: [PATCH] Use helpers and implement uncertainty band --- uhepp-js/src/components/Graph.jsx | 101 +++++++++++++++++------------- uhepp-js/src/helpers/uhepp.js | 57 ++++++++++++++++- 2 files changed, 111 insertions(+), 47 deletions(-) diff --git a/uhepp-js/src/components/Graph.jsx b/uhepp-js/src/components/Graph.jsx index f9062e2..98c058e 100644 --- a/uhepp-js/src/components/Graph.jsx +++ b/uhepp-js/src/components/Graph.jsx @@ -11,6 +11,7 @@ import { SVG } from 'mathjax-full/js/output/svg'; import { browserAdaptor } from 'mathjax-full/js/adaptors/browserAdaptor'; import { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html'; import { STATE } from 'mathjax-full/js/core/MathItem'; +import { preprocessData, sumBase, sumStat } from "../helpers/uhepp.js"; const getMaxBin =(uhepp) => { let max = 0; @@ -19,10 +20,8 @@ const getMaxBin =(uhepp) => { uhepp.stacks.forEach((stack, stack_index) => { let bottom = 0 stack.content.forEach((stack_item, si_i) => { - const y_value = stack_item["yield"].map(name => uhepp.yields[name].base[bin_i + 1]).reduce((a, b) => a + b) - if (y_value > 0) { - bottom += y_value; - } + const y_value = sumBase(uhepp.yields, stack_item["yield"], bin_i) + if (y_value > 0) { bottom += y_value; } }) max = Math.max(max, bottom) }) @@ -42,19 +41,20 @@ const Histogram = ({ children }) => { - const n_bins = uhepp.bins.edges.length - 1 + const edges = uhepp.bins.rebin || uhepp.bins.edges + const n_bins = edges.length let objects = [] - for (let bin_i=0; bin_i < n_bins; bin_i++) { + for (let bin_i=1; bin_i < n_bins; bin_i++) { uhepp.stacks.forEach((stack, stack_index) => { let bottom = 0 stack.content.forEach((stack_item, si_i) => { - const y_value = stack_item["yield"].map(name => uhepp.yields[name].base[bin_i + 1]).reduce((a, b) => a + b) + const y_value = sumBase(uhepp.yields, stack_item["yield"], bin_i) if (stack.type == "stepfilled") { objects.push(<rect key={`rect-${stack_index}-${si_i}-${bin_i}`} - x={xScale(uhepp.bins.edges[bin_i])} - width={xScale(uhepp.bins.edges[bin_i + 1]) - xScale(uhepp.bins.edges[bin_i])} + x={xScale(edges[bin_i-1])} + width={xScale(edges[bin_i + 1]) - xScale(edges[bin_i])} y={yScale(bottom + y_value)} height={yScale(bottom) - yScale(bottom + y_value)} fill={stack_item.style ? stack_item.style.color : '#1f77b4'} @@ -62,12 +62,12 @@ const Histogram = ({ onMouseOver={() => onMouseOverBin(bin_i)} />) } else if (stack.type == "points") { - const width = xScale(uhepp.bins.edges[bin_i + 1]) - xScale(uhepp.bins.edges[bin_i]) - const stat2 = stack_item["yield"].map(name => uhepp.yields[name].stat[bin_i + 1]).map(a => a*a).reduce((a, b) => a + b) - const stat = Math.sqrt(stat2) + const width = xScale(edges[bin_i]) - xScale(edges[bin_i - 1]) + const stat = sumStat(uhepp.yields, stack_item["yield"], bin_i) + const stat_up = yScale(y_value + stat) - yScale(y_value) const stat_down = yScale(y_value) - yScale(y_value - stat) - const gx = (xScale(uhepp.bins.edges[bin_i]) + xScale(uhepp.bins.edges[bin_i + 1])) / 2 + const gx = (xScale(edges[bin_i - 1]) + xScale(edges[bin_i])) / 2 const gy = yScale(bottom + y_value) if (stat && y_value) { @@ -108,6 +108,20 @@ const Histogram = ({ }) + + if (stack.type == "stepfilled") { + const whole_stack = stack.content.map(si => si["yield"]).flat() + const stat = sumStat(uhepp.yields, whole_stack, bin_i) + objects.push(<rect + key={`rect-${stack_index}-uncert-${bin_i}`} + x={xScale(edges[bin_i - 1])} + width={xScale(edges[bin_i]) - xScale(edges[bin_i - 1])} + y={yScale(bottom + stat)} + fill="url(#errorBand)" + height={yScale(bottom - stat) - yScale(bottom + stat)} + />) + } + }) } @@ -172,25 +186,6 @@ const EmbeddedMathJax = ({src, posX, posY, props}) => { dangerouslySetInnerHTML={{__html: rendered_svg.innerHTML}} { ...props}></g> } -// const MixedText = ({children, posX, posY}) => { -// const tokens = children.split("$") -// let offset = 0 -// return tokens.map((token, i) => { -// const is_tex = (i % 2 != 0) -// if (is_tex) { -// let [width, item] = EmbeddedMathJax({posX: posX + offset, posY: posY, src: token, props: {key: `emb-${i}`}}) -// if (width > 0) { -// offset += width -// } -// return item -// } else { -// let item = <text key={`emb-${i}`} x={posX + offset} y={posY}>{token}</text> -// offset += length_metrix(token) * 8.5 -// return item -// } -// }) -// } - const MixedText = ({children, x=0, y=0}) => { const embedded = `\\textsf{${children}}` const switched = embedded.replaceAll(/\$([^$]*)\$/g, "}$1\\textsf{") @@ -204,8 +199,18 @@ const UheppHist = ({width, height, uhepp}) => { const [highlightedBin, setHighlightedBin] = useState(null); const [showTotal, setShowTotal] = useState(false); + const post_uhepp = Object.assign({}, uhepp, { + yields: preprocessData({ + yields: uhepp.yields, + old_edges: uhepp.bins.edges, + new_edges: uhepp.bins.rebin, + include_underflow: uhepp.bins.include_underflow, + include_overflow: uhepp.bins.include_overflow, + }) + }) + const margin = { - top: 0, + top: 10, bottom: 60, left: 80, right: 80, @@ -220,12 +225,13 @@ const UheppHist = ({width, height, uhepp}) => { }) const yScale = scaleLinear({ range: [yMax, 0], - domain: [0, getMaxBin(uhepp) * 1.3], + domain: [0, getMaxBin(post_uhepp) * 1.3], }) + let i = 0 let legend = [] - uhepp.stacks.reverse().map(stack => { + post_uhepp.stacks.reverse().map(stack => { stack.content.reverse().map(stack_item => { if (stack.type == "points") { let color = stack_item.style ? stack_item.style.color : '#000' @@ -269,18 +275,18 @@ const UheppHist = ({width, height, uhepp}) => { />) } if (highlightedBin != null) { - const y_value = stack_item["yield"].map(name => uhepp.yields[name].base[highlightedBin + 1]).reduce((a, b) => a + b) - const stat2 = stack_item["yield"].map(name => uhepp.yields[name].stat[highlightedBin + 1]).map(a => a*a).reduce((a, b) => a + b) - const stat = Math.sqrt(stat2) + const y_value = sumBase(post_uhepp.yields, stack_item["yield"], highlightedBin) + const stat = sumStat(post_uhepp.yields, stack_item["yield"], highlightedBin) + const label = y_value.toFixed(1) + " ± " + stat.toFixed(1) - legend.push(<text fontSize={12} textAnchor="end" x={width * 0.5 + 120} y={30 + i * 20} + legend.push(<text fontSize={12} textAnchor="end" x={width * 0.5 + 100} y={30 + i * 20} key={`legend-text-${i}`}>{label}</text>) } else if (showTotal) { - const y_value = stack_item["yield"].map(name => uhepp.yields[name].base).flat().reduce((a, b) => a + b) - const stat2 = stack_item["yield"].map(name => uhepp.yields[name].stat).flat().map(a => a*a).reduce((a, b) => a + b) - const stat = Math.sqrt(stat2) + const y_value = sumBase(post_uhepp.yields, stack_item["yield"]) + const stat = sumStat(post_uhepp.yields, stack_item["yield"]) + const label = y_value.toFixed(1) + " ± " + stat.toFixed(1) - legend.push(<text fontSize={12} textAnchor="end" x={width * 0.5 + 120} y={30 + i * 20} + legend.push(<text fontSize={12} textAnchor="end" x={width * 0.5 + 100} y={30 + i * 20} key={`legend-text-${i}`}>{label}</text>) } else { @@ -293,7 +299,7 @@ const UheppHist = ({width, height, uhepp}) => { }) stack.content.reverse() }) - uhepp.stacks.reverse() + post_uhepp.stacks.reverse() return ( <div style={{width: '100%', margin: '0 auto'}}> @@ -304,13 +310,18 @@ const UheppHist = ({width, height, uhepp}) => { .uhepp-brand {'{font-style: italic; font-weight: bold}'} .uhepp-brand, .uhepp-label {'{font-size: 20px}'} </style> + + <pattern id="errorBand" patternUnits="userSpaceOnUse" width="6" height="6"> + <path d="M-1,1 l1,-1 M0,6 l6,-6 M5,7 l2,-2" style={{stroke: "#666", strokeWidth: 1}} /> + </pattern> + <Group top={margin.top} left={margin.left}> <text y={30} x={13} className="uhepp-brand">ATLAS</text> <text y={30} x={75} className="uhepp-label">Internal</text> <Histogram - uhepp={uhepp} + uhepp={post_uhepp} highlightedBin={highlightedBin} onMouseOverBin={binIndex => setHighlightedBin(binIndex)} stack_index={0} diff --git a/uhepp-js/src/helpers/uhepp.js b/uhepp-js/src/helpers/uhepp.js index f14fa04..945239c 100644 --- a/uhepp-js/src/helpers/uhepp.js +++ b/uhepp-js/src/helpers/uhepp.js @@ -110,7 +110,7 @@ export const sumStat = (yields, process_names, bin_index=null) => ( * statistical uncetainties. * @param post Callback that maps values after summing them to undo * the effect of pre. Should be identify for the yields, - * and the square root for statistical uncetainties. + * and the square root for statistical uncertainties. */ export const rebin = ( series, @@ -144,4 +144,57 @@ export const rebin = ( return result.map(x => post(x)) } -export const preprocessData = ( ) => 0 +const fromEntires = (entries) => { + let result = {} + entries.forEach(([key, value]) => { + result[key] = value + }) + return result +} + +/** + * Processes the yield objects and returns a new version + * + * The preprocessing encompasses rebinning, and the merging of overflow and + * underflow bins. + * + * @param yields Uhepp yields objects to be processed + * @param old_edges List of original bin boundaries + * @param new_edges Optional list of new bin boundaries, must be subset + * of original boundaries + * @param include_underflow If true, merge content of underflow to first bin + * @param include_overflow If true, merge content of overflow to last bin + */ +export const preprocessData = ({ + yields, + old_edges, + new_edges=null, + include_underflow, + include_overflow, +}) => fromEntires(Object.entries(yields).map(([name, yield_obj]) => { + const eff_edges = new_edges ? new_edges : old_edges + const rebinned = { + "base": rebin(yield_obj.base, old_edges, eff_edges), + "stat": yield_obj.stat ? rebin(yield_obj.stat, old_edges, eff_edges, (x) => x*x, (x)=>Math.sqrt(x)) : null, + } + if (include_underflow) { + rebinned.base[1] += rebinned.base[0] + rebinned.base[0] = 0 + if (rebinned.stat) { + rebinned.stat[1] = Math.sqrt(rebinned.stat[0]**2 + rebinned.stat[1]**2) + rebinned.stat[0] = 0 + } + } + if (include_overflow) { + const b = rebinned.base.length - 1 + rebinned.base[b - 1] += rebinned.base[b] + rebinned.base[b] = 0 + if (rebinned.stat) { + rebinned.stat[b - 1] = Math.sqrt(rebinned.stat[b]**2 + rebinned.stat[b - 1]**2) + rebinned.stat[b] = 0 + } + } + return [name, fromEntires(Object.entries(rebinned).filter( + ([name, values]) => !!values + ))] +})) -- GitLab