/// <summary> /// Convert everything and save it to the directory! Also write out /// all the sub-directories. /// </summary> /// <param name="calculateEverything">If true, then trigger all calculations here and below. Normally call with false.</param> public void Write(bool calculateEverything = true) { // Write everything associated with this directory. using (ROOTLock.Lock()) { Directory.Write(); } // Trigger all the calculatiosn that are needed for these directories. // This drives the ability for parallel calculation of everything. if (calculateEverything) { TriggerResolutions().Wait(); } // Local values foreach (var item in _heldValues) { item.Save(Directory); } _heldValues.Clear(); // Next, the subdirectories if (_subDirs.IsValueCreated) { foreach (var item in _subDirs.Value) { item.Write(); } } }
/// <summary> /// Create a TH1F plot from a stream of objects (with a lambda function to give flexability in conversion). /// </summary> /// <typeparam name="TSource">The type of the sequence that the plot will be run over</typeparam> /// <param name="source">The sequence over which a plot should be made. There will be one entry per item in the sequence.</param> /// <param name="plotName">The histogram will be created with this name</param> /// <param name="plotTitle">The histogram will be created with this title</param> /// <param name="nbins">Number of bins this histogram should have</param> /// <param name="lowBin">The xmin value for this histogram</param> /// <param name="highBin">The xmax value for this histogram</param> /// <param name="xValue">A lambda that returns the xvalue for each sequence item.</param> /// <param name="weight">A lambda that returns the weight for each sequence item. By default every entry has a weight of 1.</param> /// <returns></returns> public static ROOTNET.NTH1F Plot <TSource> ( this IQueryable <TSource> source, string plotName, string plotTitle, int nbins, double lowBin, double highBin, Expression <Func <TSource, double> > xValue, Expression <Func <TSource, double> > weight = null) { using (ROOTLock.Lock()) { if (weight == null) { Expression <Func <TSource, double> > constWeight = s => 1.0; weight = constWeight; } var hParameter = Expression.Parameter(typeof(ROOTNET.NTH1F), "h"); var vParameter = Expression.Parameter(typeof(TSource), "v"); // h.Fill(getter(v), weight(v)) is what we want to code up var callGetter = Expression.Invoke(xValue, vParameter); var callWeight = Expression.Invoke(weight, vParameter); var fillMethod = typeof(ROOTNET.NTH1F).GetMethod("Fill", new[] { typeof(double), typeof(double) }); var callFill = Expression.Call(hParameter, fillMethod, callGetter, callWeight); var lambda = Expression.Lambda <Action <ROOTNET.NTH1F, TSource> >(callFill, hParameter, vParameter); var h = new ROOTNET.NTH1F(plotName, plotTitle.ReplaceLatexStrings(), nbins, lowBin, highBin); ConfigureHisto(h); return(source.ApplyToObject(h, lambda)); } }
/// <summary> /// Write out an object. Eventually, with ROOTNET improvements this will work better and perahps /// won't be needed! /// </summary> /// <param name="obj">The object to be written. Assumed not null.</param> /// <param name="dir"></param> internal static void InternalWriteObject(this ROOTNET.Interface.NTObject obj, ROOTNET.Interface.NTDirectory dir) { if (obj == null) { Console.WriteLine("WARNING: Unable to write out null object to a TDirectory!"); return; } using (ROOTLock.Lock()) { if (obj is ROOTNET.Interface.NTH1 h) { var copy = h.Clone(); dir.WriteTObject(copy); // Ugly from a memory pov, but... copy.SetNull(); } else { dir.WriteTObject(obj); obj.SetNull(); } } }
/// <summary> /// Create a canvas that is a set of stacked plots. /// </summary> /// <remarks> /// Only TH1F plots are dealt with properly here. Everything else is ignored and no stacked plot will be emitted. /// </remarks> /// <param name="histos"></param> /// <param name="canvasName">Name given to the canvas</param> /// <param name="canvasTitle">Title that will be put at the top of the canvas</param> /// <param name="colorize">True if colors should be automattically assigned to the canvas.</param> /// <param name="logy">True if the y axis should be log scale</param> /// <param name="normalize">True if the histograms should be set to normal area (1) before being plotted</param> /// <param name="legendContainsOnlyUniqueTitleWords">If true, then common words in the histogram titles are removed before they are used for the legend</param> /// <returns></returns> public static ROOTNET.Interface.NTCanvas PlotStacked(this ROOTNET.Interface.NTH1[] histos, string canvasName, string canvasTitle, bool logy = false, bool normalize = false, bool legendContainsOnlyUniqueTitleWords = true, bool colorize = true) { if (histos == null || histos.Length == 0) { return(null); } // Always build a clone... because that way if the histogram is modified after we look at it, the plot will be what // the user intended. using (ROOTLock.Lock()) { var hToPlot = (from h in histos where (h as ROOTNET.Interface.NTH1) != null select h.Clone(string.Format("{0}{1}", h.Name, canvasName)) as ROOTNET.Interface.NTH1).ToArray(); if (hToPlot.Length == 0) { var msg = new StringBuilder(); msg.Append("Warning: Only able to build a stacked plot for TH1F type plots ("); foreach (var p in histos) { msg.AppendFormat(" {0}", p.Name); } msg.Append(")"); Console.WriteLine(msg.ToString()); return(null); } foreach (var h in hToPlot) { h.SetDirectory(null); } // // If we have to normalize first, we need to normalize first! // if (normalize) { hToPlot = (from h in hToPlot select h.Normalize()).ToArray(); } // // Reset the colors on these guys // if (colorize) { var cloop = new ColorLoop(); foreach (var h in hToPlot) { h.LineColor = cloop.NextColor(); } } // // Remove common words from the titles. // if (legendContainsOnlyUniqueTitleWords && hToPlot.Length > 1) { var splitTitles = from h in hToPlot select h.Title.Split(); var wordList = from index in Enumerable.Range(0, splitTitles.Select(ar => ar.Count()).Max()) select(from titleWords in splitTitles select titleWords.Skip(index).FirstOrDefault()).ToArray(); var isTheSame = (from wl in wordList select(wl.All(tword => tword == wl.First()))).ToArray(); var fixedTitleStrings = from twords in splitTitles select( from h in twords.Zip(isTheSame, (tword, issame) => issame ? "" : tword) where !string.IsNullOrWhiteSpace(h) select h ); foreach (var histAndTitle in hToPlot.Zip(fixedTitleStrings, (h, strArr) => Tuple.Create(h, strArr))) { string title = string.Join(" ", histAndTitle.Item2); histAndTitle.Item1.Title = title; } } // // Grab the x and y axis titles from the first histogram // var xaxisTitle = hToPlot[0].Xaxis.Title; var yaxisTitle = hToPlot[0].Yaxis.Title; // // Use the nice ROOT utility THStack to make the plot. Once we do this, the plot is now owned by the TCanvas. // var stack = new ROOTNET.NTHStack(canvasName + "Stack", canvasTitle.ReplaceLatexStrings()); foreach (var h in hToPlot) { stack.Add(h); h.SetNull(); } // // Now do the plotting. Use the THStack to get all the axis stuff correct. // If we are plotting a log plot, then make sure to set that first before // calling it as it will use that information during its painting. // var result = new ROOTNET.NTCanvas(canvasName, canvasTitle.ReplaceLatexStrings()) { FillColor = ROOTNET.NTStyle.gStyle.FrameFillColor // This is not a sticky setting! }; if (logy) { result.Logy = 1; } stack.Draw("nostack"); if (!string.IsNullOrWhiteSpace(xaxisTitle)) { stack.Xaxis.Title = xaxisTitle; } if (!string.IsNullOrWhiteSpace(yaxisTitle)) { stack.Yaxis.Title = yaxisTitle; } stack.Draw("nostack"); // // The stack is now "attached" to the canvas. This means the canvas now owns it. So we // definately don't want the GC to delete it - so here we need to turn off the delete. // stack.SetNull(); // // And a legend! // result.BuildLegend(); // // Return the canvas so it can be saved to the file (or whatever). // return(result); } }