From 09a2f553b79e9b448e851bf95474789370679607 Mon Sep 17 00:00:00 2001 From: Patryk Pilichowski Date: Fri, 22 Aug 2025 16:57:14 +0200 Subject: [PATCH] Add RTreeMap --- gui/treemap/CMakeLists.txt | 25 +++ gui/treemap/inc/LinkDef.h | 17 ++ gui/treemap/inc/ROOT/RTreeMapBase.hxx | 91 +++++++++ gui/treemap/inc/ROOT/RTreeMapPainter.hxx | 77 ++++++++ gui/treemap/src/RTreeMapBase.cxx | 229 +++++++++++++++++++++++ gui/treemap/src/RTreeMapImporter.cxx | 90 +++++++++ gui/treemap/src/RTreeMapPainter.cxx | 56 ++++++ 7 files changed, 585 insertions(+) create mode 100644 gui/treemap/CMakeLists.txt create mode 100644 gui/treemap/inc/LinkDef.h create mode 100644 gui/treemap/inc/ROOT/RTreeMapBase.hxx create mode 100644 gui/treemap/inc/ROOT/RTreeMapPainter.hxx create mode 100644 gui/treemap/src/RTreeMapBase.cxx create mode 100644 gui/treemap/src/RTreeMapImporter.cxx create mode 100644 gui/treemap/src/RTreeMapPainter.cxx diff --git a/gui/treemap/CMakeLists.txt b/gui/treemap/CMakeLists.txt new file mode 100644 index 0000000000000..833f8dc0bf2d5 --- /dev/null +++ b/gui/treemap/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +############################################################################ +# CMakeLists.txt file for building ROOT treemap package +# @author Patryk Tymoteusz Pilichowski CERN +############################################################################ + +ROOT_STANDARD_LIBRARY_PACKAGE(ROOTTreeMap + HEADERS + ROOT/RTreeMapBase.hxx + ROOT/RTreeMapPainter.hxx + SOURCES + RTreeMapBase.cxx + RTreeMapImporter.cxx + RTreeMapPainter.cxx + DEPENDENCIES + ROOTNTuple + ROOTNTupleUtil + Gpad + RIO +) diff --git a/gui/treemap/inc/LinkDef.h b/gui/treemap/inc/LinkDef.h new file mode 100644 index 0000000000000..1280dcf521fb1 --- /dev/null +++ b/gui/treemap/inc/LinkDef.h @@ -0,0 +1,17 @@ +// Author: Patryk Pilichowski 08/2025 + +/************************************************************************* + * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; +#pragma link C++ class ROOT::Experimental::RTreeMapBase+; +#pragma link C++ class ROOT::Experimental::RTreeMapPainter+; +#endif diff --git a/gui/treemap/inc/ROOT/RTreeMapBase.hxx b/gui/treemap/inc/ROOT/RTreeMapBase.hxx new file mode 100644 index 0000000000000..a91ab8cdbb63a --- /dev/null +++ b/gui/treemap/inc/ROOT/RTreeMapBase.hxx @@ -0,0 +1,91 @@ +/// \file ROOT/RTreeMapBase.hxx +/// \ingroup TreeMap ROOT7 +/// \author Patryk Tymoteusz Pilichowski +/// \date 2025-08-21 +/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback +/// is welcome! + +/************************************************************************* + * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef RTREEMAPBASE_HXX +#define RTREEMAPBASE_HXX + +#include +#include +#include + +namespace ROOT::Experimental { + +// clang-format off +/** +\class ROOT::Experimental::RTreeMapBase +\ingroup TreeMap +\brief Base logic for drawing a treemap visualization + +A treemap can be used for analyzing a hierarchical data structure whose elements have a certain size. It visualizes this +hierarchical data as nested rectangles which allows for easy comparison of proportions within categories, but +also the whole structure. The squarification algorithm is used to make these rectangles as close to squares as +possible for visual clarity. + +Furthermore, we assume that each node has a type and that the size of a non-leaf node equals to the total size of its children. This +allows for drawing a legend of types of leaf nodes, and see which types occupy how much of the total space. + +Note: this visualization class/technique is independent/unrelated to `TTree`. +*/ +// clang-format on +class RTreeMapBase { +public: + struct Node { + std::string fName, fType; + uint64_t fSize; + uint64_t fChildrenIdx; + uint64_t fNChildren; + Node() = default; + Node(const std::string &name, const std::string &type, uint64_t size, uint64_t childrenIdx, uint64_t nChildren) + : fName(name), fType(type), fSize(size), fChildrenIdx(childrenIdx), fNChildren(nChildren) + { + } + }; + + struct Vec2 { + float x, y; + Vec2(float xArg, float yArg) : x(xArg), y(yArg) {} + }; + struct Rect { + Vec2 fBottomLeft, fTopRight; + Rect(const Vec2 &bottomLeftArg, const Vec2 &topRightArg) : fBottomLeft(bottomLeftArg), fTopRight(topRightArg) {} + }; + struct RGBColor { + uint8_t r, g, b, a; + RGBColor(uint8_t rArg, uint8_t gArg, uint8_t bArg, uint8_t aArg = 255) : r(rArg), g(gArg), b(bArg), a(aArg) {} + }; + std::vector fNodes; + RTreeMapBase() = default; + virtual ~RTreeMapBase() = default; + +protected: + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for drawing the entirety of the treemap. + void DrawTreeMap(const Node &elem, Rect rect, int depth) const; + + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for drawing the legend of leaf types + void DrawLegend() const; + + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for drawing a box + virtual void AddBox(const Rect &rect, const RGBColor &color, float borderWidth = 0.15f) const = 0; + + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for drawing a text + virtual void AddText(const Vec2 &pos, const std::string &content, float size, + const RGBColor &color = RGBColor(0, 0, 0), bool alignCenter = false) const = 0; +}; +} // namespace ROOT::Experimental +#endif \ No newline at end of file diff --git a/gui/treemap/inc/ROOT/RTreeMapPainter.hxx b/gui/treemap/inc/ROOT/RTreeMapPainter.hxx new file mode 100644 index 0000000000000..33c7aab72cebb --- /dev/null +++ b/gui/treemap/inc/ROOT/RTreeMapPainter.hxx @@ -0,0 +1,77 @@ +/// \file ROOT/RTreeMapPainter.hxx +/// \ingroup TreeMap ROOT7 +/// \author Patryk Tymoteusz Pilichowski +/// \date 2025-08-21 +/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback +/// is welcome! + +/************************************************************************* + * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef TTREEMAP_HXX +#define TTREEMAP_HXX + +#include "RTreeMapBase.hxx" + +#include "TROOT.h" +#include "TObject.h" +#include "TCanvas.h" +#include "TPad.h" + +#include + +namespace ROOT::Experimental { +class RNTupleInspector; + +// clang-format off +/** +\class ROOT::Experimental::RTreeMapPainter +\ingroup TreeMap +\brief Logic for drawing a treemap on a TVirtualPad + +One can visualize an RNTuple in a TCanvas as a treemap like this: +~~~ {.cpp} +auto tm = RTreeMapPainter::ImportRNTuple("file.root", "ntuple_name"); +auto c = new TCanvas("c_tm","TreeMap"); +c->Add(tm.release()); +~~~ +*/ +// clang-format on +class RTreeMapPainter final : public ROOT::Experimental::RTreeMapBase, public TObject { +public: + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for converting an RNTuple to RTreeMapPainter given RNTupleInspector + static std::unique_ptr ImportRNTuple(const ROOT::Experimental::RNTupleInspector &insp); + + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for converting an RNTuple to RTreeMapPainter given file and tuple names + static std::unique_ptr ImportRNTuple(std::string_view sourceFileName, std::string_view tupleName); + + struct Node final : public ROOT::Experimental::RTreeMapBase::Node, public TObject { + public: + ClassDef(Node, 1); + }; + RTreeMapPainter() = default; + void Paint(Option_t *opt) override; + + ClassDefOverride(RTreeMapPainter, 1); + + ~RTreeMapPainter() override = default; + +private: + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for drawing a box on TVirtualPad + void AddBox(const Rect &rect, const RGBColor &color, float borderWidth) const final; + + ///////////////////////////////////////////////////////////////////////////// + /// \brief Logic for drawing a text on TVirtualPad + void AddText(const Vec2 &pos, const std::string &content, float size, const RGBColor &color = RGBColor(0, 0, 0), + bool alignCenter = false) const final; +}; +} // namespace ROOT::Experimental +#endif \ No newline at end of file diff --git a/gui/treemap/src/RTreeMapBase.cxx b/gui/treemap/src/RTreeMapBase.cxx new file mode 100644 index 0000000000000..47ee4052dbef6 --- /dev/null +++ b/gui/treemap/src/RTreeMapBase.cxx @@ -0,0 +1,229 @@ +/// \file RTreeMapBase.cxx +/// \ingroup TreeMap ROOT7 +/// \author Patryk Tymoteusz Pilichowski +/// \date 2025-08-21 +/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback +/// is welcome! + +/************************************************************************* + * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +static constexpr float kIndentationOffset = 0.005f; +static constexpr float kPadTextOffset = 0.004f; +static constexpr float kTextSizeFactor = 0.009f; +static constexpr const char *kUnits[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB"}; + +using namespace ROOT::Experimental; + +static uint64_t ComputeFnv(const std::string &str) +{ + uint64_t h = 14695981039346656037ULL; + for (char c : str) + h = (h ^ static_cast(c)) * 1099511628211ULL; + return h; +} + +static RTreeMapBase::RGBColor ComputeColor(const std::string &str) +{ + const uint64_t hash = ComputeFnv(str); + return RTreeMapBase::RGBColor((hash >> 16) & 0xFF, (hash >> 8) & 0xFF, hash & 0xFF); +} + +static std::string GetFloatStr(const float &n, const uint8_t &precision) +{ + std::stringstream stream; + stream << std::fixed << std::setprecision(precision) << n; + return stream.str(); +} + +static std::string GetDataStr(uint64_t bytes) +{ + const uint64_t order = std::log10(bytes) / 3.0f; + const std::string unit = kUnits[order]; + const float finalSize = static_cast(bytes) / std::pow(1000, order); + return GetFloatStr(finalSize, 2) + unit; +} + +static std::vector> GetDiskOccupation(const std::vector &nodes) +{ + std::unordered_map acc; + for (const auto &node : nodes) { + if (node.fNChildren > 0) + continue; + acc[node.fType] += node.fSize; + } + + std::vector> vec; + vec.reserve(acc.size()); + for (auto &p : acc) + vec.emplace_back(std::move(p.first), p.second); + + std::sort(vec.begin(), vec.end(), [](const auto &a, const auto &b) { return a.second > b.second; }); + return vec; +} + +/* algorithm: https://vanwijk.win.tue.nl/stm.pdf */ +static float ComputeWorstRatio(const std::vector &row, float width, float height, + uint64_t totalSize, bool horizontalRows) +{ + if (row.empty()) + return 0.0f; + uint64_t sumRow = 0; + for (const auto &child : row) + sumRow += child.fSize; + if (sumRow == 0) + return 0.0f; + float worstRatio = 0.0f; + for (const auto &child : row) { + float ratio = horizontalRows ? static_cast(child.fSize * width * totalSize) / (sumRow * sumRow * height) + : static_cast(child.fSize * height * totalSize) / (sumRow * sumRow * width); + float aspectRatio = std::max(ratio, 1.0f / ratio); + if (aspectRatio > worstRatio) + worstRatio = aspectRatio; + } + return worstRatio; +} + +static std::vector> +SquarifyChildren(const std::vector &children, RTreeMapBase::Rect rect, bool horizontalRows, + uint64_t totalSize) +{ + float width = rect.fTopRight.x - rect.fBottomLeft.x; + float height = rect.fTopRight.y - rect.fBottomLeft.y; + std::vector remainingChildren = children; + std::sort(remainingChildren.begin(), remainingChildren.end(), + [](const RTreeMapBase::Node &a, const RTreeMapBase::Node &b) { return a.fSize > b.fSize; }); + std::vector> result; + RTreeMapBase::Vec2 remainingBegin = rect.fBottomLeft; + while (!remainingChildren.empty()) { + std::vector row; + float currentWorstRatio = std::numeric_limits::max(); + float remainingWidth = rect.fTopRight.x - remainingBegin.x; + float remainingHeight = rect.fTopRight.y - remainingBegin.y; + if (remainingWidth <= 0 || remainingHeight <= 0) + break; + while (!remainingChildren.empty()) { + row.push_back(remainingChildren.front()); + remainingChildren.erase(remainingChildren.begin()); + float newWorstRatio = ComputeWorstRatio(row, remainingWidth, remainingHeight, totalSize, horizontalRows); + if (newWorstRatio > currentWorstRatio) { + remainingChildren.insert(remainingChildren.begin(), row.back()); + row.pop_back(); + break; + } + currentWorstRatio = newWorstRatio; + } + uint64_t sumRow = 0; + for (const auto &child : row) + sumRow += child.fSize; + if (sumRow == 0) + continue; + float dimension = horizontalRows ? (static_cast(sumRow) / totalSize * height) + : (static_cast(sumRow) / totalSize * width); + float position = 0.0f; + for (const auto &child : row) { + float childDimension = static_cast(child.fSize) / sumRow * (horizontalRows ? width : height); + RTreeMapBase::Vec2 childBegin = horizontalRows + ? RTreeMapBase::Vec2{remainingBegin.x + position, remainingBegin.y} + : RTreeMapBase::Vec2{remainingBegin.x, remainingBegin.y + position}; + RTreeMapBase::Vec2 childEnd = + horizontalRows + ? RTreeMapBase::Vec2{remainingBegin.x + position + childDimension, remainingBegin.y + dimension} + : RTreeMapBase::Vec2{remainingBegin.x + dimension, remainingBegin.y + position + childDimension}; + result.push_back({child, {childBegin, childEnd}}); + position += childDimension; + } + if (horizontalRows) + remainingBegin.y += dimension; + else + remainingBegin.x += dimension; + } + return result; +} +void RTreeMapBase::DrawLegend() const +{ + const auto diskOccupation = GetDiskOccupation(fNodes); + + if (fNodes.empty()) + return; + const uint64_t totalSize = fNodes[0].fSize; + if (totalSize == 0) + return; + + uint8_t counter = 0; + for (const auto &entry : diskOccupation) { + const auto &typeName = entry.first; + const uint64_t entrySize = entry.second; + if (entrySize == 0) + continue; + + const auto offset = 0.835f, factor = 0.05f; + const auto posY = offset - counter * factor; + + AddBox(Rect(Vec2(offset, posY), Vec2(offset + factor, posY - factor)), ComputeColor(typeName)); + + const float percent = (entrySize / static_cast(totalSize)) * 100.0f; + const auto content = "(" + GetDataStr(entrySize) + " / " + GetDataStr(totalSize) + ")"; + + float currOffset = 0.0125f; + for (const auto &currContent : {typeName, content, GetFloatStr(percent, 2) + "%"}) { + AddText(Vec2(offset + factor, posY - currOffset), currContent, kTextSizeFactor); + currOffset += 0.01f; + } + + counter++; + } +} + +void RTreeMapBase::DrawTreeMap(const RTreeMapBase::Node &element, RTreeMapBase::Rect rect, int depth) const +{ + RTreeMapBase::Rect drawRect = RTreeMapBase::Rect(RTreeMapBase::Vec2(rect.fBottomLeft.x, rect.fBottomLeft.y), + RTreeMapBase::Vec2(rect.fTopRight.x, rect.fTopRight.y)); + bool isLeaf = (element.fNChildren == 0); + RTreeMapBase::RGBColor boxColor = isLeaf ? ComputeColor(element.fType) : RTreeMapBase::RGBColor(100, 100, 100); + AddBox(drawRect, boxColor, 0.15f); + + const std::string label = element.fName + " (" + GetDataStr(element.fSize) + ")"; + const Vec2 &labelPos = isLeaf ? Vec2((drawRect.fBottomLeft.x + drawRect.fTopRight.x) / 2.0f, + (drawRect.fBottomLeft.y + drawRect.fTopRight.y) / 2.0f) + : Vec2(drawRect.fBottomLeft.x + kPadTextOffset, drawRect.fTopRight.y - kPadTextOffset); + + float rectWidth = rect.fTopRight.x - rect.fBottomLeft.x; + float rectHeight = rect.fTopRight.y - rect.fBottomLeft.y; + float textSize = std::min(std::min(rectWidth, rectHeight) * 0.1f, kTextSizeFactor); + AddText(labelPos, label, textSize, RTreeMapBase::RGBColor(255, 255, 255), isLeaf); + + if (!isLeaf) { + float indent = kIndentationOffset; + RTreeMapBase::Rect innerRect = + RTreeMapBase::Rect(RTreeMapBase::Vec2(rect.fBottomLeft.x + indent, rect.fBottomLeft.y + indent), + RTreeMapBase::Vec2(rect.fTopRight.x - indent, rect.fTopRight.y - indent * 4.0f)); + std::vector children; + for (std::uint64_t i = 0; i < element.fNChildren; ++i) + children.push_back(fNodes[element.fChildrenIdx + i]); + uint64_t totalSize = 0; + for (const auto &child : children) + totalSize += child.fSize; + if (totalSize == 0) + return; + float width = innerRect.fTopRight.x - innerRect.fBottomLeft.x; + float height = innerRect.fTopRight.y - innerRect.fBottomLeft.y; + bool horizontalRows = width > height; + auto childRects = SquarifyChildren(children, innerRect, horizontalRows, totalSize); + for (const auto &[child, childRect] : childRects) + DrawTreeMap(child, childRect, depth + 1); + } +} diff --git a/gui/treemap/src/RTreeMapImporter.cxx b/gui/treemap/src/RTreeMapImporter.cxx new file mode 100644 index 0000000000000..63b749d230be8 --- /dev/null +++ b/gui/treemap/src/RTreeMapImporter.cxx @@ -0,0 +1,90 @@ +/// \file RTreeMapImporter.cxx +/// \ingroup TreeMap ROOT7 +/// \author Patryk Tymoteusz Pilichowski +/// \date 2025-08-21 +/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback +/// is welcome! + +/************************************************************************* + * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#include +#include +#include + +#include + +using namespace ROOT::Experimental; + +static RTreeMapBase::Node CreateNode(const RNTupleInspector &insp, const ROOT::RFieldDescriptor &fldDesc, + std::uint64_t childrenIdx, std::uint64_t nChildren, ROOT::DescriptorId_t rootId, + size_t rootSize) +{ + uint64_t size = + (rootId != fldDesc.GetId()) ? insp.GetFieldTreeInspector(fldDesc.GetId()).GetCompressedSize() : rootSize; + return {fldDesc.GetFieldName(), "", size, childrenIdx, nChildren}; +} + +static RTreeMapBase::Node CreateNode(const RNTupleInspector::RColumnInspector &colInsp, std::uint64_t childrenIdx) +{ + return {"", ROOT::Internal::RColumnElementBase::GetColumnTypeName(colInsp.GetType()), colInsp.GetCompressedSize(), + childrenIdx, 0}; +} + +std::unique_ptr RTreeMapPainter::ImportRNTuple(const ROOT::Experimental::RNTupleInspector &insp) +{ + auto treemap = std::make_unique(); + const auto &descriptor = insp.GetDescriptor(); + const auto rootId = descriptor.GetFieldZero().GetId(); + size_t rootSize = 0; + for (const auto &childId : descriptor.GetFieldDescriptor(rootId).GetLinkIds()) { + rootSize += insp.GetFieldTreeInspector(childId).GetCompressedSize(); + } + + std::queue> queue; // (columnid/fieldid, isfield) + queue.emplace(rootId, true); + while (!queue.empty()) { + size_t levelSize = queue.size(); + size_t levelChildrenStart = treemap->fNodes.size() + levelSize; + for (size_t i = 0; i < levelSize; ++i) { + const auto ¤t = queue.front(); + queue.pop(); + + size_t nChildren = 0; + if (current.second) { + std::vector children; + const auto &fldDesc = descriptor.GetFieldDescriptor(current.first); + children = fldDesc.GetLinkIds(); + for (const auto childId : children) { + queue.emplace(childId, 1); + } + for (const auto &columnDesc : descriptor.GetColumnIterable(fldDesc.GetId())) { + const auto &columnId = columnDesc.GetPhysicalId(); + children.push_back(columnId); + queue.emplace(columnId, 0); + } + nChildren = children.size(); + const auto &node = CreateNode(insp, fldDesc, levelChildrenStart, nChildren, rootId, rootSize); + treemap->fNodes.push_back(node); + } else { + const auto &colInsp = insp.GetColumnInspector(current.first); + const auto &node = CreateNode(colInsp, levelChildrenStart); + treemap->fNodes.push_back(node); + } + + levelChildrenStart += nChildren; + } + } + return treemap; +} + +std::unique_ptr RTreeMapPainter::ImportRNTuple(std::string_view sourceFileName, std::string_view tupleName) +{ + auto insp = RNTupleInspector::Create(tupleName, sourceFileName); + return RTreeMapPainter::ImportRNTuple(*insp); +} \ No newline at end of file diff --git a/gui/treemap/src/RTreeMapPainter.cxx b/gui/treemap/src/RTreeMapPainter.cxx new file mode 100644 index 0000000000000..10092a3d5d170 --- /dev/null +++ b/gui/treemap/src/RTreeMapPainter.cxx @@ -0,0 +1,56 @@ +/// \file RTreeMapPainter.cxx +/// \ingroup TreeMap ROOT7 +/// \author Patryk Tymoteusz Pilichowski +/// \date 2025-08-21 +/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback +/// is welcome! + +/************************************************************************* + * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include + +void ROOT::Experimental::RTreeMapPainter::Paint(Option_t *) +{ + if (!gPad) + return; + gPad->Clear(); + gPad->Range(0, 0, 1, 1); + gPad->cd(); + gPad->SetEditable(kFALSE); + DrawTreeMap(fNodes[0], Rect(Vec2(0.025, 0.05), Vec2(0.825, 0.9)), 0); + DrawLegend(); +} + +void ROOT::Experimental::RTreeMapPainter::AddBox(const Rect &rect, const RGBColor &color, float borderWidth) const +{ + auto box = new TBox(rect.fBottomLeft.x, rect.fBottomLeft.y, rect.fTopRight.x, rect.fTopRight.y); + box->SetFillColor(TColor::GetColor(color.r, color.g, color.b, color.a)); + box->SetLineColor(kGray); + box->SetLineWidth(std::ceil(borderWidth)); + gPad->Add(box, "l"); +} + +void ROOT::Experimental::RTreeMapPainter::AddText(const Vec2 &pos, const std::string &content, float size, + const RGBColor &color, bool alignCenter) const +{ + auto t = new TLatex(pos.x, pos.y, content.c_str()); + t->SetTextFont(42); + t->SetTextSize(size); + t->SetTextAlign((alignCenter) ? 22 : 13); + t->SetTextColor(TColor::GetColor(color.r, color.g, color.b, color.a)); + gPad->Add(t); +}