@ndmspc/ndmvr-core 1.1.0-rc.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/assets/{RCanvasPainter-DQGWienR.js → RCanvasPainter-BN1wz_jA.js} +1 -1
- package/dist/assets/RNTuple-B19bW6t4.js +1 -0
- package/dist/assets/{RPavePainter-CQHUOnSn.js → RPavePainter-BOw0-OFU.js} +1 -1
- package/dist/assets/{RTreeMapPainter-B2r2zCJG.js → RTreeMapPainter-DNojexp9.js} +1 -1
- package/dist/assets/{TASImagePainter-DRdiRLt1.js → TASImagePainter-BNJ5OAGP.js} +1 -1
- package/dist/assets/{TAnnotation3DPainter-BCJen6cS.js → TAnnotation3DPainter-BCGBhq-q.js} +1 -1
- package/dist/assets/{TArrowPainter-Dspl2Ikh.js → TArrowPainter-C7a0r3SV.js} +1 -1
- package/dist/assets/{TBoxPainter-BQEN4gj8.js → TBoxPainter-Djh43LYY.js} +1 -1
- package/dist/assets/{TEfficiencyPainter-TxRejkVD.js → TEfficiencyPainter-dXd5DL3h.js} +1 -1
- package/dist/assets/{TF1Painter-BuwSTmhs.js → TF1Painter-DwE8KsBp.js} +1 -1
- package/dist/assets/{TF2Painter-BTh6LWBk.js → TF2Painter-SGNNKiK5.js} +1 -1
- package/dist/assets/{TF3Painter-R5EVXlD4.js → TF3Painter-yaRyTX9u.js} +1 -1
- package/dist/assets/{TGaxisPainter-BJuDLLel.js → TGaxisPainter-DSiIbikC.js} +1 -1
- package/dist/assets/{TGraph2DPainter-aeM0xqgu.js → TGraph2DPainter-COqZBIPT.js} +1 -1
- package/dist/assets/{TGraphPainter-DLkx_1ES.js → TGraphPainter-Bp7xEbQz.js} +1 -1
- package/dist/assets/{TGraphPainter-CtqNpDiH.js → TGraphPainter-CurXuifu.js} +1 -1
- package/dist/assets/{TGraphPolarPainter-bMuBuvEZ.js → TGraphPolarPainter-u-FCb_Gb.js} +1 -1
- package/dist/assets/{TGraphTimePainter-D_1RHDMz.js → TGraphTimePainter-DDPwiVGX.js} +1 -1
- package/dist/assets/{TH1Painter-CMr7gXzd.js → TH1Painter-BXzutgI3.js} +1 -1
- package/dist/assets/{TH1Painter--tuoUZTH.js → TH1Painter-DyN-WKyv.js} +1 -1
- package/dist/assets/{TH2Painter-DHaL_lR_.js → TH2Painter-BpixRP4j.js} +1 -1
- package/dist/assets/{TH2Painter-BLfyBVB5.js → TH2Painter-DceuYQAL.js} +1 -1
- package/dist/assets/{TH3Painter-DLCndhZ0.js → TH3Painter-BA_W8tmJ.js} +1 -1
- package/dist/assets/{THStackPainter-B3TUpsVt.js → THStackPainter-BWLN_w9A.js} +1 -1
- package/dist/assets/{THistPainter-Df-RmCQH.js → THistPainter-B2S5BnuY.js} +1 -1
- package/dist/assets/{TLinePainter-RinEQkrM.js → TLinePainter-D8inyuoX.js} +1 -1
- package/dist/assets/{TMultiGraphPainter-BQyL8zWX.js → TMultiGraphPainter-DcwdyhOr.js} +1 -1
- package/dist/assets/{TPavePainter-Dx40jhkF.js → TPavePainter-1yngbgp8.js} +1 -1
- package/dist/assets/{TPiePainter-BaTIH6-e.js → TPiePainter-BgDv4srX.js} +1 -1
- package/dist/assets/{TPolyLinePainter-7_jf8MLI.js → TPolyLinePainter-BYlrAjCB.js} +1 -1
- package/dist/assets/{TPolyMarker3D-DYlLB9Ww.js → TPolyMarker3D-BaWOSDy6.js} +1 -1
- package/dist/assets/{TRatioPlotPainter-BvUop_Na.js → TRatioPlotPainter-CJgkL58z.js} +1 -1
- package/dist/assets/{TScatterPainter-CvLCMGW2.js → TScatterPainter-Bt11j8z8.js} +1 -1
- package/dist/assets/{TSplinePainter-DJPtQW6I.js → TSplinePainter-p6k_OB02.js} +1 -1
- package/dist/assets/{TTextPainter-BwCh-g5B.js → TTextPainter-B26aOYGd.js} +1 -1
- package/dist/assets/{TTree-CE4EQRMu.js → TTree-BtFtnLeD.js} +1 -1
- package/dist/assets/{TWebPaintingPainter-CexNsDDp.js → TWebPaintingPainter-CZ6lo-7N.js} +1 -1
- package/dist/assets/{draw3d-BPJMZpwL.js → draw3d-ejHvJRmG.js} +1 -1
- package/dist/assets/{func-C1jNXIub.js → func-C6FX2eN2.js} +1 -1
- package/dist/assets/{hist3d-DCmav5Ga.js → hist3d-D9VevdP6.js} +1 -1
- package/dist/assets/{latex3d-ITrfFgp8.js → latex3d-8Hbnfz6c.js} +1 -1
- package/dist/assets/{main-CvPDcTGr.js → main-DwRPe_ox.js} +191 -191
- package/dist/assets/{more-CGvLPt4r.js → more-64laEJ4X.js} +1 -1
- package/dist/assets/{ndmvr-aframe-core-Cw91_9_Y.js → ndmvr-aframe-core-DEHQ38JB.js} +4 -4
- package/dist/assets/{rntuple-BCwqxNeP.js → rntuple-i8TaG-6V.js} +1 -1
- package/dist/assets/{stress-Ihg3ljmc.js → stress-KrXoDujC.js} +1 -1
- package/dist/assets/{v7more-H3D-KQGQ.js → v7more-C-UZAxz1.js} +1 -1
- package/dist/docs/components/configuration/configuration/index.html +243 -238
- package/dist/docs/components/tutorial/canvas/canvas/index.html +1 -1
- package/dist/docs/components/tutorial/configurationChapter/configuration-chapter/index.html +1 -1
- package/dist/docs/components/tutorial/firstVisualization/first-visualization/index.html +1 -1
- package/dist/docs/components/tutorial/interactions/interactions/index.html +1 -1
- package/dist/docs/downloads/canvas.zip +0 -0
- package/dist/docs/downloads/configuration.zip +0 -0
- package/dist/docs/downloads/first-visualization.zip +0 -0
- package/dist/docs/downloads/interactions.zip +0 -0
- package/dist/docs/index.html +1 -1
- package/dist/docs/search/search_index.json +1 -1
- package/dist/docs/sitemap.xml.gz +0 -0
- package/dist/index.es.js +962 -953
- package/dist/index.html +2 -2
- package/dist/index.umd.js +10 -10
- package/dist/stress.html +2 -2
- package/package.json +1 -1
- package/dist/assets/RNTuple-L4uyxl3s.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"NDMVR","text":""},{"location":"#introduction","title":"Introduction","text":"<p><code>ndmvr-core</code> is the foundational library of the NDMVR project. It provides a collection of reusable Three.js-based components that form the basis for 3D object visualization and inter-component communication using RxJS.</p> <p>The main object of visualization is an N-Dimensional histogram. It is a multi-dimensional representation of data using the hypercube concept.</p> <p>The library is designed to be modular and extensible, allowing developers to customize and integrate specific features as needed.</p> <p>The library is framework-agnostic. Each component is implemented as a plain JavaScript class and can be used independently of NDMVR in any Three.js-based project or framework.</p>"},{"location":"#features","title":"Features","text":"<ul> <li>3D objects capable of representing multi-dimensional data</li> <li>Reactive communication between components powered by RxJS</li> <li>High performance through optimized rendering and raycasting techniques</li> <li>Seamless integration with any Three.js-based application</li> </ul>"},{"location":"#getting-started","title":"Getting Started","text":"<p>If you are new to the NDMSPC project, see the Getting Started guide for installation and basic usage instructions.</p> <p>This section of the documentation focuses specifically on the core library, its components, and the communication interfaces they provide.</p>"},{"location":"quickstart/","title":"Quickstart","text":""},{"location":"quickstart/#ways-to-get-started","title":"Ways to get started","text":"<p>It is possible to get started using multiple ways.</p>"},{"location":"quickstart/#gitlab-pages","title":"Gitlab pages","text":"<p>For the quickest preview, you can use our gitlab pages.</p>"},{"location":"quickstart/#copy-repository","title":"Copy repository","text":"<ul> <li>If you wish to play around with the demo code on our Gitlab pages, or change the components itself, you can clone the repository.</li> <li>After copying the repository, you can run <code>npm install</code> to install all dependencies.</li> <li>Then, you can run <code>npm run dev</code> to start the development server.</li> </ul>"},{"location":"quickstart/#npm-package","title":"NPM package","text":"<ul> <li>If you wish to use provided components in your own project, you can install the package using <code>npm install ndmvr-core</code>.</li> <li>Then, you can import and use the components in your project.</li> <li>Provided components are written in Three.js, so you can use them in any project that uses Three.js or any other Three.js-based framework.</li> <li>You can get inspired on how to use these components at our Gtilab demo which uses A-Frame, or you can check out our other open-source project NdmVr, which is built on top of React-Three-Fiber.</li> <li>Alternatively, you can head to our tutorial and see how to use the components in basic Three.js applications.</li> </ul>"},{"location":"components/communication/bin-info-subject/","title":"Bin info subject","text":""},{"location":"components/communication/bin-info-subject/#bin-info-subject","title":"Bin Info Subject","text":""},{"location":"components/communication/bin-info-subject/#overview","title":"Overview","text":"<p>The Bin Info Subject is a singleton RxJS-based communication channel for managing and broadcasting histogram bin information throughout the application. It uses a <code>ReplaySubject</code> to ensure that the latest bin information is always available to new subscribers.</p>"},{"location":"components/communication/bin-info-subject/#class-structure","title":"Class Structure","text":"<pre><code>class BinInfoSubject {\n #subject; // Private ReplaySubject(1)\n}\n</code></pre>"},{"location":"components/communication/bin-info-subject/#methods","title":"Methods","text":""},{"location":"components/communication/bin-info-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>BinInfoSubject</code> instance with a <code>ReplaySubject(1)</code>.</p> <p>Behavior: - Initializes a private <code>ReplaySubject</code> with buffer size of 1 - The buffer ensures the last emitted value is replayed to new subscribers</p>"},{"location":"components/communication/bin-info-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to bin info updates.</p> <p>Returns: <code>Observable</code> - Observable stream of bin information</p> <p>Usage: </p><pre><code>import { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nbinInfoSubjectGet().getObservable().subscribe(binInfo => {\n console.log('Bin info updated:', binInfo);\n});\n</code></pre><p></p>"},{"location":"components/communication/bin-info-subject/#getvalue","title":"getValue()","text":"<p>Retrieves the current value of the subject.</p> <p>Returns: Current bin information value</p> <p>Note: This method is defined but may not work as expected since <code>ReplaySubject</code> doesn't have a <code>getValue()</code> method by default. This might require using <code>BehaviorSubject</code> instead.</p>"},{"location":"components/communication/bin-info-subject/#nexte","title":"next(e)","text":"<p>Broadcasts new bin information to all subscribers.</p> <p>Parameters: - <code>e</code> - The bin information object to broadcast</p> <p>Usage: </p><pre><code>import { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nbinInfoSubjectGet().next({\n binIndex: 42,\n content: 123.45,\n pos: { x: 1, y: 2, z: 3 },\n // ... other bin properties\n});\n</code></pre><p></p>"},{"location":"components/communication/bin-info-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const binInfoSubjectGet = () => {\n if (!binInfoSubject) binInfoSubject = new BinInfoSubject();\n return binInfoSubject;\n};\n</code></pre> <p>Benefits: - Ensures only one instance exists throughout the application - Provides global access to the bin info communication channel - Maintains consistent state across all components</p>"},{"location":"components/communication/bin-info-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Bin Hover Information:</li> <li>Display bin details when user hovers over histogram bins</li> <li> <p>Show bin content, position, and statistics</p> </li> <li> <p>Bin Selection:</p> </li> <li>Communicate selected bin information to UI components</li> <li> <p>Update info panels with selected bin data</p> </li> <li> <p>Interactive Tooltips:</p> </li> <li>Power tooltip displays in VR environment</li> <li>Synchronize bin information across multiple views</li> </ol>"},{"location":"components/communication/bin-info-subject/#data-flow-example","title":"Data Flow Example","text":"<pre><code>// Component A: Raycaster detects bin hover\nimport { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nfunction onBinHover(bin) {\n binInfoSubjectGet().next({\n binId: bin.id,\n content: bin.value,\n pos: bin.pos,\n histogram: bin.histogramId\n });\n}\n\n// Component B: Info visualizer subscribes to updates\nimport { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nbinInfoSubjectGet().getObservable().subscribe(binInfo => {\n updateBinInfoDisplay(binInfo);\n});\n</code></pre>"},{"location":"components/communication/bin-info-subject/#subject-type-replaysubject1","title":"Subject Type: ReplaySubject(1)","text":"<p>Characteristics: - Buffer Size: 1 (stores the last emitted value) - Replay Behavior: New subscribers immediately receive the last value - Use Case: Ensures components always have access to current bin information</p>"},{"location":"components/communication/bin-info-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> </ul>"},{"location":"components/communication/bin-info-subject/#related-components","title":"Related Components","text":"<ul> <li>Bin Info JSROOT</li> <li>NDMVR Raycaster</li> <li>Histogram Subject</li> </ul>"},{"location":"components/communication/bin-info-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use the singleton accessor: Use <code>binInfoSubjectGet()</code> instead of creating new instances</li> <li>Unsubscribe when done: Always unsubscribe from observables in component cleanup</li> <li>Type safety: Consider defining TypeScript interfaces for bin info objects</li> <li>Error handling: Wrap subscriptions with error handlers for robustness</li> </ol>"},{"location":"components/communication/bin-info-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/canvas-subject/","title":"Canvas subject","text":""},{"location":"components/communication/canvas-subject/#canvas-subject","title":"Canvas Subject","text":""},{"location":"components/communication/canvas-subject/#overview","title":"Overview","text":"<p>The Canvas Subject is a singleton RxJS-based communication channel for managing and broadcasting canvas-related events and data throughout the application. It uses a <code>ReplaySubject</code> to ensure that the latest canvas state is always available to new subscribers.</p>"},{"location":"components/communication/canvas-subject/#class-structure","title":"Class Structure","text":"<pre><code>class CanvasSubject {\n #subject; // Private ReplaySubject(1)\n}\n</code></pre>"},{"location":"components/communication/canvas-subject/#methods","title":"Methods","text":""},{"location":"components/communication/canvas-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>CanvasSubject</code> instance with a <code>ReplaySubject(1)</code>.</p> <p>Behavior: - Initializes a private <code>ReplaySubject</code> with buffer size of 1 - The buffer ensures the last emitted value is replayed to new subscribers</p>"},{"location":"components/communication/canvas-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to canvas updates.</p> <p>Returns: <code>Observable</code> - Observable stream of canvas events and data</p> <p>Usage: </p><pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().getObservable().subscribe(canvasData => {\n console.log('Canvas updated:', canvasData);\n});\n</code></pre><p></p>"},{"location":"components/communication/canvas-subject/#nexte","title":"next(e)","text":"<p>Broadcasts new canvas data to all subscribers.</p> <p>Parameters: - <code>e</code> - The canvas event or data object to broadcast</p> <p>Usage: </p><pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().next({\n canvasId: 'canvas1',\n action: 'update',\n data: { /* canvas state */ }\n});\n</code></pre><p></p>"},{"location":"components/communication/canvas-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const canvasSubjectGet = () => {\n if (!canvasSubject) canvasSubject = new CanvasSubject();\n return canvasSubject;\n};\n</code></pre> <p>Benefits: - Ensures only one instance exists throughout the application - Provides global access to the canvas communication channel - Maintains consistent state across all components</p>"},{"location":"components/communication/canvas-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Canvas State Management:</li> <li>Broadcast canvas creation, updates, and deletion</li> <li> <p>Synchronize canvas state across multiple components</p> </li> <li> <p>Canvas Interaction Events:</p> </li> <li>Communicate user interactions with canvas elements</li> <li> <p>Handle canvas selection and focus changes</p> </li> <li> <p>Canvas Configuration:</p> </li> <li>Update canvas properties (position, scale, rotation)</li> <li> <p>Apply global canvas settings</p> </li> <li> <p>Multi-Canvas Coordination:</p> </li> <li>Coordinate multiple canvases in the scene</li> <li>Manage canvas visibility and layering</li> </ol>"},{"location":"components/communication/canvas-subject/#data-flow-example","title":"Data Flow Example","text":"<pre><code>// Component A: Canvas component emits state\nimport { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\nfunction onCanvasCreated(canvas) {\n canvasSubjectGet().next({\n type: 'created',\n canvasId: canvas.id,\n pos: canvas.pos,\n dimensions: canvas.dimensions\n });\n}\n\n// Component B: UI subscribes to canvas updates\nimport { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().getObservable().subscribe(event => {\n if (event.type === 'created') {\n addCanvasToList(event.canvasId);\n }\n});\n</code></pre>"},{"location":"components/communication/canvas-subject/#subject-type-replaysubject1","title":"Subject Type: ReplaySubject(1)","text":"<p>Characteristics: - Buffer Size: 1 (stores the last emitted value) - Replay Behavior: New subscribers immediately receive the last value - Use Case: Ensures components always have access to current canvas state</p>"},{"location":"components/communication/canvas-subject/#event-types-suggested","title":"Event Types (Suggested)","text":"<p>While not enforced by the implementation, common event types might include:</p> <ul> <li><code>created</code> - Canvas was created</li> <li><code>updated</code> - Canvas properties changed</li> <li><code>removed</code> - Canvas was removed</li> <li><code>selected</code> - Canvas was selected by user</li> <li><code>deselected</code> - Canvas was deselected</li> </ul>"},{"location":"components/communication/canvas-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> </ul>"},{"location":"components/communication/canvas-subject/#related-components","title":"Related Components","text":"<ul> <li>Canvas Component</li> <li>Histogram JSROOT</li> <li>Config Subject</li> </ul>"},{"location":"components/communication/canvas-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use the singleton accessor: Use <code>canvasSubjectGet()</code> instead of creating new instances</li> <li>Unsubscribe when done: Always unsubscribe from observables in component cleanup</li> <li>Event structure: Maintain consistent event object structure across the application</li> <li>Type definitions: Consider defining TypeScript interfaces for canvas events</li> <li>Error handling: Wrap subscriptions with error handlers for robustness</li> </ol>"},{"location":"components/communication/canvas-subject/#example-complete-canvas-lifecycle","title":"Example: Complete Canvas Lifecycle","text":"<pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\n// Subscribe to all canvas events\nconst subscription = canvasSubjectGet().getObservable().subscribe({\n next: (event) => {\n switch(event.type) {\n case 'created':\n console.log('Canvas created:', event.canvasId);\n break;\n case 'updated':\n console.log('Canvas updated:', event.canvasId);\n break;\n case 'removed':\n console.log('Canvas removed:', event.canvasId);\n break;\n }\n },\n error: (err) => console.error('Canvas subject error:', err)\n});\n\n// Emit canvas events\ncanvasSubjectGet().next({ type: 'created', canvasId: 'canvas1' });\ncanvasSubjectGet().next({ type: 'updated', canvasId: 'canvas1', sc: 1.5 });\ncanvasSubjectGet().next({ type: 'removed', canvasId: 'canvas1' });\n\n// Cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/canvas-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/config-subject/","title":"Config subject","text":""},{"location":"components/communication/config-subject/#config-subject","title":"Config Subject","text":""},{"location":"components/communication/config-subject/#overview","title":"Overview","text":"<p>The Config Subject is a singleton RxJS-based communication channel for managing application-wide configuration. It uses a <code>BehaviorSubject</code> initialized with default configuration values and provides sophisticated config merging capabilities, particularly for histogram configurations.</p>"},{"location":"components/communication/config-subject/#class-structure","title":"Class Structure","text":"<pre><code>class ConfigSubject {\n #subject; // Private BehaviorSubject with default config\n}\n</code></pre>"},{"location":"components/communication/config-subject/#methods","title":"Methods","text":""},{"location":"components/communication/config-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>ConfigSubject</code> instance with parsed default configuration.</p> <p>Behavior: - Initializes a private <code>BehaviorSubject</code> with <code>parseConfig(defaultConfig, {})</code> - Loads and parses configuration from <code>config-default.json</code> - Ensures the subject always has a valid configuration value</p>"},{"location":"components/communication/config-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to config updates.</p> <p>Returns: <code>Observable</code> - Observable stream of configuration changes</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconfigSubjectGet().getObservable().subscribe(config => {\n console.log('Config updated:', config);\n});\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#getvalue","title":"getValue()","text":"<p>Retrieves the current configuration value synchronously.</p> <p>Returns: Current configuration object</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconst currentConfig = configSubjectGet().getValue();\nconsole.log('Current config:', currentConfig);\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#nexte","title":"next(e)","text":"<p>Updates the configuration by parsing and merging new values with existing config.</p> <p>Parameters: - <code>e</code> - New configuration object to merge</p> <p>Returns: Parsed and merged configuration object</p> <p>Behavior: - Parses the new configuration using <code>parseConfig()</code> - Merges with existing configuration - Broadcasts the updated configuration to all subscribers</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconst updatedConfig = configSubjectGet().next({\n histogram: {\n color: new Color(0xff0000),\n sc: 1.5\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#appendpadsids-disp_kind-settings","title":"appendPads(ids, disp_kind, settings)","text":"<p>Appends pad configurations to the current configuration.</p> <p>Parameters: - <code>ids</code> - Array of pad IDs - <code>disp_kind</code> - Display kind/type - <code>settings</code> - Pad-specific settings</p> <p>Behavior: - Uses <code>appendPads()</code> utility function to update configuration - Broadcasts the updated configuration to all subscribers</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconfigSubjectGet().appendPads(\n ['pad1', 'pad2'],\n 'histogram',\n { visible: true }\n);\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#mergehistogramconfigpartialconfig-defaultconfig","title":"mergeHistogramConfig(partialConfig, defaultConfig)","text":"<p>Deep merges histogram configuration objects with special handling for THREE.js objects.</p> <p>Parameters: - <code>partialConfig</code> - Partial configuration to merge - <code>defaultConfig</code> (optional) - Defaults to <code>this.#subject.value.config.histogram</code></p> <p>Returns: Merged configuration object</p> <p>Special Handling: - Arrays are replaced entirely (not merged) - THREE.js objects (<code>Color</code>, <code>Vector3</code>) are replaced entirely - Plain objects are deep merged recursively - <code>undefined</code> values preserve default values</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\nimport { Color, Vector3 } from 'three';\n\nconst merged = configSubjectGet().mergeHistogramConfig({\n color: new Color(0xff0000),\n pos: new Vector3(0, 1, 0),\n options: {\n wireframe: true,\n opacity: 0.8\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const configSubjectGet = () => {\n if (!configSubject) configSubject = new ConfigSubject();\n return configSubject;\n};\n</code></pre>"},{"location":"components/communication/config-subject/#configuration-merging-logic","title":"Configuration Merging Logic","text":""},{"location":"components/communication/config-subject/#value-objects-non-mergeable","title":"Value Objects (Non-Mergeable)","text":"<p>The following types are treated as atomic values and replaced entirely: - Arrays - <code>THREE.Color</code> instances - <code>THREE.Vector3</code> instances - Objects with <code>isColor === true</code> - Objects with <code>isVector3 === true</code></p>"},{"location":"components/communication/config-subject/#plain-objects-mergeable","title":"Plain Objects (Mergeable)","text":"<p>Regular JavaScript objects are deep merged: - Existing properties are preserved unless overridden - New properties are added - Nested objects are merged recursively</p>"},{"location":"components/communication/config-subject/#example-merge-behavior","title":"Example Merge Behavior","text":"<pre><code>// Default config\n{\n color: new Color(0x0000ff),\n position: new Vector3(0, 0, 0),\n options: {\n wireframe: false,\n opacity: 1.0,\n sc: 1.0\n }\n}\n\n// Partial config\n{\n color: new Color(0xff0000), // Replaces entirely\n options: {\n wireframe: true, // Merged\n opacity: 0.5 // Merged, scale preserved\n }\n}\n\n// Result\n{\n color: new Color(0xff0000),\n position: new Vector3(0, 0, 0), // Preserved\n options: {\n wireframe: true,\n opacity: 0.5,\n sc: 1.0 // Preserved from default\n }\n}\n</code></pre>"},{"location":"components/communication/config-subject/#subject-type-behaviorsubject","title":"Subject Type: BehaviorSubject","text":"<p>Characteristics: - Initial Value: Parsed default configuration - Current Value Access: Provides synchronous <code>getValue()</code> method - Replay Behavior: New subscribers immediately receive current config - Use Case: Perfect for application-wide configuration management</p>"},{"location":"components/communication/config-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>BehaviorSubject</code>)</li> <li><code>parseConfig</code>, <code>appendPads</code> from <code>../utils/baseUtil.js</code></li> <li><code>Vector3</code>, <code>Color</code> from <code>three</code></li> <li><code>defaultConfig</code> from <code>../config-default.json</code></li> </ul>"},{"location":"components/communication/config-subject/#related-components","title":"Related Components","text":"<ul> <li>Histogram Subject</li> <li>State Subject</li> <li>Canvas Subject</li> </ul>"},{"location":"components/communication/config-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Use getValue() for synchronous access: When you need current config immediately</li> <li>Use getObservable() for reactive updates: When you want to react to config changes</li> <li>Always use the singleton accessor: Use <code>configSubjectGet()</code></li> <li>Partial updates: Only provide properties you want to change</li> <li>THREE.js objects: Remember that Color and Vector3 replace entirely, not merge</li> <li>Type safety: Consider TypeScript interfaces for configuration structure</li> </ol>"},{"location":"components/communication/config-subject/#example-complete-configuration-workflow","title":"Example: Complete Configuration Workflow","text":"<pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\nimport { Color, Vector3 } from 'three';\n\n// Get current config synchronously\nconst currentConfig = configSubjectGet().getValue();\nconsole.log('Current:', currentConfig);\n\n// Subscribe to config changes\nconst subscription = configSubjectGet().getObservable().subscribe(config => {\n console.log('Config updated:', config);\n applyConfigToScene(config);\n});\n\n// Update configuration\nconfigSubjectGet().next({\n histogram: {\n color: new Color(0xff0000),\n options: {\n wireframe: true\n }\n }\n});\n\n// Append pads\nconfigSubjectGet().appendPads(\n ['pad1', 'pad2'],\n 'histogram',\n { visible: true, opacity: 0.8 }\n);\n\n// Merge histogram-specific config\nconst mergedHistoConfig = configSubjectGet().mergeHistogramConfig({\n binColor: new Color(0x00ff00),\n axes: {\n showLabels: true\n }\n});\n\n// Cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/config-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/dispatch-subject/","title":"Dispatch subject","text":""},{"location":"components/communication/dispatch-subject/#dispatch-subject","title":"Dispatch Subject","text":""},{"location":"components/communication/dispatch-subject/#overview","title":"Overview","text":"<p>The Dispatch Subject is a singleton RxJS-based communication channel for dispatching general-purpose events throughout the application. It uses a standard <code>Subject</code> without buffering, making it ideal for fire-and-forget event notifications.</p>"},{"location":"components/communication/dispatch-subject/#class-structure","title":"Class Structure","text":"<pre><code>class DispatchSubject {\n #subject; // Private Subject\n}\n</code></pre>"},{"location":"components/communication/dispatch-subject/#methods","title":"Methods","text":""},{"location":"components/communication/dispatch-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>DispatchSubject</code> instance with a standard <code>Subject</code>.</p> <p>Behavior: - Initializes a private <code>Subject</code> with no initial value - No replay functionality - subscribers only receive events emitted after subscription</p>"},{"location":"components/communication/dispatch-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to dispatch events.</p> <p>Returns: <code>Observable</code> - Observable stream of dispatch events</p> <p>Usage: </p><pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\n\ndispatchSubjectGet().getObservable().subscribe(event => {\n console.log('Event dispatched:', event);\n});\n</code></pre><p></p>"},{"location":"components/communication/dispatch-subject/#nexte","title":"next(e)","text":"<p>Dispatches an event to all current subscribers.</p> <p>Parameters: - <code>e</code> - The event object to dispatch</p> <p>Usage: </p><pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\n\ndispatchSubjectGet().next({\n type: 'histogram-selected',\n histogramId: 'histo1',\n timestamp: Date.now()\n});\n</code></pre><p></p>"},{"location":"components/communication/dispatch-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const dispatchSubjectGet = () => {\n if (!dispatchSubject) dispatchSubject = new DispatchSubject();\n return dispatchSubject;\n};\n</code></pre> <p>Benefits: - Ensures only one instance exists throughout the application - Provides global access to the event dispatch system - Lightweight event bus for application-wide events</p>"},{"location":"components/communication/dispatch-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Global Event Bus:</li> <li>Dispatch application-level events</li> <li>Notify multiple components of state changes</li> <li> <p>Implement loosely coupled component communication</p> </li> <li> <p>Action Notifications:</p> </li> <li>User interaction events</li> <li>System state changes</li> <li> <p>Async operation completions</p> </li> <li> <p>Cross-Component Communication:</p> </li> <li>Components that don't have direct relationships</li> <li>Broadcasting events to multiple listeners</li> <li> <p>Decoupling event producers from consumers</p> </li> <li> <p>Command Pattern:</p> </li> <li>Dispatch commands to be executed by handlers</li> <li>Implement undo/redo functionality</li> <li>Queue and process actions</li> </ol>"},{"location":"components/communication/dispatch-subject/#subject-type-subject","title":"Subject Type: Subject","text":"<p>Characteristics: - No Initial Value: Unlike BehaviorSubject, has no initial state - No Replay: New subscribers don't receive past events - Hot Observable: Events are missed if not subscribed at emit time - Use Case: Perfect for fire-and-forget event notifications</p> <p>Important: Subscribers must be subscribed before events are emitted to receive them.</p>"},{"location":"components/communication/dispatch-subject/#example-event-structures","title":"Example Event Structures","text":"<p>While the implementation is flexible, consider standardizing event structure:</p> <pre><code>// Basic event\n{\n type: 'event-name',\n payload: { /* event data */ }\n}\n\n// Action event\n{\n type: 'action',\n action: 'update-histogram',\n target: 'histogram1',\n data: { /* action data */ }\n}\n\n// Lifecycle event\n{\n type: 'lifecycle',\n phase: 'mounted',\n component: 'histogram-visualizer'\n}\n</code></pre>"},{"location":"components/communication/dispatch-subject/#data-flow-example","title":"Data Flow Example","text":"<pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\n\n// Component A: Subscribe to events (must subscribe first!)\nconst subscription = dispatchSubjectGet().getObservable().subscribe({\n next: (event) => {\n console.log('Event received:', event);\n if (event.type === 'histogram-updated') {\n refreshHistogramDisplay(event.histogramId);\n }\n },\n error: (err) => console.error('Dispatch error:', err)\n});\n\n// Component B: Dispatch events\nfunction onHistogramUpdate(histogramId) {\n dispatchSubjectGet().next({\n type: 'histogram-updated',\n histogramId: histogramId,\n timestamp: Date.now()\n });\n}\n\n// Later: cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/dispatch-subject/#filtering-events","title":"Filtering Events","text":"<p>Use RxJS operators to filter specific event types:</p> <pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\nimport { filter } from 'rxjs/operators';\n\n// Only receive histogram events\nconst histogramEvents$ = dispatchSubjectGet()\n .getObservable()\n .pipe(\n filter(event => event.type?.startsWith('histogram-'))\n );\n\nhistogramEvents$.subscribe(event => {\n console.log('Histogram event:', event);\n});\n</code></pre>"},{"location":"components/communication/dispatch-subject/#comparison-with-other-subjects","title":"Comparison with Other Subjects","text":"Feature DispatchSubject BehaviorSubject ReplaySubject Initial Value \u274c None \u2705 Required \u274c None Replay \u274c No \u2705 Current only \u2705 Buffer size Use Case Events State Recent state"},{"location":"components/communication/dispatch-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>Subject</code>)</li> </ul>"},{"location":"components/communication/dispatch-subject/#related-components","title":"Related Components","text":"<ul> <li>Function Subject</li> <li>State Subject</li> <li>Config Subject</li> </ul>"},{"location":"components/communication/dispatch-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Subscribe early: Subscribe before events are emitted to avoid missing them</li> <li>Always use singleton accessor: Use <code>dispatchSubjectGet()</code></li> <li>Standardize event structure: Define consistent event shapes</li> <li>Use type field: Include a <code>type</code> field for event identification</li> <li>Unsubscribe: Always clean up subscriptions to prevent memory leaks</li> <li>Error handling: Include error handlers in subscriptions</li> <li>Consider alternatives: For state management, consider BehaviorSubject or StateSubject</li> </ol>"},{"location":"components/communication/dispatch-subject/#example-event-driven-architecture","title":"Example: Event-Driven Architecture","text":"<pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\nimport { filter } from 'rxjs/operators';\n\n// Define event types\nconst EventTypes = {\n HISTOGRAM_SELECTED: 'histogram-selected',\n HISTOGRAM_UPDATED: 'histogram-updated',\n USER_INTERACTION: 'user-interaction',\n ERROR_OCCURRED: 'error-occurred'\n};\n\n// Event dispatcher utility\nclass EventDispatcher {\n static dispatch(type, payload) {\n dispatchSubjectGet().next({\n type,\n payload,\n timestamp: Date.now()\n });\n }\n}\n\n// Event listener utility\nclass EventListener {\n static listen(eventType, handler) {\n return dispatchSubjectGet()\n .getObservable()\n .pipe(filter(event => event.type === eventType))\n .subscribe(handler);\n }\n}\n\n// Usage\nconst subscription = EventListener.listen(\n EventTypes.HISTOGRAM_SELECTED,\n (event) => {\n console.log('Histogram selected:', event.payload);\n }\n);\n\n// Dispatch events\nEventDispatcher.dispatch(EventTypes.HISTOGRAM_SELECTED, {\n histogramId: 'histo1',\n source: 'user-click'\n});\n\n// Cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/dispatch-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/function-subject/","title":"Function subject","text":""},{"location":"components/communication/function-subject/#function-subject","title":"Function Subject","text":""},{"location":"components/communication/function-subject/#overview","title":"Overview","text":"<p>The Function Subject is a singleton RxJS-based communication channel for dynamically adding and removing event handlers to histogram entities. It uses a <code>ReplaySubject</code> to manage function registration, ensuring that all function registrations are replayed to new subscribers.</p>"},{"location":"components/communication/function-subject/#class-structure","title":"Class Structure","text":"<pre><code>class FunctionSubject {\n #subject; // Private ReplaySubject\n}\n</code></pre>"},{"location":"components/communication/function-subject/#methods","title":"Methods","text":""},{"location":"components/communication/function-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>FunctionSubject</code> instance with an unbounded <code>ReplaySubject</code>.</p> <p>Behavior: - Initializes a private <code>ReplaySubject</code> with no size limit - All function add/remove operations are replayed to new subscribers - Comment indicates: \"only new functions are promoted to updated subscribers and all functions are promoted to new subscriber\"</p>"},{"location":"components/communication/function-subject/#addfunctionsinput","title":"addFunctions(input)","text":"<p>Registers one or more event handler functions to be attached to target entities.</p> <p>Parameters: - <code>input</code> - Single function object or array of function objects</p> <p>Function Object Structure: </p><pre><code>{\n target: {\n entity: 'entity-type', // e.g., 'histogram', 'canvas'\n id: 'entity-id' | ['id1', 'id2'] // Single ID or array of IDs\n },\n event: 'event-name', // e.g., 'click', 'hover', 'instance-hover'\n function: handlerFunction // The actual function to execute\n}\n</code></pre><p></p> <p>Behavior: - Accepts single object or array of objects - Normalizes <code>target.id</code> to always be an array - Emits event with <code>flag: \"add\"</code> for each function - Broadcasts to all subscribers</p> <p>Usage: </p><pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\n// Add single function\nfunctionSubjectGet().addFunctions({\n target: {\n entity: 'histogram',\n id: 'histogram1'\n },\n event: 'click',\n function: (event) => {\n console.log('Histogram clicked:', event);\n }\n});\n\n// Add multiple functions\nfunctionSubjectGet().addFunctions([\n {\n target: { entity: 'histogram', id: ['histo1', 'histo2'] },\n event: 'hover',\n function: onHover\n },\n {\n target: { entity: 'canvas', id: 'canvas1' },\n event: 'click',\n function: onCanvasClick\n }\n]);\n</code></pre><p></p>"},{"location":"components/communication/function-subject/#removefunctionsinput","title":"removeFunctions(input)","text":"<p>Unregisters one or more event handler functions from target entities.</p> <p>Parameters: - <code>input</code> - Single function object or array of function objects</p> <p>Behavior: - Accepts single object or array of objects - Normalizes <code>target.id</code> to always be an array - Emits with <code>flag: \"remove\"</code> if event is specified - Emits with <code>flag: \"removeAll\"</code> if no event specified (removes all handlers) - Broadcasts to all subscribers</p> <p>Remove Modes: 1. Specific Event: <code>flag: \"remove\"</code> - Removes specific event handler 2. All Events: <code>flag: \"removeAll\"</code> - Removes all handlers from target</p> <p>Usage: </p><pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\n// Remove specific event handler\nfunctionSubjectGet().removeFunctions({\n target: {\n entity: 'histogram',\n id: 'histogram1'\n },\n event: 'click',\n function: clickHandler\n});\n\n// Remove all handlers from entity (no event specified)\nfunctionSubjectGet().removeFunctions({\n target: {\n entity: 'histogram',\n id: 'histogram1'\n }\n});\n\n// Remove from multiple entities\nfunctionSubjectGet().removeFunctions({\n target: {\n entity: 'histogram',\n id: ['histo1', 'histo2']\n },\n event: 'hover'\n});\n</code></pre><p></p>"},{"location":"components/communication/function-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable for subscribing to function registration events.</p> <p>Returns: <code>Observable</code> - Stream of function add/remove operations</p> <p>Event Structure: </p><pre><code>{\n flag: 'add' | 'remove' | 'removeAll',\n target: {\n entity: string,\n id: string[] // Always an array\n },\n event: string, // Event name (may be undefined for removeAll)\n function: Function // Handler function\n}\n</code></pre><p></p> <p>Usage: </p><pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\nfunctionSubjectGet().getObservable().subscribe(operation => {\n const { flag, target, event, function: handler } = operation;\n\n target.id.forEach(id => {\n if (flag === 'add') {\n attachHandler(id, event, handler);\n } else if (flag === 'remove') {\n detachHandler(id, event, handler);\n } else if (flag === 'removeAll') {\n removeAllHandlers(id);\n }\n });\n});\n</code></pre><p></p>"},{"location":"components/communication/function-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const functionSubjectGet = () => {\n if (!functionSubject) functionSubject = new FunctionSubject();\n return functionSubject;\n};\n</code></pre>"},{"location":"components/communication/function-subject/#subject-type-replaysubject-unbounded","title":"Subject Type: ReplaySubject (Unbounded)","text":"<p>Characteristics: - Buffer Size: Unlimited (all operations are stored) - Replay Behavior: All function registrations are replayed to new subscribers - Use Case: Ensures late-subscribing components receive all function registrations - Memory Consideration: May grow unbounded - consider cleanup strategies</p>"},{"location":"components/communication/function-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Dynamic Event Handlers:</li> <li>Add click handlers to histograms at runtime</li> <li>Attach hover effects dynamically</li> <li> <p>Register custom interaction handlers</p> </li> <li> <p>Plugin System:</p> </li> <li>Allow plugins to register event handlers</li> <li>Extend histogram functionality without modifying core code</li> <li> <p>Enable/disable features by adding/removing handlers</p> </li> <li> <p>Interactive Tooltips:</p> </li> <li>Register hover handlers for info display</li> <li> <p>Remove handlers when tooltip is disabled</p> </li> <li> <p>Multi-Target Registration:</p> </li> <li>Register same handler to multiple histograms</li> <li>Bulk add/remove operations</li> </ol>"},{"location":"components/communication/function-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\n// Component subscribes to function operations\nclass HistogramManager {\n constructor() {\n this.handlers = new Map();\n\n this.subscription = functionSubjectGet()\n .getObservable()\n .subscribe(this.handleFunctionOperation.bind(this));\n }\n\n handleFunctionOperation(operation) {\n const { flag, target, event, function: handler } = operation;\n\n target.id.forEach(id => {\n const histogram = this.getHistogram(id);\n if (!histogram) return;\n\n switch(flag) {\n case 'add':\n histogram.addEventListener(event, handler);\n this.trackHandler(id, event, handler);\n break;\n\n case 'remove':\n histogram.removeEventListener(event, handler);\n this.untrackHandler(id, event, handler);\n break;\n\n case 'removeAll':\n this.removeAllHandlers(id);\n break;\n }\n });\n }\n\n trackHandler(id, event, handler) {\n const key = `${id}:${event}`;\n if (!this.handlers.has(key)) {\n this.handlers.set(key, []);\n }\n this.handlers.get(key).push(handler);\n }\n\n cleanup() {\n this.subscription.unsubscribe();\n }\n}\n\n// Usage: Add handlers\nfunctionSubjectGet().addFunctions({\n target: {\n entity: 'histogram',\n id: ['histo1', 'histo2']\n },\n event: 'instance-hover',\n function: (event) => {\n console.log('Bin hovered:', event.detail);\n }\n});\n\n// Usage: Remove specific handler\nconst hoverHandler = (e) => console.log(e);\n\nfunctionSubjectGet().addFunctions({\n target: { entity: 'histogram', id: 'histo1' },\n event: 'hover',\n function: hoverHandler\n});\n\n// Later: remove it\nfunctionSubjectGet().removeFunctions({\n target: { entity: 'histogram', id: 'histo1' },\n event: 'hover',\n function: hoverHandler\n});\n\n// Or remove all handlers\nfunctionSubjectGet().removeFunctions({\n target: { entity: 'histogram', id: 'histo1' }\n});\n</code></pre>"},{"location":"components/communication/function-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> </ul>"},{"location":"components/communication/function-subject/#related-components","title":"Related Components","text":"<ul> <li>Dispatch Subject</li> <li>Histogram JSROOT</li> <li>NDMVR Raycaster</li> </ul>"},{"location":"components/communication/function-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use singleton accessor: Use <code>functionSubjectGet()</code></li> <li>Track handler references: Store handler references if you need to remove them later</li> <li>Bulk operations: Use array input for multiple registrations</li> <li>Multi-target support: Use array of IDs to target multiple entities</li> <li>Cleanup: Always unsubscribe from the observable</li> <li>Memory management: Consider implementing cleanup for old registrations</li> <li>Type safety: Define TypeScript interfaces for function objects</li> </ol>"},{"location":"components/communication/function-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/histogram-subject/","title":"Histogram subject","text":""},{"location":"components/communication/histogram-subject/#histogram-subject","title":"Histogram Subject","text":""},{"location":"components/communication/histogram-subject/#overview","title":"Overview","text":"<p>The Histogram Subject is a sophisticated singleton RxJS-based communication channel for managing histogram data streams. Unlike other subjects, it maintains separate ReplaySubject streams for each histogram ID, allowing components to subscribe to specific histograms. It also handles automatic parsing of histogram data from various sources (files, URLs, JSON objects).</p>"},{"location":"components/communication/histogram-subject/#class-structure","title":"Class Structure","text":"<pre><code>class HistogramSubject {\n #subjects = new Map(); // id \u2192 ReplaySubject(1)\n}\n</code></pre>"},{"location":"components/communication/histogram-subject/#architecture","title":"Architecture","text":"<p>Key Innovation: Instead of one global subject, this maintains a Map of subjects: - Key: Histogram ID (string) - Value: <code>ReplaySubject(1)</code> for that specific histogram</p> <p>This allows: - Per-histogram subscriptions - Independent histogram state management - Efficient updates to specific histograms</p>"},{"location":"components/communication/histogram-subject/#methods","title":"Methods","text":""},{"location":"components/communication/histogram-subject/#getstreamid","title":"getStream(id)","text":"<p>Gets or creates an Observable stream for a specific histogram ID.</p> <p>Parameters: - <code>id</code> (string) - The unique histogram identifier</p> <p>Returns: <code>Observable</code> - Stream of histogram updates for this specific ID</p> <p>Behavior: - Creates a new <code>ReplaySubject(1)</code> if ID doesn't exist - Returns existing subject's observable if ID exists - Each histogram has its own independent stream</p> <p>Usage: </p><pre><code>import { histogramSubjectGet } from './rxjs/HistogramSubject.js';\n\n// Subscribe to specific histogram\nhistogramSubjectGet().getStream('histogram1').subscribe(histo => {\n console.log('Histogram 1 updated:', histo);\n});\n\n// Different histogram, different stream\nhistogramSubjectGet().getStream('histogram2').subscribe(histo => {\n console.log('Histogram 2 updated:', histo);\n});\n</code></pre><p></p>"},{"location":"components/communication/histogram-subject/#async-nexte","title":"async next(e)","text":"<p>Processes and broadcasts histogram data to the appropriate stream.</p> <p>Parameters: - <code>e</code> (object) - Histogram event object</p> <p>Event Object Structure: </p><pre><code>{\n id: 'histogram-id', // Required\n obj: string | object, // Histogram data (URL, file path, or JSON object)\n opts: { // Optional\n render: 'jsroot' | 'custom',\n config: { /* histogram config */ }\n }\n}\n</code></pre><p></p> <p>Behavior: 1. Validation: Throws error if <code>id</code> is missing 2. Data Preprocessing: - If <code>obj</code> is string: Parses as file using <code>FileHandler.parseFile()</code> - If <code>obj</code> is object: Parses as JSON using <code>JsonHandler.parseJson()</code> - Otherwise: Throws \"Unsupported data type\" error 3. Config Parsing: Parses <code>opts.config</code> if present 4. Stream Creation: Creates subject for ID if not exists 5. Broadcasting: Emits processed data to ID-specific stream</p> <p>Usage: </p><pre><code>import { histogramSubjectGet } from './rxjs/HistogramSubject.js';\n\n// Load from file\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: '/path/to/histogram.root'\n});\n\n// Load from URL\nawait histogramSubjectGet().next({\n id: 'histogram2',\n obj: 'https://example.com/data.root'\n});\n\n// Load from JSON object\nawait histogramSubjectGet().next({\n id: 'histogram3',\n obj: {\n fName: 'MyHistogram',\n fXaxis: { /* ... */ },\n fYaxis: { /* ... */ }\n },\n opts: {\n render: 'jsroot',\n config: {\n color: 0xff0000\n }\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/histogram-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const histogramSubjectGet = () => {\n if (!histogramSubject) histogramSubject = new HistogramSubject();\n return histogramSubject;\n};\n</code></pre>"},{"location":"components/communication/histogram-subject/#data-flow","title":"Data Flow","text":"<pre><code>1. External code calls histogramSubjectGet().next({...})\n2. HistogramSubject validates and preprocesses data\n3. Data is parsed (file/URL/JSON)\n4. Config is parsed if present\n5. Subject for specific ID is obtained/created\n6. Processed data is emitted to ID-specific stream\n7. Subscribed components receive update\n</code></pre>"},{"location":"components/communication/histogram-subject/#supported-data-sources","title":"Supported Data Sources","text":""},{"location":"components/communication/histogram-subject/#1-file-path-string","title":"1. File Path (String)","text":"<pre><code>await histogramSubjectGet().next({\n id: 'histo1',\n obj: './data/histogram.root'\n});\n</code></pre> Processing: <code>FileHandler.parseFile()</code>"},{"location":"components/communication/histogram-subject/#2-url-string","title":"2. URL (String)","text":"<pre><code>await histogramSubjectGet().next({\n id: 'histo1',\n obj: 'https://root.cern/files/histogram.root'\n});\n</code></pre> Processing: <code>FileHandler.parseFile()</code> (handles URLs)"},{"location":"components/communication/histogram-subject/#3-json-object","title":"3. JSON Object","text":"<pre><code>await histogramSubjectGet().next({\n id: 'histo1',\n obj: {\n _typename: 'TH3F',\n fXaxis: { fNbins: 10, /* ... */ },\n // ... ROOT object properties\n }\n});\n</code></pre> Processing: <code>JsonHandler.parseJson()</code>"},{"location":"components/communication/histogram-subject/#stream-isolation","title":"Stream Isolation","text":"<p>Each histogram has its own independent stream:</p> <pre><code>// Component A subscribes to histogram1\nhistogramSubjectGet().getStream('histogram1').subscribe(h => {\n console.log('H1:', h); // Only receives histogram1 updates\n});\n\n// Component B subscribes to histogram2\nhistogramSubjectGet().getStream('histogram2').subscribe(h => {\n console.log('H2:', h); // Only receives histogram2 updates\n});\n\n// Updates are isolated\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: data1 // Only Component A receives this\n});\n\nawait histogramSubjectGet().next({\n id: 'histogram2',\n obj: data2 // Only Component B receives this\n});\n</code></pre>"},{"location":"components/communication/histogram-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { histogramSubjectGet } from './rxjs/HistogramSubject.js';\n\n// Histogram component implementation\nclass HistogramComponent {\n constructor(elementId) {\n this.id = elementId;\n this.subscription = null;\n }\n\n init() {\n // Subscribe to this specific histogram's stream\n this.subscription = histogramSubjectGet()\n .getStream(this.id)\n .subscribe({\n next: (histo) => {\n console.log(`Histogram ${this.id} received data:`, histo);\n this.render(histo.obj, histo.opts);\n },\n error: (err) => {\n console.error(`Histogram ${this.id} error:`, err);\n }\n });\n }\n\n render(histogramObj, options) {\n // Render based on options\n if (options?.render === 'jsroot') {\n this.renderJSROOT(histogramObj);\n } else {\n this.renderCustom(histogramObj, options);\n }\n }\n\n cleanup() {\n if (this.subscription) {\n this.subscription.unsubscribe();\n }\n }\n}\n\n// Usage: Create multiple histogram components\nconst histo1 = new HistogramComponent('histogram1');\nconst histo2 = new HistogramComponent('histogram2');\n\nhisto1.init();\nhisto2.init();\n\n// Load data for each histogram\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: '/data/experiment1.root',\n opts: { render: 'jsroot' }\n});\n\nawait histogramSubjectGet().next({\n id: 'histogram2',\n obj: {\n _typename: 'TH3F',\n fXaxis: { /* ... */ }\n },\n opts: {\n render: 'custom',\n config: { color: 0x00ff00 }\n }\n});\n\n// Update specific histogram\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: '/data/experiment1_updated.root'\n});\n\n// Cleanup\nhisto1.cleanup();\nhisto2.cleanup();\n</code></pre>"},{"location":"components/communication/histogram-subject/#error-handling","title":"Error Handling","text":"<pre><code>try {\n await histogramSubjectGet().next({\n id: 'histogram1',\n obj: invalidData\n });\n} catch (error) {\n if (error.message === 'Missing id in event') {\n console.error('Histogram ID is required');\n } else if (error.message === 'Unsupported data type') {\n console.error('obj must be string or object');\n } else {\n console.error('Failed to process histogram:', error);\n }\n}\n</code></pre>"},{"location":"components/communication/histogram-subject/#subject-type-map-of-replaysubject1","title":"Subject Type: Map of ReplaySubject(1)","text":"<p>Characteristics: - Per-ID Subjects: Each histogram ID gets its own <code>ReplaySubject(1)</code> - Buffer Size: 1 per histogram (stores last emitted value) - Replay Behavior: New subscribers immediately receive last histogram data - Isolation: Updates to one histogram don't affect others</p>"},{"location":"components/communication/histogram-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> <li><code>FileHandler</code> from <code>../service/FileHandler.js</code></li> <li><code>JsonHandler</code> from <code>../service/JsonHandler.js</code></li> <li><code>parseConfig</code> from <code>../utils/baseUtil.js</code></li> </ul>"},{"location":"components/communication/histogram-subject/#related-components","title":"Related Components","text":"<ul> <li>Histogram JSROOT</li> <li>THnPainter</li> <li>Config Subject</li> </ul>"},{"location":"components/communication/histogram-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always provide ID: The <code>id</code> field is required</li> <li>Use async/await: <code>next()</code> is asynchronous due to file parsing</li> <li>Error handling: Wrap <code>next()</code> calls in try-catch</li> <li>Subscribe per ID: Use <code>getStream(id)</code> for specific histograms</li> <li>Cleanup subscriptions: Always unsubscribe when done</li> <li>Stream isolation: Take advantage of per-histogram streams</li> <li>Config merging: Use <code>opts.config</code> for histogram-specific settings</li> </ol>"},{"location":"components/communication/histogram-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/input-device-subject/","title":"Input device subject","text":""},{"location":"components/communication/input-device-subject/#input-device-subject","title":"Input Device Subject","text":""},{"location":"components/communication/input-device-subject/#overview","title":"Overview","text":"<p>The Input Device Subject is a singleton RxJS-based communication channel for managing the current input device state in the VR application. It uses a <code>BehaviorSubject</code> to track which input device (keyboard, mobile, or VR headset) is currently active, allowing components to adapt their behavior accordingly.</p>"},{"location":"components/communication/input-device-subject/#class-structure","title":"Class Structure","text":"<pre><code>class InputDeviceSubject {\n #subject; // Private BehaviorSubject\n}\n</code></pre>"},{"location":"components/communication/input-device-subject/#methods","title":"Methods","text":""},{"location":"components/communication/input-device-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>InputDeviceSubject</code> instance with default keyboard input.</p> <p>Behavior: - Initializes a private <code>BehaviorSubject</code> with initial state: </p><pre><code>{\n inputDevice: \"keyboard\"\n}\n</code></pre> - Ensures the subject always has a valid input device state<p></p>"},{"location":"components/communication/input-device-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable for subscribing to input device changes.</p> <p>Returns: <code>Observable</code> - Stream of input device state changes</p> <p>Usage: </p><pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\ninputDeviceSubjectGet().getObservable().subscribe(state => {\n console.log('Input device changed to:', state.inputDevice);\n\n // Adapt UI based on input device\n if (state.inputDevice === 'mobile') {\n showTouchControls();\n } else if (state.inputDevice === 'oculus') {\n enableVRControllers();\n } else {\n enableKeyboardControls();\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/input-device-subject/#nexte","title":"next(e)","text":"<p>Updates the input device state and broadcasts to all subscribers.</p> <p>Parameters: - <code>e</code> (object) - New state object</p> <p>State Object Structure: </p><pre><code>{\n inputDevice: 'keyboard' | 'mobile' | 'oculus'\n}\n</code></pre><p></p> <p>Behavior: - Retrieves current state - Updates <code>inputDevice</code> if provided in the new state - Broadcasts the updated state to all subscribers</p> <p>Usage: </p><pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\n// Switch to mobile input\ninputDeviceSubjectGet().next({\n inputDevice: 'mobile'\n});\n\n// Switch to VR headset\ninputDeviceSubjectGet().next({\n inputDevice: 'oculus'\n});\n\n// Switch to keyboard\ninputDeviceSubjectGet().next({\n inputDevice: 'keyboard'\n});\n</code></pre><p></p>"},{"location":"components/communication/input-device-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const inputDeviceSubjectGet = () => {\n if (!inputDeviceSubject) inputDeviceSubject = new InputDeviceSubject();\n return inputDeviceSubject;\n};\n</code></pre>"},{"location":"components/communication/input-device-subject/#input-device-types","title":"Input Device Types","text":""},{"location":"components/communication/input-device-subject/#1-keyboard-default","title":"1. Keyboard (Default)","text":"<p>Value: <code>\"keyboard\"</code></p> <p>Characteristics: - Desktop/laptop input - Keyboard and mouse controls - Default state on application start</p> <p>Typical Controls: - WASD for movement - Mouse for look around - Click for selection</p>"},{"location":"components/communication/input-device-subject/#2-mobile","title":"2. Mobile","text":"<p>Value: <code>\"mobile\"</code></p> <p>Characteristics: - Touch screen input - Mobile device sensors - Virtual joystick controls</p> <p>Typical Controls: - Touch and drag - Virtual joystick for movement - Tap for selection - Device orientation</p>"},{"location":"components/communication/input-device-subject/#3-oculus-vr-headset","title":"3. Oculus (VR Headset)","text":"<p>Value: <code>\"oculus\"</code></p> <p>Characteristics: - VR headset and controllers - 6DOF (six degrees of freedom) - Hand tracking or controller input</p> <p>Typical Controls: - VR controller buttons - Controller position tracking - Teleportation movement - Direct hand interaction</p>"},{"location":"components/communication/input-device-subject/#integration-with-device-detection","title":"Integration with Device Detection","text":"<p>The input device state is typically set by the <code>device-detector</code> component:</p> <pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\n// Device detection logic\nif (AFRAME.utils.device.isMobile()) {\n inputDeviceSubjectGet().next({ inputDevice: 'mobile' });\n} else if (AFRAME.utils.device.checkHeadsetConnected()) {\n inputDeviceSubjectGet().next({ inputDevice: 'oculus' });\n} else {\n inputDeviceSubjectGet().next({ inputDevice: 'keyboard' });\n}\n</code></pre>"},{"location":"components/communication/input-device-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Adaptive Controls:</li> <li>Show/hide control schemes based on device</li> <li> <p>Enable/disable input handlers</p> </li> <li> <p>UI Adaptation:</p> </li> <li>Show touch controls on mobile</li> <li>Display VR-specific UI in headset</li> <li> <p>Show keyboard hints on desktop</p> </li> <li> <p>Performance Optimization:</p> </li> <li>Adjust rendering quality per device</li> <li> <p>Enable/disable features based on capability</p> </li> <li> <p>Interaction Methods:</p> </li> <li>Switch between raycasting methods</li> <li>Adapt selection mechanisms</li> <li>Change movement systems</li> </ol>"},{"location":"components/communication/input-device-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\nclass InputManager {\n constructor() {\n this.currentDevice = 'keyboard';\n\n // Subscribe to device changes\n this.subscription = inputDeviceSubjectGet()\n .getObservable()\n .subscribe(this.onDeviceChange.bind(this));\n }\n\n onDeviceChange(state) {\n console.log('Device changed:', state.inputDevice);\n\n // Disable previous input handlers\n this.disableInputHandlers(this.currentDevice);\n\n // Enable new input handlers\n this.enableInputHandlers(state.inputDevice);\n\n // Update current device\n this.currentDevice = state.inputDevice;\n\n // Update UI\n this.updateUI(state.inputDevice);\n }\n\n enableInputHandlers(device) {\n switch(device) {\n case 'keyboard':\n this.enableKeyboardControls();\n this.enableMouseControls();\n break;\n\n case 'mobile':\n this.enableTouchControls();\n this.enableVirtualJoystick();\n break;\n\n case 'oculus':\n this.enableVRControllers();\n this.enableTeleportation();\n break;\n }\n }\n\n disableInputHandlers(device) {\n // Clean up previous handlers\n this.removeKeyboardControls();\n this.removeMouseControls();\n this.removeTouchControls();\n this.removeVirtualJoystick();\n this.removeVRControllers();\n this.removeTeleportation();\n }\n\n updateUI(device) {\n const keyboardUI = document.getElementById('keyboard-controls');\n const mobileUI = document.getElementById('mobile-controls');\n const vrUI = document.getElementById('vr-controls');\n\n keyboardUI.style.display = device === 'keyboard' ? 'block' : 'none';\n mobileUI.style.display = device === 'mobile' ? 'block' : 'none';\n vrUI.style.display = device === 'oculus' ? 'block' : 'none';\n }\n\n cleanup() {\n this.subscription.unsubscribe();\n }\n}\n\n// Usage\nconst inputManager = new InputManager();\n\n// Simulate device change (normally done by device-detector)\ninputDeviceSubjectGet().next({ inputDevice: 'mobile' });\n</code></pre>"},{"location":"components/communication/input-device-subject/#event-driven-device-switching","title":"Event-Driven Device Switching","text":"<pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\n// Listen for VR mode changes\nscene.addEventListener('enter-vr', () => {\n if (AFRAME.utils.device.checkHeadsetConnected()) {\n inputDeviceSubjectGet().next({ inputDevice: 'oculus' });\n }\n});\n\nscene.addEventListener('exit-vr', () => {\n if (AFRAME.utils.device.isMobile()) {\n inputDeviceSubjectGet().next({ inputDevice: 'mobile' });\n } else {\n inputDeviceSubjectGet().next({ inputDevice: 'keyboard' });\n }\n});\n</code></pre>"},{"location":"components/communication/input-device-subject/#subject-type-behaviorsubject","title":"Subject Type: BehaviorSubject","text":"<p>Characteristics: - Initial Value: <code>{ inputDevice: \"keyboard\" }</code> - Current Value: Always accessible via subscription - Replay Behavior: New subscribers immediately receive current device - Use Case: Perfect for device state that needs to be known immediately</p>"},{"location":"components/communication/input-device-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>BehaviorSubject</code>)</li> </ul>"},{"location":"components/communication/input-device-subject/#related-components","title":"Related Components","text":"<ul> <li>Camera Component</li> <li>Device Detector Component</li> <li>Screen Controls Component</li> <li>State Subject</li> </ul>"},{"location":"components/communication/input-device-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use singleton accessor: Use <code>inputDeviceSubjectGet()</code></li> <li>Subscribe early: Subscribe during component initialization</li> <li>Clean up handlers: Remove old input handlers before adding new ones</li> <li>Unsubscribe: Always clean up subscriptions</li> <li>Device detection: Let <code>device-detector</code> component manage device changes</li> <li>Fallback: Always have keyboard as fallback device</li> <li>Type safety: Consider using TypeScript enums for device types</li> <li>Testing: Test all three device modes thoroughly</li> </ol>"},{"location":"components/communication/input-device-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/state-subject/","title":"State subject","text":""},{"location":"components/communication/state-subject/#state-subject","title":"State Subject","text":""},{"location":"components/communication/state-subject/#overview","title":"Overview","text":"<p>The State Subject is a singleton RxJS-based communication channel for managing application-wide state. It uses a <code>BehaviorSubject</code> to maintain state related to sets, arrays, and their selections, providing a centralized state management solution for the VR histogram application.</p>"},{"location":"components/communication/state-subject/#class-structure","title":"Class Structure","text":"<pre><code>class StateSubject {\n #subject; // Private BehaviorSubject\n}\n</code></pre>"},{"location":"components/communication/state-subject/#initial-state","title":"Initial State","text":"<pre><code>{\n sets: [], // Array of available sets\n selectedSet: [], // Currently selected set(s)\n arrays: [\"content\"], // Array of available array types\n selectedArray: \"content\" // Currently selected array type\n}\n</code></pre>"},{"location":"components/communication/state-subject/#methods","title":"Methods","text":""},{"location":"components/communication/state-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>StateSubject</code> instance with default application state.</p> <p>Behavior: - Initializes a private <code>BehaviorSubject</code> with default state structure - Provides initial values for sets, arrays, and selections</p>"},{"location":"components/communication/state-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable for subscribing to state changes.</p> <p>Returns: <code>Observable</code> - Stream of state updates</p> <p>Usage: </p><pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\nstateSubjectGet().getObservable().subscribe(state => {\n console.log('State updated:', state);\n console.log('Selected set:', state.selectedSet);\n console.log('Selected array:', state.selectedArray);\n});\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#getvalue","title":"getValue()","text":"<p>Retrieves the current state value synchronously.</p> <p>Returns: Current state object</p> <p>Usage: </p><pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\nconst currentState = stateSubjectGet().getValue();\nconsole.log('Current sets:', currentState.sets);\nconsole.log('Selected array:', currentState.selectedArray);\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#nexte","title":"next(e)","text":"<p>Updates the application state and broadcasts to all subscribers.</p> <p>Parameters: - <code>e</code> (object) - New state object (partial or complete)</p> <p>Behavior: - Replaces entire state with new state object - All subscribers receive the updated state - Partial updates require spreading existing state</p> <p>Usage: </p><pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\n// Complete state update\nstateSubjectGet().next({\n sets: ['set1', 'set2', 'set3'],\n selectedSet: ['set1'],\n arrays: ['content', 'errors', 'bins'],\n selectedArray: 'content'\n});\n\n// Partial state update (preserve other fields)\nconst current = stateSubjectGet().getValue();\nstateSubjectGet().next({\n ...current,\n selectedSet: ['set2']\n});\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const stateSubjectGet = () => {\n if (!inputDeviceSubject) inputDeviceSubject = new StateSubject();\n return inputDeviceSubject;\n};\n</code></pre> <p>Note: There's a naming inconsistency in the implementation - the variable is named <code>inputDeviceSubject</code> but should be <code>stateSubject</code>. This is likely a copy-paste error but doesn't affect functionality.</p>"},{"location":"components/communication/state-subject/#state-properties","title":"State Properties","text":""},{"location":"components/communication/state-subject/#sets","title":"sets","text":"<p>Type: <code>Array</code> Default: <code>[]</code> Description: List of available histogram sets or data sets</p> <p>Example: </p><pre><code>sets: ['dataset1', 'dataset2', 'experiment_a', 'experiment_b']\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#selectedset","title":"selectedSet","text":"<p>Type: <code>Array</code> Default: <code>[]</code> Description: Currently selected set(s) - supports multi-selection</p> <p>Example: </p><pre><code>selectedSet: ['dataset1', 'dataset2']\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#arrays","title":"arrays","text":"<p>Type: <code>Array<string></code> Default: <code>[\"content\"]</code> Description: Available array types for histogram data visualization</p> <p>Common Values: - <code>\"content\"</code> - Bin content values - <code>\"errors\"</code> - Error values - <code>\"bins\"</code> - Bin indices - <code>\"entries\"</code> - Entry counts</p> <p>Example: </p><pre><code>arrays: ['content', 'errors', 'bins']\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#selectedarray","title":"selectedArray","text":"<p>Type: <code>string</code> Default: <code>\"content\"</code> Description: Currently selected array type for display</p> <p>Example: </p><pre><code>selectedArray: 'errors'\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Dataset Management:</li> <li>Track available datasets</li> <li>Manage dataset selection</li> <li> <p>Switch between different experiments</p> </li> <li> <p>Visualization Mode:</p> </li> <li>Select which histogram data to display</li> <li>Switch between content, errors, or bins view</li> <li> <p>Update visualization based on selected array type</p> </li> <li> <p>Multi-Selection:</p> </li> <li>Compare multiple datasets</li> <li> <p>Display multiple sets simultaneously</p> </li> <li> <p>UI State Synchronization:</p> </li> <li>Keep UI controls in sync with application state</li> <li>Update dropdown menus and selection lists</li> </ol>"},{"location":"components/communication/state-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\nclass StateManager {\n constructor() {\n this.subscription = stateSubjectGet()\n .getObservable()\n .subscribe(this.onStateChange.bind(this));\n }\n\n onStateChange(state) {\n console.log('State changed:', state);\n\n // Update UI to reflect current state\n this.updateSetsList(state.sets);\n this.updateSelectedSets(state.selectedSet);\n this.updateArrayOptions(state.arrays);\n this.updateSelectedArray(state.selectedArray);\n\n // Update visualizations\n this.refreshHistograms(state.selectedSet, state.selectedArray);\n }\n\n // Load available datasets\n loadDatasets(datasets) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n sets: datasets\n });\n }\n\n // Select a dataset\n selectSet(setId) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n selectedSet: [setId]\n });\n }\n\n // Select multiple datasets\n selectMultipleSets(setIds) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n selectedSet: setIds\n });\n }\n\n // Change visualization array type\n selectArrayType(arrayType) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n selectedArray: arrayType\n });\n }\n\n // Add array type option\n addArrayType(arrayType) {\n const current = stateSubjectGet().getValue();\n if (!current.arrays.includes(arrayType)) {\n stateSubjectGet().next({\n ...current,\n arrays: [...current.arrays, arrayType]\n });\n }\n }\n\n cleanup() {\n this.subscription.unsubscribe();\n }\n}\n\n// Usage\nconst stateManager = new StateManager();\n\n// Load datasets\nstateManager.loadDatasets([\n 'experiment_2024_01',\n 'experiment_2024_02',\n 'calibration_data'\n]);\n\n// Select a dataset\nstateManager.selectSet('experiment_2024_01');\n\n// Switch to errors view\nstateManager.selectArrayType('errors');\n\n// Select multiple datasets for comparison\nstateManager.selectMultipleSets([\n 'experiment_2024_01',\n 'experiment_2024_02'\n]);\n\n// Add custom array type\nstateManager.addArrayType('normalized');\n</code></pre>"},{"location":"components/communication/state-subject/#reactive-ui-binding","title":"Reactive UI Binding","text":"<pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\nimport { map, distinctUntilChanged } from 'rxjs/operators';\n\n// Subscribe to specific state properties\nconst selectedSet$ = stateSubjectGet()\n .getObservable()\n .pipe(\n map(state => state.selectedSet),\n distinctUntilChanged()\n );\n\nselectedSet$.subscribe(selectedSet => {\n console.log('Selected set changed:', selectedSet);\n updateHistogramDisplay(selectedSet);\n});\n\nconst selectedArray$ = stateSubjectGet()\n .getObservable()\n .pipe(\n map(state => state.selectedArray),\n distinctUntilChanged()\n );\n\nselectedArray$.subscribe(arrayType => {\n console.log('Array type changed:', arrayType);\n updateVisualizationMode(arrayType);\n});\n</code></pre>"},{"location":"components/communication/state-subject/#state-helpers","title":"State Helpers","text":"<pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\n// Helper functions for common operations\nconst StateHelpers = {\n // Get current state\n getCurrentState() {\n return stateSubjectGet().getValue();\n },\n\n // Update partial state\n updateState(updates) {\n const current = this.getCurrentState();\n stateSubjectGet().next({ ...current, ...updates });\n },\n\n // Check if set is selected\n isSetSelected(setId) {\n const state = this.getCurrentState();\n return state.selectedSet.includes(setId);\n },\n\n // Toggle set selection\n toggleSet(setId) {\n const state = this.getCurrentState();\n const selectedSet = state.selectedSet.includes(setId)\n ? state.selectedSet.filter(id => id !== setId)\n : [...state.selectedSet, setId];\n\n this.updateState({ selectedSet });\n },\n\n // Clear selection\n clearSelection() {\n this.updateState({ selectedSet: [] });\n }\n};\n\n// Usage\nStateHelpers.updateState({ selectedArray: 'bins' });\nStateHelpers.toggleSet('experiment_1');\nconsole.log('Is selected?', StateHelpers.isSetSelected('experiment_1'));\n</code></pre>"},{"location":"components/communication/state-subject/#subject-type-behaviorsubject","title":"Subject Type: BehaviorSubject","text":"<p>Characteristics: - Initial Value: State with empty sets and default array selection - Current Value: Always accessible via <code>getValue()</code> - Replay Behavior: New subscribers immediately receive current state - Use Case: Perfect for application-wide state management</p>"},{"location":"components/communication/state-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>BehaviorSubject</code>)</li> </ul>"},{"location":"components/communication/state-subject/#related-components","title":"Related Components","text":"<ul> <li>Config Subject</li> <li>Input Device Subject</li> <li>Histogram Subject</li> </ul>"},{"location":"components/communication/state-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use singleton accessor: Use <code>stateSubjectGet()</code></li> <li>Partial updates: Spread existing state when updating (<code>{ ...current, ...updates }</code>)</li> <li>Synchronous access: Use <code>getValue()</code> when you need immediate state</li> <li>Reactive updates: Use <code>getObservable()</code> for reactive state changes</li> <li>Unsubscribe: Always clean up subscriptions</li> <li>Immutability: Don't mutate state directly, always create new objects</li> <li>Type safety: Consider TypeScript interfaces for state structure</li> <li>State normalization: Keep state structure flat and normalized</li> </ol>"},{"location":"components/communication/state-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/configuration/configuration/","title":"Configuration","text":""},{"location":"components/configuration/configuration/#ndmvr-configuration-reference","title":"NDMVR Configuration Reference","text":""},{"location":"components/configuration/configuration/#overview","title":"Overview","text":"<p>The NDMVR configuration file controls the behavior, appearance, and interaction of histogram visualizations in a 3D environment. Configuration is provided as a JSON object with several main sections.</p>"},{"location":"components/configuration/configuration/#environment-configuration","title":"Environment Configuration","text":""},{"location":"components/configuration/configuration/#environmentdbclicktimeout","title":"<code>environment.dbClickTimeout</code>","text":"<p>Type: <code>number</code> (milliseconds) Default: <code>190</code></p> <p>Timeout to register a double-click event, measured from the first click to the second click.</p> <p>Important: Longer values increase the time it takes to register single clicks, as the system must wait to determine if a second click is incoming. </p><pre><code>\"dbClickTimeout\": 190\n</code></pre><p></p>"},{"location":"components/configuration/configuration/#environmentcamera","title":"<code>environment.camera</code>","text":"<p>Camera position in 3D space.</p>"},{"location":"components/configuration/configuration/#cameraposition","title":"<code>camera.position</code>","text":"<p>Type: <code>object</code> </p><pre><code>\"camera\": {\n \"position\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n }\n}\n</code></pre><p></p> Property Type Description <code>x</code> number X-axis position <code>y</code> number Y-axis position <code>z</code> number Z-axis position"},{"location":"components/configuration/configuration/#environmentcanvaspads","title":"<code>environment.canvasPads</code>","text":"<p>Settings for the canvas pads in the scene. Each canvas pad defines a display surface placed in 3D space.</p> <p>Type: <code>array</code></p>"},{"location":"components/configuration/configuration/#canvaspads-id-pad1-cinema-limits-position-x-15-y-20-z-30-rotation-x-10-y-0-z-0-scale-x-40-y-25-z-0","title":"<pre><code>\"canvasPads\": [\n {\n \"id\": \"pad1-cinema\",\n \"limits\": {\n \"position\": {\n \"x\": -15,\n \"y\": 20,\n \"z\": -30\n },\n \"rotation\": {\n \"x\": 10,\n \"y\": 0,\n \"z\": 0\n },\n \"scale\": {\n \"x\": 40,\n \"y\": 25,\n \"z\": 0\n }\n }\n }\n]\n</code></pre>","text":""},{"location":"components/configuration/configuration/#environmenthistogrampads","title":"<code>environment.histogramPads</code>","text":"<p>Defines the layout and positioning of histogram pads. Can be configured in two modes: Grid Mode or Manual Mode.</p>"},{"location":"components/configuration/configuration/#grid-mode","title":"Grid Mode","text":"<p>Automatically arranges histograms in a 3D grid pattern.</p> <p>Type: <code>object</code> </p><pre><code>\"histogramPads\": {\n \"type\": \"grid1x1x1\",\n \"prefix\": \"histogram\",\n \"scale\": {\n \"x\": 5,\n \"y\": 3,\n \"z\": 5\n },\n \"padding\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n },\n \"origin\": {\n \"x\": -2.5,\n \"y\": 0,\n \"z\": 2.5\n }\n}\n</code></pre><p></p> Property Type Description <code>type</code> string Grid format as <code>gridAxBxC</code> where A = number of histograms on X-axis, B = Y-axis, C = Z-axis <code>prefix</code> string Prefix for histogram IDs (e.g., <code>\"histogram\"</code> \u2192 <code>histogram1</code>, <code>histogram2</code>, ...) <code>scale</code> object Combined scale of all histograms in the grid <code>padding</code> object Space between each histogram pad <code>origin</code> object Center position of the first (left-bottom-front) histogram"},{"location":"components/configuration/configuration/#manual-mode","title":"Manual Mode","text":"<p>Manually define each histogram pad.</p> <p>Type: <code>array</code> of objects </p><pre><code>\"histogramPads\": [\n {\n \"id\": \"histogram1\",\n \"position\": {\n \"x\": 0,\n \"y\": 1.5,\n \"z\": 0\n },\n \"scale\": {\n \"x\": 5,\n \"y\": 3,\n \"z\": 5\n }\n }\n]\n</code></pre><p></p> Property Type Description <code>id</code> string Unique identifier for the histogram pad <code>position</code> object Position in 3D space <code>scale</code> object Scale of the histogram pad"},{"location":"components/configuration/configuration/#histogram-configuration","title":"Histogram Configuration","text":""},{"location":"components/configuration/configuration/#histogrampadding","title":"<code>histogram.padding</code>","text":"<p>Defines padding between bins in histograms. Padding is a normalized value (0-1) where: - <code>0</code> = no padding - <code>0.5</code> = half the space is padding</p> <p>Note: Padding reduces bin size; it does not push bins further apart.</p> <p>Type: <code>object</code> </p><pre><code>\"padding\": {\n \"default\": {\n \"x\": 0.1,\n \"y\": 0.1,\n \"z\": 0.1\n },\n \"layer\": [],\n \"sets\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n }\n}\n</code></pre><p></p> Property Type Description <code>default</code> object Default padding for all histograms <code>layer</code> array Per-layer padding overrides <code>sets</code> object Padding for histogram sets"},{"location":"components/configuration/configuration/#histogramscale","title":"<code>histogram.scale</code>","text":"<p>Defines minimum and maximum scale of bins. Scale is normalized (0-1): - <code>0</code> = bin takes no space - <code>0.5</code> = bin takes half the available space - <code>1</code> = bin takes full space</p> <p>A bin with the minimum number of entries will be scaled to <code>min</code>, while the maximum bin scales to <code>max</code>.</p> <p>Type: <code>object</code> </p><pre><code>\"scale\": {\n \"default\": {\n \"min\": 0.3,\n \"max\": 1\n },\n \"layer\": []\n}\n</code></pre><p></p> Property Type Description <code>default</code> object Default min/max scale <code>layer</code> array Per-layer scale overrides"},{"location":"components/configuration/configuration/#histogramscalescaleby","title":"<code>histogram.scale.scaleBy</code>","text":"<p>Type: <code>string</code> Options: <code>\"value\"</code> or <code>\"error\"</code></p> <p>Determines what bins are scaled by:</p> <ul> <li><code>value</code>: Uses <code>fArray</code> values</li> <li><code>error</code>: Uses <code>fSumw2</code> errors (or square root of <code>fArray</code> if <code>fSumw2</code> is empty)</li> </ul>"},{"location":"components/configuration/configuration/#histogramscaleobject","title":"<code>histogram.scale.object</code>","text":"<p>Type: <code>string</code> Options: <code>\"relative\"</code>, <code>\"global\"</code>, or <code>\"fixed\"</code></p> <p>Defines how maximum scale for either content parameter, or set is determined across histograms:</p> Mode Description <code>relative</code> Each histogram's maximum is based on its own data <code>global</code> All histograms share the same maximum (the global maximum across all histograms in that layer) <code>fixed</code> Starts as <code>global</code>, but allows user-defined min/max via stateSubject <pre><code>\"content\": \"global\",\n\"parameter\": \"fixed\",\n\"sets\": \"relative\",\n</code></pre>"},{"location":"components/configuration/configuration/#histogramth1zscale","title":"<code>histogram.TH1ZScale</code>","text":"<p>Z-axis scale for TH1 histograms. Normalized value (0-1).</p> <p>Type: <code>object</code> </p><pre><code>\"TH1ZScale\": {\n \"default\": 0.8,\n \"layer\": [0.2, 1, 1, 1],\n \"set\": 0.01\n}\n</code></pre><p></p> Property Type Description <code>default</code> number Default Z-scale <code>layer</code> array Per-layer Z-scale overrides <code>set</code> number Z-scale for sets"},{"location":"components/configuration/configuration/#histogramwireframe","title":"<code>histogram.wireframe</code>","text":"<p>Controls display and appearance of bin outlines (wireframes).</p> <p>Type: <code>object</code> </p><pre><code>\"wireframe\": {\n \"display\": {\n \"start\": 0,\n \"end\": 1\n },\n \"displaySets\": false,\n \"layer\": [],\n \"color\": {\n \"default\": \"0x00FF00\",\n \"layer\": [\"0x000000\", \"0x0000FF\", \"0x00FF00\", \"0x00FFFF\"],\n \"set\": []\n }\n}\n</code></pre><p></p>"},{"location":"components/configuration/configuration/#wireframedisplay","title":"<code>wireframe.display</code>","text":"Property Type Description <code>start</code> number First layer where wireframes are displayed (0-indexed) <code>end</code> number Last layer where wireframes are displayed <p>Example: <code>\"start\": 1</code> displays wireframes from layer 1 and greater.</p>"},{"location":"components/configuration/configuration/#wireframedisplaysets","title":"<code>wireframe.displaySets</code>","text":"<p>Type: <code>boolean</code></p> <p>Whether to display wireframes on histogram sets.</p>"},{"location":"components/configuration/configuration/#wireframecolor","title":"<code>wireframe.color</code>","text":"<p>Defines wireframe colors.</p> Property Type Description <code>default</code> string Default wireframe color (hex format) <code>layer</code> array Per-layer color overrides <code>set</code> array Per-set color overrides"},{"location":"components/configuration/configuration/#histogramcolor","title":"<code>histogram.color</code>","text":"<p>Controls bin fill colors using gradients.</p> <p>A gradient is created from <code>min</code> to <code>max</code> color based on the selected property.</p> <p>Type: <code>object</code> </p><pre><code>\"color\": {\n \"default\": {\n \"min\": \"0x0000ff\",\n \"max\": \"0xff0000\"\n },\n \"layer\": [],\n \"set\": [\n {\n \"min\": \"0x999999\",\n \"max\": \"0xffaa00\"\n }\n ]\n}\n</code></pre><p></p>"},{"location":"components/configuration/configuration/#colorcolorby","title":"<code>color.colorBy</code>","text":"<p>Type: <code>string</code> Options: <code>\"value\"</code> or <code>\"error\"</code></p> <p>Analogous to <code>scaleBy</code>, determines what drives the color gradient:</p> <ul> <li><code>value</code>: Colors based on <code>fArray</code> values</li> <li><code>error</code>: Colors based on <code>fSumw2</code> errors (or square root of <code>fArray</code> if <code>fSumw2</code> is empty)</li> </ul> Property Type Description <code>default</code> object Default min/max gradient colors <code>layer</code> array Per-layer color gradient overrides <code>set</code> array Per-set color gradient overrides"},{"location":"components/configuration/configuration/#bindings-configuration","title":"Bindings Configuration","text":""},{"location":"components/configuration/configuration/#bindings","title":"<code>bindings</code>","text":"<p>Defines keyboard shortcuts for default histogram interactions.</p> <p>Type: <code>object</code> </p><pre><code>\"bindings\": {\n \"resetHistogram\": \"r\",\n \"goToPreviousLayer\": \"z\",\n \"hideOutlines\": \"o\"\n}\n</code></pre><p></p> Binding Default Key Description <code>resetHistogram</code> <code>r</code> Reset histogram to initial state <code>goToPreviousLayer</code> <code>z</code> Navigate to previous layer <code>hideOutlines</code> <code>o</code> Toggle wireframe visibility"},{"location":"components/configuration/configuration/#example-configuration","title":"Example Configuration","text":"<pre><code>{\n \"config\": {\n \"environment\": {\n \"dbClickTimeout\": 190,\n \"camera\": {\n \"position\": { \"x\": 0, \"y\": 0, \"z\": 0 }\n },\n \"canvas\": {\n \"position\": { \"x\": 0, \"y\": 5, \"z\": -15 },\n \"rotation\": { \"x\": 10, \"y\": 0, \"z\": 0 },\n \"scale\": { \"x\": 20, \"y\": 10, \"z\": 0 }\n },\n \"histogramPads\": {\n \"type\": \"grid2x2x1\",\n \"prefix\": \"histogram\",\n \"scale\": { \"x\": 5, \"y\": 3, \"z\": 5 },\n \"padding\": { \"x\": 1, \"y\": 0, \"z\": 1 },\n \"origin\": { \"x\": -5, \"y\": 0, \"z\": 2.5 }\n }\n },\n \"histogram\": {\n \"padding\": {\n \"default\": { \"x\": 0.1, \"y\": 0.1, \"z\": 0.1 },\n \"layer\": [],\n \"sets\": { \"x\": 0, \"y\": 0, \"z\": 0 }\n },\n \"scale\": {\n \"scaleBy\": \"value\",\n \"content\": \"global\",\n \"parameter\": \"fixed\",\n \"sets\": \"relative\",\n \"default\": {\n \"min\": 0.1,\n \"max\": 1\n },\n \"layer\": []\n },\n \"TH1ZScale\": {\n \"default\": 0.8,\n \"layer\": [ 0.2, 1, 1, 1],\n \"set\": 0.01\n },\n \"wireframe\": {\n \"display\": {\n \"start\": 0,\n \"end\": 99\n },\n \"displaySets\": false,\n \"layer\": [],\n \"color\": {\n \"default\": \"0x00FF00\",\n \"layer\": [\"0x000000\", \"0x0B3D91\", \"0x00FF00\", \"0x00FFFF\"],\n \"set\": []\n }\n },\n \"color\": {\n \"colorBy\": \"error\",\n \"default\": {\n \"min\": \"0x0000ff\",\n \"max\": \"0xff0000\"\n },\n \"layer\": [],\n \"set\": [\n {\n \"min\": \"0x999999\",\n \"max\": \"0xffaa00\"\n },\n {\n \"min\": \"0x00ffff\",\n \"max\": \"0xff7f00\"\n },\n {\n \"min\": \"0x00ff00\",\n \"max\": \"0x800080\"\n },\n {\n \"min\": \"0x0000ff\",\n \"max\": \"0xff0000\"\n }\n ]\n }\n },\n \"bindings\": {\n \"resetHistogram\": \"r\",\n \"goToPreviousLayer\": \"z\",\n \"hideOutlines\": \"o\"\n }\n }\n}\n</code></pre>"},{"location":"components/configuration/configuration/#see-also","title":"See Also","text":"<ul> <li>stateSubject Documentation</li> </ul>"},{"location":"components/tutorial/tutorial/","title":"Introduction","text":""},{"location":"components/tutorial/tutorial/#ndmvr-core-tutorial","title":"NDMVR-Core Tutorial","text":"<p>Welcome to the NDMVR-Core tutorial! This guide will walk you through using Three.js with our class components, helping you understand and utilize the full potential of the NDMVR-Core visualization library.</p>"},{"location":"components/tutorial/tutorial/#what-well-cover","title":"What We'll Cover","text":"<p>Throughout this tutorial, we'll explore:</p> <ul> <li>Visualization Components</li> <li>Histogram rendering</li> <li>JSROOT integration</li> <li>Canvas setup and management</li> <li>Raycasting and interaction handling</li> <li> <p>THnPainter functionality</p> </li> <li> <p>Component Communication</p> </li> <li>RxJS-based message passing</li> <li>State management</li> <li>Configuration handling</li> <li> <p>Event dispatching</p> </li> <li> <p>Advanced Features</p> </li> <li>Extending components with custom functions</li> <li>Creating custom visualizations</li> <li>Integration with existing Three.js projects</li> </ul> <p>Each section will include practical examples and detailed explanations to help you master the NDMVR library's features and capabilities.</p> <p>Let's begin by exploring the basic setup and your first visualization component!</p> <ul> <li>First visualization</li> </ul>"},{"location":"components/tutorial/canvas/canvas/","title":"Canvas","text":""},{"location":"components/tutorial/canvas/canvas/#how-to-use-canvas-component","title":"How to use canvas component","text":"<p>In this section, you will learn how to use a canvas component in your scene. This component allows you to render 2D content onto a 3D Three.Plane object. While it can display various types of content, the supported formats are limited to HTMLImageElement, Data/URL links, and JSROOT objects.</p> <p>This component can be useful in cases when you want to:</p> <ul> <li>displaying 2D content inside a VR environment</li> <li>having an additional visualization element that complements existing 3D objects</li> <li>rendering external graphical content such as images loaded from URLs or generated dynamically</li> <li>displaying JSROOT primitives that are not directly supported by our rendering components</li> <li>creating information panels, overlays, or dashboards that remain spatially anchored in the scene</li> <li>presenting charts, plots, or other data visualizations alongside 3D representations</li> </ul>"},{"location":"components/tutorial/canvas/canvas/#what-youll-build","title":"What You'll Build","text":"<p>A simple 3D histogram visualization with: - A canvas component - Different cases on how to update the canvas texture - In the demo, canvas is initialized with HTMLImageElement, after 2 seconds, it will be updated using canvasSubject and after 2 seconds it will be updated using updateTexture method with a base64 encoded image.</p>"},{"location":"components/tutorial/canvas/canvas/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download Tutorial Files (ZIP) Download the complete tutorial package that includes: - <code>index.html</code> - The complete visualization - <code>test3</code> - 4D histogram sample.</p>"},{"location":"components/tutorial/canvas/canvas/#adding-canvas-to-your-scene","title":"Adding canvas to your scene","text":"<p>First, we need to add a canvas component to our scene. </p><pre><code>const canvasPRS = {\n pos: { x: 0, y: 5, z: 0 },\n rot: { x: 0, y: 0, z: 0 },\n sc: { x: 0.8, y: 0.6, z: 0.8 }\n};\nconst canvas = new CanvasClass(null, canvasPRS.pos, canvasPRS.rot, canvasPRS.sc, \"histogram1-canvas\");\nscene.add(canvas.getPlane());\n</code></pre><p></p> <ul> <li>Important: Mind what id you will assign to canvas, as the default behavior of THnPainter is to send histograms to display to ThnPainter id + \"-cinema\", so if ThnPainter id is \"histogram1\", then it is sent to \"histogram1-cinema\".</li> </ul>"},{"location":"components/tutorial/canvas/canvas/#how-to-update-canvas-manually","title":"How to update canvas manually.","text":"<ul> <li>All the provided examples can be found in the demo</li> </ul> <p>User can update canvas texture in these ways:</p>"},{"location":"components/tutorial/canvas/canvas/#utilizing-canvassubject","title":"Utilizing canvasSubject","text":"<ul> <li>ID can be set to \"*\" to update all canvases.</li> <li>Note: Canvas subject expects a JSROOT object as input.</li> </ul> <pre><code> canvasSubjectGet().next({id: \"histogram1-canvas\", obj: jsrootObj});\n</code></pre>"},{"location":"components/tutorial/canvas/canvas/#utilizing-updatetexture-method","title":"Utilizing updateTexture method","text":"<ul> <li>User can also update canvas texture by using updateTexture method on the instance of canvasClass.</li> <li>Note: updateTexture method expects an image element or base64 encoded image (e.g. \"data:image/png;base64,xyz...\") as input.</li> </ul> <pre><code>const canvas = new CanvasClass(null, canvasPRS.pos, canvasPRS.rot, canvasPRS.sc, \"histogram1-canvas\");\n\nconst imgEl = new Image(480, 360);\nimgEl.src = \"image.png\";\ncanvas.updateTexture(imgEl);\n</code></pre> <pre><code>// {\n// \"image\": \"data:image/png;base64,iVBORw0K...\ncanvas.updateTexture(image);\n</code></pre>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/","title":"Configuration","text":""},{"location":"components/tutorial/configurationChapter/configuration-chapter/#configuration-and-rxjs-integration","title":"Configuration and RxJS Integration","text":"<p>This chapter builds upon the first visualization tutorial, introducing RxJS concepts and showing how to configure your visualizations dynamically.</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#result","title":"Result:","text":"<ul> <li>Visualization of 4D histogram with a user-defined function to draw histogram beneath bin using jsroot by double-clicking.</li> </ul>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download Tutorial Files (ZIP)</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#understanding-rxjs-basics","title":"Understanding RxJS Basics","text":"<p>RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using Observables. This extension defines the interface between the user and the NdmVr-cores inter-component communication.</p> <p>It is used to enable reactive and dynamic updates without the need to manage references or synchronization manually, since all used Subjects follow the singleton design pattern. Additionally, this extension helps maintain compatibility with other solutions. In NDMVR, we use RxJS to:</p> <ul> <li>Handle real-time histogram updates</li> <li>Manage data streams</li> <li>React to user interactions</li> <li>Control visualization configuration</li> </ul> <p>Key RxJS concepts used in NDMVR:</p> <ul> <li>Observables: Represent a stream of data over time</li> <li>Subjects: Special type of Observable that allows values to be multicasted</li> <li>Subscribers: Consume values emitted by Observables</li> <li>Operators: Transform, combine, and manipulate Observable streams</li> </ul> <p>For more information about each defined subject, head to the Communication section of documentation.</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#histogram-component","title":"Histogram component","text":"<p>For easier manipulation with histograms we will create histogram abstraction component. It's purpose will be to implement dynamic interface provided by Histogram subject.</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#creating-the-histogram-manager-component","title":"Creating the Histogram Manager Component","text":"<ul> <li>Why THnPainterManager?</li> <li>The THnPainterManager class serves as an abstraction layer between the RxJS histogram stream and the actual visualization rendering. This pattern provides several key benefits:</li> <li> <ol> <li>Separation of Concerns</li> </ol> </li> <li> <p>Decouples histogram data management from rendering logic Isolates reactive stream handling in one place Makes the codebase more maintainable and testable</p> </li> <li> <ol> <li>Dynamic Renderer Switching The manager can dynamically switch between different rendering engines based on configuration:</li> </ol> </li> </ul> <p>NDMVR Renderer (THnPainter): Custom 3D histogram visualization optimized for performance JSRoot Renderer (HistogramJsrootClass): Standard ROOT visualization for compatibility</p> <ul> <li> <ol> <li>Automatic Resource Management</li> </ol> </li> </ul> <p>Handles subscription lifecycle (subscribe/unsubscribe) Automatically cleans up old renderers when switching Prevents memory leaks from unmanaged 3D objects</p> <ul> <li> <ol> <li>Reactive Updates</li> </ol> </li> </ul> <p>Listens to the histogram stream using RxJS Automatically re-renders when histogram data changes Updates existing visualizations without recreation when possible</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#how-it-works","title":"How It Works","text":"<ul> <li>Initialization: The init() method subscribes to the histogram stream for a specific element ID</li> <li>Stream Handling: When histogram data arrives, it checks the opts.render property</li> <li>Renderer Selection: Routes to either JSRoot or NDMVR renderer based on configuration</li> <li>Resource Cleanup: Removes old renderer before creating/updating new one</li> <li>Subscription Management: The remove() method unsubscribes and cleans up all resources</li> </ul>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#using-thnpaintermanager-in-your-application","title":"Using THnPainterManager in Your Application","text":"<p>Basic Setup </p><pre><code>// Create a group to hold histogram objects\nconst histogramGroup = new THREE.Group();\nscene.add(histogramGroup);\n\n// Initialize THnPainterManager\nconst painterManager = new THnPainterManager(\"histogram1\", histogramGroup, camera);\n\n// Load histogram data\nawait histogramSubjectGet().next({\n id: \"histogram1\",\n obj: parse(h3scat),\n opts: {\n render: \"ndmvr\" // or \"jsroot\"\n }\n});\n</code></pre><p></p> <p>Switching Renderers</p> <p>You can easily switch between rendering engines by changing the render option. On line 120 of the example code, you'll find: </p><pre><code>await histogramSubjectGet().next({\n id: \"histogram1\",\n obj: parse(h3scat),\n opts: {\n render: \"ndmvr\" // \u2190 Change this to \"jsroot\" to use JSRoot renderer\n }\n});\n</code></pre><p></p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#render-options","title":"Render Options:","text":"<ul> <li>\"ndmvr\" - Uses the custom NDMVR renderer (THnPainter) for optimized 3D visualization</li> <li>\"jsroot\" - Uses the JSRoot renderer (HistogramJsrootClass) for standard ROOT compatibility</li> </ul> <p>The manager will automatically:</p> <p>Detect the renderer change Clean up the current renderer Initialize the new renderer Display the histogram with the new rendering engine</p> <p>Global Configuration At the end of the file (after the histogram setup), you'll notice the global configuration: </p><pre><code>configSubjectGet().next(config);\n</code></pre><p></p> <p>This line loads global visualization settings from config.json. The configuration affects:</p> <p>Color schemes and palettes Default rendering options Axis labels and scales Visualization performance settings</p> <p>The configuration is applied globally through the configSubjectGet() subject, which means:</p> <p>All histograms can access these settings Changes propagate to all active visualizations You can update configuration at runtime</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#note","title":"Note:","text":"<p>Config is merged internally in config handler, so in addition to support of dynamic update according to configuration, it is possible to send just a part of config, which will be merged with existing config!</p> <p>One can send just a part of config, for example:</p> <p>Line 101 of the example code </p><pre><code>configSubjectGet().next({\n \"config\": {\n \"environment\": {\n \"histogramPads\": {\n \"type\": \"grid1x1x1\",\n \"prefix\": \"histogram\",\n \"sc\": {\n \"x\": 5,\n \"y\": 3,\n \"z\": 5\n },\n \"padding\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n },\n \"origin\": {\n \"x\": -2.5,\n \"y\": 1,\n \"z\": 2.5\n }\n }\n }\n }\n});\n</code></pre><p></p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#next-steps","title":"Next Steps","text":"<p>Now that you understand how the histogram manager, basic configuration and communication works, you can:</p> <p>If you want to find out more about all the configuration options, check out the Configuration Reference.</p> <p>Experiment with different renderers by changing the render option Modify the global configuration to customize the visualization appearance Create multiple histogram managers for different data sets Implement custom rendering logic by extending the manager class</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/","title":"First visualization","text":""},{"location":"components/tutorial/firstVisualization/first-visualization/#your-first-visualization","title":"Your First Visualization","text":"<p>In this tutorial, you'll create a basic 3D histogram visualization using NDMVR-Core's THnPainter with Three.js.</p> <p>While in this section is visualized 3-Dimensional histogram, THnPainter support up to N-Dimensions histogram, utilizing representation of data, via the the hypercube concept.</p> <p>For more information about ndmvr-core package itself, please head to: Home page</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#prerequisites","title":"Prerequisites","text":"<p>This tutorial assumes you have basic knowledge of: - Three.js fundamentals (scene, camera, renderer) - JavaScript ES6+ (async/await, imports) - Basic understanding of 3D graphics concepts</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#what-youll-build","title":"What You'll Build","text":"<p>A simple 3D histogram visualization with: - Interactive orbit controls for rotation and zoom - A TH3 (3D histogram) rendered using NDMVR-Core - No build tools required - just HTML and JavaScript</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download the complete tutorial package that includes: - <code>index.html</code> - The complete visualization - <code>h3scat.json</code> - Sample 3D histogram data from ROOT</p> <p>Download Tutorial Files (ZIP)</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#key-concepts","title":"Key Concepts","text":""},{"location":"components/tutorial/firstVisualization/first-visualization/#understanding-thnpainter","title":"Understanding THnPainter","text":"<p>The <code>THnPainter</code> class is NDMVR-Core's main tool for visualizing ROOT histograms. It automatically: - Detects the histogram type (TH1, TH2, TH3...) - Creates appropriate 3D geometry - Generates materials and textures - Provides outlines overlays for clarity - Handles coordinate systems and scaling - For more details, see the THnPainter API reference.</p> <p>Key properties: - <code>painter.mesh</code> - The main rendered histogram (THREE.Mesh) - <code>painter.wireframe</code> - Object containing wireframe visualization - <code>painter.wireframe.wireframe</code> - The wireframe mesh to add to scene</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#understanding-the-data-flow","title":"Understanding the Data Flow","text":"<pre><code>h3scat.json (ROOT JSON)\n \u2193\nparse(h3scat) - JSROOT parses ROOT format\n \u2193\n{obj: parsedData} - Wrap in required structure\n \u2193\nnew THnPainter() - Create painter instance\n \u2193\npainter.mesh + painter.wireframe - Three.js meshes\n \u2193\nscene.add() - Add to Three.js scene\n</code></pre>"},{"location":"components/tutorial/firstVisualization/first-visualization/#importing-libraries","title":"Importing Libraries","text":"<p>The visualization uses several libraries loaded via import map:</p> <pre><code>import * as THREE from \"three\";\nimport {OrbitControls} from \"three/examples/jsm/controls/OrbitControls.js\";\nimport {THnPainter} from \"ndmvr-core\";\nimport h3scat from \"./h3scat.json\" with { type: \"json\" };\nimport {parse} from \"jsroot\";\n</code></pre> <p>Breaking it down: - <code>THnPainter</code> - NDMVR-Core's painter for ROOT histograms (TH1, TH2, TH3...) - <code>h3scat.json</code> - Your histogram data file (imported with JSON type assertion) - <code>parse</code> - JSROOT's parser to convert ROOT JSON format to JavaScript objects</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#loading-and-rendering-the-histogram","title":"Loading and Rendering the Histogram","text":"<p>This is where NDMVR-Core does its work:</p> <pre><code>async function loadHistogram() {\n try {\n // Parse the ROOT JSON format using JSROOT\n const histoObj = {obj: parse(h3scat)}\n\n // Create THn painter with the parsed histogram\n const painter = new THnPainter(histoObj, \"histogram1\");\n\n // Add the histogram mesh and wireframe to the scene\n scene.add(painter.mesh);\n scene.add(painter.wireframe.wireframe);\n\n console.log(\"Histogram loaded successfully!\");\n\n } catch (error) {\n console.error(\"Error loading histogram:\", error);\n }\n}\n</code></pre> <p>Understanding the code:</p> <ol> <li> <p>Parsing ROOT data: <code>parse(h3scat)</code> converts the ROOT JSON format into a JavaScript object that NDMVR-Core can understand. The result is wrapped in an object with an <code>obj</code> property as required by THnPainter.</p> </li> <li> <p>Creating the painter: <code>new THnPainter(histoObj, \"histogram1\")</code> creates a painter instance:</p> </li> <li>First argument: The histogram data wrapped in <code>{obj: parsedData}</code></li> <li> <p>Second argument: A unique identifier for this histogram</p> </li> <li> <p>Adding to the scene: THnPainter provides two renderable objects:</p> </li> <li><code>painter.mesh</code> - The solid 3D histogram bars</li> <li><code>painter.wireframe.wireframe</code> - The wireframe outline for better visibility</li> </ol>"},{"location":"components/tutorial/firstVisualization/first-visualization/#initializing-the-visualization","title":"Initializing the Visualization","text":"<pre><code>loadHistogram();\nanimate();\n</code></pre> <p>Simply call <code>loadHistogram()</code> to load and render your histogram, then start the animation loop.</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#the-complete-html-structure","title":"The Complete HTML Structure","text":"<p>The visualization is completely self-contained in a single HTML file:</p> <pre><code><!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>First Visualization - NDMVR</title>\n <style>\n /* Styling... */\n </style>\n</head>\n<body>\n <div id=\"info\">\n <h3>First Visualization</h3>\n <p>Use mouse to rotate, scroll to zoom</p>\n </div>\n\n <!-- Import map for CDN libraries -->\n <script type=\"importmap\">\n { \"imports\": { /* library mappings */ } }\n </script>\n\n <!-- Main visualization code -->\n <script type=\"module\">\n // All your code here\n </script>\n</body>\n</html>\n</code></pre>"},{"location":"components/tutorial/firstVisualization/first-visualization/#running-your-visualization","title":"Running Your Visualization","text":"<ol> <li>Extract the downloaded ZIP file to a folder</li> <li> <p>Start a local web server in that folder: </p><pre><code># Python 3\npython -m http.server 8000\n\n# Node.js\nnpx serve\n\n# Or use VS Code's Live Server extension\n</code></pre><p></p> </li> <li> <p>Open your browser and navigate to <code>http://localhost:8000</code></p> </li> </ol> <p>Important: You must use a local server because browsers block ES modules and JSON imports when loaded from <code>file://</code> URLs.</p> <p>Note: For more convenient way, you can also use vscode's Live Server extension, or Built-in preview provided in any Jetbrains IDE.</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#next-steps","title":"Next Steps","text":"<p>We now have basic visualization, not that good, but we have one. In tutorials later in the series, we'll improve and customize it further.</p> <p>Now that you understand the basics: - Explore painter configuration options - Add interactive features with raycasting - Create dynamic histogram updates using RxJS</p> <p>The complete working code is in the downloaded <code>index.html</code> file. Use it as a starting point for your own visualizations!</p> <p>Tutorial continues in configuration section.</p>"},{"location":"components/tutorial/interactions/interactions/","title":"interactions","text":""},{"location":"components/tutorial/interactions/interactions/#histograms-and-interactions","title":"Histograms and interactions","text":"<p>In this section, you will learn how to add interactions to your histograms.</p>"},{"location":"components/tutorial/interactions/interactions/#what-youll-build","title":"What You'll Build","text":"<p>A simple 3D histogram visualization with: - Visualization of THn histogram - Add basic interactions with the histogram - Change default interactions to user defined ones</p>"},{"location":"components/tutorial/interactions/interactions/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download Tutorial Files (ZIP)</p>"},{"location":"components/tutorial/interactions/interactions/#adding-basic-interactions","title":"Adding basic interactions","text":"<p>As of now, you will be able to utilize some of the basic interactions done by keys, including:</p> <ul> <li>Navigate to layer of histogram by pressing <code>num keys</code>.</li> <li>Reset histogram state by pressing <code>r</code> key.</li> <li>Hide/Show bin outline by pressing <code>o</code> key.</li> </ul>"},{"location":"components/tutorial/interactions/interactions/#adding-ndmvr-raycaster","title":"Adding NdmVr raycaster","text":"<ul> <li>To add basic interactions with the mouse to your histogram, you need to add a raycaster to Three scene.</li> <li>We strongly recommend using NdmVr raycaster to handle mouse events.</li> <li>Ndmvr raycaster is built on top of Threejs raycaster. As it only adds handling of double clicks and clicks while some keys can be hold. This information is added to the checkIntersection calls. </li> </ul> <p>To add NdmVr raycaster to your scene, you can simply add this line: </p><pre><code> const raycaster = new NdmvrRaycaster(scene, renderer.domElement);\n</code></pre><p></p> Property Type Description <code>scene</code> object Three.js scene <code>rendererElement</code> object DOM element from Three.js renderer"},{"location":"components/tutorial/interactions/interactions/#now-you-can-use-basic-interactions-with-the-histogram-using-also-the-mouse-including","title":"Now you can use basic interactions with the histogram using also the mouse including =>","text":"<ul> <li>Left click on bin to expand it by histogram in the layer beneath.</li> <li>Shift + Left click on expanded bin to hide histogram in the layer beneath.</li> <li>Double click on bin to navigate to histogram on layer beneath.</li> <li>Shift + Double click on bin to navigate to histogram on layer above.</li> </ul>"},{"location":"components/tutorial/interactions/interactions/#adding-custom-user-functions","title":"Adding custom user functions.","text":"<p>Now that we have basic interactions, we can add custom user functions to the histogram. Support for custom functions is provided in cases where:</p> <ul> <li>the default interactions do not offer enough or right functionality.</li> <li>custom behavior is required, such as integrating with your own solution or making HTTP requests etc.</li> </ul> <p>Note: User and default functions behave exactly the same in terms of their context and lifecycle, ensuring developers can easily change them to their liking. the default ones are just simply there from the start, as they were identified as most-likely to be used.</p> <p>To add custom user functions, you can use the function subject.</p>"},{"location":"components/tutorial/interactions/interactions/#manipulating-functions","title":"Manipulating functions","text":"<ul> <li>Here we provide examples for basic use cases on how one can manipulate functions.</li> <li>You can try these examples in the interactions.html file, by simply copy pasting below codes to the script (e.g. at line 92).</li> </ul> <p>Note: If you want to set functions right away, it is stronly recommended to set them in block of setTimeout with timeout of 0. This is done for ensuring all the components are initialized, thus the event will be recorded. (See example at line 99 of interactions.html) </p>"},{"location":"components/tutorial/interactions/interactions/#how-to-utilize-custom-functions","title":"How to utilize custom functions","text":"<ul> <li>To fully utilize custom functions one need to understand what is provided and what is possible.</li> <li>Every custom function has access to the event and the context.</li> <li>event consists of:</li> <li> Property Type Description <code>index</code> array: Object Array of objects, contains xyz coordinates of intersected bin (including all layers) <code>instanceId</code> array: Int Array of ID's (Int) identifying instance of intersected bin in NdmVr indexing. <code>jsrootInstance</code> array: Int Array of ID's (Int) identifying instance of intersected bin in ROOT indexing. <code>jsrootObj</code> Object JSROOT Object linked to intersected bin. <code>origin</code> Object Instance of ThnPainter linked to intersected bin. <code>range</code> array: Object Array of objects, contains info about intersected bin, such as: range of bin, title color of bin... <code>target</code> THREE.Vector3 Point in space where bin was intersected. <code>content</code> Float Content value of intersected bin. <code>error</code> Float Error of intersected bin. <code>distance</code> Float Distance from origin of raycaster to the point of intersection. </li> <li>context property provides THnPainter instance.</li> <li>User can manipulate histogram itself by using the context.</li> </ul>"},{"location":"components/tutorial/interactions/interactions/#remove-all-functions","title":"Remove all functions","text":"<ul> <li>If you want to remove all functions from al histograms.</li> <li>You can also specify the target id to remove only functions from specific histogram. <pre><code>functionSubjectGet().removeFunctions({\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n }\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#remove-all-functions-on-event","title":"Remove all functions on event","text":"<ul> <li>If you want to remove all functions on specific event. <pre><code>functionSubjectGet().removeFunctions({\n event: \"mousemove\",\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n }\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#add-default-function","title":"Add default function","text":"<ul> <li>If you wish to bring back the default function on a specific event. <pre><code>functionSubjectGet().addFunctions({\n event: \"mousemove\",\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n },\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#add-custom-function","title":"Add custom function","text":"<ul> <li>If you wish to add custom function on a specific event.</li> <li>If you add multiple functions on the same event, they will be all executed, not rewritten.</li> <li>Functions will be executed in the order they were added. <pre><code>functionSubjectGet().addFunctions({\n event: \"mouseclick\",\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n },\n function: function (event, context) {\n console.log(\"my-custom-function: \", event);\n }\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#set-custom-functions","title":"Set custom functions","text":"<ul> <li>One can also set all functions at once by using the <code>setFunctions</code> method.</li> <li>This will overwrite all existing functions on the entity. <pre><code> functionSubjectGet().setFunctions([\n //array of functions\n]);\n</code></pre></li> <li>With this approach you can quickly change the behavior of interactions.</li> <li>It can simply be linked to user defined tools like this: <pre><code>const functions = {\n empty: [{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }\n }],\n second: [{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }, event: \"mousedbclick\",\n function: function (event, context) {\n console.log(\"custom function from set functions: \", event, context);\n const histogramBelow = context.pointer.getChildByPosition(event.jsrootInstance);\n redraw(\"jsrootdiv\", histogramBelow);\n }\n },{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }, event: \"mouseclick\",\n },{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }, event: \"shiftmouseclick\",\n }]\n};\n\nfunctionSubjectGet().setFunctions(functions.empty);\nfunctionSubjectGet().setFunctions(functions.second);\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#power-of-user-defined-functions","title":"Power of user defined functions","text":"<ul> <li>With the power of user-defined functions, you can create custom tools for your histogram.</li> <li>In provided demo you might notice that you cannot navigate to histogram below by double-clicking on bin. As this was overridden by custom function, which takes clicked bin and redraws it onto jsroot div.</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/","title":"Bin info jsroot","text":""},{"location":"components/visualization/bin-info-jsroot/#bininfovisualizer","title":"BinInfoVisualizer","text":""},{"location":"components/visualization/bin-info-jsroot/#overview","title":"Overview","text":"<p>The <code>BinInfoVisualizer</code> class creates a 3D panel in THREE.js that displays histogram bin information. It uses JSROOT's TLatex for text rendering and automatically positions itself in front of the camera. The visualizer subscribes to an RxJS subject queue for efficient, non-blocking updates.</p>"},{"location":"components/visualization/bin-info-jsroot/#constructor","title":"Constructor","text":"<pre><code>constructor(camera, options = {})\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#parameters","title":"Parameters","text":"<ul> <li>camera (<code>THREE.Camera</code>): Camera reference for billboard positioning</li> <li>options (<code>Object</code>): Configuration options for styling and layout</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#options-object","title":"Options Object","text":"Property Type Default Description <code>backgroundColor</code> <code>number</code> <code>0xAAAAAA</code> Panel background color (hex) <code>textColor</code> <code>number</code> <code>1</code> ROOT color index for text <code>titleColor</code> <code>number</code> <code>0</code> ROOT color index for title <code>padding</code> <code>number</code> <code>0.002</code> Padding around text (THREE.js units) <code>lineHeight</code> <code>number</code> <code>0.002</code> Height per line of text <code>textSize</code> <code>number</code> <code>10</code> Font size for text <code>width</code> <code>number</code> <code>0.031</code> Panel width"},{"location":"components/visualization/bin-info-jsroot/#created-objects","title":"Created Objects","text":"<ul> <li>group (<code>THREE.Group</code>): Container for panel and text elements</li> <li>queue (<code>RxJS.Subject</code>): Event queue for bin data updates</li> <li>queueSub (<code>RxJS.Subscription</code>): Manages sequential processing of updates</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#properties","title":"Properties","text":"<ul> <li>camera (<code>THREE.Camera</code>): Reference to the camera</li> <li>options (<code>Object</code>): Merged configuration options</li> <li>loader (<code>FontLoader</code>): THREE.js font loader instance</li> <li>queue (<code>Subject</code>): RxJS subject for queuing bin data</li> <li>queueSub (<code>Subscription</code>): Subscription managing update queue</li> <li>group (<code>THREE.Group</code>): THREE.js group containing the visualization</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#methods","title":"Methods","text":""},{"location":"components/visualization/bin-info-jsroot/#parsedatadata","title":"parseData(data)","text":"<p>Extracts and formats display information from bin data.</p> <p>Parameters: - data (<code>Object</code>): Bin information object</p> <p>Returns: <code>Array<Object></code> - Array of line objects with <code>text</code> and <code>isTitle</code> properties</p> <p>Parsed Information: - Title (if present) - Coordinate ranges for each axis (x, y, z) - Bin index and position - Bin content value - Bin error (if present)</p>"},{"location":"components/visualization/bin-info-jsroot/#createbackgroundpanelheight","title":"createBackgroundPanel(height)","text":"<p>Creates the background panel for the info display.</p> <p>Parameters: - height (<code>number</code>): Panel height in THREE.js units</p> <p>Returns: <code>THREE.Mesh</code> - Panel mesh with configured material</p> <p>Material: - <code>PlaneGeometry</code> sized to fit content - <code>MeshBasicMaterial</code> with configured background color - <code>DoubleSide</code> rendering</p>"},{"location":"components/visualization/bin-info-jsroot/#updatevisualizationdata","title":"updateVisualization(data)","text":"<p>Updates the 3D visualization with new bin information.</p> <p>Important: This method is called automatically by the queue subject. Do not call directly - use <code>queue.next(data)</code> instead.</p> <p>Parameters: - data (<code>Object</code>): Bin data to visualize (or <code>null</code> to clear)</p> <p>Behavior: - Clears previous visualization - Parses data into display lines - Creates background panel - Renders each line using JSROOT TLatex - Positions panel in front of camera - Attaches to camera for billboard effect</p> <p>Async Processing: - Uses RxJS <code>concatMap</code> for sequential updates - Prevents concurrent updates that could cause race conditions - Queues latest event while processing</p>"},{"location":"components/visualization/bin-info-jsroot/#getgroup","title":"getGroup()","text":"<p>Returns the THREE.Group containing the visualization.</p> <p>Returns: <code>THREE.Group</code></p>"},{"location":"components/visualization/bin-info-jsroot/#setpositionx-y-z","title":"setPosition(x, y, z)","text":"<p>Manually sets the position of the info panel.</p> <p>Parameters: - x, y, z (<code>number</code>): Position coordinates</p> <p>Note: Position is typically managed automatically by the camera attachment.</p>"},{"location":"components/visualization/bin-info-jsroot/#setrotationx-y-z","title":"setRotation(x, y, z)","text":"<p>Manually sets the rotation of the info panel.</p> <p>Parameters: - x, y, z (<code>number</code>): Rotation angles in radians</p>"},{"location":"components/visualization/bin-info-jsroot/#clear","title":"clear()","text":"<p>Clears all content from the panel.</p> <p>Behavior: - Removes all children from group - Disposes geometries and materials</p>"},{"location":"components/visualization/bin-info-jsroot/#dispose","title":"dispose()","text":"<p>Cleans up all resources and subscriptions.</p> <p>Behavior: - Unsubscribes from queue subject - Removes all children from group - Disposes all geometries and materials</p>"},{"location":"components/visualization/bin-info-jsroot/#usage-example","title":"Usage Example","text":"<pre><code>import { BinInfoVisualizer } from './bininfo-jsroot-class.js';\nimport * as THREE from 'three';\n\n// Create camera\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\n\n// Create visualizer with custom options\nconst binInfo = new BinInfoVisualizer(camera, {\n backgroundColor: 0x36454F, // Charcoal gray\n textColor: 0, // Black text\n titleColor: 0, // Black title\n padding: 0.003,\n width: 0.04\n});\n\n// Add to scene (typically attached to camera automatically)\nconst scene = new THREE.Scene();\nscene.add(camera);\n\n// Queue bin data for display\nbinInfo.queue.next({\n title: 'Histogram Bin Info',\n coords: [{\n x: { min: 0.5, max: 1.5, name: 'X Axis', title: 'X' },\n y: { min: 2.0, max: 3.0, name: 'Y Axis', title: 'Y' },\n z: { min: 1.0, max: 2.0, name: 'Z Axis', title: 'Z' }\n }],\n index: [{ x: 1, y: 2, z: 1 }],\n content: 42.7,\n error: 2.3,\n object: { bins: [15] },\n instanceId: 15,\n point: { x: 1, y: 1.5, z: 1.5 }\n});\n\n// Clear display\nbinInfo.queue.next(null);\n\n// Clean up when done\nbinInfo.dispose();\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#integration-with-histogramjsrootclass","title":"Integration with HistogramJsrootClass","text":"<p>The <code>BinInfoVisualizer</code> is automatically created by <code>HistogramJsrootClass</code> and receives updates from mouse interactions:</p> <pre><code>import { HistogramJsrootClass } from './histogram-jsroot-class.js';\n\nconst histogram = new HistogramJsrootClass('hist1', rootObject, camera);\n\n// The bin info visualizer is available at:\n// histogram.binInfoComponent\n\n// It receives updates automatically from mousemove events\n// through the histogram's raycast handler\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#rxjs-subject-integration","title":"RxJS Subject Integration","text":"<p>The visualizer can be driven by RxJS subjects for decoupled architecture:</p> <pre><code>import { binInfoSubjectGet } from '../rxjs/BinInfoSubject.js';\n\n// Subscribe visualizer to subject\nconst subscription = binInfoSubjectGet()\n .subscribe(binData => {\n binInfo.queue.next(binData);\n });\n\n// Publish bin data from anywhere\nbinInfoSubjectGet().next({\n coords: [/* ... */],\n content: 100,\n error: 5\n});\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#text-rendering-with-tlatex","title":"Text Rendering with TLatex","text":"<p>The visualizer uses JSROOT's TLatex for text rendering, which supports: - ROOT-style text formatting - ROOT color indices - Scalable vector text - LaTeX-like syntax</p> <p>Each line is created using: </p><pre><code>const latex = create(\"TLatex\");\nlatex.fTitle = \"X = [0.5, 1.5)\";\nlatex.fTextAlign = 12;\nlatex.fTextFont = 2;\nlatex.fTextColor = 0; // ROOT color index\nconst textGroup = await build3d(latex, \"p\", y * 100, \"\", \"\");\n</code></pre><p></p>"},{"location":"components/visualization/bin-info-jsroot/#billboard-behavior","title":"Billboard Behavior","text":"<p>The panel is attached to the camera and positioned at a fixed distance in front of it, ensuring: - Always faces the user - Readable from any angle - Maintains consistent size - Positioned near the point of interaction</p>"},{"location":"components/visualization/bin-info-jsroot/#queue-processing","title":"Queue Processing","text":"<p>The <code>concatMap</code> operator ensures: - Sequential processing of updates - No race conditions from concurrent builds - Latest event is queued if update arrives during processing - Non-blocking asynchronous rendering</p>"},{"location":"components/visualization/bin-info-jsroot/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: <code>Group</code>, <code>Mesh</code>, <code>MeshBasicMaterial</code>, <code>PlaneGeometry</code>, <code>DoubleSide</code>, <code>Box3</code>, <code>Vector3</code></li> <li>THREE.js Addons: <code>TextGeometry</code>, <code>FontLoader</code></li> <li>JSROOT: <code>create</code>, <code>build3d</code> for TLatex rendering</li> <li>RxJS: <code>Subject</code>, <code>from</code>, <code>concatMap</code>, <code>finalize</code>, <code>EMPTY</code></li> <li>RxJS Subjects:</li> <li><code>canvasSubjectGet</code> from <code>../rxjs/CanvasSubject.js</code></li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#related-components","title":"Related Components","text":"<ul> <li>HistogramJsrootClass - Creates and manages BinInfoVisualizer</li> <li>THnPainter - Alternative histogram that can use bin info</li> <li>NdmvrRaycaster - Provides raycasting for interaction</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/canvas/","title":"Canvas","text":""},{"location":"components/visualization/canvas/#canvas","title":"Canvas","text":""},{"location":"components/visualization/canvas/#overview","title":"Overview","text":"<p>The <code>CanvasClass</code> creates a 3D canvas plane using THREE.js that can display ROOT canvas objects as textures in a 3D environment. It manages texture updates, transformation (position, rotation, scale), and subscribes to canvas and configuration subjects for reactive updates.</p>"},{"location":"components/visualization/canvas/#constructor","title":"Constructor","text":"<pre><code>constructor(image, position, rotation, sc, id)\n</code></pre> <p>Parameters: - <code>image</code> - Initial image (optional): data URL, HTTP URL, or HTMLImageElement - <code>position</code> - THREE.Vector3 or object with <code>{x, y, z}</code> coordinates - <code>rotation</code> - THREE.Vector3 or object with <code>{x, y, z}</code> rotation in degrees - <code>scale</code> - THREE.Vector3 or object with <code>{x, y, z}</code> scale factors - <code>id</code> (string) - Unique identifier for this canvas instance</p> <p>Behavior: - Creates a <code>PlaneGeometry</code> with specified scale - Creates a white <code>MeshBasicMaterial</code> with <code>DoubleSide</code> rendering - Initializes texture if image is provided - Subscribes to <code>canvasSubject</code> for canvas object updates - Subscribes to <code>configSubject</code> for configuration changes</p>"},{"location":"components/visualization/canvas/#properties","title":"Properties","text":"Property Type Description <code>plane</code> <code>THREE.Mesh</code> The THREE.js mesh object representing the canvas <code>cinemaSub</code> <code>Subscription</code> RxJS subscription to canvas subject <code>configSub</code> <code>Subscription</code> RxJS subscription to config subject <code>position</code> <code>Vector3</code> Current position of the canvas <code>rotation</code> <code>Vector3</code> Current rotation of the canvas (degrees) <code>scale</code> <code>Vector3</code> Current scale of the canvas <code>id</code> <code>string</code> Unique identifier for this canvas"},{"location":"components/visualization/canvas/#methods","title":"Methods","text":""},{"location":"components/visualization/canvas/#updatemesh","title":"updateMesh()","text":"<p>Updates the mesh transformation based on current position, rotation, and scale properties.</p> <p>Behavior: - Applies scale to the plane - Sets position of the plane - Converts rotation from degrees to radians and applies it</p> <pre><code>updateMesh()\n</code></pre>"},{"location":"components/visualization/canvas/#updatetextureimage","title":"updateTexture(image)","text":"<p>Updates the canvas texture with a new image.</p> <p>Parameters: - <code>image</code> - String (data URL or HTTP URL) or HTMLImageElement</p> <p>Supported Image Types: 1. Data URL: <code>data:image/png;base64,...</code> 2. HTTP URL: <code>http://</code> or <code>https://</code> URLs 3. HTMLImageElement: Direct image element reference</p> <p>Behavior: - Loads texture using THREE.TextureLoader - Updates material's texture map - Handles async loading for URLs - Direct assignment for HTMLImageElement - Logs warning for null/undefined input - Logs error for unsupported types</p> <pre><code>canvas.updateTexture('data:image/png;base64,iVBORw0KGg...');\ncanvas.updateTexture('https://example.com/image.png');\ncanvas.updateTexture(imageElement);\n</code></pre>"},{"location":"components/visualization/canvas/#remove","title":"remove()","text":"<p>Removes the canvas from the scene and cleans up resources.</p> <p>Behavior: - Removes plane from its parent in the scene graph - Unsubscribes from canvas subject - Unsubscribes from config subject - Only executes if plane has a parent</p> <pre><code>canvas.remove();\n</code></pre>"},{"location":"components/visualization/canvas/#getplane","title":"getPlane()","text":"<p>Returns the THREE.js mesh object representing the canvas.</p> <p>Returns: <code>THREE.Mesh</code> - The canvas plane mesh</p> <pre><code>const plane = canvas.getPlane();\nscene.add(plane);\n</code></pre>"},{"location":"components/visualization/canvas/#reactive-updates","title":"Reactive Updates","text":""},{"location":"components/visualization/canvas/#canvas-subject-integration","title":"Canvas Subject Integration","text":"<p>The canvas subscribes to <code>canvasSubjectGet()</code> and filters events by ID:</p> <p>Matching Criteria: - Event ID matches canvas ID exactly - Event ID is <code>\"*\"</code> (wildcard, targets all canvases)</p> <p>On Canvas Update: 1. Receives ROOT canvas object via <code>obj.obj</code> 2. Converts object to PNG image using JSROOT's <code>makeImage()</code> 3. Image dimensions: 1200\u00d7600 pixels 4. Updates texture with generated PNG</p> <pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().next({\n id: 'canvas1', // or \"*\" for all canvases\n obj: myRootObj // ROOT object\n});\n</code></pre>"},{"location":"components/visualization/canvas/#config-subject-integration","title":"Config Subject Integration","text":"<p>The canvas subscribes to <code>configSubjectGet()</code> and filters events:</p> <p>Matching Criteria: - Config target ID includes <code>\"*\"</code> OR - Config target ID includes canvas ID</p> <p>On Config Update: Extracts canvas configuration from: </p><pre><code>config.environment.canvas.pos // {x, y, z}\nconfig.environment.canvas.rot // {x, y, z} in degrees\nconfig.environment.canvas.sc // {x, y, z}\n</code></pre><p></p> <p>Then calls <code>updateMesh()</code> to apply transformations.</p>"},{"location":"components/visualization/canvas/#complete-usage-example","title":"Complete Usage Example","text":"<pre><code>import { CanvasClass } from './component/canvas-class.js';\nimport { canvasSubjectGet } from './rxjs/CanvasSubject.js';\nimport { configSubjectGet } from './rxjs/ConfigSubject.js';\nimport { Vector3 } from 'three';\n\n// Create canvas\nconst canvas = new CanvasClass(\n null, // No initial image\n new Vector3(0, 1.6, -2), // Position\n new Vector3(0, 0, 0), // Rotation (degrees)\n new Vector3(1, 0.5, 1), // Scale\n 'canvas1' // ID\n);\n\n// Add to scene\nscene.add(canvas.getPlane());\n\n// Update canvas with ROOT object\ncanvasSubjectGet().next({\n id: 'canvas1',\n obj: myRootCanvas\n});\n\n// Update configuration\nconfigSubjectGet().next({\n target: { id: ['canvas1'] },\n config: {\n environment: {\n canvas: {\n pos: { x: 0, y: 2, z: -3 },\n rot: { x: 0, y: 45, z: 0 },\n sc: { x: 1.5, y: 0.75, z: 1 }\n }\n }\n }\n});\n\n// Manual texture update\ncanvas.updateTexture('data:image/png;base64,...');\n\n// Cleanup\ncanvas.remove();\n</code></pre>"},{"location":"components/visualization/canvas/#jsroot-integration","title":"JSROOT Integration","text":"<p>The canvas uses JSROOT's <code>makeImage()</code> function to convert ROOT canvas objects to PNG:</p> <pre><code>import { makeImage } from 'jsroot';\n\nmakeImage({\n format: \"png\",\n object: rootCanvasObject,\n width: 1200,\n height: 600\n})\n.then(png => {\n canvas.updateTexture(png);\n});\n</code></pre>"},{"location":"components/visualization/canvas/#error-handling","title":"Error Handling","text":"<pre><code>// Warns about null/undefined\ncanvas.updateTexture(null);\n// Console: \"updateTexture called with null/undefined\"\n\n// Errors on unsupported types\ncanvas.updateTexture(123);\n// Console: \"Unsupported image type passed to updateTexture: 123\"\n\n// Handles texture load failures\ncanvas.updateTexture('https://invalid-url.com/image.png');\n// Console: \"Texture load failed\" + error details\n</code></pre>"},{"location":"components/visualization/canvas/#material-configuration","title":"Material Configuration","text":"<p>The canvas uses: - Geometry: <code>THREE.PlaneGeometry</code> (1\u00d71 base, scaled by constructor) - Material: <code>THREE.MeshBasicMaterial</code> - <code>color</code>: <code>0xffffff</code> (white, allows texture colors to show) - <code>side</code>: <code>THREE.DoubleSide</code> (visible from both sides) - <code>map</code>: Updated with canvas texture</p>"},{"location":"components/visualization/canvas/#coordinate-system","title":"Coordinate System","text":"<p>Position: - Standard THREE.js right-handed coordinate system - Y-up orientation</p> <p>Rotation: - Input: Degrees - Conversion: Multiplied by <code>Math.PI / 180</code> - Applied in XYZ order</p> <p>Scale: - Applied directly to geometry - Uniform scale if all components equal - Non-uniform scale supported</p>"},{"location":"components/visualization/canvas/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: <code>Vector3</code>, <code>PlaneGeometry</code>, <code>MeshBasicMaterial</code>, <code>Color</code>, <code>DoubleSide</code>, <code>Mesh</code>, <code>TextureLoader</code></li> <li>JSROOT: <code>makeImage</code></li> <li>RxJS: <code>filter</code> operator</li> <li>Communication: <code>canvasSubjectGet</code>, <code>configSubjectGet</code></li> </ul>"},{"location":"components/visualization/canvas/#related-components","title":"Related Components","text":"<ul> <li>Histogram JSROOT - 3D histogram visualization</li> <li>Canvas Subject - Canvas communication channel</li> <li>Config Subject - Configuration management</li> </ul>"},{"location":"components/visualization/canvas/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/histogram-jsroot/","title":"Histogram JSROOT","text":""},{"location":"components/visualization/histogram-jsroot/#histogramjsrootclass","title":"HistogramJsrootClass","text":""},{"location":"components/visualization/histogram-jsroot/#overview","title":"Overview","text":"<p>The <code>HistogramJsrootClass</code> is a THREE.js-based histogram renderer that uses the JSROOT library to build and render 3D histogram visualizations. It provides interactive raycasting, event handling, bin information display, and supports dynamic configuration updates through RxJS subjects.</p>"},{"location":"components/visualization/histogram-jsroot/#constructor","title":"Constructor","text":"<pre><code>constructor(id, rootObj, camera)\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#parameters","title":"Parameters","text":"<ul> <li>id (<code>string</code>): Unique identifier for the histogram instance</li> <li>rootObj (<code>Object</code>): ROOT object (Not all objects are supported see related issues for more: issue1, issue2)</li> <li>camera (<code>THREE.Camera</code>): THREE.js camera reference for positioning and interaction</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#created-objects","title":"Created Objects","text":"<ul> <li>histogramGroup (<code>THREE.Group</code>): Main container for all ROOT object meshes</li> <li>binInfoComponent (<code>BinInfoVisualizer</code>): Visualizer for displaying bin information</li> <li>configSub (<code>Subscription</code>): Subscribes to configuration updates</li> <li>buildPromise (<code>Promise</code>): Tracks the histogram building process</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#properties","title":"Properties","text":""},{"location":"components/visualization/histogram-jsroot/#core-properties","title":"Core Properties","text":"<ul> <li>id (<code>string</code>): Object identifier</li> <li>rootObj (<code>Object</code>): Current ROOT object</li> <li>camera (<code>THREE.Camera</code>): Camera reference</li> <li>histogramGroup (<code>THREE.Group</code>): THREE.js group containing histogram meshes</li> <li>binInfoComponent (<code>BinInfoVisualizer</code>): Bin information display component</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#event-management","title":"Event Management","text":"<ul> <li>mouseEvents (<code>Array</code>): Collection of registered mouse event handlers</li> <li>defaultRaycastHandler (<code>Function</code>): Original THREE.js raycast function</li> <li>color (<code>THREE.Color</code>): Current color for bin highlighting</li> <li>colorTarget (<code>THREE.Color</code>): Target color for hover effects (cyan)</li> <li>dirtyInstance (<code>number</code>): Index of currently highlighted bin</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#subscriptions","title":"Subscriptions","text":"<ul> <li>configSub (<code>Subscription</code>): Configuration updates from <code>configSubjectGet()</code></li> <li>sub (<code>Subscription</code>): Function/event management from <code>functionSubjectGet()</code></li> </ul>"},{"location":"components/visualization/histogram-jsroot/#methods","title":"Methods","text":""},{"location":"components/visualization/histogram-jsroot/#updatehistogramhisto","title":"updateHistogram(histo)","text":"<p>Updates the histogram with new ROOT data.</p> <p>Parameters: - histo (<code>Object</code>): New ROOT object</p> <p>Behavior: - Clears existing Three.js group - Rebuilds visualization with new data - Maintains camera and configuration settings</p>"},{"location":"components/visualization/histogram-jsroot/#renderwithbuild3d","title":"renderWithBuild3d()","text":"<p>Renders the histogram using JSROOT's <code>build3d</code> function.</p> <p>Returns: <code>Promise<void></code></p> <p>Behavior: - Uses JSROOT <code>build3d()</code> to create THREE.js meshes - Applies scaling and rotation based on configuration - Adjusts position and orientation for proper display - Sets up custom raycast handler for interaction - Handles errors during build process</p>"},{"location":"components/visualization/histogram-jsroot/#raycasthandlerraycaster-intersects","title":"raycastHandler(raycaster, intersects)","text":"<p>Custom raycaster implementation for histogram interaction.</p> <p>Parameters: - raycaster (<code>THREE.Raycaster</code>): THREE.js raycaster instance - intersects (<code>Array</code>): Array of intersection objects</p> <p>Behavior: - Processes raycaster intersections with histogram bins - Calculates bin indices from instanced mesh intersections - Computes bin content, error, and coordinate ranges - Triggers registered mouse event handlers - Updates bin information display</p>"},{"location":"components/visualization/histogram-jsroot/#addeventevent-func","title":"addEvent(event, func)","text":"<p>Registers an event handler for mouse or keyboard interactions.</p> <p>Parameters: - event (<code>string | Object</code>): Event name or keyboard event object with <code>state</code> and <code>key</code> - func (<code>Function</code>): Handler function to execute</p> <p>Supported Events: - <code>\"mouseclick\"</code> - Single click - <code>\"shiftmouseclick\"</code> - Shift + click - <code>\"mousedbclick\"</code> - Double click - <code>\"shiftmousedbclick\"</code> - Shift + double click - <code>\"mousemove\"</code> - Mouse movement - Keyboard events with <code>{state: \"keydown\"|\"keyup\", key: \"KeyCode\"}</code></p>"},{"location":"components/visualization/histogram-jsroot/#removeeventevent-func","title":"removeEvent(event, func)","text":"<p>Removes a registered event handler.</p> <p>Parameters: - event (<code>string | Object</code>): Event identifier - func (<code>Function</code>): Handler function reference to remove</p>"},{"location":"components/visualization/histogram-jsroot/#default-event-handlers","title":"Default Event Handlers","text":""},{"location":"components/visualization/histogram-jsroot/#mouseclickdefaultevent","title":"mouseClickDefault(event)","text":"<p>Default handler for mouse clicks.</p>"},{"location":"components/visualization/histogram-jsroot/#mousemovedefaultevent","title":"mousemoveDefault(event)","text":"<p>Highlights hovered bins and displays bin information.</p> <p>Behavior: - Applies color tint to hovered bin - Reverts color of previously hovered bin - Updates <code>BinInfoVisualizer</code> with bin data - Publishes bin information to <code>binInfoSubjectGet()</code></p>"},{"location":"components/visualization/histogram-jsroot/#shiftmouseclickdefaultevent","title":"shiftMouseClickDefault(event)","text":"<p>Default handler for shift+click events.</p>"},{"location":"components/visualization/histogram-jsroot/#mousedbclickdefaultevent","title":"mouseDBClickDefault(event)","text":"<p>Default handler for double-click events.</p>"},{"location":"components/visualization/histogram-jsroot/#shiftmousedbclickdefaultevent","title":"shiftMouseDBClickDefault(event)","text":"<p>Default handler for shift+double-click events.</p>"},{"location":"components/visualization/histogram-jsroot/#getinstancedmeshnode","title":"getInstancedMesh(node)","text":"<p>Recursively searches for the InstancedMesh in the histogram group.</p> <p>Parameters: - node (<code>THREE.Object3D</code>): Starting node (defaults to <code>histogramGroup</code>)</p> <p>Returns: <code>THREE.InstancedMesh | null</code></p>"},{"location":"components/visualization/histogram-jsroot/#getrangebypositionposition","title":"getRangeByPosition(position)","text":"<p>Calculates axis ranges for given bin positions.</p> <p>Parameters: - position (<code>Array<Object></code>): Array of position objects with <code>x</code>, <code>y</code>, <code>z</code> properties</p> <p>Returns: <code>Object</code> with axis ranges including <code>min</code>, <code>max</code>, <code>name</code>, <code>title</code>, and <code>color</code></p>"},{"location":"components/visualization/histogram-jsroot/#remove","title":"remove()","text":"<p>Cleans up all resources and subscriptions.</p> <p>Behavior: - Removes histogram group from parent - Removes dummy DOM element - Unsubscribes from all RxJS subscriptions - Disposes of THREE.js resources</p>"},{"location":"components/visualization/histogram-jsroot/#gethistogrammesh","title":"getHistogramMesh()","text":"<p>Returns the main histogram group.</p> <p>Returns: <code>THREE.Group</code></p>"},{"location":"components/visualization/histogram-jsroot/#usage-example","title":"Usage Example","text":"<pre><code>import { HistogramJsrootClass } from './histogram-jsroot-class.js';\nimport * as THREE from 'three';\n\n// Create a scene and camera\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\n\n// Create histogram instance\nconst histogram = new HistogramJsrootClass('histogram1', rootHistogramObject, camera);\n\n// Add to scene\nscene.add(histogram.getHistogramMesh());\n\n// Update with new data\nhistogram.updateHistogram(newRootHistogramObject);\n\n// Add custom event handler\nhistogram.addEvent('mouseclick', (intersection, histogramInstance) => {\n console.log('Clicked bin:', intersection.coords);\n console.log('Bin content:', intersection.content);\n});\n\n// Clean up when done\nhistogram.remove();\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#event-system","title":"Event System","text":"<p>The class integrates with <code>functionSubjectGet()</code> for dynamic event management:</p> <pre><code>import { functionSubjectGet } from '../rxjs/FunctionSubject.js';\n\n// Add event handler remotely\nfunctionSubjectGet().next({\n flag: 'add',\n target: { id: 'histogram1' },\n event: 'mouseclick',\n function: (intersection, histogram) => {\n console.log('Remote click handler', intersection);\n }\n});\n\n// Remove event handler\nfunctionSubjectGet().next({\n flag: 'remove',\n target: { id: 'histogram1' },\n event: 'mouseclick'\n});\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#configuration-integration","title":"Configuration Integration","text":"<p>The class subscribes to <code>configSubjectGet()</code> for dynamic updates:</p> <pre><code>import { configSubjectGet } from '../rxjs/ConfigSubject.js';\n\n// Update histogram position and scale\nconfigSubjectGet().next({\n target: { id: 'histogram1' },\n config: {\n environment: {\n histogramPads: [{\n id: 'histogram1',\n pos: { x: 0, y: 1, z: -2 },\n sc: { x: 1, y: 1, z: 1 }\n }]\n }\n }\n});\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: Core 3D rendering (<code>Group</code>, <code>Color</code>, <code>Box3</code>, <code>Vector3</code>)</li> <li>JSROOT: <code>build3d</code> for histogram rendering</li> <li>RxJS: <code>filter</code> operator, Subject subscriptions</li> <li>Internal Classes:</li> <li><code>BinInfoVisualizer</code> from <code>./bininfo-jsroot-class.js</code></li> <li>RxJS Subjects:</li> <li><code>configSubjectGet</code> from <code>../rxjs/ConfigSubject.js</code></li> <li><code>functionSubjectGet</code> from <code>../rxjs/FunctionSubject.js</code></li> <li><code>canvasSubjectGet</code> from <code>../rxjs/CanvasSubject.js</code></li> <li><code>binInfoSubjectGet</code> from <code>../rxjs/BinInfoSubject.js</code></li> </ul>"},{"location":"components/visualization/histogram-jsroot/#related-components","title":"Related Components","text":"<ul> <li>BinInfoVisualizer - Bin information display</li> <li>THnPainter - Alternative histogram rendering</li> <li>NdmvrRaycaster - Scene-level raycasting</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/ndmvr-raycaster/","title":"NDMVR raycaster","text":""},{"location":"components/visualization/ndmvr-raycaster/#ndmvrraycaster","title":"NdmvrRaycaster","text":""},{"location":"components/visualization/ndmvr-raycaster/#overview","title":"Overview","text":"<p>The <code>NdmvrRaycaster</code> class provides scene-level raycasting functionality for THREE.js applications. It handles mouse movement and click events, casting rays through the scene to detect intersections with 3D objects. The class distinguishes between single clicks, double clicks, and shift-modified clicks, and assigns a <code>_triggerSource</code> property to the raycaster for event identification.</p>"},{"location":"components/visualization/ndmvr-raycaster/#constructor","title":"Constructor","text":"<pre><code>constructor(scene, rendererElement)\n</code></pre>"},{"location":"components/visualization/ndmvr-raycaster/#parameters","title":"Parameters","text":"<ul> <li>scene (<code>THREE.Object3D</code>): THREE.js scene or object to traverse for intersections. Must contain a camera as a child.</li> <li>rendererElement (<code>HTMLElement</code>): DOM element attached to the renderer, used for mouse coordinate calculations</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#initialization","title":"Initialization","text":"<ul> <li>Sets up mouse tracking with <code>THREE.Vector2</code></li> <li>Creates <code>THREE.Raycaster</code> instance</li> <li>Finds camera in scene hierarchy</li> <li>Configures double-click timeout (default 190ms)</li> <li>Subscribes to configuration updates via <code>configSubjectGet()</code></li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#properties","title":"Properties","text":""},{"location":"components/visualization/ndmvr-raycaster/#core-properties","title":"Core Properties","text":"<ul> <li>raycaster (<code>THREE.Raycaster</code>): THREE.js raycaster instance</li> <li>mouse (<code>THREE.Vector2</code>): Normalized device coordinates (-1 to +1)</li> <li>cameraElement (<code>THREE.Camera</code>): Camera found in scene hierarchy</li> <li>sceneElement (<code>THREE.Object3D</code>): Scene object to raycast against</li> <li>rendererElement (<code>HTMLElement</code>): Renderer DOM element reference</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#event-management","title":"Event Management","text":"<ul> <li>singleClickTimer (<code>number | null</code>): Timeout ID for single click detection</li> <li>dbClickTimeout (<code>number</code>): Double-click threshold in milliseconds (default 190)</li> <li>lastClick (<code>number</code>): Timestamp of last click for double-click detection</li> <li>raycastOn (<code>boolean</code>): Toggle for enabling/disabling raycasting</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#performance","title":"Performance","text":"<ul> <li>checkInterval (<code>number</code>): Minimum interval between mousemove checks (1ms)</li> <li>lastCheck (<code>number</code>): Timestamp of last mousemove check</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#subscriptions","title":"Subscriptions","text":"<ul> <li>configSub (<code>RxJS.Subscription</code>): Configuration updates subscription</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#methods","title":"Methods","text":""},{"location":"components/visualization/ndmvr-raycaster/#setupraycasting","title":"setupRaycasting()","text":"<p>Initializes event listeners for mouse interactions.</p> <p>Behavior: - Adds <code>mousemove</code> event listener to window - Adds <code>click</code> event listener to window - Binds event handlers to instance context</p>"},{"location":"components/visualization/ndmvr-raycaster/#destroyraycasting","title":"destroyRaycasting()","text":"<p>Removes event listeners for mouse interactions.</p> <p>Behavior: - Removes <code>mousemove</code> event listener - Removes <code>click</code> event listener</p>"},{"location":"components/visualization/ndmvr-raycaster/#toggleraycasting","title":"toggleRaycasting()","text":"<p>Toggles raycasting on/off.</p> <p>Behavior: - If enabled, calls <code>setupRaycasting()</code> - If disabled, calls <code>destroyRaycasting()</code> - Updates <code>raycastOn</code> state</p>"},{"location":"components/visualization/ndmvr-raycaster/#mousemoveeventhandleevent","title":"mousemoveEventHandle(event)","text":"<p>Handles mouse movement with throttling.</p> <p>Parameters: - event (<code>MouseEvent</code>): Browser mouse event</p> <p>Behavior: - Throttles updates based on <code>checkInterval</code> - Prevents excessive raycasting during fast mouse movement - Calls <code>updateRaycaster()</code> at controlled intervals</p>"},{"location":"components/visualization/ndmvr-raycaster/#clickeventhandleevent","title":"clickEventHandle(event)","text":"<p>Handles click and double-click detection.</p> <p>Parameters: - event (<code>MouseEvent</code>): Browser click event</p> <p>Behavior: - Calculates normalized mouse coordinates - Detects double-clicks based on <code>dbClickTimeout</code> - Distinguishes shift-modified clicks - Sets <code>raycaster._triggerSource</code> to: - <code>\"mouseclick\"</code> - Single click - <code>\"shiftmouseclick\"</code> - Shift + click - <code>\"mousedbclick\"</code> - Double click - <code>\"shiftmousedbclick\"</code> - Shift + double click - Calls <code>handleRaycast()</code> with appropriate delay</p>"},{"location":"components/visualization/ndmvr-raycaster/#updateraycasterevent","title":"updateRaycaster(event)","text":"<p>Updates raycaster with current mouse position.</p> <p>Parameters: - event (<code>MouseEvent</code>): Browser mouse event</p> <p>Behavior: - Converts mouse coordinates to normalized device coordinates - Updates <code>raycaster.setFromCamera()</code> - Sets <code>_triggerSource</code> to <code>\"mousemove\"</code> - Performs intersection test with scene children</p>"},{"location":"components/visualization/ndmvr-raycaster/#handleraycast","title":"handleRaycast()","text":"<p>Processes raycaster intersections.</p> <p>Behavior: - Calls <code>raycaster.intersectObjects()</code> on scene children - Recursively checks all descendants - Returns array of intersections</p> <p>Note: Individual objects handle their own raycast logic through custom <code>raycast</code> methods.</p>"},{"location":"components/visualization/ndmvr-raycaster/#trigger-source-identification","title":"Trigger Source Identification","text":"<p>The raycaster's <code>_triggerSource</code> property identifies the event type:</p> Trigger Source Description <code>\"mousemove\"</code> Mouse cursor movement <code>\"mouseclick\"</code> Single left click <code>\"shiftmouseclick\"</code> Shift + left click <code>\"mousedbclick\"</code> Double left click <code>\"shiftmousedbclick\"</code> Shift + double click <p>Custom <code>raycast</code> methods on objects can check this property: </p><pre><code>mesh.raycast = function(raycaster, intersects) {\n // Call default raycast\n this.defaultRaycast(raycaster, intersects);\n\n // Handle based on trigger\n if (raycaster._triggerSource === 'mouseclick') {\n console.log('Object was clicked');\n }\n};\n</code></pre><p></p>"},{"location":"components/visualization/ndmvr-raycaster/#usage-example","title":"Usage Example","text":"<pre><code>import { NdmvrRaycaster } from './ndmvr-raycaster-class.js';\nimport * as THREE from 'three';\n\n// Create scene with camera\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\ncamera.pos.z = 5;\nscene.add(camera);\n\n// Create renderer\nconst renderer = new THREE.WebGLRenderer();\nrenderer.setSize(window.innerWidth, window.innerHeight);\ndocument.body.appendChild(renderer.domElement);\n\n// Create raycaster\nconst raycaster = new NdmvrRaycaster(scene, renderer.domElement);\n\n// Add interactive object with custom raycast handler\nconst geometry = new THREE.BoxGeometry();\nconst material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });\nconst cube = new THREE.Mesh(geometry, material);\n\n// Store original raycast\ncube.defaultRaycast = cube.raycast.bind(cube);\n\n// Custom raycast handler\ncube.raycast = function(raycaster, intersects) {\n this.defaultRaycast(raycaster, intersects);\n\n if (intersects.length > 0) {\n const trigger = raycaster._triggerSource;\n console.log(`Cube intersected by: ${trigger}`);\n\n if (trigger === 'mouseclick') {\n this.material.color.set(0xff0000);\n } else if (trigger === 'mousedbclick') {\n this.material.color.set(0x0000ff);\n }\n }\n};\n\nscene.add(cube);\n\n// Animation loop\nfunction animate() {\n requestAnimationFrame(animate);\n renderer.render(scene, camera);\n}\nanimate();\n</code></pre>"},{"location":"components/visualization/ndmvr-raycaster/#configuration-integration","title":"Configuration Integration","text":"<p>The raycaster subscribes to configuration updates for double-click timing:</p> <pre><code>import { configSubjectGet } from '../rxjs/ConfigSubject.js';\n\n// Update double-click timeout\nconfigSubjectGet().next({\n config: {\n environment: {\n dbClickTimeout: 250 // milliseconds\n }\n }\n});\n</code></pre>"},{"location":"components/visualization/ndmvr-raycaster/#performance-optimization","title":"Performance Optimization","text":""},{"location":"components/visualization/ndmvr-raycaster/#mousemove-throttling","title":"Mousemove Throttling","text":"<p>The <code>checkInterval</code> property (default 1ms) throttles mousemove events: </p><pre><code>const now = performance.now();\nif (now - this.lastCheck < this.checkInterval) return;\nthis.lastCheck = now;\n</code></pre><p></p> <p>This prevents excessive raycasting during fast mouse movement.</p>"},{"location":"components/visualization/ndmvr-raycaster/#double-click-detection","title":"Double-Click Detection","text":"<p>Single clicks are delayed by <code>dbClickTimeout</code> to allow double-click detection: - First click: Wait for timeout before triggering single click - Second click within timeout: Clear single click timer, trigger double click - Second click after timeout: Treated as new single click</p>"},{"location":"components/visualization/ndmvr-raycaster/#integration-with-histogram-classes","title":"Integration with Histogram Classes","text":"<p>Both <code>HistogramJsrootClass</code> and <code>THnPainter</code> override the default mesh <code>raycast</code> method to implement custom intersection handling:</p> <pre><code>// In HistogramJsrootClass\nconst mesh = this.getInstancedMesh();\nif (mesh) {\n this.defaultRaycastHandler = mesh.raycast.bind(mesh);\n mesh.raycast = this.raycastHandler.bind(this);\n}\n</code></pre> <p>The histogram's custom raycast handler: 1. Calls default THREE.js raycast 2. Filters intersections for instanced meshes 3. Calculates bin indices from instance IDs 4. Triggers registered event handlers based on <code>_triggerSource</code></p>"},{"location":"components/visualization/ndmvr-raycaster/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: <code>Raycaster</code>, <code>Vector2</code></li> <li>RxJS: Configuration subscription</li> <li>RxJS Subjects:</li> <li><code>configSubjectGet</code> from <code>../rxjs/ConfigSubject.js</code></li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#related-components","title":"Related Components","text":"<ul> <li>HistogramJsrootClass - Uses custom raycast handlers</li> <li>THnPainter - Uses custom raycast handlers with BVH optimization</li> <li>BinInfoVisualizer - Receives data from raycast events</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/thnpainter/","title":"THnPainter","text":""},{"location":"components/visualization/thnpainter/#thnpainter","title":"THnPainter","text":""},{"location":"components/visualization/thnpainter/#overview","title":"Overview","text":"<p>The <code>THnPainter</code> class is a high-performance THREE.js histogram renderer for nested ROOT histograms (THn). It uses instanced rendering with custom shaders, implements BVH (Bounding Volume Hierarchy) for optimized raycasting, and supports hierarchical navigation through nested histogram layers. The class extends <code>TPainter</code> and integrates with RxJS subjects for state management and bin information display.</p>"},{"location":"components/visualization/thnpainter/#constructor","title":"Constructor","text":"<pre><code>constructor(histo, id, opts)\n</code></pre>"},{"location":"components/visualization/thnpainter/#parameters","title":"Parameters","text":"<ul> <li>histo (<code>Object</code>): ROOT histogram object (TH1, TH2, TH3, or THn)</li> <li>id (<code>string</code>): Unique identifier for the histogram instance</li> <li>opts (<code>Object</code>): Configuration options (extends from <code>TPainter</code>)</li> </ul>"},{"location":"components/visualization/thnpainter/#initialization","title":"Initialization","text":"<ul> <li>Creates <code>HistogramPointerClass</code> for navigation</li> <li>Subscribes to <code>stateSubjectGet()</code> for state changes</li> <li>Initializes instanced buffer geometry</li> <li>Creates wireframe representation</li> <li>Renders initial layer (layer 0)</li> </ul>"},{"location":"components/visualization/thnpainter/#properties","title":"Properties","text":""},{"location":"components/visualization/thnpainter/#core-components","title":"Core Components","text":"<ul> <li>pointer (<code>HistogramPointerClass</code>): Manages navigation through nested histogram hierarchy</li> <li>wireframe (<code>HistogramWireframeClass</code>): Bin outlines visualization component</li> <li>mesh (<code>THREE.Mesh</code>): Main instanced mesh with custom shader material</li> <li>instGeom (<code>THREE.InstancedBufferGeometry</code>): Geometry with per-instance attributes</li> <li>material (<code>THREE.ShaderMaterial</code>): Custom shader for gradient coloring</li> </ul>"},{"location":"components/visualization/thnpainter/#rendering-data","title":"Rendering Data","text":"<ul> <li>matrixCache (<code>Array</code>): Cached position/scale data for all instances per layer</li> <li>BVHTree (<code>Array</code>): Bounding volume hierarchy for optimized raycasting</li> <li>maxInstancesPerLayer (<code>Array<number></code>): Maximum instances per hierarchy layer</li> <li>maxContentPerLayer (<code>Object</code>): Maximum bin content per layer and set</li> <li>minContentPerLayer (<code>Object</code>): Minimum bin content per layer and set</li> <li>totalInstances (<code>number</code>): Total number of instances across all layers</li> </ul>"},{"location":"components/visualization/thnpainter/#instance-attributes","title":"Instance Attributes","text":"<ul> <li>instancePositions (<code>Float32Array</code>): Per-instance position data (x, y, z)</li> <li>instanceScales (<code>Float32Array</code>): Per-instance scale data (x, y, z)</li> <li>instanceColors (<code>Float32Array</code>): Per-instance color indices for gradient</li> <li>colorArray (<code>Float32Array</code>): Color palette for gradient shader (32 colors \u00d7 6 channels)</li> </ul>"},{"location":"components/visualization/thnpainter/#state-management","title":"State Management","text":"<ul> <li>selectedSet (<code>Array<string></code>): Currently selected data sets</li> <li>selectedArray (<code>string</code>): Currently selected array ('content' or custom arrays)</li> <li>availableSets (<code>Array<string></code>): Available data sets in histogram</li> <li>renderHistory (<code>Array<Object></code>): History of render operations for state reconstruction</li> <li>stateSub (<code>RxJS.Subscription</code>): State change subscription</li> </ul>"},{"location":"components/visualization/thnpainter/#interaction","title":"Interaction","text":"<ul> <li>dirtyInstance (<code>Array</code>): Indices of currently highlighted instances</li> </ul>"},{"location":"components/visualization/thnpainter/#methods","title":"Methods","text":""},{"location":"components/visualization/thnpainter/#updatehistogramhisto","title":"updateHistogram(histo)","text":"<p>Updates the histogram with new data.</p> <p>Parameters: - histo (<code>Object</code>): Object containing new ROOT histogram in <code>obj</code> property</p> <p>Behavior: - Preserves raycast handler - Clears caches and trees - Disposes old geometry and wireframe - Reinitializes with new data - Re-renders histogram</p>"},{"location":"components/visualization/thnpainter/#remove","title":"remove()","text":"<p>Cleans up all resources.</p> <p>Behavior: - Calls <code>super.remove()</code> - Clears matrix cache - Disposes geometry - Removes mesh from parent - Disposes wireframe - Unsubscribes from state subject</p>"},{"location":"components/visualization/thnpainter/#init","title":"init()","text":"<p>Initializes histogram rendering data structures.</p> <p>Behavior: - Sets available sets and arrays from histogram - Computes max/min content per layer - Calculates total instance count - Sets up instanced buffer geometry - Creates wireframe with configuration</p>"},{"location":"components/visualization/thnpainter/#setupmatrixcache","title":"setupMatrixCache()","text":"<p>Creates cache structure for instance transformations.</p> <p>Structure: - Array of objects per layer - Each layer contains <code>pos</code>, <code>scale</code>, and <code>rendered</code> arrays - For histograms with sets, last layer is array of objects per set - Pre-allocated Float32Arrays for performance</p>"},{"location":"components/visualization/thnpainter/#setupinsbufgeom","title":"setupInsBufGeom()","text":"<p>Creates instanced buffer geometry with custom attributes.</p> <p>Behavior: - Calls <code>setupMatrixCache()</code> - Creates <code>InstancedBufferGeometry</code> with base box - Sets up instance attributes: position, scale, colorIndex - Creates custom shader material - Fills color array from configuration - Creates mesh with custom raycast handler</p>"},{"location":"components/visualization/thnpainter/#renderhistogramstartindex-endindex-layer","title":"renderHistogram(startIndex, endIndex, layer)","text":"<p>Renders histogram bins for specified range and layer.</p> <p>Parameters: - startIndex (<code>number</code>): Linear index to start rendering from - endIndex (<code>number</code>): Linear index to end rendering at - layer (<code>number</code>): Hierarchy layer to render (0 = top layer)</p> <p>Behavior: - Recursively processes histogram hierarchy - Calculates bin positions and scales based on content - Applies padding and scale factors from configuration - Handles TH1, TH2, and TH3 differently - Updates matrix cache with rendered instances - Creates BVH tree for raycasting - Updates wireframe visualization</p> <p>Rendering Logic: - Content scaling: Maps bin content to scale factor (min/max) - Gradient coloring: Maps content to color gradient - Layer-specific padding and scale configuration - Set-based rendering for multi-dataset histograms</p>"},{"location":"components/visualization/thnpainter/#pushvisibleinstances","title":"pushVisibleInstances()","text":"<p>Transfers visible instances from cache to GPU.</p> <p>Behavior: - Counts visible instances (rendered !== -1) - Pre-allocates exact-size arrays - Copies position, scale, and color data - Creates new <code>InstancedBufferGeometry</code> - Updates mesh with new geometry - Disposes old geometry</p>"},{"location":"components/visualization/thnpainter/#setmatrixcacheatlayer-setindex-index-binsizepos-rendered","title":"setMatrixCacheAt(layer, setIndex, index, binSizePos, rendered)","text":"<p>Updates matrix cache at specific position.</p> <p>Parameters: - layer (<code>number</code>): Layer index - setIndex (<code>number | null</code>): Set index or null for content - index (<code>number</code>): Instance index within layer - binSizePos (<code>Object</code>): Position and size for x, y, z axes - rendered (<code>number</code>): Color index (-1 for hidden)</p>"},{"location":"components/visualization/thnpainter/#creatematerial","title":"createMaterial()","text":"<p>Creates custom shader material with gradient support.</p> <p>Returns: <code>THREE.ShaderMaterial</code></p> <p>Features: - Vertex shader applies per-instance transformations - Fragment shader renders per-instance colors - Supports 32-color gradient array - Smooth interpolation between gradient stops</p> <p>Shader Code: </p><pre><code>// Vertex Shader\nattribute vec3 instancePosition;\nattribute vec3 instanceScale;\nattribute float instanceColorIndex;\nuniform vec3 colorArray[32];\nvarying vec3 vColor;\n\nvoid main() {\n vec3 transformed = position * instanceScale + instancePosition;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n\n float colorIndex = clamp(instanceColorIndex, 0.0, 31.9999);\n int idx0 = int(floor(colorIndex));\n int idx1 = int(ceil(colorIndex));\n float t = fract(colorIndex);\n vColor = mix(colorArray[idx0], colorArray[idx1], t);\n}\n\n// Fragment Shader\nvarying vec3 vColor;\nvoid main() {\n gl_FragColor = vec4(vColor, 1.0);\n}\n</code></pre><p></p>"},{"location":"components/visualization/thnpainter/#event-handlers","title":"Event Handlers","text":""},{"location":"components/visualization/thnpainter/#mouseclickdefaultevent","title":"mouseClickDefault(event)","text":"<p>Shows child histogram and publishes to canvas subject.</p>"},{"location":"components/visualization/thnpainter/#mousemovedefaultevent","title":"mousemoveDefault(event)","text":"<p>Updates bin information display via <code>binInfoSubjectGet()</code>.</p> <p>Behavior: - Checks if index changed from previous event - Merges parent path with current event - Publishes minimized event with coords, content, error, set</p>"},{"location":"components/visualization/thnpainter/#shiftmouseclickdefaultevent","title":"shiftMouseClickDefault(event)","text":"<p>Hides child histogram.</p>"},{"location":"components/visualization/thnpainter/#mousedbclickdefaultevent","title":"mouseDBClickDefault(event)","text":"<p>Navigates to child histogram (drill-down).</p>"},{"location":"components/visualization/thnpainter/#shiftmousedbclickdefaultevent","title":"shiftMouseDBClickDefault(event)","text":"<p>Navigates to parent histogram (drill-up).</p>"},{"location":"components/visualization/thnpainter/#navigation-methods","title":"Navigation Methods","text":""},{"location":"components/visualization/thnpainter/#setpointertochildposition-set","title":"setPointerToChild(position, set)","text":"<p>Drills down into nested histogram.</p> <p>Parameters: - position (<code>Array<Object></code>): Array of positions with x, y, z - set (<code>string</code>): Set name if applicable</p> <p>Behavior: - Clears matrix cache and BVH tree - Disposes old geometry and wireframe - Updates pointer to child - Reinitializes and renders new view</p>"},{"location":"components/visualization/thnpainter/#setpointertoparent","title":"setPointerToParent()","text":"<p>Navigates back to parent histogram.</p> <p>Behavior: - Clears matrix cache and BVH tree - Disposes old geometry and wireframe - Updates pointer to parent - Reinitializes and renders new view</p>"},{"location":"components/visualization/thnpainter/#showchildhistogramposition","title":"showChildHistogram(position)","text":"<p>Renders child histogram without navigation.</p> <p>Parameters: - position (<code>Array<Object></code>): Bin positions</p> <p>Behavior: - Calculates index and range - Calls <code>renderHistogram()</code> for next layer - Adds detailed histogram view while keeping parent</p>"},{"location":"components/visualization/thnpainter/#hidechildhistogramposition","title":"hideChildHistogram(position)","text":"<p>Hides child histogram and shows parent bin.</p> <p>Parameters: - position (<code>Array<Object></code>): Bin positions</p> <p>Behavior: - Clears matrix cache range for child - Re-renders parent layer - Restores bin representation</p>"},{"location":"components/visualization/thnpainter/#raycasting","title":"Raycasting","text":""},{"location":"components/visualization/thnpainter/#raycasthandlerraycaster","title":"raycastHandler(raycaster)","text":"<p>Custom raycast implementation using BVH tree.</p> <p>Parameters: - raycaster (<code>THREE.Raycaster</code>): Raycaster with <code>_triggerSource</code> property</p> <p>Behavior: - Uses <code>checkIntersectionBVH()</code> for optimized intersection - Calls <code>intersectionHandler()</code> with results - Handles trigger source identification</p>"},{"location":"components/visualization/thnpainter/#checkintersectionbvhray","title":"checkIntersectionBVH(ray)","text":"<p>Performs BVH-accelerated ray intersection.</p> <p>Parameters: - ray (<code>THREE.Ray</code>): Ray to test intersections</p> <p>Returns: <code>Array<Object></code> - Sorted intersections with metadata</p> <p>Algorithm: 1. Recursively traverses BVH tree 2. Tests ray against bounding boxes 3. Uses DFS for leaf node discovery 4. Sorts results by distance 5. Returns intersection with full context: - <code>index</code>: Multi-dimensional bin position - <code>instanceId</code>: Linear instance index - <code>jsrootInstance</code>: JSROOT bin indices - <code>range</code>: Axis ranges - <code>content</code>: Bin content value - <code>error</code>: Bin error - <code>set</code>: Dataset name (if applicable)</p>"},{"location":"components/visualization/thnpainter/#state-management_1","title":"State Management","text":""},{"location":"components/visualization/thnpainter/#handlestatechangestate","title":"handleStateChange(state)","text":"<p>Responds to state changes from <code>stateSubjectGet()</code>.</p> <p>Parameters: - state (<code>Object</code>): New state with sets, selectedSet, arrays, selectedArray</p> <p>Behavior: - Checks if sets/arrays changed - Updates selected set and array - Re-renders histogram if needed - Replays render history for consistency</p>"},{"location":"components/visualization/thnpainter/#setavailablesetsorigin","title":"setAvailableSets(origin)","text":"<p>Recursively finds available sets in histogram.</p> <p>Parameters: - origin (<code>Object</code>): ROOT histogram object</p> <p>Behavior: - Traverses histogram hierarchy - Identifies data sets in <code>children</code> property - Updates <code>stateSubjectGet()</code> with available sets</p>"},{"location":"components/visualization/thnpainter/#setavailablearraysorigin","title":"setAvailableArrays(origin)","text":"<p>Identifies custom arrays in histogram.</p> <p>Parameters: - origin (<code>Object</code>): ROOT histogram object</p> <p>Behavior: - Extracts keys from <code>fArrays</code> property - Always includes 'content' as first option - Updates <code>stateSubjectGet()</code> with available arrays</p>"},{"location":"components/visualization/thnpainter/#utility-methods","title":"Utility Methods","text":""},{"location":"components/visualization/thnpainter/#clearmatrixcacherangestartindex-multiplier-dimensions-baselayerindex","title":"clearMatrixCacheRange(startIndex, multiplier, dimensions, baseLayerIndex)","text":"<p>Clears matrix cache for a range of bins.</p> <p>Parameters: - startIndex (<code>number</code>): Starting linear index - multiplier (<code>number</code>): Range size - dimensions (<code>Array<number></code>): Instances per layer - baseLayerIndex (<code>number</code>): Starting layer</p>"},{"location":"components/visualization/thnpainter/#getbincontentobj-posx-posy-posz-selectedarray","title":"getBinContent(obj, posX, posY, posZ, selectedArray)","text":"<p>Gets bin content from histogram or custom array.</p> <p>Parameters: - obj (<code>Object</code>): ROOT histogram object - posX, posY, posZ (<code>number</code>): Bin positions (0-indexed) - selectedArray (<code>string</code>): Array name</p> <p>Returns: <code>number</code> - Bin content value</p>"},{"location":"components/visualization/thnpainter/#logrenderobj","title":"logRender(obj)","text":"<p>Logs render operations to history.</p> <p>Parameters: - obj (<code>Object</code>): Render operation details</p> <p>Purpose: Enables state reconstruction when selected set changes</p>"},{"location":"components/visualization/thnpainter/#keydownhandlerevent","title":"keyDownHandler(event)","text":"<p>Handles keyboard shortcuts.</p> <p>Supported Keys: - <code>Digit1-9</code> or <code>Numpad1-9</code>: Render specific layer - Configured <code>hideOutlines</code>: Toggle wireframe visibility - Configured <code>resetHistogram</code>: Reset to original view - Configured <code>goToPreviousLayer</code>: Navigate to parent</p>"},{"location":"components/visualization/thnpainter/#resethistogram","title":"resetHistogram()","text":"<p>Resets histogram to initial state.</p> <p>Behavior: - Calls <code>updateHistogram()</code> with original root object - Clears navigation history - Returns to top-level view</p>"},{"location":"components/visualization/thnpainter/#usage-example","title":"Usage Example","text":"<pre><code>import { THnPainter } from './THnPainter.js';\nimport * as THREE from 'three';\n\n// Create scene\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\n\n// Create histogram painter\nconst painter = new THnPainter(rootHistogramObject, 'histogram1', {\n padding: {\n default: { x: 0.1, y: 0.1, z: 0.1 },\n layer: [{ x: 0.05, y: 0.05, z: 0.05 }]\n },\n sc: {\n default: { min: 0.1, max: 1.0 }\n },\n color: {\n gradient: ['#0000ff', '#00ffff', '#00ff00', '#ffff00', '#ff0000']\n },\n wireframe: {\n visible: true,\n color: 0x888888\n }\n});\n\n// Add to scene\nscene.add(painter.mesh);\nscene.add(painter.wireframe.wireframe);\n\n// Custom event handler\npainter.addEvent('mouseclick', (intersection, painterInstance) => {\n console.log('Clicked bin:', intersection.index);\n console.log('Content:', intersection.content);\n console.log('Range:', intersection.range);\n});\n\n// Drill down to child\npainter.mouseDBClickDefault({\n index: [{ x: 5, y: 3, z: 2 }],\n set: 'dataSet1'\n});\n\n// Navigate back\npainter.shiftMouseDBClickDefault({});\n\n// Update with new data\npainter.updateHistogram({ obj: newRootHistogramObject });\n\n// Clean up\npainter.remove();\n</code></pre>"},{"location":"components/visualization/thnpainter/#state-integration","title":"State Integration","text":"<pre><code>import { stateSubjectGet } from '../rxjs/StateSubject.js';\n\n// Get current state\nconst currentState = stateSubjectGet().getValue();\nconsole.log('Available sets:', currentState.sets);\nconsole.log('Selected set:', currentState.selectedSet);\nconsole.log('Available arrays:', currentState.arrays);\n\n// Change selected set\nstateSubjectGet().next({\n ...currentState,\n selectedSet: ['dataSet2', 'dataSet3']\n});\n\n// Change selected array\nstateSubjectGet().next({\n ...currentState,\n selectedArray: 'customArray1'\n});\n</code></pre>"},{"location":"components/visualization/thnpainter/#performance-optimizations","title":"Performance Optimizations","text":""},{"location":"components/visualization/thnpainter/#instanced-rendering","title":"Instanced Rendering","text":"<ul> <li>Uses <code>InstancedBufferGeometry</code> for rendering thousands of bins</li> <li>Single draw call per histogram</li> <li>GPU-efficient attribute updates</li> </ul>"},{"location":"components/visualization/thnpainter/#bvh-tree","title":"BVH Tree","text":"<ul> <li>Hierarchical bounding volumes for raycasting</li> <li>O(log n) intersection tests instead of O(n)</li> <li>Recursive tree traversal with early exit</li> </ul>"},{"location":"components/visualization/thnpainter/#matrix-cache","title":"Matrix Cache","text":"<ul> <li>Pre-computed transformations stored in typed arrays</li> <li>Avoids recalculation during navigation</li> <li>Efficient memory layout for GPU transfer</li> </ul>"},{"location":"components/visualization/thnpainter/#render-history","title":"Render History","text":"<ul> <li>Records render operations for state replay</li> <li>Avoids full re-render on state changes</li> <li>Maintains visual consistency during navigation</li> </ul>"},{"location":"components/visualization/thnpainter/#configuration","title":"Configuration","text":"<p>The painter accepts extensive configuration through <code>opts</code>:</p> <pre><code>{\n padding: {\n default: { x: 0.1, y: 0.1, z: 0.1 },\n layer: [/* per-layer padding */],\n sets: { x: 0.05, y: 0, z: 0 } // padding between sets\n },\n scale: {\n default: { min: 0.1, max: 1.0 },\n layer: [/* per-layer scale */]\n },\n color: {\n gradient: ['#color1', '#color2', ...], // up to 32 colors\n mode: 'gradient' | 'solid'\n },\n sets: {\n scale: {\n maximum: 'absolute' | 'relative' // scale relative to layer or global\n }\n },\n wireframe: {\n visible: true,\n color: 0x888888,\n linewidth: 1\n },\n TH1ZScale: {\n default: 0.1,\n layer: [/* per-layer z-scale for TH1 */],\n set: 0.01 // z-scale for set rendering\n }\n}\n</code></pre>"},{"location":"components/visualization/thnpainter/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: Core rendering, instanced geometry, custom shaders</li> <li>JSROOT: Histogram data structures</li> <li>RxJS: State management, bin info publishing</li> <li>Internal Classes:</li> <li><code>TPainter</code> (parent class) from <code>./TPainter.js</code></li> <li><code>HistogramPointerClass</code> from <code>../core/histogram-pointer-class.js</code></li> <li><code>HistogramWireframeClass</code> from <code>./histogram-wireframe-class.js</code></li> <li>RxJS Subjects:</li> <li><code>stateSubjectGet</code> from <code>../rxjs/StateSubject.js</code></li> <li><code>canvasSubjectGet</code> from <code>../rxjs/CanvasSubject.js</code></li> <li><code>binInfoSubjectGet</code> from <code>../rxjs/BinInfoSubject.js</code></li> <li>Utilities: Extensive histogram utilities from <code>../utils/histogramUtils.js</code></li> </ul>"},{"location":"components/visualization/thnpainter/#related-components","title":"Related Components","text":"<ul> <li>HistogramJsrootClass - JSROOT-based alternative renderer</li> <li>BinInfoVisualizer - Receives bin information</li> <li>NdmvrRaycaster - Scene-level raycasting infrastructure</li> </ul>"},{"location":"components/visualization/thnpainter/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"}]}
|
|
1
|
+
{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"NDMVR","text":""},{"location":"#introduction","title":"Introduction","text":"<p><code>ndmvr-core</code> is the foundational library of the NDMVR project. It provides a collection of reusable Three.js-based components that form the basis for 3D object visualization and inter-component communication using RxJS.</p> <p>The main object of visualization is an N-Dimensional histogram. It is a multi-dimensional representation of data using the hypercube concept.</p> <p>The library is designed to be modular and extensible, allowing developers to customize and integrate specific features as needed.</p> <p>The library is framework-agnostic. Each component is implemented as a plain JavaScript class and can be used independently of NDMVR in any Three.js-based project or framework.</p>"},{"location":"#features","title":"Features","text":"<ul> <li>3D objects capable of representing multi-dimensional data</li> <li>Reactive communication between components powered by RxJS</li> <li>High performance through optimized rendering and raycasting techniques</li> <li>Seamless integration with any Three.js-based application</li> </ul>"},{"location":"#getting-started","title":"Getting Started","text":"<p>If you are new to the NDMSPC project, see the Getting Started guide for installation and basic usage instructions.</p> <p>This section of the documentation focuses specifically on the core library, its components, and the communication interfaces they provide.</p>"},{"location":"quickstart/","title":"Quickstart","text":""},{"location":"quickstart/#ways-to-get-started","title":"Ways to get started","text":"<p>It is possible to get started using multiple ways.</p>"},{"location":"quickstart/#gitlab-pages","title":"Gitlab pages","text":"<p>For the quickest preview, you can use our gitlab pages.</p>"},{"location":"quickstart/#copy-repository","title":"Copy repository","text":"<ul> <li>If you wish to play around with the demo code on our Gitlab pages, or change the components itself, you can clone the repository.</li> <li>After copying the repository, you can run <code>npm install</code> to install all dependencies.</li> <li>Then, you can run <code>npm run dev</code> to start the development server.</li> </ul>"},{"location":"quickstart/#npm-package","title":"NPM package","text":"<ul> <li>If you wish to use provided components in your own project, you can install the package using <code>npm install ndmvr-core</code>.</li> <li>Then, you can import and use the components in your project.</li> <li>Provided components are written in Three.js, so you can use them in any project that uses Three.js or any other Three.js-based framework.</li> <li>You can get inspired on how to use these components at our Gtilab demo which uses A-Frame, or you can check out our other open-source project NdmVr, which is built on top of React-Three-Fiber.</li> <li>Alternatively, you can head to our tutorial and see how to use the components in basic Three.js applications.</li> </ul>"},{"location":"components/communication/bin-info-subject/","title":"Bin info subject","text":""},{"location":"components/communication/bin-info-subject/#bin-info-subject","title":"Bin Info Subject","text":""},{"location":"components/communication/bin-info-subject/#overview","title":"Overview","text":"<p>The Bin Info Subject is a singleton RxJS-based communication channel for managing and broadcasting histogram bin information throughout the application. It uses a <code>ReplaySubject</code> to ensure that the latest bin information is always available to new subscribers.</p>"},{"location":"components/communication/bin-info-subject/#class-structure","title":"Class Structure","text":"<pre><code>class BinInfoSubject {\n #subject; // Private ReplaySubject(1)\n}\n</code></pre>"},{"location":"components/communication/bin-info-subject/#methods","title":"Methods","text":""},{"location":"components/communication/bin-info-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>BinInfoSubject</code> instance with a <code>ReplaySubject(1)</code>.</p> <p>Behavior: - Initializes a private <code>ReplaySubject</code> with buffer size of 1 - The buffer ensures the last emitted value is replayed to new subscribers</p>"},{"location":"components/communication/bin-info-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to bin info updates.</p> <p>Returns: <code>Observable</code> - Observable stream of bin information</p> <p>Usage: </p><pre><code>import { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nbinInfoSubjectGet().getObservable().subscribe(binInfo => {\n console.log('Bin info updated:', binInfo);\n});\n</code></pre><p></p>"},{"location":"components/communication/bin-info-subject/#getvalue","title":"getValue()","text":"<p>Retrieves the current value of the subject.</p> <p>Returns: Current bin information value</p> <p>Note: This method is defined but may not work as expected since <code>ReplaySubject</code> doesn't have a <code>getValue()</code> method by default. This might require using <code>BehaviorSubject</code> instead.</p>"},{"location":"components/communication/bin-info-subject/#nexte","title":"next(e)","text":"<p>Broadcasts new bin information to all subscribers.</p> <p>Parameters: - <code>e</code> - The bin information object to broadcast</p> <p>Usage: </p><pre><code>import { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nbinInfoSubjectGet().next({\n binIndex: 42,\n content: 123.45,\n pos: { x: 1, y: 2, z: 3 },\n // ... other bin properties\n});\n</code></pre><p></p>"},{"location":"components/communication/bin-info-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const binInfoSubjectGet = () => {\n if (!binInfoSubject) binInfoSubject = new BinInfoSubject();\n return binInfoSubject;\n};\n</code></pre> <p>Benefits: - Ensures only one instance exists throughout the application - Provides global access to the bin info communication channel - Maintains consistent state across all components</p>"},{"location":"components/communication/bin-info-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Bin Hover Information:</li> <li>Display bin details when user hovers over histogram bins</li> <li> <p>Show bin content, position, and statistics</p> </li> <li> <p>Bin Selection:</p> </li> <li>Communicate selected bin information to UI components</li> <li> <p>Update info panels with selected bin data</p> </li> <li> <p>Interactive Tooltips:</p> </li> <li>Power tooltip displays in VR environment</li> <li>Synchronize bin information across multiple views</li> </ol>"},{"location":"components/communication/bin-info-subject/#data-flow-example","title":"Data Flow Example","text":"<pre><code>// Component A: Raycaster detects bin hover\nimport { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nfunction onBinHover(bin) {\n binInfoSubjectGet().next({\n binId: bin.id,\n content: bin.value,\n pos: bin.pos,\n histogram: bin.histogramId\n });\n}\n\n// Component B: Info visualizer subscribes to updates\nimport { binInfoSubjectGet } from './rxjs/BinInfoSubject.js';\n\nbinInfoSubjectGet().getObservable().subscribe(binInfo => {\n updateBinInfoDisplay(binInfo);\n});\n</code></pre>"},{"location":"components/communication/bin-info-subject/#subject-type-replaysubject1","title":"Subject Type: ReplaySubject(1)","text":"<p>Characteristics: - Buffer Size: 1 (stores the last emitted value) - Replay Behavior: New subscribers immediately receive the last value - Use Case: Ensures components always have access to current bin information</p>"},{"location":"components/communication/bin-info-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> </ul>"},{"location":"components/communication/bin-info-subject/#related-components","title":"Related Components","text":"<ul> <li>Bin Info JSROOT</li> <li>NDMVR Raycaster</li> <li>Histogram Subject</li> </ul>"},{"location":"components/communication/bin-info-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use the singleton accessor: Use <code>binInfoSubjectGet()</code> instead of creating new instances</li> <li>Unsubscribe when done: Always unsubscribe from observables in component cleanup</li> <li>Type safety: Consider defining TypeScript interfaces for bin info objects</li> <li>Error handling: Wrap subscriptions with error handlers for robustness</li> </ol>"},{"location":"components/communication/bin-info-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/canvas-subject/","title":"Canvas subject","text":""},{"location":"components/communication/canvas-subject/#canvas-subject","title":"Canvas Subject","text":""},{"location":"components/communication/canvas-subject/#overview","title":"Overview","text":"<p>The Canvas Subject is a singleton RxJS-based communication channel for managing and broadcasting canvas-related events and data throughout the application. It uses a <code>ReplaySubject</code> to ensure that the latest canvas state is always available to new subscribers.</p>"},{"location":"components/communication/canvas-subject/#class-structure","title":"Class Structure","text":"<pre><code>class CanvasSubject {\n #subject; // Private ReplaySubject(1)\n}\n</code></pre>"},{"location":"components/communication/canvas-subject/#methods","title":"Methods","text":""},{"location":"components/communication/canvas-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>CanvasSubject</code> instance with a <code>ReplaySubject(1)</code>.</p> <p>Behavior: - Initializes a private <code>ReplaySubject</code> with buffer size of 1 - The buffer ensures the last emitted value is replayed to new subscribers</p>"},{"location":"components/communication/canvas-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to canvas updates.</p> <p>Returns: <code>Observable</code> - Observable stream of canvas events and data</p> <p>Usage: </p><pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().getObservable().subscribe(canvasData => {\n console.log('Canvas updated:', canvasData);\n});\n</code></pre><p></p>"},{"location":"components/communication/canvas-subject/#nexte","title":"next(e)","text":"<p>Broadcasts new canvas data to all subscribers.</p> <p>Parameters: - <code>e</code> - The canvas event or data object to broadcast</p> <p>Usage: </p><pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().next({\n canvasId: 'canvas1',\n action: 'update',\n data: { /* canvas state */ }\n});\n</code></pre><p></p>"},{"location":"components/communication/canvas-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const canvasSubjectGet = () => {\n if (!canvasSubject) canvasSubject = new CanvasSubject();\n return canvasSubject;\n};\n</code></pre> <p>Benefits: - Ensures only one instance exists throughout the application - Provides global access to the canvas communication channel - Maintains consistent state across all components</p>"},{"location":"components/communication/canvas-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Canvas State Management:</li> <li>Broadcast canvas creation, updates, and deletion</li> <li> <p>Synchronize canvas state across multiple components</p> </li> <li> <p>Canvas Interaction Events:</p> </li> <li>Communicate user interactions with canvas elements</li> <li> <p>Handle canvas selection and focus changes</p> </li> <li> <p>Canvas Configuration:</p> </li> <li>Update canvas properties (position, scale, rotation)</li> <li> <p>Apply global canvas settings</p> </li> <li> <p>Multi-Canvas Coordination:</p> </li> <li>Coordinate multiple canvases in the scene</li> <li>Manage canvas visibility and layering</li> </ol>"},{"location":"components/communication/canvas-subject/#data-flow-example","title":"Data Flow Example","text":"<pre><code>// Component A: Canvas component emits state\nimport { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\nfunction onCanvasCreated(canvas) {\n canvasSubjectGet().next({\n type: 'created',\n canvasId: canvas.id,\n pos: canvas.pos,\n dimensions: canvas.dimensions\n });\n}\n\n// Component B: UI subscribes to canvas updates\nimport { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().getObservable().subscribe(event => {\n if (event.type === 'created') {\n addCanvasToList(event.canvasId);\n }\n});\n</code></pre>"},{"location":"components/communication/canvas-subject/#subject-type-replaysubject1","title":"Subject Type: ReplaySubject(1)","text":"<p>Characteristics: - Buffer Size: 1 (stores the last emitted value) - Replay Behavior: New subscribers immediately receive the last value - Use Case: Ensures components always have access to current canvas state</p>"},{"location":"components/communication/canvas-subject/#event-types-suggested","title":"Event Types (Suggested)","text":"<p>While not enforced by the implementation, common event types might include:</p> <ul> <li><code>created</code> - Canvas was created</li> <li><code>updated</code> - Canvas properties changed</li> <li><code>removed</code> - Canvas was removed</li> <li><code>selected</code> - Canvas was selected by user</li> <li><code>deselected</code> - Canvas was deselected</li> </ul>"},{"location":"components/communication/canvas-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> </ul>"},{"location":"components/communication/canvas-subject/#related-components","title":"Related Components","text":"<ul> <li>Canvas Component</li> <li>Histogram JSROOT</li> <li>Config Subject</li> </ul>"},{"location":"components/communication/canvas-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use the singleton accessor: Use <code>canvasSubjectGet()</code> instead of creating new instances</li> <li>Unsubscribe when done: Always unsubscribe from observables in component cleanup</li> <li>Event structure: Maintain consistent event object structure across the application</li> <li>Type definitions: Consider defining TypeScript interfaces for canvas events</li> <li>Error handling: Wrap subscriptions with error handlers for robustness</li> </ol>"},{"location":"components/communication/canvas-subject/#example-complete-canvas-lifecycle","title":"Example: Complete Canvas Lifecycle","text":"<pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\n// Subscribe to all canvas events\nconst subscription = canvasSubjectGet().getObservable().subscribe({\n next: (event) => {\n switch(event.type) {\n case 'created':\n console.log('Canvas created:', event.canvasId);\n break;\n case 'updated':\n console.log('Canvas updated:', event.canvasId);\n break;\n case 'removed':\n console.log('Canvas removed:', event.canvasId);\n break;\n }\n },\n error: (err) => console.error('Canvas subject error:', err)\n});\n\n// Emit canvas events\ncanvasSubjectGet().next({ type: 'created', canvasId: 'canvas1' });\ncanvasSubjectGet().next({ type: 'updated', canvasId: 'canvas1', sc: 1.5 });\ncanvasSubjectGet().next({ type: 'removed', canvasId: 'canvas1' });\n\n// Cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/canvas-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/config-subject/","title":"Config subject","text":""},{"location":"components/communication/config-subject/#config-subject","title":"Config Subject","text":""},{"location":"components/communication/config-subject/#overview","title":"Overview","text":"<p>The Config Subject is a singleton RxJS-based communication channel for managing application-wide configuration. It uses a <code>BehaviorSubject</code> initialized with default configuration values and provides sophisticated config merging capabilities, particularly for histogram configurations.</p>"},{"location":"components/communication/config-subject/#class-structure","title":"Class Structure","text":"<pre><code>class ConfigSubject {\n #subject; // Private BehaviorSubject with default config\n}\n</code></pre>"},{"location":"components/communication/config-subject/#methods","title":"Methods","text":""},{"location":"components/communication/config-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>ConfigSubject</code> instance with parsed default configuration.</p> <p>Behavior: - Initializes a private <code>BehaviorSubject</code> with <code>parseConfig(defaultConfig, {})</code> - Loads and parses configuration from <code>config-default.json</code> - Ensures the subject always has a valid configuration value</p>"},{"location":"components/communication/config-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to config updates.</p> <p>Returns: <code>Observable</code> - Observable stream of configuration changes</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconfigSubjectGet().getObservable().subscribe(config => {\n console.log('Config updated:', config);\n});\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#getvalue","title":"getValue()","text":"<p>Retrieves the current configuration value synchronously.</p> <p>Returns: Current configuration object</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconst currentConfig = configSubjectGet().getValue();\nconsole.log('Current config:', currentConfig);\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#nexte","title":"next(e)","text":"<p>Updates the configuration by parsing and merging new values with existing config.</p> <p>Parameters: - <code>e</code> - New configuration object to merge</p> <p>Returns: Parsed and merged configuration object</p> <p>Behavior: - Parses the new configuration using <code>parseConfig()</code> - Merges with existing configuration - Broadcasts the updated configuration to all subscribers</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconst updatedConfig = configSubjectGet().next({\n histogram: {\n color: new Color(0xff0000),\n sc: 1.5\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#appendpadsids-disp_kind-settings","title":"appendPads(ids, disp_kind, settings)","text":"<p>Appends pad configurations to the current configuration.</p> <p>Parameters: - <code>ids</code> - Array of pad IDs - <code>disp_kind</code> - Display kind/type - <code>settings</code> - Pad-specific settings</p> <p>Behavior: - Uses <code>appendPads()</code> utility function to update configuration - Broadcasts the updated configuration to all subscribers</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\n\nconfigSubjectGet().appendPads(\n ['pad1', 'pad2'],\n 'histogram',\n { visible: true }\n);\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#mergehistogramconfigpartialconfig-defaultconfig","title":"mergeHistogramConfig(partialConfig, defaultConfig)","text":"<p>Deep merges histogram configuration objects with special handling for THREE.js objects.</p> <p>Parameters: - <code>partialConfig</code> - Partial configuration to merge - <code>defaultConfig</code> (optional) - Defaults to <code>this.#subject.value.config.histogram</code></p> <p>Returns: Merged configuration object</p> <p>Special Handling: - Arrays are replaced entirely (not merged) - THREE.js objects (<code>Color</code>, <code>Vector3</code>) are replaced entirely - Plain objects are deep merged recursively - <code>undefined</code> values preserve default values</p> <p>Usage: </p><pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\nimport { Color, Vector3 } from 'three';\n\nconst merged = configSubjectGet().mergeHistogramConfig({\n color: new Color(0xff0000),\n pos: new Vector3(0, 1, 0),\n options: {\n wireframe: true,\n opacity: 0.8\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/config-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const configSubjectGet = () => {\n if (!configSubject) configSubject = new ConfigSubject();\n return configSubject;\n};\n</code></pre>"},{"location":"components/communication/config-subject/#configuration-merging-logic","title":"Configuration Merging Logic","text":""},{"location":"components/communication/config-subject/#value-objects-non-mergeable","title":"Value Objects (Non-Mergeable)","text":"<p>The following types are treated as atomic values and replaced entirely: - Arrays - <code>THREE.Color</code> instances - <code>THREE.Vector3</code> instances - Objects with <code>isColor === true</code> - Objects with <code>isVector3 === true</code></p>"},{"location":"components/communication/config-subject/#plain-objects-mergeable","title":"Plain Objects (Mergeable)","text":"<p>Regular JavaScript objects are deep merged: - Existing properties are preserved unless overridden - New properties are added - Nested objects are merged recursively</p>"},{"location":"components/communication/config-subject/#example-merge-behavior","title":"Example Merge Behavior","text":"<pre><code>// Default config\n{\n color: new Color(0x0000ff),\n position: new Vector3(0, 0, 0),\n options: {\n wireframe: false,\n opacity: 1.0,\n sc: 1.0\n }\n}\n\n// Partial config\n{\n color: new Color(0xff0000), // Replaces entirely\n options: {\n wireframe: true, // Merged\n opacity: 0.5 // Merged, scale preserved\n }\n}\n\n// Result\n{\n color: new Color(0xff0000),\n position: new Vector3(0, 0, 0), // Preserved\n options: {\n wireframe: true,\n opacity: 0.5,\n sc: 1.0 // Preserved from default\n }\n}\n</code></pre>"},{"location":"components/communication/config-subject/#subject-type-behaviorsubject","title":"Subject Type: BehaviorSubject","text":"<p>Characteristics: - Initial Value: Parsed default configuration - Current Value Access: Provides synchronous <code>getValue()</code> method - Replay Behavior: New subscribers immediately receive current config - Use Case: Perfect for application-wide configuration management</p>"},{"location":"components/communication/config-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>BehaviorSubject</code>)</li> <li><code>parseConfig</code>, <code>appendPads</code> from <code>../utils/baseUtil.js</code></li> <li><code>Vector3</code>, <code>Color</code> from <code>three</code></li> <li><code>defaultConfig</code> from <code>../config-default.json</code></li> </ul>"},{"location":"components/communication/config-subject/#related-components","title":"Related Components","text":"<ul> <li>Histogram Subject</li> <li>State Subject</li> <li>Canvas Subject</li> </ul>"},{"location":"components/communication/config-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Use getValue() for synchronous access: When you need current config immediately</li> <li>Use getObservable() for reactive updates: When you want to react to config changes</li> <li>Always use the singleton accessor: Use <code>configSubjectGet()</code></li> <li>Partial updates: Only provide properties you want to change</li> <li>THREE.js objects: Remember that Color and Vector3 replace entirely, not merge</li> <li>Type safety: Consider TypeScript interfaces for configuration structure</li> </ol>"},{"location":"components/communication/config-subject/#example-complete-configuration-workflow","title":"Example: Complete Configuration Workflow","text":"<pre><code>import { configSubjectGet } from './rxjs/ConfigSubject.js';\nimport { Color, Vector3 } from 'three';\n\n// Get current config synchronously\nconst currentConfig = configSubjectGet().getValue();\nconsole.log('Current:', currentConfig);\n\n// Subscribe to config changes\nconst subscription = configSubjectGet().getObservable().subscribe(config => {\n console.log('Config updated:', config);\n applyConfigToScene(config);\n});\n\n// Update configuration\nconfigSubjectGet().next({\n histogram: {\n color: new Color(0xff0000),\n options: {\n wireframe: true\n }\n }\n});\n\n// Append pads\nconfigSubjectGet().appendPads(\n ['pad1', 'pad2'],\n 'histogram',\n { visible: true, opacity: 0.8 }\n);\n\n// Merge histogram-specific config\nconst mergedHistoConfig = configSubjectGet().mergeHistogramConfig({\n binColor: new Color(0x00ff00),\n axes: {\n showLabels: true\n }\n});\n\n// Cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/config-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/dispatch-subject/","title":"Dispatch subject","text":""},{"location":"components/communication/dispatch-subject/#dispatch-subject","title":"Dispatch Subject","text":""},{"location":"components/communication/dispatch-subject/#overview","title":"Overview","text":"<p>The Dispatch Subject is a singleton RxJS-based communication channel for dispatching general-purpose events throughout the application. It uses a standard <code>Subject</code> without buffering, making it ideal for fire-and-forget event notifications.</p>"},{"location":"components/communication/dispatch-subject/#class-structure","title":"Class Structure","text":"<pre><code>class DispatchSubject {\n #subject; // Private Subject\n}\n</code></pre>"},{"location":"components/communication/dispatch-subject/#methods","title":"Methods","text":""},{"location":"components/communication/dispatch-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>DispatchSubject</code> instance with a standard <code>Subject</code>.</p> <p>Behavior: - Initializes a private <code>Subject</code> with no initial value - No replay functionality - subscribers only receive events emitted after subscription</p>"},{"location":"components/communication/dispatch-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable from the subject for subscribing to dispatch events.</p> <p>Returns: <code>Observable</code> - Observable stream of dispatch events</p> <p>Usage: </p><pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\n\ndispatchSubjectGet().getObservable().subscribe(event => {\n console.log('Event dispatched:', event);\n});\n</code></pre><p></p>"},{"location":"components/communication/dispatch-subject/#nexte","title":"next(e)","text":"<p>Dispatches an event to all current subscribers.</p> <p>Parameters: - <code>e</code> - The event object to dispatch</p> <p>Usage: </p><pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\n\ndispatchSubjectGet().next({\n type: 'histogram-selected',\n histogramId: 'histo1',\n timestamp: Date.now()\n});\n</code></pre><p></p>"},{"location":"components/communication/dispatch-subject/#singleton-pattern","title":"Singleton Pattern","text":"<p>The module exports a singleton accessor function:</p> <pre><code>export const dispatchSubjectGet = () => {\n if (!dispatchSubject) dispatchSubject = new DispatchSubject();\n return dispatchSubject;\n};\n</code></pre> <p>Benefits: - Ensures only one instance exists throughout the application - Provides global access to the event dispatch system - Lightweight event bus for application-wide events</p>"},{"location":"components/communication/dispatch-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Global Event Bus:</li> <li>Dispatch application-level events</li> <li>Notify multiple components of state changes</li> <li> <p>Implement loosely coupled component communication</p> </li> <li> <p>Action Notifications:</p> </li> <li>User interaction events</li> <li>System state changes</li> <li> <p>Async operation completions</p> </li> <li> <p>Cross-Component Communication:</p> </li> <li>Components that don't have direct relationships</li> <li>Broadcasting events to multiple listeners</li> <li> <p>Decoupling event producers from consumers</p> </li> <li> <p>Command Pattern:</p> </li> <li>Dispatch commands to be executed by handlers</li> <li>Implement undo/redo functionality</li> <li>Queue and process actions</li> </ol>"},{"location":"components/communication/dispatch-subject/#subject-type-subject","title":"Subject Type: Subject","text":"<p>Characteristics: - No Initial Value: Unlike BehaviorSubject, has no initial state - No Replay: New subscribers don't receive past events - Hot Observable: Events are missed if not subscribed at emit time - Use Case: Perfect for fire-and-forget event notifications</p> <p>Important: Subscribers must be subscribed before events are emitted to receive them.</p>"},{"location":"components/communication/dispatch-subject/#example-event-structures","title":"Example Event Structures","text":"<p>While the implementation is flexible, consider standardizing event structure:</p> <pre><code>// Basic event\n{\n type: 'event-name',\n payload: { /* event data */ }\n}\n\n// Action event\n{\n type: 'action',\n action: 'update-histogram',\n target: 'histogram1',\n data: { /* action data */ }\n}\n\n// Lifecycle event\n{\n type: 'lifecycle',\n phase: 'mounted',\n component: 'histogram-visualizer'\n}\n</code></pre>"},{"location":"components/communication/dispatch-subject/#data-flow-example","title":"Data Flow Example","text":"<pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\n\n// Component A: Subscribe to events (must subscribe first!)\nconst subscription = dispatchSubjectGet().getObservable().subscribe({\n next: (event) => {\n console.log('Event received:', event);\n if (event.type === 'histogram-updated') {\n refreshHistogramDisplay(event.histogramId);\n }\n },\n error: (err) => console.error('Dispatch error:', err)\n});\n\n// Component B: Dispatch events\nfunction onHistogramUpdate(histogramId) {\n dispatchSubjectGet().next({\n type: 'histogram-updated',\n histogramId: histogramId,\n timestamp: Date.now()\n });\n}\n\n// Later: cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/dispatch-subject/#filtering-events","title":"Filtering Events","text":"<p>Use RxJS operators to filter specific event types:</p> <pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\nimport { filter } from 'rxjs/operators';\n\n// Only receive histogram events\nconst histogramEvents$ = dispatchSubjectGet()\n .getObservable()\n .pipe(\n filter(event => event.type?.startsWith('histogram-'))\n );\n\nhistogramEvents$.subscribe(event => {\n console.log('Histogram event:', event);\n});\n</code></pre>"},{"location":"components/communication/dispatch-subject/#comparison-with-other-subjects","title":"Comparison with Other Subjects","text":"Feature DispatchSubject BehaviorSubject ReplaySubject Initial Value \u274c None \u2705 Required \u274c None Replay \u274c No \u2705 Current only \u2705 Buffer size Use Case Events State Recent state"},{"location":"components/communication/dispatch-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>Subject</code>)</li> </ul>"},{"location":"components/communication/dispatch-subject/#related-components","title":"Related Components","text":"<ul> <li>Function Subject</li> <li>State Subject</li> <li>Config Subject</li> </ul>"},{"location":"components/communication/dispatch-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Subscribe early: Subscribe before events are emitted to avoid missing them</li> <li>Always use singleton accessor: Use <code>dispatchSubjectGet()</code></li> <li>Standardize event structure: Define consistent event shapes</li> <li>Use type field: Include a <code>type</code> field for event identification</li> <li>Unsubscribe: Always clean up subscriptions to prevent memory leaks</li> <li>Error handling: Include error handlers in subscriptions</li> <li>Consider alternatives: For state management, consider BehaviorSubject or StateSubject</li> </ol>"},{"location":"components/communication/dispatch-subject/#example-event-driven-architecture","title":"Example: Event-Driven Architecture","text":"<pre><code>import { dispatchSubjectGet } from './rxjs/DispatchSubject.js';\nimport { filter } from 'rxjs/operators';\n\n// Define event types\nconst EventTypes = {\n HISTOGRAM_SELECTED: 'histogram-selected',\n HISTOGRAM_UPDATED: 'histogram-updated',\n USER_INTERACTION: 'user-interaction',\n ERROR_OCCURRED: 'error-occurred'\n};\n\n// Event dispatcher utility\nclass EventDispatcher {\n static dispatch(type, payload) {\n dispatchSubjectGet().next({\n type,\n payload,\n timestamp: Date.now()\n });\n }\n}\n\n// Event listener utility\nclass EventListener {\n static listen(eventType, handler) {\n return dispatchSubjectGet()\n .getObservable()\n .pipe(filter(event => event.type === eventType))\n .subscribe(handler);\n }\n}\n\n// Usage\nconst subscription = EventListener.listen(\n EventTypes.HISTOGRAM_SELECTED,\n (event) => {\n console.log('Histogram selected:', event.payload);\n }\n);\n\n// Dispatch events\nEventDispatcher.dispatch(EventTypes.HISTOGRAM_SELECTED, {\n histogramId: 'histo1',\n source: 'user-click'\n});\n\n// Cleanup\nsubscription.unsubscribe();\n</code></pre>"},{"location":"components/communication/dispatch-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/function-subject/","title":"Function subject","text":""},{"location":"components/communication/function-subject/#function-subject","title":"Function Subject","text":""},{"location":"components/communication/function-subject/#overview","title":"Overview","text":"<p>The Function Subject is a singleton RxJS-based communication channel for dynamically adding and removing event handlers to histogram entities. It uses a <code>ReplaySubject</code> to manage function registration, ensuring that all function registrations are replayed to new subscribers.</p>"},{"location":"components/communication/function-subject/#class-structure","title":"Class Structure","text":"<pre><code>class FunctionSubject {\n #subject; // Private ReplaySubject\n}\n</code></pre>"},{"location":"components/communication/function-subject/#methods","title":"Methods","text":""},{"location":"components/communication/function-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>FunctionSubject</code> instance with an unbounded <code>ReplaySubject</code>.</p> <p>Behavior: - Initializes a private <code>ReplaySubject</code> with no size limit - All function add/remove operations are replayed to new subscribers - Comment indicates: \"only new functions are promoted to updated subscribers and all functions are promoted to new subscriber\"</p>"},{"location":"components/communication/function-subject/#addfunctionsinput","title":"addFunctions(input)","text":"<p>Registers one or more event handler functions to be attached to target entities.</p> <p>Parameters: - <code>input</code> - Single function object or array of function objects</p> <p>Function Object Structure: </p><pre><code>{\n target: {\n entity: 'entity-type', // e.g., 'histogram', 'canvas'\n id: 'entity-id' | ['id1', 'id2'] // Single ID or array of IDs\n },\n event: 'event-name', // e.g., 'click', 'hover', 'instance-hover'\n function: handlerFunction // The actual function to execute\n}\n</code></pre><p></p> <p>Behavior: - Accepts single object or array of objects - Normalizes <code>target.id</code> to always be an array - Emits event with <code>flag: \"add\"</code> for each function - Broadcasts to all subscribers</p> <p>Usage: </p><pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\n// Add single function\nfunctionSubjectGet().addFunctions({\n target: {\n entity: 'histogram',\n id: 'histogram1'\n },\n event: 'click',\n function: (event) => {\n console.log('Histogram clicked:', event);\n }\n});\n\n// Add multiple functions\nfunctionSubjectGet().addFunctions([\n {\n target: { entity: 'histogram', id: ['histo1', 'histo2'] },\n event: 'hover',\n function: onHover\n },\n {\n target: { entity: 'canvas', id: 'canvas1' },\n event: 'click',\n function: onCanvasClick\n }\n]);\n</code></pre><p></p>"},{"location":"components/communication/function-subject/#removefunctionsinput","title":"removeFunctions(input)","text":"<p>Unregisters one or more event handler functions from target entities.</p> <p>Parameters: - <code>input</code> - Single function object or array of function objects</p> <p>Behavior: - Accepts single object or array of objects - Normalizes <code>target.id</code> to always be an array - Emits with <code>flag: \"remove\"</code> if event is specified - Emits with <code>flag: \"removeAll\"</code> if no event specified (removes all handlers) - Broadcasts to all subscribers</p> <p>Remove Modes: 1. Specific Event: <code>flag: \"remove\"</code> - Removes specific event handler 2. All Events: <code>flag: \"removeAll\"</code> - Removes all handlers from target</p> <p>Usage: </p><pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\n// Remove specific event handler\nfunctionSubjectGet().removeFunctions({\n target: {\n entity: 'histogram',\n id: 'histogram1'\n },\n event: 'click',\n function: clickHandler\n});\n\n// Remove all handlers from entity (no event specified)\nfunctionSubjectGet().removeFunctions({\n target: {\n entity: 'histogram',\n id: 'histogram1'\n }\n});\n\n// Remove from multiple entities\nfunctionSubjectGet().removeFunctions({\n target: {\n entity: 'histogram',\n id: ['histo1', 'histo2']\n },\n event: 'hover'\n});\n</code></pre><p></p>"},{"location":"components/communication/function-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable for subscribing to function registration events.</p> <p>Returns: <code>Observable</code> - Stream of function add/remove operations</p> <p>Event Structure: </p><pre><code>{\n flag: 'add' | 'remove' | 'removeAll',\n target: {\n entity: string,\n id: string[] // Always an array\n },\n event: string, // Event name (may be undefined for removeAll)\n function: Function // Handler function\n}\n</code></pre><p></p> <p>Usage: </p><pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\nfunctionSubjectGet().getObservable().subscribe(operation => {\n const { flag, target, event, function: handler } = operation;\n\n target.id.forEach(id => {\n if (flag === 'add') {\n attachHandler(id, event, handler);\n } else if (flag === 'remove') {\n detachHandler(id, event, handler);\n } else if (flag === 'removeAll') {\n removeAllHandlers(id);\n }\n });\n});\n</code></pre><p></p>"},{"location":"components/communication/function-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const functionSubjectGet = () => {\n if (!functionSubject) functionSubject = new FunctionSubject();\n return functionSubject;\n};\n</code></pre>"},{"location":"components/communication/function-subject/#subject-type-replaysubject-unbounded","title":"Subject Type: ReplaySubject (Unbounded)","text":"<p>Characteristics: - Buffer Size: Unlimited (all operations are stored) - Replay Behavior: All function registrations are replayed to new subscribers - Use Case: Ensures late-subscribing components receive all function registrations - Memory Consideration: May grow unbounded - consider cleanup strategies</p>"},{"location":"components/communication/function-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Dynamic Event Handlers:</li> <li>Add click handlers to histograms at runtime</li> <li>Attach hover effects dynamically</li> <li> <p>Register custom interaction handlers</p> </li> <li> <p>Plugin System:</p> </li> <li>Allow plugins to register event handlers</li> <li>Extend histogram functionality without modifying core code</li> <li> <p>Enable/disable features by adding/removing handlers</p> </li> <li> <p>Interactive Tooltips:</p> </li> <li>Register hover handlers for info display</li> <li> <p>Remove handlers when tooltip is disabled</p> </li> <li> <p>Multi-Target Registration:</p> </li> <li>Register same handler to multiple histograms</li> <li>Bulk add/remove operations</li> </ol>"},{"location":"components/communication/function-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { functionSubjectGet } from './rxjs/FunctionSubject.js';\n\n// Component subscribes to function operations\nclass HistogramManager {\n constructor() {\n this.handlers = new Map();\n\n this.subscription = functionSubjectGet()\n .getObservable()\n .subscribe(this.handleFunctionOperation.bind(this));\n }\n\n handleFunctionOperation(operation) {\n const { flag, target, event, function: handler } = operation;\n\n target.id.forEach(id => {\n const histogram = this.getHistogram(id);\n if (!histogram) return;\n\n switch(flag) {\n case 'add':\n histogram.addEventListener(event, handler);\n this.trackHandler(id, event, handler);\n break;\n\n case 'remove':\n histogram.removeEventListener(event, handler);\n this.untrackHandler(id, event, handler);\n break;\n\n case 'removeAll':\n this.removeAllHandlers(id);\n break;\n }\n });\n }\n\n trackHandler(id, event, handler) {\n const key = `${id}:${event}`;\n if (!this.handlers.has(key)) {\n this.handlers.set(key, []);\n }\n this.handlers.get(key).push(handler);\n }\n\n cleanup() {\n this.subscription.unsubscribe();\n }\n}\n\n// Usage: Add handlers\nfunctionSubjectGet().addFunctions({\n target: {\n entity: 'histogram',\n id: ['histo1', 'histo2']\n },\n event: 'instance-hover',\n function: (event) => {\n console.log('Bin hovered:', event.detail);\n }\n});\n\n// Usage: Remove specific handler\nconst hoverHandler = (e) => console.log(e);\n\nfunctionSubjectGet().addFunctions({\n target: { entity: 'histogram', id: 'histo1' },\n event: 'hover',\n function: hoverHandler\n});\n\n// Later: remove it\nfunctionSubjectGet().removeFunctions({\n target: { entity: 'histogram', id: 'histo1' },\n event: 'hover',\n function: hoverHandler\n});\n\n// Or remove all handlers\nfunctionSubjectGet().removeFunctions({\n target: { entity: 'histogram', id: 'histo1' }\n});\n</code></pre>"},{"location":"components/communication/function-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> </ul>"},{"location":"components/communication/function-subject/#related-components","title":"Related Components","text":"<ul> <li>Dispatch Subject</li> <li>Histogram JSROOT</li> <li>NDMVR Raycaster</li> </ul>"},{"location":"components/communication/function-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use singleton accessor: Use <code>functionSubjectGet()</code></li> <li>Track handler references: Store handler references if you need to remove them later</li> <li>Bulk operations: Use array input for multiple registrations</li> <li>Multi-target support: Use array of IDs to target multiple entities</li> <li>Cleanup: Always unsubscribe from the observable</li> <li>Memory management: Consider implementing cleanup for old registrations</li> <li>Type safety: Define TypeScript interfaces for function objects</li> </ol>"},{"location":"components/communication/function-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/histogram-subject/","title":"Histogram subject","text":""},{"location":"components/communication/histogram-subject/#histogram-subject","title":"Histogram Subject","text":""},{"location":"components/communication/histogram-subject/#overview","title":"Overview","text":"<p>The Histogram Subject is a sophisticated singleton RxJS-based communication channel for managing histogram data streams. Unlike other subjects, it maintains separate ReplaySubject streams for each histogram ID, allowing components to subscribe to specific histograms. It also handles automatic parsing of histogram data from various sources (files, URLs, JSON objects).</p>"},{"location":"components/communication/histogram-subject/#class-structure","title":"Class Structure","text":"<pre><code>class HistogramSubject {\n #subjects = new Map(); // id \u2192 ReplaySubject(1)\n}\n</code></pre>"},{"location":"components/communication/histogram-subject/#architecture","title":"Architecture","text":"<p>Key Innovation: Instead of one global subject, this maintains a Map of subjects: - Key: Histogram ID (string) - Value: <code>ReplaySubject(1)</code> for that specific histogram</p> <p>This allows: - Per-histogram subscriptions - Independent histogram state management - Efficient updates to specific histograms</p>"},{"location":"components/communication/histogram-subject/#methods","title":"Methods","text":""},{"location":"components/communication/histogram-subject/#getstreamid","title":"getStream(id)","text":"<p>Gets or creates an Observable stream for a specific histogram ID.</p> <p>Parameters: - <code>id</code> (string) - The unique histogram identifier</p> <p>Returns: <code>Observable</code> - Stream of histogram updates for this specific ID</p> <p>Behavior: - Creates a new <code>ReplaySubject(1)</code> if ID doesn't exist - Returns existing subject's observable if ID exists - Each histogram has its own independent stream</p> <p>Usage: </p><pre><code>import { histogramSubjectGet } from './rxjs/HistogramSubject.js';\n\n// Subscribe to specific histogram\nhistogramSubjectGet().getStream('histogram1').subscribe(histo => {\n console.log('Histogram 1 updated:', histo);\n});\n\n// Different histogram, different stream\nhistogramSubjectGet().getStream('histogram2').subscribe(histo => {\n console.log('Histogram 2 updated:', histo);\n});\n</code></pre><p></p>"},{"location":"components/communication/histogram-subject/#async-nexte","title":"async next(e)","text":"<p>Processes and broadcasts histogram data to the appropriate stream.</p> <p>Parameters: - <code>e</code> (object) - Histogram event object</p> <p>Event Object Structure: </p><pre><code>{\n id: 'histogram-id', // Required\n obj: string | object, // Histogram data (URL, file path, or JSON object)\n opts: { // Optional\n render: 'jsroot' | 'custom',\n config: { /* histogram config */ }\n }\n}\n</code></pre><p></p> <p>Behavior: 1. Validation: Throws error if <code>id</code> is missing 2. Data Preprocessing: - If <code>obj</code> is string: Parses as file using <code>FileHandler.parseFile()</code> - If <code>obj</code> is object: Parses as JSON using <code>JsonHandler.parseJson()</code> - Otherwise: Throws \"Unsupported data type\" error 3. Config Parsing: Parses <code>opts.config</code> if present 4. Stream Creation: Creates subject for ID if not exists 5. Broadcasting: Emits processed data to ID-specific stream</p> <p>Usage: </p><pre><code>import { histogramSubjectGet } from './rxjs/HistogramSubject.js';\n\n// Load from file\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: '/path/to/histogram.root'\n});\n\n// Load from URL\nawait histogramSubjectGet().next({\n id: 'histogram2',\n obj: 'https://example.com/data.root'\n});\n\n// Load from JSON object\nawait histogramSubjectGet().next({\n id: 'histogram3',\n obj: {\n fName: 'MyHistogram',\n fXaxis: { /* ... */ },\n fYaxis: { /* ... */ }\n },\n opts: {\n render: 'jsroot',\n config: {\n color: 0xff0000\n }\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/histogram-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const histogramSubjectGet = () => {\n if (!histogramSubject) histogramSubject = new HistogramSubject();\n return histogramSubject;\n};\n</code></pre>"},{"location":"components/communication/histogram-subject/#data-flow","title":"Data Flow","text":"<pre><code>1. External code calls histogramSubjectGet().next({...})\n2. HistogramSubject validates and preprocesses data\n3. Data is parsed (file/URL/JSON)\n4. Config is parsed if present\n5. Subject for specific ID is obtained/created\n6. Processed data is emitted to ID-specific stream\n7. Subscribed components receive update\n</code></pre>"},{"location":"components/communication/histogram-subject/#supported-data-sources","title":"Supported Data Sources","text":""},{"location":"components/communication/histogram-subject/#1-file-path-string","title":"1. File Path (String)","text":"<pre><code>await histogramSubjectGet().next({\n id: 'histo1',\n obj: './data/histogram.root'\n});\n</code></pre> Processing: <code>FileHandler.parseFile()</code>"},{"location":"components/communication/histogram-subject/#2-url-string","title":"2. URL (String)","text":"<pre><code>await histogramSubjectGet().next({\n id: 'histo1',\n obj: 'https://root.cern/files/histogram.root'\n});\n</code></pre> Processing: <code>FileHandler.parseFile()</code> (handles URLs)"},{"location":"components/communication/histogram-subject/#3-json-object","title":"3. JSON Object","text":"<pre><code>await histogramSubjectGet().next({\n id: 'histo1',\n obj: {\n _typename: 'TH3F',\n fXaxis: { fNbins: 10, /* ... */ },\n // ... ROOT object properties\n }\n});\n</code></pre> Processing: <code>JsonHandler.parseJson()</code>"},{"location":"components/communication/histogram-subject/#stream-isolation","title":"Stream Isolation","text":"<p>Each histogram has its own independent stream:</p> <pre><code>// Component A subscribes to histogram1\nhistogramSubjectGet().getStream('histogram1').subscribe(h => {\n console.log('H1:', h); // Only receives histogram1 updates\n});\n\n// Component B subscribes to histogram2\nhistogramSubjectGet().getStream('histogram2').subscribe(h => {\n console.log('H2:', h); // Only receives histogram2 updates\n});\n\n// Updates are isolated\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: data1 // Only Component A receives this\n});\n\nawait histogramSubjectGet().next({\n id: 'histogram2',\n obj: data2 // Only Component B receives this\n});\n</code></pre>"},{"location":"components/communication/histogram-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { histogramSubjectGet } from './rxjs/HistogramSubject.js';\n\n// Histogram component implementation\nclass HistogramComponent {\n constructor(elementId) {\n this.id = elementId;\n this.subscription = null;\n }\n\n init() {\n // Subscribe to this specific histogram's stream\n this.subscription = histogramSubjectGet()\n .getStream(this.id)\n .subscribe({\n next: (histo) => {\n console.log(`Histogram ${this.id} received data:`, histo);\n this.render(histo.obj, histo.opts);\n },\n error: (err) => {\n console.error(`Histogram ${this.id} error:`, err);\n }\n });\n }\n\n render(histogramObj, options) {\n // Render based on options\n if (options?.render === 'jsroot') {\n this.renderJSROOT(histogramObj);\n } else {\n this.renderCustom(histogramObj, options);\n }\n }\n\n cleanup() {\n if (this.subscription) {\n this.subscription.unsubscribe();\n }\n }\n}\n\n// Usage: Create multiple histogram components\nconst histo1 = new HistogramComponent('histogram1');\nconst histo2 = new HistogramComponent('histogram2');\n\nhisto1.init();\nhisto2.init();\n\n// Load data for each histogram\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: '/data/experiment1.root',\n opts: { render: 'jsroot' }\n});\n\nawait histogramSubjectGet().next({\n id: 'histogram2',\n obj: {\n _typename: 'TH3F',\n fXaxis: { /* ... */ }\n },\n opts: {\n render: 'custom',\n config: { color: 0x00ff00 }\n }\n});\n\n// Update specific histogram\nawait histogramSubjectGet().next({\n id: 'histogram1',\n obj: '/data/experiment1_updated.root'\n});\n\n// Cleanup\nhisto1.cleanup();\nhisto2.cleanup();\n</code></pre>"},{"location":"components/communication/histogram-subject/#error-handling","title":"Error Handling","text":"<pre><code>try {\n await histogramSubjectGet().next({\n id: 'histogram1',\n obj: invalidData\n });\n} catch (error) {\n if (error.message === 'Missing id in event') {\n console.error('Histogram ID is required');\n } else if (error.message === 'Unsupported data type') {\n console.error('obj must be string or object');\n } else {\n console.error('Failed to process histogram:', error);\n }\n}\n</code></pre>"},{"location":"components/communication/histogram-subject/#subject-type-map-of-replaysubject1","title":"Subject Type: Map of ReplaySubject(1)","text":"<p>Characteristics: - Per-ID Subjects: Each histogram ID gets its own <code>ReplaySubject(1)</code> - Buffer Size: 1 per histogram (stores last emitted value) - Replay Behavior: New subscribers immediately receive last histogram data - Isolation: Updates to one histogram don't affect others</p>"},{"location":"components/communication/histogram-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>ReplaySubject</code>)</li> <li><code>FileHandler</code> from <code>../service/FileHandler.js</code></li> <li><code>JsonHandler</code> from <code>../service/JsonHandler.js</code></li> <li><code>parseConfig</code> from <code>../utils/baseUtil.js</code></li> </ul>"},{"location":"components/communication/histogram-subject/#related-components","title":"Related Components","text":"<ul> <li>Histogram JSROOT</li> <li>THnPainter</li> <li>Config Subject</li> </ul>"},{"location":"components/communication/histogram-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always provide ID: The <code>id</code> field is required</li> <li>Use async/await: <code>next()</code> is asynchronous due to file parsing</li> <li>Error handling: Wrap <code>next()</code> calls in try-catch</li> <li>Subscribe per ID: Use <code>getStream(id)</code> for specific histograms</li> <li>Cleanup subscriptions: Always unsubscribe when done</li> <li>Stream isolation: Take advantage of per-histogram streams</li> <li>Config merging: Use <code>opts.config</code> for histogram-specific settings</li> </ol>"},{"location":"components/communication/histogram-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/input-device-subject/","title":"Input device subject","text":""},{"location":"components/communication/input-device-subject/#input-device-subject","title":"Input Device Subject","text":""},{"location":"components/communication/input-device-subject/#overview","title":"Overview","text":"<p>The Input Device Subject is a singleton RxJS-based communication channel for managing the current input device state in the VR application. It uses a <code>BehaviorSubject</code> to track which input device (keyboard, mobile, or VR headset) is currently active, allowing components to adapt their behavior accordingly.</p>"},{"location":"components/communication/input-device-subject/#class-structure","title":"Class Structure","text":"<pre><code>class InputDeviceSubject {\n #subject; // Private BehaviorSubject\n}\n</code></pre>"},{"location":"components/communication/input-device-subject/#methods","title":"Methods","text":""},{"location":"components/communication/input-device-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>InputDeviceSubject</code> instance with default keyboard input.</p> <p>Behavior: - Initializes a private <code>BehaviorSubject</code> with initial state: </p><pre><code>{\n inputDevice: \"keyboard\"\n}\n</code></pre> - Ensures the subject always has a valid input device state<p></p>"},{"location":"components/communication/input-device-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable for subscribing to input device changes.</p> <p>Returns: <code>Observable</code> - Stream of input device state changes</p> <p>Usage: </p><pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\ninputDeviceSubjectGet().getObservable().subscribe(state => {\n console.log('Input device changed to:', state.inputDevice);\n\n // Adapt UI based on input device\n if (state.inputDevice === 'mobile') {\n showTouchControls();\n } else if (state.inputDevice === 'oculus') {\n enableVRControllers();\n } else {\n enableKeyboardControls();\n }\n});\n</code></pre><p></p>"},{"location":"components/communication/input-device-subject/#nexte","title":"next(e)","text":"<p>Updates the input device state and broadcasts to all subscribers.</p> <p>Parameters: - <code>e</code> (object) - New state object</p> <p>State Object Structure: </p><pre><code>{\n inputDevice: 'keyboard' | 'mobile' | 'oculus'\n}\n</code></pre><p></p> <p>Behavior: - Retrieves current state - Updates <code>inputDevice</code> if provided in the new state - Broadcasts the updated state to all subscribers</p> <p>Usage: </p><pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\n// Switch to mobile input\ninputDeviceSubjectGet().next({\n inputDevice: 'mobile'\n});\n\n// Switch to VR headset\ninputDeviceSubjectGet().next({\n inputDevice: 'oculus'\n});\n\n// Switch to keyboard\ninputDeviceSubjectGet().next({\n inputDevice: 'keyboard'\n});\n</code></pre><p></p>"},{"location":"components/communication/input-device-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const inputDeviceSubjectGet = () => {\n if (!inputDeviceSubject) inputDeviceSubject = new InputDeviceSubject();\n return inputDeviceSubject;\n};\n</code></pre>"},{"location":"components/communication/input-device-subject/#input-device-types","title":"Input Device Types","text":""},{"location":"components/communication/input-device-subject/#1-keyboard-default","title":"1. Keyboard (Default)","text":"<p>Value: <code>\"keyboard\"</code></p> <p>Characteristics: - Desktop/laptop input - Keyboard and mouse controls - Default state on application start</p> <p>Typical Controls: - WASD for movement - Mouse for look around - Click for selection</p>"},{"location":"components/communication/input-device-subject/#2-mobile","title":"2. Mobile","text":"<p>Value: <code>\"mobile\"</code></p> <p>Characteristics: - Touch screen input - Mobile device sensors - Virtual joystick controls</p> <p>Typical Controls: - Touch and drag - Virtual joystick for movement - Tap for selection - Device orientation</p>"},{"location":"components/communication/input-device-subject/#3-oculus-vr-headset","title":"3. Oculus (VR Headset)","text":"<p>Value: <code>\"oculus\"</code></p> <p>Characteristics: - VR headset and controllers - 6DOF (six degrees of freedom) - Hand tracking or controller input</p> <p>Typical Controls: - VR controller buttons - Controller position tracking - Teleportation movement - Direct hand interaction</p>"},{"location":"components/communication/input-device-subject/#integration-with-device-detection","title":"Integration with Device Detection","text":"<p>The input device state is typically set by the <code>device-detector</code> component:</p> <pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\n// Device detection logic\nif (AFRAME.utils.device.isMobile()) {\n inputDeviceSubjectGet().next({ inputDevice: 'mobile' });\n} else if (AFRAME.utils.device.checkHeadsetConnected()) {\n inputDeviceSubjectGet().next({ inputDevice: 'oculus' });\n} else {\n inputDeviceSubjectGet().next({ inputDevice: 'keyboard' });\n}\n</code></pre>"},{"location":"components/communication/input-device-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Adaptive Controls:</li> <li>Show/hide control schemes based on device</li> <li> <p>Enable/disable input handlers</p> </li> <li> <p>UI Adaptation:</p> </li> <li>Show touch controls on mobile</li> <li>Display VR-specific UI in headset</li> <li> <p>Show keyboard hints on desktop</p> </li> <li> <p>Performance Optimization:</p> </li> <li>Adjust rendering quality per device</li> <li> <p>Enable/disable features based on capability</p> </li> <li> <p>Interaction Methods:</p> </li> <li>Switch between raycasting methods</li> <li>Adapt selection mechanisms</li> <li>Change movement systems</li> </ol>"},{"location":"components/communication/input-device-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\nclass InputManager {\n constructor() {\n this.currentDevice = 'keyboard';\n\n // Subscribe to device changes\n this.subscription = inputDeviceSubjectGet()\n .getObservable()\n .subscribe(this.onDeviceChange.bind(this));\n }\n\n onDeviceChange(state) {\n console.log('Device changed:', state.inputDevice);\n\n // Disable previous input handlers\n this.disableInputHandlers(this.currentDevice);\n\n // Enable new input handlers\n this.enableInputHandlers(state.inputDevice);\n\n // Update current device\n this.currentDevice = state.inputDevice;\n\n // Update UI\n this.updateUI(state.inputDevice);\n }\n\n enableInputHandlers(device) {\n switch(device) {\n case 'keyboard':\n this.enableKeyboardControls();\n this.enableMouseControls();\n break;\n\n case 'mobile':\n this.enableTouchControls();\n this.enableVirtualJoystick();\n break;\n\n case 'oculus':\n this.enableVRControllers();\n this.enableTeleportation();\n break;\n }\n }\n\n disableInputHandlers(device) {\n // Clean up previous handlers\n this.removeKeyboardControls();\n this.removeMouseControls();\n this.removeTouchControls();\n this.removeVirtualJoystick();\n this.removeVRControllers();\n this.removeTeleportation();\n }\n\n updateUI(device) {\n const keyboardUI = document.getElementById('keyboard-controls');\n const mobileUI = document.getElementById('mobile-controls');\n const vrUI = document.getElementById('vr-controls');\n\n keyboardUI.style.display = device === 'keyboard' ? 'block' : 'none';\n mobileUI.style.display = device === 'mobile' ? 'block' : 'none';\n vrUI.style.display = device === 'oculus' ? 'block' : 'none';\n }\n\n cleanup() {\n this.subscription.unsubscribe();\n }\n}\n\n// Usage\nconst inputManager = new InputManager();\n\n// Simulate device change (normally done by device-detector)\ninputDeviceSubjectGet().next({ inputDevice: 'mobile' });\n</code></pre>"},{"location":"components/communication/input-device-subject/#event-driven-device-switching","title":"Event-Driven Device Switching","text":"<pre><code>import { inputDeviceSubjectGet } from './rxjs/InputDeviceSubject.js';\n\n// Listen for VR mode changes\nscene.addEventListener('enter-vr', () => {\n if (AFRAME.utils.device.checkHeadsetConnected()) {\n inputDeviceSubjectGet().next({ inputDevice: 'oculus' });\n }\n});\n\nscene.addEventListener('exit-vr', () => {\n if (AFRAME.utils.device.isMobile()) {\n inputDeviceSubjectGet().next({ inputDevice: 'mobile' });\n } else {\n inputDeviceSubjectGet().next({ inputDevice: 'keyboard' });\n }\n});\n</code></pre>"},{"location":"components/communication/input-device-subject/#subject-type-behaviorsubject","title":"Subject Type: BehaviorSubject","text":"<p>Characteristics: - Initial Value: <code>{ inputDevice: \"keyboard\" }</code> - Current Value: Always accessible via subscription - Replay Behavior: New subscribers immediately receive current device - Use Case: Perfect for device state that needs to be known immediately</p>"},{"location":"components/communication/input-device-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>BehaviorSubject</code>)</li> </ul>"},{"location":"components/communication/input-device-subject/#related-components","title":"Related Components","text":"<ul> <li>Camera Component</li> <li>Device Detector Component</li> <li>Screen Controls Component</li> <li>State Subject</li> </ul>"},{"location":"components/communication/input-device-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use singleton accessor: Use <code>inputDeviceSubjectGet()</code></li> <li>Subscribe early: Subscribe during component initialization</li> <li>Clean up handlers: Remove old input handlers before adding new ones</li> <li>Unsubscribe: Always clean up subscriptions</li> <li>Device detection: Let <code>device-detector</code> component manage device changes</li> <li>Fallback: Always have keyboard as fallback device</li> <li>Type safety: Consider using TypeScript enums for device types</li> <li>Testing: Test all three device modes thoroughly</li> </ol>"},{"location":"components/communication/input-device-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/communication/state-subject/","title":"State subject","text":""},{"location":"components/communication/state-subject/#state-subject","title":"State Subject","text":""},{"location":"components/communication/state-subject/#overview","title":"Overview","text":"<p>The State Subject is a singleton RxJS-based communication channel for managing application-wide state. It uses a <code>BehaviorSubject</code> to maintain state related to sets, arrays, and their selections, providing a centralized state management solution for the VR histogram application.</p>"},{"location":"components/communication/state-subject/#class-structure","title":"Class Structure","text":"<pre><code>class StateSubject {\n #subject; // Private BehaviorSubject\n}\n</code></pre>"},{"location":"components/communication/state-subject/#initial-state","title":"Initial State","text":"<pre><code>{\n sets: [], // Array of available sets\n selectedSet: [], // Currently selected set(s)\n arrays: [\"content\"], // Array of available array types\n selectedArray: \"content\" // Currently selected array type\n}\n</code></pre>"},{"location":"components/communication/state-subject/#methods","title":"Methods","text":""},{"location":"components/communication/state-subject/#constructor","title":"constructor()","text":"<p>Creates a new <code>StateSubject</code> instance with default application state.</p> <p>Behavior: - Initializes a private <code>BehaviorSubject</code> with default state structure - Provides initial values for sets, arrays, and selections</p>"},{"location":"components/communication/state-subject/#getobservable","title":"getObservable()","text":"<p>Returns an Observable for subscribing to state changes.</p> <p>Returns: <code>Observable</code> - Stream of state updates</p> <p>Usage: </p><pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\nstateSubjectGet().getObservable().subscribe(state => {\n console.log('State updated:', state);\n console.log('Selected set:', state.selectedSet);\n console.log('Selected array:', state.selectedArray);\n});\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#getvalue","title":"getValue()","text":"<p>Retrieves the current state value synchronously.</p> <p>Returns: Current state object</p> <p>Usage: </p><pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\nconst currentState = stateSubjectGet().getValue();\nconsole.log('Current sets:', currentState.sets);\nconsole.log('Selected array:', currentState.selectedArray);\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#nexte","title":"next(e)","text":"<p>Updates the application state and broadcasts to all subscribers.</p> <p>Parameters: - <code>e</code> (object) - New state object (partial or complete)</p> <p>Behavior: - Replaces entire state with new state object - All subscribers receive the updated state - Partial updates require spreading existing state</p> <p>Usage: </p><pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\n// Complete state update\nstateSubjectGet().next({\n sets: ['set1', 'set2', 'set3'],\n selectedSet: ['set1'],\n arrays: ['content', 'errors', 'bins'],\n selectedArray: 'content'\n});\n\n// Partial state update (preserve other fields)\nconst current = stateSubjectGet().getValue();\nstateSubjectGet().next({\n ...current,\n selectedSet: ['set2']\n});\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#singleton-pattern","title":"Singleton Pattern","text":"<pre><code>export const stateSubjectGet = () => {\n if (!inputDeviceSubject) inputDeviceSubject = new StateSubject();\n return inputDeviceSubject;\n};\n</code></pre> <p>Note: There's a naming inconsistency in the implementation - the variable is named <code>inputDeviceSubject</code> but should be <code>stateSubject</code>. This is likely a copy-paste error but doesn't affect functionality.</p>"},{"location":"components/communication/state-subject/#state-properties","title":"State Properties","text":""},{"location":"components/communication/state-subject/#sets","title":"sets","text":"<p>Type: <code>Array</code> Default: <code>[]</code> Description: List of available histogram sets or data sets</p> <p>Example: </p><pre><code>sets: ['dataset1', 'dataset2', 'experiment_a', 'experiment_b']\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#selectedset","title":"selectedSet","text":"<p>Type: <code>Array</code> Default: <code>[]</code> Description: Currently selected set(s) - supports multi-selection</p> <p>Example: </p><pre><code>selectedSet: ['dataset1', 'dataset2']\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#arrays","title":"arrays","text":"<p>Type: <code>Array<string></code> Default: <code>[\"content\"]</code> Description: Available array types for histogram data visualization</p> <p>Common Values: - <code>\"content\"</code> - Bin content values - <code>\"errors\"</code> - Error values - <code>\"bins\"</code> - Bin indices - <code>\"entries\"</code> - Entry counts</p> <p>Example: </p><pre><code>arrays: ['content', 'errors', 'bins']\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#selectedarray","title":"selectedArray","text":"<p>Type: <code>string</code> Default: <code>\"content\"</code> Description: Currently selected array type for display</p> <p>Example: </p><pre><code>selectedArray: 'errors'\n</code></pre><p></p>"},{"location":"components/communication/state-subject/#use-cases","title":"Use Cases","text":"<ol> <li>Dataset Management:</li> <li>Track available datasets</li> <li>Manage dataset selection</li> <li> <p>Switch between different experiments</p> </li> <li> <p>Visualization Mode:</p> </li> <li>Select which histogram data to display</li> <li>Switch between content, errors, or bins view</li> <li> <p>Update visualization based on selected array type</p> </li> <li> <p>Multi-Selection:</p> </li> <li>Compare multiple datasets</li> <li> <p>Display multiple sets simultaneously</p> </li> <li> <p>UI State Synchronization:</p> </li> <li>Keep UI controls in sync with application state</li> <li>Update dropdown menus and selection lists</li> </ol>"},{"location":"components/communication/state-subject/#complete-example","title":"Complete Example","text":"<pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\nclass StateManager {\n constructor() {\n this.subscription = stateSubjectGet()\n .getObservable()\n .subscribe(this.onStateChange.bind(this));\n }\n\n onStateChange(state) {\n console.log('State changed:', state);\n\n // Update UI to reflect current state\n this.updateSetsList(state.sets);\n this.updateSelectedSets(state.selectedSet);\n this.updateArrayOptions(state.arrays);\n this.updateSelectedArray(state.selectedArray);\n\n // Update visualizations\n this.refreshHistograms(state.selectedSet, state.selectedArray);\n }\n\n // Load available datasets\n loadDatasets(datasets) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n sets: datasets\n });\n }\n\n // Select a dataset\n selectSet(setId) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n selectedSet: [setId]\n });\n }\n\n // Select multiple datasets\n selectMultipleSets(setIds) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n selectedSet: setIds\n });\n }\n\n // Change visualization array type\n selectArrayType(arrayType) {\n const current = stateSubjectGet().getValue();\n stateSubjectGet().next({\n ...current,\n selectedArray: arrayType\n });\n }\n\n // Add array type option\n addArrayType(arrayType) {\n const current = stateSubjectGet().getValue();\n if (!current.arrays.includes(arrayType)) {\n stateSubjectGet().next({\n ...current,\n arrays: [...current.arrays, arrayType]\n });\n }\n }\n\n cleanup() {\n this.subscription.unsubscribe();\n }\n}\n\n// Usage\nconst stateManager = new StateManager();\n\n// Load datasets\nstateManager.loadDatasets([\n 'experiment_2024_01',\n 'experiment_2024_02',\n 'calibration_data'\n]);\n\n// Select a dataset\nstateManager.selectSet('experiment_2024_01');\n\n// Switch to errors view\nstateManager.selectArrayType('errors');\n\n// Select multiple datasets for comparison\nstateManager.selectMultipleSets([\n 'experiment_2024_01',\n 'experiment_2024_02'\n]);\n\n// Add custom array type\nstateManager.addArrayType('normalized');\n</code></pre>"},{"location":"components/communication/state-subject/#reactive-ui-binding","title":"Reactive UI Binding","text":"<pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\nimport { map, distinctUntilChanged } from 'rxjs/operators';\n\n// Subscribe to specific state properties\nconst selectedSet$ = stateSubjectGet()\n .getObservable()\n .pipe(\n map(state => state.selectedSet),\n distinctUntilChanged()\n );\n\nselectedSet$.subscribe(selectedSet => {\n console.log('Selected set changed:', selectedSet);\n updateHistogramDisplay(selectedSet);\n});\n\nconst selectedArray$ = stateSubjectGet()\n .getObservable()\n .pipe(\n map(state => state.selectedArray),\n distinctUntilChanged()\n );\n\nselectedArray$.subscribe(arrayType => {\n console.log('Array type changed:', arrayType);\n updateVisualizationMode(arrayType);\n});\n</code></pre>"},{"location":"components/communication/state-subject/#state-helpers","title":"State Helpers","text":"<pre><code>import { stateSubjectGet } from './rxjs/StateSubject.js';\n\n// Helper functions for common operations\nconst StateHelpers = {\n // Get current state\n getCurrentState() {\n return stateSubjectGet().getValue();\n },\n\n // Update partial state\n updateState(updates) {\n const current = this.getCurrentState();\n stateSubjectGet().next({ ...current, ...updates });\n },\n\n // Check if set is selected\n isSetSelected(setId) {\n const state = this.getCurrentState();\n return state.selectedSet.includes(setId);\n },\n\n // Toggle set selection\n toggleSet(setId) {\n const state = this.getCurrentState();\n const selectedSet = state.selectedSet.includes(setId)\n ? state.selectedSet.filter(id => id !== setId)\n : [...state.selectedSet, setId];\n\n this.updateState({ selectedSet });\n },\n\n // Clear selection\n clearSelection() {\n this.updateState({ selectedSet: [] });\n }\n};\n\n// Usage\nStateHelpers.updateState({ selectedArray: 'bins' });\nStateHelpers.toggleSet('experiment_1');\nconsole.log('Is selected?', StateHelpers.isSetSelected('experiment_1'));\n</code></pre>"},{"location":"components/communication/state-subject/#subject-type-behaviorsubject","title":"Subject Type: BehaviorSubject","text":"<p>Characteristics: - Initial Value: State with empty sets and default array selection - Current Value: Always accessible via <code>getValue()</code> - Replay Behavior: New subscribers immediately receive current state - Use Case: Perfect for application-wide state management</p>"},{"location":"components/communication/state-subject/#dependencies","title":"Dependencies","text":"<ul> <li>RxJS library (<code>BehaviorSubject</code>)</li> </ul>"},{"location":"components/communication/state-subject/#related-components","title":"Related Components","text":"<ul> <li>Config Subject</li> <li>Input Device Subject</li> <li>Histogram Subject</li> </ul>"},{"location":"components/communication/state-subject/#best-practices","title":"Best Practices","text":"<ol> <li>Always use singleton accessor: Use <code>stateSubjectGet()</code></li> <li>Partial updates: Spread existing state when updating (<code>{ ...current, ...updates }</code>)</li> <li>Synchronous access: Use <code>getValue()</code> when you need immediate state</li> <li>Reactive updates: Use <code>getObservable()</code> for reactive state changes</li> <li>Unsubscribe: Always clean up subscriptions</li> <li>Immutability: Don't mutate state directly, always create new objects</li> <li>Type safety: Consider TypeScript interfaces for state structure</li> <li>State normalization: Keep state structure flat and normalized</li> </ol>"},{"location":"components/communication/state-subject/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/configuration/configuration/","title":"Configuration","text":""},{"location":"components/configuration/configuration/#ndmvr-configuration-reference","title":"NDMVR Configuration Reference","text":""},{"location":"components/configuration/configuration/#overview","title":"Overview","text":"<p>The NDMVR configuration file controls the behavior, appearance, and interaction of histogram visualizations in a 3D environment. Configuration is provided as a JSON object with several main sections.</p> <ul> <li>If one wants to change only the specific part of the configuration, it is possible to send partial config and the rest will be merged from current config.</li> <li>Note If partial config is sent, it needs to be wrapped in its hierarchy ensuring the correct attribute is set.</li> </ul> <ul> <li> <p>\u274c Wrong approach</p> <pre><code>configSubjectGet().next({\n dbClickTimeout: 250\n});\n</code></pre> </li> <li> <p>\u2705 Correct approach</p> <pre><code>configSubjectGet().next({\n config: {\n environment: {\n dbClickTimeout: 250\n }\n }\n});\n</code></pre> </li> </ul>"},{"location":"components/configuration/configuration/#environment-configuration","title":"Environment Configuration","text":""},{"location":"components/configuration/configuration/#environmentdbclicktimeout","title":"<code>environment.dbClickTimeout</code>","text":"<p>Type: <code>number</code> (milliseconds) Default: <code>190</code></p> <p>Timeout to register a double-click event, measured from the first click to the second click.</p> <p>Important: Longer values increase the time it takes to register single clicks, as the system must wait to determine if a second click is incoming. </p><pre><code>\"dbClickTimeout\": 190\n</code></pre><p></p>"},{"location":"components/configuration/configuration/#environmentcamera","title":"<code>environment.camera</code>","text":"<p>Camera position in 3D space.</p>"},{"location":"components/configuration/configuration/#cameraposition","title":"<code>camera.position</code>","text":"<p>Type: <code>object</code> </p><pre><code>\"camera\": {\n \"position\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n }\n}\n</code></pre><p></p> Property Type Description <code>x</code> number X-axis position <code>y</code> number Y-axis position <code>z</code> number Z-axis position"},{"location":"components/configuration/configuration/#environmentcanvaspads","title":"<code>environment.canvasPads</code>","text":"<p>Settings for the canvas pads in the scene. Each canvas pad defines a display surface placed in 3D space.</p> <p>Type: <code>array</code></p> <pre><code>\"canvasPads\": [\n {\n \"id\": \"pad1-cinema\",\n \"limits\": {\n \"position\": {\n \"x\": -15,\n \"y\": 20,\n \"z\": -30\n },\n \"rotation\": {\n \"x\": 10,\n \"y\": 0,\n \"z\": 0\n },\n \"scale\": {\n \"x\": 40,\n \"y\": 25,\n \"z\": 0\n }\n }\n }\n]\n</code></pre>"},{"location":"components/configuration/configuration/#environmenthistogrampads","title":"<code>environment.histogramPads</code>","text":"<p>Defines the layout and positioning of histogram pads. Can be configured in two modes: Grid Mode or Manual Mode.</p>"},{"location":"components/configuration/configuration/#grid-mode","title":"Grid Mode","text":"<p>Automatically arranges histograms in a 3D grid pattern.</p> <p>Type: <code>object</code></p> <pre><code>\"histogramPads\": {\n \"type\": \"grid1x1x1\",\n \"prefix\": \"histogram\",\n \"scale\": {\n \"x\": 5,\n \"y\": 3,\n \"z\": 5\n },\n \"padding\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n },\n \"origin\": {\n \"x\": -2.5,\n \"y\": 0,\n \"z\": 2.5\n }\n}\n</code></pre> Property Type Description <code>type</code> string Grid format as <code>gridAxBxC</code> where A = number of histograms on X-axis, B = Y-axis, C = Z-axis <code>prefix</code> string Prefix for histogram IDs (e.g., <code>\"histogram\"</code> \u2192 <code>histogram1</code>, <code>histogram2</code>, ...) <code>scale</code> object Combined scale of all histograms in the grid <code>padding</code> object Space between each histogram pad <code>origin</code> object Center position of the first (left-bottom-front) histogram"},{"location":"components/configuration/configuration/#manual-mode","title":"Manual Mode","text":"<p>Manually define each histogram pad.</p> <p>Type: <code>array</code> of objects </p><pre><code>\"histogramPads\": [\n {\n \"id\": \"histogram1\",\n \"position\": {\n \"x\": 0,\n \"y\": 1.5,\n \"z\": 0\n },\n \"scale\": {\n \"x\": 5,\n \"y\": 3,\n \"z\": 5\n }\n }\n]\n</code></pre><p></p> Property Type Description <code>id</code> string Unique identifier for the histogram pad <code>position</code> object Position in 3D space <code>scale</code> object Scale of the histogram pad"},{"location":"components/configuration/configuration/#histogram-configuration","title":"Histogram Configuration","text":""},{"location":"components/configuration/configuration/#histogrampadding","title":"<code>histogram.padding</code>","text":"<p>Defines padding between bins in histograms. Padding is a normalized value (0-1) where: - <code>0</code> = no padding - <code>0.5</code> = half the space is padding</p> <p>Note: Padding reduces bin size; it does not push bins further apart.</p> <p>Type: <code>object</code> </p><pre><code>\"padding\": {\n \"default\": {\n \"x\": 0.1,\n \"y\": 0.1,\n \"z\": 0.1\n },\n \"layer\": [],\n \"sets\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n }\n}\n</code></pre><p></p> Property Type Description <code>default</code> object Default padding for all histograms <code>layer</code> array Per-layer padding overrides <code>sets</code> object Padding for histogram sets"},{"location":"components/configuration/configuration/#histogramscale","title":"<code>histogram.scale</code>","text":"<p>Defines minimum and maximum scale of bins. Scale is normalized (0-1): - <code>0</code> = bin takes no space - <code>0.5</code> = bin takes half the available space - <code>1</code> = bin takes full space</p> <p>A bin with the minimum number of entries will be scaled to <code>min</code>, while the maximum bin scales to <code>max</code>.</p> <p>Type: <code>object</code> </p><pre><code>\"scale\": {\n \"default\": {\n \"min\": 0.3,\n \"max\": 1\n },\n \"layer\": []\n}\n</code></pre><p></p> Property Type Description <code>default</code> object Default min/max scale <code>layer</code> array Per-layer scale overrides"},{"location":"components/configuration/configuration/#histogramscalescaleby","title":"<code>histogram.scale.scaleBy</code>","text":"<p>Type: <code>string</code> Options: <code>\"value\"</code> or <code>\"error\"</code></p> <p>Determines what bins are scaled by:</p> <ul> <li><code>value</code>: Uses <code>fArray</code> values</li> <li><code>error</code>: Uses <code>fSumw2</code> errors (or square root of <code>fArray</code> if <code>fSumw2</code> is empty)</li> </ul>"},{"location":"components/configuration/configuration/#histogramscaleobject","title":"<code>histogram.scale.object</code>","text":"<p>Type: <code>string</code> Options: <code>\"relative\"</code>, <code>\"global\"</code>, or <code>\"fixed\"</code></p> <p>Defines how maximum scale for either content parameter, or set is determined across histograms:</p> Mode Description <code>relative</code> Each histogram's maximum is based on its own data <code>global</code> All histograms share the same maximum (the global maximum across all histograms in that layer) <code>fixed</code> Starts as <code>global</code>, but allows user-defined min/max via stateSubject <pre><code>\"content\": \"global\",\n\"parameter\": \"fixed\",\n\"sets\": \"relative\",\n</code></pre>"},{"location":"components/configuration/configuration/#histogramth1zscale","title":"<code>histogram.TH1ZScale</code>","text":"<p>Z-axis scale for TH1 histograms. Normalized value (0-1).</p> <p>Type: <code>object</code> </p><pre><code>\"TH1ZScale\": {\n \"default\": 0.8,\n \"layer\": [0.2, 1, 1, 1],\n \"set\": 0.01\n}\n</code></pre><p></p> Property Type Description <code>default</code> number Default Z-scale <code>layer</code> array Per-layer Z-scale overrides <code>set</code> number Z-scale for sets"},{"location":"components/configuration/configuration/#histogramwireframe","title":"<code>histogram.wireframe</code>","text":"<p>Controls display and appearance of bin outlines (wireframes).</p> <p>Type: <code>object</code> </p><pre><code>\"wireframe\": {\n \"display\": {\n \"start\": 0,\n \"end\": 1\n },\n \"displaySets\": false,\n \"layer\": [],\n \"color\": {\n \"default\": \"0x00FF00\",\n \"layer\": [\"0x000000\", \"0x0000FF\", \"0x00FF00\", \"0x00FFFF\"],\n \"set\": []\n }\n}\n</code></pre><p></p>"},{"location":"components/configuration/configuration/#wireframedisplay","title":"<code>wireframe.display</code>","text":"Property Type Description <code>start</code> number First layer where wireframes are displayed (0-indexed) <code>end</code> number Last layer where wireframes are displayed <p>Example: <code>\"start\": 1</code> displays wireframes from layer 1 and greater.</p>"},{"location":"components/configuration/configuration/#wireframedisplaysets","title":"<code>wireframe.displaySets</code>","text":"<p>Type: <code>boolean</code></p> <p>Whether to display wireframes on histogram sets.</p>"},{"location":"components/configuration/configuration/#wireframecolor","title":"<code>wireframe.color</code>","text":"<p>Defines wireframe colors.</p> Property Type Description <code>default</code> string Default wireframe color (hex format) <code>layer</code> array Per-layer color overrides <code>set</code> array Per-set color overrides"},{"location":"components/configuration/configuration/#histogramcolor","title":"<code>histogram.color</code>","text":"<p>Controls bin fill colors using gradients.</p> <p>A gradient is created from <code>min</code> to <code>max</code> color based on the selected property.</p> <p>Type: <code>object</code> </p><pre><code>\"color\": {\n \"default\": {\n \"min\": \"0x0000ff\",\n \"max\": \"0xff0000\"\n },\n \"layer\": [],\n \"set\": [\n {\n \"min\": \"0x999999\",\n \"max\": \"0xffaa00\"\n }\n ]\n}\n</code></pre><p></p>"},{"location":"components/configuration/configuration/#colorcolorby","title":"<code>color.colorBy</code>","text":"<p>Type: <code>string</code> Options: <code>\"value\"</code> or <code>\"error\"</code></p> <p>Analogous to <code>scaleBy</code>, determines what drives the color gradient:</p> <ul> <li><code>value</code>: Colors based on <code>fArray</code> values</li> <li><code>error</code>: Colors based on <code>fSumw2</code> errors (or square root of <code>fArray</code> if <code>fSumw2</code> is empty)</li> </ul> Property Type Description <code>default</code> object Default min/max gradient colors <code>layer</code> array Per-layer color gradient overrides <code>set</code> array Per-set color gradient overrides"},{"location":"components/configuration/configuration/#bindings-configuration","title":"Bindings Configuration","text":""},{"location":"components/configuration/configuration/#bindings","title":"<code>bindings</code>","text":"<p>Defines keyboard shortcuts for default histogram interactions.</p> <p>Type: <code>object</code> </p><pre><code>\"bindings\": {\n \"resetHistogram\": \"r\",\n \"goToPreviousLayer\": \"z\",\n \"hideOutlines\": \"o\"\n}\n</code></pre><p></p> Binding Default Key Description <code>resetHistogram</code> <code>r</code> Reset histogram to initial state <code>goToPreviousLayer</code> <code>z</code> Navigate to previous layer <code>hideOutlines</code> <code>o</code> Toggle wireframe visibility"},{"location":"components/configuration/configuration/#example-configuration","title":"Example Configuration","text":"<pre><code>{\n \"config\": {\n \"environment\": {\n \"dbClickTimeout\": 190,\n \"camera\": {\n \"position\": { \"x\": 0, \"y\": 0, \"z\": 0 }\n },\n \"canvas\": {\n \"position\": { \"x\": 0, \"y\": 5, \"z\": -15 },\n \"rotation\": { \"x\": 10, \"y\": 0, \"z\": 0 },\n \"scale\": { \"x\": 20, \"y\": 10, \"z\": 0 }\n },\n \"histogramPads\": {\n \"type\": \"grid2x2x1\",\n \"prefix\": \"histogram\",\n \"scale\": { \"x\": 5, \"y\": 3, \"z\": 5 },\n \"padding\": { \"x\": 1, \"y\": 0, \"z\": 1 },\n \"origin\": { \"x\": -5, \"y\": 0, \"z\": 2.5 }\n }\n },\n \"histogram\": {\n \"padding\": {\n \"default\": { \"x\": 0.1, \"y\": 0.1, \"z\": 0.1 },\n \"layer\": [],\n \"sets\": { \"x\": 0, \"y\": 0, \"z\": 0 }\n },\n \"scale\": {\n \"scaleBy\": \"value\",\n \"content\": \"global\",\n \"parameter\": \"fixed\",\n \"sets\": \"relative\",\n \"default\": {\n \"min\": 0.1,\n \"max\": 1\n },\n \"layer\": []\n },\n \"TH1ZScale\": {\n \"default\": 0.8,\n \"layer\": [ 0.2, 1, 1, 1],\n \"set\": 0.01\n },\n \"wireframe\": {\n \"display\": {\n \"start\": 0,\n \"end\": 99\n },\n \"displaySets\": false,\n \"layer\": [],\n \"color\": {\n \"default\": \"0x00FF00\",\n \"layer\": [\"0x000000\", \"0x0B3D91\", \"0x00FF00\", \"0x00FFFF\"],\n \"set\": []\n }\n },\n \"color\": {\n \"colorBy\": \"error\",\n \"default\": {\n \"min\": \"0x0000ff\",\n \"max\": \"0xff0000\"\n },\n \"layer\": [],\n \"set\": [\n {\n \"min\": \"0x999999\",\n \"max\": \"0xffaa00\"\n },\n {\n \"min\": \"0x00ffff\",\n \"max\": \"0xff7f00\"\n },\n {\n \"min\": \"0x00ff00\",\n \"max\": \"0x800080\"\n },\n {\n \"min\": \"0x0000ff\",\n \"max\": \"0xff0000\"\n }\n ]\n }\n },\n \"bindings\": {\n \"resetHistogram\": \"r\",\n \"goToPreviousLayer\": \"z\",\n \"hideOutlines\": \"o\"\n }\n }\n}\n</code></pre>"},{"location":"components/configuration/configuration/#see-also","title":"See Also","text":"<ul> <li>stateSubject Documentation</li> </ul>"},{"location":"components/tutorial/tutorial/","title":"Introduction","text":""},{"location":"components/tutorial/tutorial/#ndmvr-core-tutorial","title":"NDMVR-Core Tutorial","text":"<p>Welcome to the NDMVR-Core tutorial! This guide will walk you through using Three.js with our class components, helping you understand and utilize the full potential of the NDMVR-Core visualization library.</p>"},{"location":"components/tutorial/tutorial/#what-well-cover","title":"What We'll Cover","text":"<p>Throughout this tutorial, we'll explore:</p> <ul> <li>Visualization Components</li> <li>Histogram rendering</li> <li>JSROOT integration</li> <li>Canvas setup and management</li> <li>Raycasting and interaction handling</li> <li> <p>THnPainter functionality</p> </li> <li> <p>Component Communication</p> </li> <li>RxJS-based message passing</li> <li>State management</li> <li>Configuration handling</li> <li> <p>Event dispatching</p> </li> <li> <p>Advanced Features</p> </li> <li>Extending components with custom functions</li> <li>Creating custom visualizations</li> <li>Integration with existing Three.js projects</li> </ul> <p>Each section will include practical examples and detailed explanations to help you master the NDMVR library's features and capabilities.</p> <p>Let's begin by exploring the basic setup and your first visualization component!</p> <ul> <li>First visualization</li> </ul>"},{"location":"components/tutorial/canvas/canvas/","title":"Canvas","text":""},{"location":"components/tutorial/canvas/canvas/#how-to-use-canvas-component","title":"How to use canvas component","text":"<p>In this section, you will learn how to use a canvas component in your scene. This component allows you to render 2D content onto a 3D Three.Plane object. While it can display various types of content, the supported formats are limited to HTMLImageElement, Data/URL links, and JSROOT objects.</p> <p>This component can be useful in cases when you want to:</p> <ul> <li>displaying 2D content inside a VR environment</li> <li>having an additional visualization element that complements existing 3D objects</li> <li>rendering external graphical content such as images loaded from URLs or generated dynamically</li> <li>displaying JSROOT primitives that are not directly supported by our rendering components</li> <li>creating information panels, overlays, or dashboards that remain spatially anchored in the scene</li> <li>presenting charts, plots, or other data visualizations alongside 3D representations</li> </ul>"},{"location":"components/tutorial/canvas/canvas/#what-youll-build","title":"What You'll Build","text":"<p>A simple 3D histogram visualization with: - A canvas component - Different cases on how to update the canvas texture - In the demo, canvas is initialized with HTMLImageElement, after 2 seconds, it will be updated using canvasSubject and after 2 seconds it will be updated using updateTexture method with a base64 encoded image.</p>"},{"location":"components/tutorial/canvas/canvas/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download Tutorial Files (ZIP) Download the complete tutorial package that includes: - <code>index.html</code> - The complete visualization - <code>test3</code> - 4D histogram sample.</p>"},{"location":"components/tutorial/canvas/canvas/#adding-canvas-to-your-scene","title":"Adding canvas to your scene","text":"<p>First, we need to add a canvas component to our scene. </p><pre><code>const canvasPRS = {\n pos: { x: 0, y: 5, z: 0 },\n rot: { x: 0, y: 0, z: 0 },\n sc: { x: 0.8, y: 0.6, z: 0.8 }\n};\nconst canvas = new CanvasClass(null, canvasPRS.pos, canvasPRS.rot, canvasPRS.sc, \"histogram1-canvas\");\nscene.add(canvas.getPlane());\n</code></pre><p></p> <ul> <li>Important: Mind what id you will assign to canvas, as the default behavior of THnPainter is to send histograms to display to ThnPainter id + \"-cinema\", so if ThnPainter id is \"histogram1\", then it is sent to \"histogram1-cinema\".</li> </ul>"},{"location":"components/tutorial/canvas/canvas/#how-to-update-canvas-manually","title":"How to update canvas manually.","text":"<ul> <li>All the provided examples can be found in the demo</li> </ul> <p>User can update canvas texture in these ways:</p>"},{"location":"components/tutorial/canvas/canvas/#utilizing-canvassubject","title":"Utilizing canvasSubject","text":"<ul> <li>ID can be set to \"*\" to update all canvases.</li> <li>Note: Canvas subject expects a JSROOT object as input.</li> </ul> <pre><code> canvasSubjectGet().next({id: \"histogram1-canvas\", obj: jsrootObj});\n</code></pre>"},{"location":"components/tutorial/canvas/canvas/#utilizing-updatetexture-method","title":"Utilizing updateTexture method","text":"<ul> <li>User can also update canvas texture by using updateTexture method on the instance of canvasClass.</li> <li>Note: updateTexture method expects an image element or base64 encoded image (e.g. \"data:image/png;base64,xyz...\") as input.</li> </ul> <pre><code>const canvas = new CanvasClass(null, canvasPRS.pos, canvasPRS.rot, canvasPRS.sc, \"histogram1-canvas\");\n\nconst imgEl = new Image(480, 360);\nimgEl.src = \"image.png\";\ncanvas.updateTexture(imgEl);\n</code></pre> <pre><code>// {\n// \"image\": \"data:image/png;base64,iVBORw0K...\ncanvas.updateTexture(image);\n</code></pre>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/","title":"Configuration","text":""},{"location":"components/tutorial/configurationChapter/configuration-chapter/#configuration-and-rxjs-integration","title":"Configuration and RxJS Integration","text":"<p>This chapter builds upon the first visualization tutorial, introducing RxJS concepts and showing how to configure your visualizations dynamically.</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#result","title":"Result:","text":"<ul> <li>Visualization of 4D histogram with a user-defined function to draw histogram beneath bin using jsroot by double-clicking.</li> </ul>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download Tutorial Files (ZIP)</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#understanding-rxjs-basics","title":"Understanding RxJS Basics","text":"<p>RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using Observables. This extension defines the interface between the user and the NdmVr-cores inter-component communication.</p> <p>It is used to enable reactive and dynamic updates without the need to manage references or synchronization manually, since all used Subjects follow the singleton design pattern. Additionally, this extension helps maintain compatibility with other solutions. In NDMVR, we use RxJS to:</p> <ul> <li>Handle real-time histogram updates</li> <li>Manage data streams</li> <li>React to user interactions</li> <li>Control visualization configuration</li> </ul> <p>Key RxJS concepts used in NDMVR:</p> <ul> <li>Observables: Represent a stream of data over time</li> <li>Subjects: Special type of Observable that allows values to be multicasted</li> <li>Subscribers: Consume values emitted by Observables</li> <li>Operators: Transform, combine, and manipulate Observable streams</li> </ul> <p>For more information about each defined subject, head to the Communication section of documentation.</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#histogram-component","title":"Histogram component","text":"<p>For easier manipulation with histograms we will create histogram abstraction component. It's purpose will be to implement dynamic interface provided by Histogram subject.</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#creating-the-histogram-manager-component","title":"Creating the Histogram Manager Component","text":"<ul> <li>Why THnPainterManager?</li> <li>The THnPainterManager class serves as an abstraction layer between the RxJS histogram stream and the actual visualization rendering. This pattern provides several key benefits:</li> <li> <ol> <li>Separation of Concerns</li> </ol> </li> <li> <p>Decouples histogram data management from rendering logic Isolates reactive stream handling in one place Makes the codebase more maintainable and testable</p> </li> <li> <ol> <li>Dynamic Renderer Switching The manager can dynamically switch between different rendering engines based on configuration:</li> </ol> </li> </ul> <p>NDMVR Renderer (THnPainter): Custom 3D histogram visualization optimized for performance JSRoot Renderer (HistogramJsrootClass): Standard ROOT visualization for compatibility</p> <ul> <li> <ol> <li>Automatic Resource Management</li> </ol> </li> </ul> <p>Handles subscription lifecycle (subscribe/unsubscribe) Automatically cleans up old renderers when switching Prevents memory leaks from unmanaged 3D objects</p> <ul> <li> <ol> <li>Reactive Updates</li> </ol> </li> </ul> <p>Listens to the histogram stream using RxJS Automatically re-renders when histogram data changes Updates existing visualizations without recreation when possible</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#how-it-works","title":"How It Works","text":"<ul> <li>Initialization: The init() method subscribes to the histogram stream for a specific element ID</li> <li>Stream Handling: When histogram data arrives, it checks the opts.render property</li> <li>Renderer Selection: Routes to either JSRoot or NDMVR renderer based on configuration</li> <li>Resource Cleanup: Removes old renderer before creating/updating new one</li> <li>Subscription Management: The remove() method unsubscribes and cleans up all resources</li> </ul>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#using-thnpaintermanager-in-your-application","title":"Using THnPainterManager in Your Application","text":"<p>Basic Setup </p><pre><code>// Create a group to hold histogram objects\nconst histogramGroup = new THREE.Group();\nscene.add(histogramGroup);\n\n// Initialize THnPainterManager\nconst painterManager = new THnPainterManager(\"histogram1\", histogramGroup, camera);\n\n// Load histogram data\nawait histogramSubjectGet().next({\n id: \"histogram1\",\n obj: parse(h3scat),\n opts: {\n render: \"ndmvr\" // or \"jsroot\"\n }\n});\n</code></pre><p></p> <p>Switching Renderers</p> <p>You can easily switch between rendering engines by changing the render option. On line 120 of the example code, you'll find: </p><pre><code>await histogramSubjectGet().next({\n id: \"histogram1\",\n obj: parse(h3scat),\n opts: {\n render: \"ndmvr\" // \u2190 Change this to \"jsroot\" to use JSRoot renderer\n }\n});\n</code></pre><p></p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#render-options","title":"Render Options:","text":"<ul> <li>\"ndmvr\" - Uses the custom NDMVR renderer (THnPainter) for optimized 3D visualization</li> <li>\"jsroot\" - Uses the JSRoot renderer (HistogramJsrootClass) for standard ROOT compatibility</li> </ul> <p>The manager will automatically:</p> <p>Detect the renderer change Clean up the current renderer Initialize the new renderer Display the histogram with the new rendering engine</p> <p>Global Configuration At the end of the file (after the histogram setup), you'll notice the global configuration: </p><pre><code>configSubjectGet().next(config);\n</code></pre><p></p> <p>This line loads global visualization settings from config.json. The configuration affects:</p> <p>Color schemes and palettes Default rendering options Axis labels and scales Visualization performance settings</p> <p>The configuration is applied globally through the configSubjectGet() subject, which means:</p> <p>All histograms can access these settings Changes propagate to all active visualizations You can update configuration at runtime</p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#note","title":"Note:","text":"<p>Config is merged internally in config handler, so in addition to support of dynamic update according to configuration, it is possible to send just a part of config, which will be merged with existing config!</p> <p>One can send just a part of config, for example:</p> <p>Line 101 of the example code </p><pre><code>configSubjectGet().next({\n \"config\": {\n \"environment\": {\n \"histogramPads\": {\n \"type\": \"grid1x1x1\",\n \"prefix\": \"histogram\",\n \"sc\": {\n \"x\": 5,\n \"y\": 3,\n \"z\": 5\n },\n \"padding\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n },\n \"origin\": {\n \"x\": -2.5,\n \"y\": 1,\n \"z\": 2.5\n }\n }\n }\n }\n});\n</code></pre><p></p>"},{"location":"components/tutorial/configurationChapter/configuration-chapter/#next-steps","title":"Next Steps","text":"<p>Now that you understand how the histogram manager, basic configuration and communication works, you can:</p> <p>If you want to find out more about all the configuration options, check out the Configuration Reference.</p> <p>Experiment with different renderers by changing the render option Modify the global configuration to customize the visualization appearance Create multiple histogram managers for different data sets Implement custom rendering logic by extending the manager class</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/","title":"First visualization","text":""},{"location":"components/tutorial/firstVisualization/first-visualization/#your-first-visualization","title":"Your First Visualization","text":"<p>In this tutorial, you'll create a basic 3D histogram visualization using NDMVR-Core's THnPainter with Three.js.</p> <p>While in this section is visualized 3-Dimensional histogram, THnPainter support up to N-Dimensions histogram, utilizing representation of data, via the the hypercube concept.</p> <p>For more information about ndmvr-core package itself, please head to: Home page</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#prerequisites","title":"Prerequisites","text":"<p>This tutorial assumes you have basic knowledge of: - Three.js fundamentals (scene, camera, renderer) - JavaScript ES6+ (async/await, imports) - Basic understanding of 3D graphics concepts</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#what-youll-build","title":"What You'll Build","text":"<p>A simple 3D histogram visualization with: - Interactive orbit controls for rotation and zoom - A TH3 (3D histogram) rendered using NDMVR-Core - No build tools required - just HTML and JavaScript</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download the complete tutorial package that includes: - <code>index.html</code> - The complete visualization - <code>h3scat.json</code> - Sample 3D histogram data from ROOT</p> <p>Download Tutorial Files (ZIP)</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#key-concepts","title":"Key Concepts","text":""},{"location":"components/tutorial/firstVisualization/first-visualization/#understanding-thnpainter","title":"Understanding THnPainter","text":"<p>The <code>THnPainter</code> class is NDMVR-Core's main tool for visualizing ROOT histograms. It automatically: - Detects the histogram type (TH1, TH2, TH3...) - Creates appropriate 3D geometry - Generates materials and textures - Provides outlines overlays for clarity - Handles coordinate systems and scaling - For more details, see the THnPainter API reference.</p> <p>Key properties: - <code>painter.mesh</code> - The main rendered histogram (THREE.Mesh) - <code>painter.wireframe</code> - Object containing wireframe visualization - <code>painter.wireframe.wireframe</code> - The wireframe mesh to add to scene</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#understanding-the-data-flow","title":"Understanding the Data Flow","text":"<pre><code>h3scat.json (ROOT JSON)\n \u2193\nparse(h3scat) - JSROOT parses ROOT format\n \u2193\n{obj: parsedData} - Wrap in required structure\n \u2193\nnew THnPainter() - Create painter instance\n \u2193\npainter.mesh + painter.wireframe - Three.js meshes\n \u2193\nscene.add() - Add to Three.js scene\n</code></pre>"},{"location":"components/tutorial/firstVisualization/first-visualization/#importing-libraries","title":"Importing Libraries","text":"<p>The visualization uses several libraries loaded via import map:</p> <pre><code>import * as THREE from \"three\";\nimport {OrbitControls} from \"three/examples/jsm/controls/OrbitControls.js\";\nimport {THnPainter} from \"ndmvr-core\";\nimport h3scat from \"./h3scat.json\" with { type: \"json\" };\nimport {parse} from \"jsroot\";\n</code></pre> <p>Breaking it down: - <code>THnPainter</code> - NDMVR-Core's painter for ROOT histograms (TH1, TH2, TH3...) - <code>h3scat.json</code> - Your histogram data file (imported with JSON type assertion) - <code>parse</code> - JSROOT's parser to convert ROOT JSON format to JavaScript objects</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#loading-and-rendering-the-histogram","title":"Loading and Rendering the Histogram","text":"<p>This is where NDMVR-Core does its work:</p> <pre><code>async function loadHistogram() {\n try {\n // Parse the ROOT JSON format using JSROOT\n const histoObj = {obj: parse(h3scat)}\n\n // Create THn painter with the parsed histogram\n const painter = new THnPainter(histoObj, \"histogram1\");\n\n // Add the histogram mesh and wireframe to the scene\n scene.add(painter.mesh);\n scene.add(painter.wireframe.wireframe);\n\n console.log(\"Histogram loaded successfully!\");\n\n } catch (error) {\n console.error(\"Error loading histogram:\", error);\n }\n}\n</code></pre> <p>Understanding the code:</p> <ol> <li> <p>Parsing ROOT data: <code>parse(h3scat)</code> converts the ROOT JSON format into a JavaScript object that NDMVR-Core can understand. The result is wrapped in an object with an <code>obj</code> property as required by THnPainter.</p> </li> <li> <p>Creating the painter: <code>new THnPainter(histoObj, \"histogram1\")</code> creates a painter instance:</p> </li> <li>First argument: The histogram data wrapped in <code>{obj: parsedData}</code></li> <li> <p>Second argument: A unique identifier for this histogram</p> </li> <li> <p>Adding to the scene: THnPainter provides two renderable objects:</p> </li> <li><code>painter.mesh</code> - The solid 3D histogram bars</li> <li><code>painter.wireframe.wireframe</code> - The wireframe outline for better visibility</li> </ol>"},{"location":"components/tutorial/firstVisualization/first-visualization/#initializing-the-visualization","title":"Initializing the Visualization","text":"<pre><code>loadHistogram();\nanimate();\n</code></pre> <p>Simply call <code>loadHistogram()</code> to load and render your histogram, then start the animation loop.</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#the-complete-html-structure","title":"The Complete HTML Structure","text":"<p>The visualization is completely self-contained in a single HTML file:</p> <pre><code><!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>First Visualization - NDMVR</title>\n <style>\n /* Styling... */\n </style>\n</head>\n<body>\n <div id=\"info\">\n <h3>First Visualization</h3>\n <p>Use mouse to rotate, scroll to zoom</p>\n </div>\n\n <!-- Import map for CDN libraries -->\n <script type=\"importmap\">\n { \"imports\": { /* library mappings */ } }\n </script>\n\n <!-- Main visualization code -->\n <script type=\"module\">\n // All your code here\n </script>\n</body>\n</html>\n</code></pre>"},{"location":"components/tutorial/firstVisualization/first-visualization/#running-your-visualization","title":"Running Your Visualization","text":"<ol> <li>Extract the downloaded ZIP file to a folder</li> <li> <p>Start a local web server in that folder: </p><pre><code># Python 3\npython -m http.server 8000\n\n# Node.js\nnpx serve\n\n# Or use VS Code's Live Server extension\n</code></pre><p></p> </li> <li> <p>Open your browser and navigate to <code>http://localhost:8000</code></p> </li> </ol> <p>Important: You must use a local server because browsers block ES modules and JSON imports when loaded from <code>file://</code> URLs.</p> <p>Note: For more convenient way, you can also use vscode's Live Server extension, or Built-in preview provided in any Jetbrains IDE.</p>"},{"location":"components/tutorial/firstVisualization/first-visualization/#next-steps","title":"Next Steps","text":"<p>We now have basic visualization, not that good, but we have one. In tutorials later in the series, we'll improve and customize it further.</p> <p>Now that you understand the basics: - Explore painter configuration options - Add interactive features with raycasting - Create dynamic histogram updates using RxJS</p> <p>The complete working code is in the downloaded <code>index.html</code> file. Use it as a starting point for your own visualizations!</p> <p>Tutorial continues in configuration section.</p>"},{"location":"components/tutorial/interactions/interactions/","title":"interactions","text":""},{"location":"components/tutorial/interactions/interactions/#histograms-and-interactions","title":"Histograms and interactions","text":"<p>In this section, you will learn how to add interactions to your histograms.</p>"},{"location":"components/tutorial/interactions/interactions/#what-youll-build","title":"What You'll Build","text":"<p>A simple 3D histogram visualization with: - Visualization of THn histogram - Add basic interactions with the histogram - Change default interactions to user defined ones</p>"},{"location":"components/tutorial/interactions/interactions/#download-the-tutorial-files","title":"Download the Tutorial Files","text":"<p>Download Tutorial Files (ZIP)</p>"},{"location":"components/tutorial/interactions/interactions/#adding-basic-interactions","title":"Adding basic interactions","text":"<p>As of now, you will be able to utilize some of the basic interactions done by keys, including:</p> <ul> <li>Navigate to layer of histogram by pressing <code>num keys</code>.</li> <li>Reset histogram state by pressing <code>r</code> key.</li> <li>Hide/Show bin outline by pressing <code>o</code> key.</li> </ul>"},{"location":"components/tutorial/interactions/interactions/#adding-ndmvr-raycaster","title":"Adding NdmVr raycaster","text":"<ul> <li>To add basic interactions with the mouse to your histogram, you need to add a raycaster to Three scene.</li> <li>We strongly recommend using NdmVr raycaster to handle mouse events.</li> <li>Ndmvr raycaster is built on top of Threejs raycaster. As it only adds handling of double clicks and clicks while some keys can be hold. This information is added to the checkIntersection calls. </li> </ul> <p>To add NdmVr raycaster to your scene, you can simply add this line: </p><pre><code> const raycaster = new NdmvrRaycaster(scene, renderer.domElement);\n</code></pre><p></p> Property Type Description <code>scene</code> object Three.js scene <code>rendererElement</code> object DOM element from Three.js renderer"},{"location":"components/tutorial/interactions/interactions/#now-you-can-use-basic-interactions-with-the-histogram-using-also-the-mouse-including","title":"Now you can use basic interactions with the histogram using also the mouse including =>","text":"<ul> <li>Left click on bin to expand it by histogram in the layer beneath.</li> <li>Shift + Left click on expanded bin to hide histogram in the layer beneath.</li> <li>Double click on bin to navigate to histogram on layer beneath.</li> <li>Shift + Double click on bin to navigate to histogram on layer above.</li> </ul>"},{"location":"components/tutorial/interactions/interactions/#adding-custom-user-functions","title":"Adding custom user functions.","text":"<p>Now that we have basic interactions, we can add custom user functions to the histogram. Support for custom functions is provided in cases where:</p> <ul> <li>the default interactions do not offer enough or right functionality.</li> <li>custom behavior is required, such as integrating with your own solution or making HTTP requests etc.</li> </ul> <p>Note: User and default functions behave exactly the same in terms of their context and lifecycle, ensuring developers can easily change them to their liking. the default ones are just simply there from the start, as they were identified as most-likely to be used.</p> <p>To add custom user functions, you can use the function subject.</p>"},{"location":"components/tutorial/interactions/interactions/#manipulating-functions","title":"Manipulating functions","text":"<ul> <li>Here we provide examples for basic use cases on how one can manipulate functions.</li> <li>You can try these examples in the interactions.html file, by simply copy pasting below codes to the script (e.g. at line 92).</li> </ul> <p>Note: If you want to set functions right away, it is stronly recommended to set them in block of setTimeout with timeout of 0. This is done for ensuring all the components are initialized, thus the event will be recorded. (See example at line 99 of interactions.html) </p>"},{"location":"components/tutorial/interactions/interactions/#how-to-utilize-custom-functions","title":"How to utilize custom functions","text":"<ul> <li>To fully utilize custom functions one need to understand what is provided and what is possible.</li> <li>Every custom function has access to the event and the context.</li> <li>event consists of:</li> <li> Property Type Description <code>index</code> array: Object Array of objects, contains xyz coordinates of intersected bin (including all layers) <code>instanceId</code> array: Int Array of ID's (Int) identifying instance of intersected bin in NdmVr indexing. <code>jsrootInstance</code> array: Int Array of ID's (Int) identifying instance of intersected bin in ROOT indexing. <code>jsrootObj</code> Object JSROOT Object linked to intersected bin. <code>origin</code> Object Instance of ThnPainter linked to intersected bin. <code>range</code> array: Object Array of objects, contains info about intersected bin, such as: range of bin, title color of bin... <code>target</code> THREE.Vector3 Point in space where bin was intersected. <code>content</code> Float Content value of intersected bin. <code>error</code> Float Error of intersected bin. <code>distance</code> Float Distance from origin of raycaster to the point of intersection. </li> <li>context property provides THnPainter instance.</li> <li>User can manipulate histogram itself by using the context.</li> </ul>"},{"location":"components/tutorial/interactions/interactions/#remove-all-functions","title":"Remove all functions","text":"<ul> <li>If you want to remove all functions from al histograms.</li> <li>You can also specify the target id to remove only functions from specific histogram. <pre><code>functionSubjectGet().removeFunctions({\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n }\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#remove-all-functions-on-event","title":"Remove all functions on event","text":"<ul> <li>If you want to remove all functions on specific event. <pre><code>functionSubjectGet().removeFunctions({\n event: \"mousemove\",\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n }\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#add-default-function","title":"Add default function","text":"<ul> <li>If you wish to bring back the default function on a specific event. <pre><code>functionSubjectGet().addFunctions({\n event: \"mousemove\",\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n },\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#add-custom-function","title":"Add custom function","text":"<ul> <li>If you wish to add custom function on a specific event.</li> <li>If you add multiple functions on the same event, they will be all executed, not rewritten.</li> <li>Functions will be executed in the order they were added. <pre><code>functionSubjectGet().addFunctions({\n event: \"mouseclick\",\n target: {\n entity: \"nested-histogram\",\n id: \"*\"\n },\n function: function (event, context) {\n console.log(\"my-custom-function: \", event);\n }\n});\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#set-custom-functions","title":"Set custom functions","text":"<ul> <li>One can also set all functions at once by using the <code>setFunctions</code> method.</li> <li>This will overwrite all existing functions on the entity. <pre><code> functionSubjectGet().setFunctions([\n //array of functions\n]);\n</code></pre></li> <li>With this approach you can quickly change the behavior of interactions.</li> <li>It can simply be linked to user defined tools like this: <pre><code>const functions = {\n empty: [{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }\n }],\n second: [{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }, event: \"mousedbclick\",\n function: function (event, context) {\n console.log(\"custom function from set functions: \", event, context);\n const histogramBelow = context.pointer.getChildByPosition(event.jsrootInstance);\n redraw(\"jsrootdiv\", histogramBelow);\n }\n },{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }, event: \"mouseclick\",\n },{\n target: {\n entity: \"nested-histogram\",\n id: \"histogram1\"\n }, event: \"shiftmouseclick\",\n }]\n};\n\nfunctionSubjectGet().setFunctions(functions.empty);\nfunctionSubjectGet().setFunctions(functions.second);\n</code></pre></li> </ul>"},{"location":"components/tutorial/interactions/interactions/#power-of-user-defined-functions","title":"Power of user defined functions","text":"<ul> <li>With the power of user-defined functions, you can create custom tools for your histogram.</li> <li>In provided demo you might notice that you cannot navigate to histogram below by double-clicking on bin. As this was overridden by custom function, which takes clicked bin and redraws it onto jsroot div.</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/","title":"Bin info jsroot","text":""},{"location":"components/visualization/bin-info-jsroot/#bininfovisualizer","title":"BinInfoVisualizer","text":""},{"location":"components/visualization/bin-info-jsroot/#overview","title":"Overview","text":"<p>The <code>BinInfoVisualizer</code> class creates a 3D panel in THREE.js that displays histogram bin information. It uses JSROOT's TLatex for text rendering and automatically positions itself in front of the camera. The visualizer subscribes to an RxJS subject queue for efficient, non-blocking updates.</p>"},{"location":"components/visualization/bin-info-jsroot/#constructor","title":"Constructor","text":"<pre><code>constructor(camera, options = {})\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#parameters","title":"Parameters","text":"<ul> <li>camera (<code>THREE.Camera</code>): Camera reference for billboard positioning</li> <li>options (<code>Object</code>): Configuration options for styling and layout</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#options-object","title":"Options Object","text":"Property Type Default Description <code>backgroundColor</code> <code>number</code> <code>0xAAAAAA</code> Panel background color (hex) <code>textColor</code> <code>number</code> <code>1</code> ROOT color index for text <code>titleColor</code> <code>number</code> <code>0</code> ROOT color index for title <code>padding</code> <code>number</code> <code>0.002</code> Padding around text (THREE.js units) <code>lineHeight</code> <code>number</code> <code>0.002</code> Height per line of text <code>textSize</code> <code>number</code> <code>10</code> Font size for text <code>width</code> <code>number</code> <code>0.031</code> Panel width"},{"location":"components/visualization/bin-info-jsroot/#created-objects","title":"Created Objects","text":"<ul> <li>group (<code>THREE.Group</code>): Container for panel and text elements</li> <li>queue (<code>RxJS.Subject</code>): Event queue for bin data updates</li> <li>queueSub (<code>RxJS.Subscription</code>): Manages sequential processing of updates</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#properties","title":"Properties","text":"<ul> <li>camera (<code>THREE.Camera</code>): Reference to the camera</li> <li>options (<code>Object</code>): Merged configuration options</li> <li>loader (<code>FontLoader</code>): THREE.js font loader instance</li> <li>queue (<code>Subject</code>): RxJS subject for queuing bin data</li> <li>queueSub (<code>Subscription</code>): Subscription managing update queue</li> <li>group (<code>THREE.Group</code>): THREE.js group containing the visualization</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#methods","title":"Methods","text":""},{"location":"components/visualization/bin-info-jsroot/#parsedatadata","title":"parseData(data)","text":"<p>Extracts and formats display information from bin data.</p> <p>Parameters: - data (<code>Object</code>): Bin information object</p> <p>Returns: <code>Array<Object></code> - Array of line objects with <code>text</code> and <code>isTitle</code> properties</p> <p>Parsed Information: - Title (if present) - Coordinate ranges for each axis (x, y, z) - Bin index and position - Bin content value - Bin error (if present)</p>"},{"location":"components/visualization/bin-info-jsroot/#createbackgroundpanelheight","title":"createBackgroundPanel(height)","text":"<p>Creates the background panel for the info display.</p> <p>Parameters: - height (<code>number</code>): Panel height in THREE.js units</p> <p>Returns: <code>THREE.Mesh</code> - Panel mesh with configured material</p> <p>Material: - <code>PlaneGeometry</code> sized to fit content - <code>MeshBasicMaterial</code> with configured background color - <code>DoubleSide</code> rendering</p>"},{"location":"components/visualization/bin-info-jsroot/#updatevisualizationdata","title":"updateVisualization(data)","text":"<p>Updates the 3D visualization with new bin information.</p> <p>Important: This method is called automatically by the queue subject. Do not call directly - use <code>queue.next(data)</code> instead.</p> <p>Parameters: - data (<code>Object</code>): Bin data to visualize (or <code>null</code> to clear)</p> <p>Behavior: - Clears previous visualization - Parses data into display lines - Creates background panel - Renders each line using JSROOT TLatex - Positions panel in front of camera - Attaches to camera for billboard effect</p> <p>Async Processing: - Uses RxJS <code>concatMap</code> for sequential updates - Prevents concurrent updates that could cause race conditions - Queues latest event while processing</p>"},{"location":"components/visualization/bin-info-jsroot/#getgroup","title":"getGroup()","text":"<p>Returns the THREE.Group containing the visualization.</p> <p>Returns: <code>THREE.Group</code></p>"},{"location":"components/visualization/bin-info-jsroot/#setpositionx-y-z","title":"setPosition(x, y, z)","text":"<p>Manually sets the position of the info panel.</p> <p>Parameters: - x, y, z (<code>number</code>): Position coordinates</p> <p>Note: Position is typically managed automatically by the camera attachment.</p>"},{"location":"components/visualization/bin-info-jsroot/#setrotationx-y-z","title":"setRotation(x, y, z)","text":"<p>Manually sets the rotation of the info panel.</p> <p>Parameters: - x, y, z (<code>number</code>): Rotation angles in radians</p>"},{"location":"components/visualization/bin-info-jsroot/#clear","title":"clear()","text":"<p>Clears all content from the panel.</p> <p>Behavior: - Removes all children from group - Disposes geometries and materials</p>"},{"location":"components/visualization/bin-info-jsroot/#dispose","title":"dispose()","text":"<p>Cleans up all resources and subscriptions.</p> <p>Behavior: - Unsubscribes from queue subject - Removes all children from group - Disposes all geometries and materials</p>"},{"location":"components/visualization/bin-info-jsroot/#usage-example","title":"Usage Example","text":"<pre><code>import { BinInfoVisualizer } from './bininfo-jsroot-class.js';\nimport * as THREE from 'three';\n\n// Create camera\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\n\n// Create visualizer with custom options\nconst binInfo = new BinInfoVisualizer(camera, {\n backgroundColor: 0x36454F, // Charcoal gray\n textColor: 0, // Black text\n titleColor: 0, // Black title\n padding: 0.003,\n width: 0.04\n});\n\n// Add to scene (typically attached to camera automatically)\nconst scene = new THREE.Scene();\nscene.add(camera);\n\n// Queue bin data for display\nbinInfo.queue.next({\n title: 'Histogram Bin Info',\n coords: [{\n x: { min: 0.5, max: 1.5, name: 'X Axis', title: 'X' },\n y: { min: 2.0, max: 3.0, name: 'Y Axis', title: 'Y' },\n z: { min: 1.0, max: 2.0, name: 'Z Axis', title: 'Z' }\n }],\n index: [{ x: 1, y: 2, z: 1 }],\n content: 42.7,\n error: 2.3,\n object: { bins: [15] },\n instanceId: 15,\n point: { x: 1, y: 1.5, z: 1.5 }\n});\n\n// Clear display\nbinInfo.queue.next(null);\n\n// Clean up when done\nbinInfo.dispose();\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#integration-with-histogramjsrootclass","title":"Integration with HistogramJsrootClass","text":"<p>The <code>BinInfoVisualizer</code> is automatically created by <code>HistogramJsrootClass</code> and receives updates from mouse interactions:</p> <pre><code>import { HistogramJsrootClass } from './histogram-jsroot-class.js';\n\nconst histogram = new HistogramJsrootClass('hist1', rootObject, camera);\n\n// The bin info visualizer is available at:\n// histogram.binInfoComponent\n\n// It receives updates automatically from mousemove events\n// through the histogram's raycast handler\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#rxjs-subject-integration","title":"RxJS Subject Integration","text":"<p>The visualizer can be driven by RxJS subjects for decoupled architecture:</p> <pre><code>import { binInfoSubjectGet } from '../rxjs/BinInfoSubject.js';\n\n// Subscribe visualizer to subject\nconst subscription = binInfoSubjectGet()\n .subscribe(binData => {\n binInfo.queue.next(binData);\n });\n\n// Publish bin data from anywhere\nbinInfoSubjectGet().next({\n coords: [/* ... */],\n content: 100,\n error: 5\n});\n</code></pre>"},{"location":"components/visualization/bin-info-jsroot/#text-rendering-with-tlatex","title":"Text Rendering with TLatex","text":"<p>The visualizer uses JSROOT's TLatex for text rendering, which supports: - ROOT-style text formatting - ROOT color indices - Scalable vector text - LaTeX-like syntax</p> <p>Each line is created using: </p><pre><code>const latex = create(\"TLatex\");\nlatex.fTitle = \"X = [0.5, 1.5)\";\nlatex.fTextAlign = 12;\nlatex.fTextFont = 2;\nlatex.fTextColor = 0; // ROOT color index\nconst textGroup = await build3d(latex, \"p\", y * 100, \"\", \"\");\n</code></pre><p></p>"},{"location":"components/visualization/bin-info-jsroot/#billboard-behavior","title":"Billboard Behavior","text":"<p>The panel is attached to the camera and positioned at a fixed distance in front of it, ensuring: - Always faces the user - Readable from any angle - Maintains consistent size - Positioned near the point of interaction</p>"},{"location":"components/visualization/bin-info-jsroot/#queue-processing","title":"Queue Processing","text":"<p>The <code>concatMap</code> operator ensures: - Sequential processing of updates - No race conditions from concurrent builds - Latest event is queued if update arrives during processing - Non-blocking asynchronous rendering</p>"},{"location":"components/visualization/bin-info-jsroot/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: <code>Group</code>, <code>Mesh</code>, <code>MeshBasicMaterial</code>, <code>PlaneGeometry</code>, <code>DoubleSide</code>, <code>Box3</code>, <code>Vector3</code></li> <li>THREE.js Addons: <code>TextGeometry</code>, <code>FontLoader</code></li> <li>JSROOT: <code>create</code>, <code>build3d</code> for TLatex rendering</li> <li>RxJS: <code>Subject</code>, <code>from</code>, <code>concatMap</code>, <code>finalize</code>, <code>EMPTY</code></li> <li>RxJS Subjects:</li> <li><code>canvasSubjectGet</code> from <code>../rxjs/CanvasSubject.js</code></li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#related-components","title":"Related Components","text":"<ul> <li>HistogramJsrootClass - Creates and manages BinInfoVisualizer</li> <li>THnPainter - Alternative histogram that can use bin info</li> <li>NdmvrRaycaster - Provides raycasting for interaction</li> </ul>"},{"location":"components/visualization/bin-info-jsroot/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/canvas/","title":"Canvas","text":""},{"location":"components/visualization/canvas/#canvas","title":"Canvas","text":""},{"location":"components/visualization/canvas/#overview","title":"Overview","text":"<p>The <code>CanvasClass</code> creates a 3D canvas plane using THREE.js that can display ROOT canvas objects as textures in a 3D environment. It manages texture updates, transformation (position, rotation, scale), and subscribes to canvas and configuration subjects for reactive updates.</p>"},{"location":"components/visualization/canvas/#constructor","title":"Constructor","text":"<pre><code>constructor(image, position, rotation, sc, id)\n</code></pre> <p>Parameters: - <code>image</code> - Initial image (optional): data URL, HTTP URL, or HTMLImageElement - <code>position</code> - THREE.Vector3 or object with <code>{x, y, z}</code> coordinates - <code>rotation</code> - THREE.Vector3 or object with <code>{x, y, z}</code> rotation in degrees - <code>scale</code> - THREE.Vector3 or object with <code>{x, y, z}</code> scale factors - <code>id</code> (string) - Unique identifier for this canvas instance</p> <p>Behavior: - Creates a <code>PlaneGeometry</code> with specified scale - Creates a white <code>MeshBasicMaterial</code> with <code>DoubleSide</code> rendering - Initializes texture if image is provided - Subscribes to <code>canvasSubject</code> for canvas object updates - Subscribes to <code>configSubject</code> for configuration changes</p>"},{"location":"components/visualization/canvas/#properties","title":"Properties","text":"Property Type Description <code>plane</code> <code>THREE.Mesh</code> The THREE.js mesh object representing the canvas <code>cinemaSub</code> <code>Subscription</code> RxJS subscription to canvas subject <code>configSub</code> <code>Subscription</code> RxJS subscription to config subject <code>position</code> <code>Vector3</code> Current position of the canvas <code>rotation</code> <code>Vector3</code> Current rotation of the canvas (degrees) <code>scale</code> <code>Vector3</code> Current scale of the canvas <code>id</code> <code>string</code> Unique identifier for this canvas"},{"location":"components/visualization/canvas/#methods","title":"Methods","text":""},{"location":"components/visualization/canvas/#updatemesh","title":"updateMesh()","text":"<p>Updates the mesh transformation based on current position, rotation, and scale properties.</p> <p>Behavior: - Applies scale to the plane - Sets position of the plane - Converts rotation from degrees to radians and applies it</p> <pre><code>updateMesh()\n</code></pre>"},{"location":"components/visualization/canvas/#updatetextureimage","title":"updateTexture(image)","text":"<p>Updates the canvas texture with a new image.</p> <p>Parameters: - <code>image</code> - String (data URL or HTTP URL) or HTMLImageElement</p> <p>Supported Image Types: 1. Data URL: <code>data:image/png;base64,...</code> 2. HTTP URL: <code>http://</code> or <code>https://</code> URLs 3. HTMLImageElement: Direct image element reference</p> <p>Behavior: - Loads texture using THREE.TextureLoader - Updates material's texture map - Handles async loading for URLs - Direct assignment for HTMLImageElement - Logs warning for null/undefined input - Logs error for unsupported types</p> <pre><code>canvas.updateTexture('data:image/png;base64,iVBORw0KGg...');\ncanvas.updateTexture('https://example.com/image.png');\ncanvas.updateTexture(imageElement);\n</code></pre>"},{"location":"components/visualization/canvas/#remove","title":"remove()","text":"<p>Removes the canvas from the scene and cleans up resources.</p> <p>Behavior: - Removes plane from its parent in the scene graph - Unsubscribes from canvas subject - Unsubscribes from config subject - Only executes if plane has a parent</p> <pre><code>canvas.remove();\n</code></pre>"},{"location":"components/visualization/canvas/#getplane","title":"getPlane()","text":"<p>Returns the THREE.js mesh object representing the canvas.</p> <p>Returns: <code>THREE.Mesh</code> - The canvas plane mesh</p> <pre><code>const plane = canvas.getPlane();\nscene.add(plane);\n</code></pre>"},{"location":"components/visualization/canvas/#reactive-updates","title":"Reactive Updates","text":""},{"location":"components/visualization/canvas/#canvas-subject-integration","title":"Canvas Subject Integration","text":"<p>The canvas subscribes to <code>canvasSubjectGet()</code> and filters events by ID:</p> <p>Matching Criteria: - Event ID matches canvas ID exactly - Event ID is <code>\"*\"</code> (wildcard, targets all canvases)</p> <p>On Canvas Update: 1. Receives ROOT canvas object via <code>obj.obj</code> 2. Converts object to PNG image using JSROOT's <code>makeImage()</code> 3. Image dimensions: 1200\u00d7600 pixels 4. Updates texture with generated PNG</p> <pre><code>import { canvasSubjectGet } from './rxjs/CanvasSubject.js';\n\ncanvasSubjectGet().next({\n id: 'canvas1', // or \"*\" for all canvases\n obj: myRootObj // ROOT object\n});\n</code></pre>"},{"location":"components/visualization/canvas/#config-subject-integration","title":"Config Subject Integration","text":"<p>The canvas subscribes to <code>configSubjectGet()</code> and filters events:</p> <p>Matching Criteria: - Config target ID includes <code>\"*\"</code> OR - Config target ID includes canvas ID</p> <p>On Config Update: Extracts canvas configuration from: </p><pre><code>config.environment.canvas.pos // {x, y, z}\nconfig.environment.canvas.rot // {x, y, z} in degrees\nconfig.environment.canvas.sc // {x, y, z}\n</code></pre><p></p> <p>Then calls <code>updateMesh()</code> to apply transformations.</p>"},{"location":"components/visualization/canvas/#complete-usage-example","title":"Complete Usage Example","text":"<pre><code>import { CanvasClass } from './component/canvas-class.js';\nimport { canvasSubjectGet } from './rxjs/CanvasSubject.js';\nimport { configSubjectGet } from './rxjs/ConfigSubject.js';\nimport { Vector3 } from 'three';\n\n// Create canvas\nconst canvas = new CanvasClass(\n null, // No initial image\n new Vector3(0, 1.6, -2), // Position\n new Vector3(0, 0, 0), // Rotation (degrees)\n new Vector3(1, 0.5, 1), // Scale\n 'canvas1' // ID\n);\n\n// Add to scene\nscene.add(canvas.getPlane());\n\n// Update canvas with ROOT object\ncanvasSubjectGet().next({\n id: 'canvas1',\n obj: myRootCanvas\n});\n\n// Update configuration\nconfigSubjectGet().next({\n target: { id: ['canvas1'] },\n config: {\n environment: {\n canvas: {\n pos: { x: 0, y: 2, z: -3 },\n rot: { x: 0, y: 45, z: 0 },\n sc: { x: 1.5, y: 0.75, z: 1 }\n }\n }\n }\n});\n\n// Manual texture update\ncanvas.updateTexture('data:image/png;base64,...');\n\n// Cleanup\ncanvas.remove();\n</code></pre>"},{"location":"components/visualization/canvas/#jsroot-integration","title":"JSROOT Integration","text":"<p>The canvas uses JSROOT's <code>makeImage()</code> function to convert ROOT canvas objects to PNG:</p> <pre><code>import { makeImage } from 'jsroot';\n\nmakeImage({\n format: \"png\",\n object: rootCanvasObject,\n width: 1200,\n height: 600\n})\n.then(png => {\n canvas.updateTexture(png);\n});\n</code></pre>"},{"location":"components/visualization/canvas/#error-handling","title":"Error Handling","text":"<pre><code>// Warns about null/undefined\ncanvas.updateTexture(null);\n// Console: \"updateTexture called with null/undefined\"\n\n// Errors on unsupported types\ncanvas.updateTexture(123);\n// Console: \"Unsupported image type passed to updateTexture: 123\"\n\n// Handles texture load failures\ncanvas.updateTexture('https://invalid-url.com/image.png');\n// Console: \"Texture load failed\" + error details\n</code></pre>"},{"location":"components/visualization/canvas/#material-configuration","title":"Material Configuration","text":"<p>The canvas uses: - Geometry: <code>THREE.PlaneGeometry</code> (1\u00d71 base, scaled by constructor) - Material: <code>THREE.MeshBasicMaterial</code> - <code>color</code>: <code>0xffffff</code> (white, allows texture colors to show) - <code>side</code>: <code>THREE.DoubleSide</code> (visible from both sides) - <code>map</code>: Updated with canvas texture</p>"},{"location":"components/visualization/canvas/#coordinate-system","title":"Coordinate System","text":"<p>Position: - Standard THREE.js right-handed coordinate system - Y-up orientation</p> <p>Rotation: - Input: Degrees - Conversion: Multiplied by <code>Math.PI / 180</code> - Applied in XYZ order</p> <p>Scale: - Applied directly to geometry - Uniform scale if all components equal - Non-uniform scale supported</p>"},{"location":"components/visualization/canvas/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: <code>Vector3</code>, <code>PlaneGeometry</code>, <code>MeshBasicMaterial</code>, <code>Color</code>, <code>DoubleSide</code>, <code>Mesh</code>, <code>TextureLoader</code></li> <li>JSROOT: <code>makeImage</code></li> <li>RxJS: <code>filter</code> operator</li> <li>Communication: <code>canvasSubjectGet</code>, <code>configSubjectGet</code></li> </ul>"},{"location":"components/visualization/canvas/#related-components","title":"Related Components","text":"<ul> <li>Histogram JSROOT - 3D histogram visualization</li> <li>Canvas Subject - Canvas communication channel</li> <li>Config Subject - Configuration management</li> </ul>"},{"location":"components/visualization/canvas/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/histogram-jsroot/","title":"Histogram JSROOT","text":""},{"location":"components/visualization/histogram-jsroot/#histogramjsrootclass","title":"HistogramJsrootClass","text":""},{"location":"components/visualization/histogram-jsroot/#overview","title":"Overview","text":"<p>The <code>HistogramJsrootClass</code> is a THREE.js-based histogram renderer that uses the JSROOT library to build and render 3D histogram visualizations. It provides interactive raycasting, event handling, bin information display, and supports dynamic configuration updates through RxJS subjects.</p>"},{"location":"components/visualization/histogram-jsroot/#constructor","title":"Constructor","text":"<pre><code>constructor(id, rootObj, camera)\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#parameters","title":"Parameters","text":"<ul> <li>id (<code>string</code>): Unique identifier for the histogram instance</li> <li>rootObj (<code>Object</code>): ROOT object (Not all objects are supported see related issues for more: issue1, issue2)</li> <li>camera (<code>THREE.Camera</code>): THREE.js camera reference for positioning and interaction</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#created-objects","title":"Created Objects","text":"<ul> <li>histogramGroup (<code>THREE.Group</code>): Main container for all ROOT object meshes</li> <li>binInfoComponent (<code>BinInfoVisualizer</code>): Visualizer for displaying bin information</li> <li>configSub (<code>Subscription</code>): Subscribes to configuration updates</li> <li>buildPromise (<code>Promise</code>): Tracks the histogram building process</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#properties","title":"Properties","text":""},{"location":"components/visualization/histogram-jsroot/#core-properties","title":"Core Properties","text":"<ul> <li>id (<code>string</code>): Object identifier</li> <li>rootObj (<code>Object</code>): Current ROOT object</li> <li>camera (<code>THREE.Camera</code>): Camera reference</li> <li>histogramGroup (<code>THREE.Group</code>): THREE.js group containing histogram meshes</li> <li>binInfoComponent (<code>BinInfoVisualizer</code>): Bin information display component</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#event-management","title":"Event Management","text":"<ul> <li>mouseEvents (<code>Array</code>): Collection of registered mouse event handlers</li> <li>defaultRaycastHandler (<code>Function</code>): Original THREE.js raycast function</li> <li>color (<code>THREE.Color</code>): Current color for bin highlighting</li> <li>colorTarget (<code>THREE.Color</code>): Target color for hover effects (cyan)</li> <li>dirtyInstance (<code>number</code>): Index of currently highlighted bin</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#subscriptions","title":"Subscriptions","text":"<ul> <li>configSub (<code>Subscription</code>): Configuration updates from <code>configSubjectGet()</code></li> <li>sub (<code>Subscription</code>): Function/event management from <code>functionSubjectGet()</code></li> </ul>"},{"location":"components/visualization/histogram-jsroot/#methods","title":"Methods","text":""},{"location":"components/visualization/histogram-jsroot/#updatehistogramhisto","title":"updateHistogram(histo)","text":"<p>Updates the histogram with new ROOT data.</p> <p>Parameters: - histo (<code>Object</code>): New ROOT object</p> <p>Behavior: - Clears existing Three.js group - Rebuilds visualization with new data - Maintains camera and configuration settings</p>"},{"location":"components/visualization/histogram-jsroot/#renderwithbuild3d","title":"renderWithBuild3d()","text":"<p>Renders the histogram using JSROOT's <code>build3d</code> function.</p> <p>Returns: <code>Promise<void></code></p> <p>Behavior: - Uses JSROOT <code>build3d()</code> to create THREE.js meshes - Applies scaling and rotation based on configuration - Adjusts position and orientation for proper display - Sets up custom raycast handler for interaction - Handles errors during build process</p>"},{"location":"components/visualization/histogram-jsroot/#raycasthandlerraycaster-intersects","title":"raycastHandler(raycaster, intersects)","text":"<p>Custom raycaster implementation for histogram interaction.</p> <p>Parameters: - raycaster (<code>THREE.Raycaster</code>): THREE.js raycaster instance - intersects (<code>Array</code>): Array of intersection objects</p> <p>Behavior: - Processes raycaster intersections with histogram bins - Calculates bin indices from instanced mesh intersections - Computes bin content, error, and coordinate ranges - Triggers registered mouse event handlers - Updates bin information display</p>"},{"location":"components/visualization/histogram-jsroot/#addeventevent-func","title":"addEvent(event, func)","text":"<p>Registers an event handler for mouse or keyboard interactions.</p> <p>Parameters: - event (<code>string | Object</code>): Event name or keyboard event object with <code>state</code> and <code>key</code> - func (<code>Function</code>): Handler function to execute</p> <p>Supported Events: - <code>\"mouseclick\"</code> - Single click - <code>\"shiftmouseclick\"</code> - Shift + click - <code>\"mousedbclick\"</code> - Double click - <code>\"shiftmousedbclick\"</code> - Shift + double click - <code>\"mousemove\"</code> - Mouse movement - Keyboard events with <code>{state: \"keydown\"|\"keyup\", key: \"KeyCode\"}</code></p>"},{"location":"components/visualization/histogram-jsroot/#removeeventevent-func","title":"removeEvent(event, func)","text":"<p>Removes a registered event handler.</p> <p>Parameters: - event (<code>string | Object</code>): Event identifier - func (<code>Function</code>): Handler function reference to remove</p>"},{"location":"components/visualization/histogram-jsroot/#default-event-handlers","title":"Default Event Handlers","text":""},{"location":"components/visualization/histogram-jsroot/#mouseclickdefaultevent","title":"mouseClickDefault(event)","text":"<p>Default handler for mouse clicks.</p>"},{"location":"components/visualization/histogram-jsroot/#mousemovedefaultevent","title":"mousemoveDefault(event)","text":"<p>Highlights hovered bins and displays bin information.</p> <p>Behavior: - Applies color tint to hovered bin - Reverts color of previously hovered bin - Updates <code>BinInfoVisualizer</code> with bin data - Publishes bin information to <code>binInfoSubjectGet()</code></p>"},{"location":"components/visualization/histogram-jsroot/#shiftmouseclickdefaultevent","title":"shiftMouseClickDefault(event)","text":"<p>Default handler for shift+click events.</p>"},{"location":"components/visualization/histogram-jsroot/#mousedbclickdefaultevent","title":"mouseDBClickDefault(event)","text":"<p>Default handler for double-click events.</p>"},{"location":"components/visualization/histogram-jsroot/#shiftmousedbclickdefaultevent","title":"shiftMouseDBClickDefault(event)","text":"<p>Default handler for shift+double-click events.</p>"},{"location":"components/visualization/histogram-jsroot/#getinstancedmeshnode","title":"getInstancedMesh(node)","text":"<p>Recursively searches for the InstancedMesh in the histogram group.</p> <p>Parameters: - node (<code>THREE.Object3D</code>): Starting node (defaults to <code>histogramGroup</code>)</p> <p>Returns: <code>THREE.InstancedMesh | null</code></p>"},{"location":"components/visualization/histogram-jsroot/#getrangebypositionposition","title":"getRangeByPosition(position)","text":"<p>Calculates axis ranges for given bin positions.</p> <p>Parameters: - position (<code>Array<Object></code>): Array of position objects with <code>x</code>, <code>y</code>, <code>z</code> properties</p> <p>Returns: <code>Object</code> with axis ranges including <code>min</code>, <code>max</code>, <code>name</code>, <code>title</code>, and <code>color</code></p>"},{"location":"components/visualization/histogram-jsroot/#remove","title":"remove()","text":"<p>Cleans up all resources and subscriptions.</p> <p>Behavior: - Removes histogram group from parent - Removes dummy DOM element - Unsubscribes from all RxJS subscriptions - Disposes of THREE.js resources</p>"},{"location":"components/visualization/histogram-jsroot/#gethistogrammesh","title":"getHistogramMesh()","text":"<p>Returns the main histogram group.</p> <p>Returns: <code>THREE.Group</code></p>"},{"location":"components/visualization/histogram-jsroot/#usage-example","title":"Usage Example","text":"<pre><code>import { HistogramJsrootClass } from './histogram-jsroot-class.js';\nimport * as THREE from 'three';\n\n// Create a scene and camera\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\n\n// Create histogram instance\nconst histogram = new HistogramJsrootClass('histogram1', rootHistogramObject, camera);\n\n// Add to scene\nscene.add(histogram.getHistogramMesh());\n\n// Update with new data\nhistogram.updateHistogram(newRootHistogramObject);\n\n// Add custom event handler\nhistogram.addEvent('mouseclick', (intersection, histogramInstance) => {\n console.log('Clicked bin:', intersection.coords);\n console.log('Bin content:', intersection.content);\n});\n\n// Clean up when done\nhistogram.remove();\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#event-system","title":"Event System","text":"<p>The class integrates with <code>functionSubjectGet()</code> for dynamic event management:</p> <pre><code>import { functionSubjectGet } from '../rxjs/FunctionSubject.js';\n\n// Add event handler remotely\nfunctionSubjectGet().next({\n flag: 'add',\n target: { id: 'histogram1' },\n event: 'mouseclick',\n function: (intersection, histogram) => {\n console.log('Remote click handler', intersection);\n }\n});\n\n// Remove event handler\nfunctionSubjectGet().next({\n flag: 'remove',\n target: { id: 'histogram1' },\n event: 'mouseclick'\n});\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#configuration-integration","title":"Configuration Integration","text":"<p>The class subscribes to <code>configSubjectGet()</code> for dynamic updates:</p> <pre><code>import { configSubjectGet } from '../rxjs/ConfigSubject.js';\n\n// Update histogram position and scale\nconfigSubjectGet().next({\n target: { id: 'histogram1' },\n config: {\n environment: {\n histogramPads: [{\n id: 'histogram1',\n pos: { x: 0, y: 1, z: -2 },\n sc: { x: 1, y: 1, z: 1 }\n }]\n }\n }\n});\n</code></pre>"},{"location":"components/visualization/histogram-jsroot/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: Core 3D rendering (<code>Group</code>, <code>Color</code>, <code>Box3</code>, <code>Vector3</code>)</li> <li>JSROOT: <code>build3d</code> for histogram rendering</li> <li>RxJS: <code>filter</code> operator, Subject subscriptions</li> <li>Internal Classes:</li> <li><code>BinInfoVisualizer</code> from <code>./bininfo-jsroot-class.js</code></li> <li>RxJS Subjects:</li> <li><code>configSubjectGet</code> from <code>../rxjs/ConfigSubject.js</code></li> <li><code>functionSubjectGet</code> from <code>../rxjs/FunctionSubject.js</code></li> <li><code>canvasSubjectGet</code> from <code>../rxjs/CanvasSubject.js</code></li> <li><code>binInfoSubjectGet</code> from <code>../rxjs/BinInfoSubject.js</code></li> </ul>"},{"location":"components/visualization/histogram-jsroot/#related-components","title":"Related Components","text":"<ul> <li>BinInfoVisualizer - Bin information display</li> <li>THnPainter - Alternative histogram rendering</li> <li>NdmvrRaycaster - Scene-level raycasting</li> </ul>"},{"location":"components/visualization/histogram-jsroot/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/ndmvr-raycaster/","title":"NDMVR raycaster","text":""},{"location":"components/visualization/ndmvr-raycaster/#ndmvrraycaster","title":"NdmvrRaycaster","text":""},{"location":"components/visualization/ndmvr-raycaster/#overview","title":"Overview","text":"<p>The <code>NdmvrRaycaster</code> class provides scene-level raycasting functionality for THREE.js applications. It handles mouse movement and click events, casting rays through the scene to detect intersections with 3D objects. The class distinguishes between single clicks, double clicks, and shift-modified clicks, and assigns a <code>_triggerSource</code> property to the raycaster for event identification.</p>"},{"location":"components/visualization/ndmvr-raycaster/#constructor","title":"Constructor","text":"<pre><code>constructor(scene, rendererElement)\n</code></pre>"},{"location":"components/visualization/ndmvr-raycaster/#parameters","title":"Parameters","text":"<ul> <li>scene (<code>THREE.Object3D</code>): THREE.js scene or object to traverse for intersections. Must contain a camera as a child.</li> <li>rendererElement (<code>HTMLElement</code>): DOM element attached to the renderer, used for mouse coordinate calculations</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#initialization","title":"Initialization","text":"<ul> <li>Sets up mouse tracking with <code>THREE.Vector2</code></li> <li>Creates <code>THREE.Raycaster</code> instance</li> <li>Finds camera in scene hierarchy</li> <li>Configures double-click timeout (default 190ms)</li> <li>Subscribes to configuration updates via <code>configSubjectGet()</code></li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#properties","title":"Properties","text":""},{"location":"components/visualization/ndmvr-raycaster/#core-properties","title":"Core Properties","text":"<ul> <li>raycaster (<code>THREE.Raycaster</code>): THREE.js raycaster instance</li> <li>mouse (<code>THREE.Vector2</code>): Normalized device coordinates (-1 to +1)</li> <li>cameraElement (<code>THREE.Camera</code>): Camera found in scene hierarchy</li> <li>sceneElement (<code>THREE.Object3D</code>): Scene object to raycast against</li> <li>rendererElement (<code>HTMLElement</code>): Renderer DOM element reference</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#event-management","title":"Event Management","text":"<ul> <li>singleClickTimer (<code>number | null</code>): Timeout ID for single click detection</li> <li>dbClickTimeout (<code>number</code>): Double-click threshold in milliseconds (default 190)</li> <li>lastClick (<code>number</code>): Timestamp of last click for double-click detection</li> <li>raycastOn (<code>boolean</code>): Toggle for enabling/disabling raycasting</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#performance","title":"Performance","text":"<ul> <li>checkInterval (<code>number</code>): Minimum interval between mousemove checks (1ms)</li> <li>lastCheck (<code>number</code>): Timestamp of last mousemove check</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#subscriptions","title":"Subscriptions","text":"<ul> <li>configSub (<code>RxJS.Subscription</code>): Configuration updates subscription</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#methods","title":"Methods","text":""},{"location":"components/visualization/ndmvr-raycaster/#setupraycasting","title":"setupRaycasting()","text":"<p>Initializes event listeners for mouse interactions.</p> <p>Behavior: - Adds <code>mousemove</code> event listener to window - Adds <code>click</code> event listener to window - Binds event handlers to instance context</p>"},{"location":"components/visualization/ndmvr-raycaster/#destroyraycasting","title":"destroyRaycasting()","text":"<p>Removes event listeners for mouse interactions.</p> <p>Behavior: - Removes <code>mousemove</code> event listener - Removes <code>click</code> event listener</p>"},{"location":"components/visualization/ndmvr-raycaster/#toggleraycasting","title":"toggleRaycasting()","text":"<p>Toggles raycasting on/off.</p> <p>Behavior: - If enabled, calls <code>setupRaycasting()</code> - If disabled, calls <code>destroyRaycasting()</code> - Updates <code>raycastOn</code> state</p>"},{"location":"components/visualization/ndmvr-raycaster/#mousemoveeventhandleevent","title":"mousemoveEventHandle(event)","text":"<p>Handles mouse movement with throttling.</p> <p>Parameters: - event (<code>MouseEvent</code>): Browser mouse event</p> <p>Behavior: - Throttles updates based on <code>checkInterval</code> - Prevents excessive raycasting during fast mouse movement - Calls <code>updateRaycaster()</code> at controlled intervals</p>"},{"location":"components/visualization/ndmvr-raycaster/#clickeventhandleevent","title":"clickEventHandle(event)","text":"<p>Handles click and double-click detection.</p> <p>Parameters: - event (<code>MouseEvent</code>): Browser click event</p> <p>Behavior: - Calculates normalized mouse coordinates - Detects double-clicks based on <code>dbClickTimeout</code> - Distinguishes shift-modified clicks - Sets <code>raycaster._triggerSource</code> to: - <code>\"mouseclick\"</code> - Single click - <code>\"shiftmouseclick\"</code> - Shift + click - <code>\"mousedbclick\"</code> - Double click - <code>\"shiftmousedbclick\"</code> - Shift + double click - Calls <code>handleRaycast()</code> with appropriate delay</p>"},{"location":"components/visualization/ndmvr-raycaster/#updateraycasterevent","title":"updateRaycaster(event)","text":"<p>Updates raycaster with current mouse position.</p> <p>Parameters: - event (<code>MouseEvent</code>): Browser mouse event</p> <p>Behavior: - Converts mouse coordinates to normalized device coordinates - Updates <code>raycaster.setFromCamera()</code> - Sets <code>_triggerSource</code> to <code>\"mousemove\"</code> - Performs intersection test with scene children</p>"},{"location":"components/visualization/ndmvr-raycaster/#handleraycast","title":"handleRaycast()","text":"<p>Processes raycaster intersections.</p> <p>Behavior: - Calls <code>raycaster.intersectObjects()</code> on scene children - Recursively checks all descendants - Returns array of intersections</p> <p>Note: Individual objects handle their own raycast logic through custom <code>raycast</code> methods.</p>"},{"location":"components/visualization/ndmvr-raycaster/#trigger-source-identification","title":"Trigger Source Identification","text":"<p>The raycaster's <code>_triggerSource</code> property identifies the event type:</p> Trigger Source Description <code>\"mousemove\"</code> Mouse cursor movement <code>\"mouseclick\"</code> Single left click <code>\"shiftmouseclick\"</code> Shift + left click <code>\"mousedbclick\"</code> Double left click <code>\"shiftmousedbclick\"</code> Shift + double click <p>Custom <code>raycast</code> methods on objects can check this property: </p><pre><code>mesh.raycast = function(raycaster, intersects) {\n // Call default raycast\n this.defaultRaycast(raycaster, intersects);\n\n // Handle based on trigger\n if (raycaster._triggerSource === 'mouseclick') {\n console.log('Object was clicked');\n }\n};\n</code></pre><p></p>"},{"location":"components/visualization/ndmvr-raycaster/#usage-example","title":"Usage Example","text":"<pre><code>import { NdmvrRaycaster } from './ndmvr-raycaster-class.js';\nimport * as THREE from 'three';\n\n// Create scene with camera\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\ncamera.pos.z = 5;\nscene.add(camera);\n\n// Create renderer\nconst renderer = new THREE.WebGLRenderer();\nrenderer.setSize(window.innerWidth, window.innerHeight);\ndocument.body.appendChild(renderer.domElement);\n\n// Create raycaster\nconst raycaster = new NdmvrRaycaster(scene, renderer.domElement);\n\n// Add interactive object with custom raycast handler\nconst geometry = new THREE.BoxGeometry();\nconst material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });\nconst cube = new THREE.Mesh(geometry, material);\n\n// Store original raycast\ncube.defaultRaycast = cube.raycast.bind(cube);\n\n// Custom raycast handler\ncube.raycast = function(raycaster, intersects) {\n this.defaultRaycast(raycaster, intersects);\n\n if (intersects.length > 0) {\n const trigger = raycaster._triggerSource;\n console.log(`Cube intersected by: ${trigger}`);\n\n if (trigger === 'mouseclick') {\n this.material.color.set(0xff0000);\n } else if (trigger === 'mousedbclick') {\n this.material.color.set(0x0000ff);\n }\n }\n};\n\nscene.add(cube);\n\n// Animation loop\nfunction animate() {\n requestAnimationFrame(animate);\n renderer.render(scene, camera);\n}\nanimate();\n</code></pre>"},{"location":"components/visualization/ndmvr-raycaster/#configuration-integration","title":"Configuration Integration","text":"<p>The raycaster subscribes to configuration updates for double-click timing:</p> <pre><code>import { configSubjectGet } from '../rxjs/ConfigSubject.js';\n\n// Update double-click timeout\nconfigSubjectGet().next({\n config: {\n environment: {\n dbClickTimeout: 250 // milliseconds\n }\n }\n});\n</code></pre>"},{"location":"components/visualization/ndmvr-raycaster/#performance-optimization","title":"Performance Optimization","text":""},{"location":"components/visualization/ndmvr-raycaster/#mousemove-throttling","title":"Mousemove Throttling","text":"<p>The <code>checkInterval</code> property (default 1ms) throttles mousemove events: </p><pre><code>const now = performance.now();\nif (now - this.lastCheck < this.checkInterval) return;\nthis.lastCheck = now;\n</code></pre><p></p> <p>This prevents excessive raycasting during fast mouse movement.</p>"},{"location":"components/visualization/ndmvr-raycaster/#double-click-detection","title":"Double-Click Detection","text":"<p>Single clicks are delayed by <code>dbClickTimeout</code> to allow double-click detection: - First click: Wait for timeout before triggering single click - Second click within timeout: Clear single click timer, trigger double click - Second click after timeout: Treated as new single click</p>"},{"location":"components/visualization/ndmvr-raycaster/#integration-with-histogram-classes","title":"Integration with Histogram Classes","text":"<p>Both <code>HistogramJsrootClass</code> and <code>THnPainter</code> override the default mesh <code>raycast</code> method to implement custom intersection handling:</p> <pre><code>// In HistogramJsrootClass\nconst mesh = this.getInstancedMesh();\nif (mesh) {\n this.defaultRaycastHandler = mesh.raycast.bind(mesh);\n mesh.raycast = this.raycastHandler.bind(this);\n}\n</code></pre> <p>The histogram's custom raycast handler: 1. Calls default THREE.js raycast 2. Filters intersections for instanced meshes 3. Calculates bin indices from instance IDs 4. Triggers registered event handlers based on <code>_triggerSource</code></p>"},{"location":"components/visualization/ndmvr-raycaster/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: <code>Raycaster</code>, <code>Vector2</code></li> <li>RxJS: Configuration subscription</li> <li>RxJS Subjects:</li> <li><code>configSubjectGet</code> from <code>../rxjs/ConfigSubject.js</code></li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#related-components","title":"Related Components","text":"<ul> <li>HistogramJsrootClass - Uses custom raycast handlers</li> <li>THnPainter - Uses custom raycast handlers with BVH optimization</li> <li>BinInfoVisualizer - Receives data from raycast events</li> </ul>"},{"location":"components/visualization/ndmvr-raycaster/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"},{"location":"components/visualization/thnpainter/","title":"THnPainter","text":""},{"location":"components/visualization/thnpainter/#thnpainter","title":"THnPainter","text":""},{"location":"components/visualization/thnpainter/#overview","title":"Overview","text":"<p>The <code>THnPainter</code> class is a high-performance THREE.js histogram renderer for nested ROOT histograms (THn). It uses instanced rendering with custom shaders, implements BVH (Bounding Volume Hierarchy) for optimized raycasting, and supports hierarchical navigation through nested histogram layers. The class extends <code>TPainter</code> and integrates with RxJS subjects for state management and bin information display.</p>"},{"location":"components/visualization/thnpainter/#constructor","title":"Constructor","text":"<pre><code>constructor(histo, id, opts)\n</code></pre>"},{"location":"components/visualization/thnpainter/#parameters","title":"Parameters","text":"<ul> <li>histo (<code>Object</code>): ROOT histogram object (TH1, TH2, TH3, or THn)</li> <li>id (<code>string</code>): Unique identifier for the histogram instance</li> <li>opts (<code>Object</code>): Configuration options (extends from <code>TPainter</code>)</li> </ul>"},{"location":"components/visualization/thnpainter/#initialization","title":"Initialization","text":"<ul> <li>Creates <code>HistogramPointerClass</code> for navigation</li> <li>Subscribes to <code>stateSubjectGet()</code> for state changes</li> <li>Initializes instanced buffer geometry</li> <li>Creates wireframe representation</li> <li>Renders initial layer (layer 0)</li> </ul>"},{"location":"components/visualization/thnpainter/#properties","title":"Properties","text":""},{"location":"components/visualization/thnpainter/#core-components","title":"Core Components","text":"<ul> <li>pointer (<code>HistogramPointerClass</code>): Manages navigation through nested histogram hierarchy</li> <li>wireframe (<code>HistogramWireframeClass</code>): Bin outlines visualization component</li> <li>mesh (<code>THREE.Mesh</code>): Main instanced mesh with custom shader material</li> <li>instGeom (<code>THREE.InstancedBufferGeometry</code>): Geometry with per-instance attributes</li> <li>material (<code>THREE.ShaderMaterial</code>): Custom shader for gradient coloring</li> </ul>"},{"location":"components/visualization/thnpainter/#rendering-data","title":"Rendering Data","text":"<ul> <li>matrixCache (<code>Array</code>): Cached position/scale data for all instances per layer</li> <li>BVHTree (<code>Array</code>): Bounding volume hierarchy for optimized raycasting</li> <li>maxInstancesPerLayer (<code>Array<number></code>): Maximum instances per hierarchy layer</li> <li>maxContentPerLayer (<code>Object</code>): Maximum bin content per layer and set</li> <li>minContentPerLayer (<code>Object</code>): Minimum bin content per layer and set</li> <li>totalInstances (<code>number</code>): Total number of instances across all layers</li> </ul>"},{"location":"components/visualization/thnpainter/#instance-attributes","title":"Instance Attributes","text":"<ul> <li>instancePositions (<code>Float32Array</code>): Per-instance position data (x, y, z)</li> <li>instanceScales (<code>Float32Array</code>): Per-instance scale data (x, y, z)</li> <li>instanceColors (<code>Float32Array</code>): Per-instance color indices for gradient</li> <li>colorArray (<code>Float32Array</code>): Color palette for gradient shader (32 colors \u00d7 6 channels)</li> </ul>"},{"location":"components/visualization/thnpainter/#state-management","title":"State Management","text":"<ul> <li>selectedSet (<code>Array<string></code>): Currently selected data sets</li> <li>selectedArray (<code>string</code>): Currently selected array ('content' or custom arrays)</li> <li>availableSets (<code>Array<string></code>): Available data sets in histogram</li> <li>renderHistory (<code>Array<Object></code>): History of render operations for state reconstruction</li> <li>stateSub (<code>RxJS.Subscription</code>): State change subscription</li> </ul>"},{"location":"components/visualization/thnpainter/#interaction","title":"Interaction","text":"<ul> <li>dirtyInstance (<code>Array</code>): Indices of currently highlighted instances</li> </ul>"},{"location":"components/visualization/thnpainter/#methods","title":"Methods","text":""},{"location":"components/visualization/thnpainter/#updatehistogramhisto","title":"updateHistogram(histo)","text":"<p>Updates the histogram with new data.</p> <p>Parameters: - histo (<code>Object</code>): Object containing new ROOT histogram in <code>obj</code> property</p> <p>Behavior: - Preserves raycast handler - Clears caches and trees - Disposes old geometry and wireframe - Reinitializes with new data - Re-renders histogram</p>"},{"location":"components/visualization/thnpainter/#remove","title":"remove()","text":"<p>Cleans up all resources.</p> <p>Behavior: - Calls <code>super.remove()</code> - Clears matrix cache - Disposes geometry - Removes mesh from parent - Disposes wireframe - Unsubscribes from state subject</p>"},{"location":"components/visualization/thnpainter/#init","title":"init()","text":"<p>Initializes histogram rendering data structures.</p> <p>Behavior: - Sets available sets and arrays from histogram - Computes max/min content per layer - Calculates total instance count - Sets up instanced buffer geometry - Creates wireframe with configuration</p>"},{"location":"components/visualization/thnpainter/#setupmatrixcache","title":"setupMatrixCache()","text":"<p>Creates cache structure for instance transformations.</p> <p>Structure: - Array of objects per layer - Each layer contains <code>pos</code>, <code>scale</code>, and <code>rendered</code> arrays - For histograms with sets, last layer is array of objects per set - Pre-allocated Float32Arrays for performance</p>"},{"location":"components/visualization/thnpainter/#setupinsbufgeom","title":"setupInsBufGeom()","text":"<p>Creates instanced buffer geometry with custom attributes.</p> <p>Behavior: - Calls <code>setupMatrixCache()</code> - Creates <code>InstancedBufferGeometry</code> with base box - Sets up instance attributes: position, scale, colorIndex - Creates custom shader material - Fills color array from configuration - Creates mesh with custom raycast handler</p>"},{"location":"components/visualization/thnpainter/#renderhistogramstartindex-endindex-layer","title":"renderHistogram(startIndex, endIndex, layer)","text":"<p>Renders histogram bins for specified range and layer.</p> <p>Parameters: - startIndex (<code>number</code>): Linear index to start rendering from - endIndex (<code>number</code>): Linear index to end rendering at - layer (<code>number</code>): Hierarchy layer to render (0 = top layer)</p> <p>Behavior: - Recursively processes histogram hierarchy - Calculates bin positions and scales based on content - Applies padding and scale factors from configuration - Handles TH1, TH2, and TH3 differently - Updates matrix cache with rendered instances - Creates BVH tree for raycasting - Updates wireframe visualization</p> <p>Rendering Logic: - Content scaling: Maps bin content to scale factor (min/max) - Gradient coloring: Maps content to color gradient - Layer-specific padding and scale configuration - Set-based rendering for multi-dataset histograms</p>"},{"location":"components/visualization/thnpainter/#pushvisibleinstances","title":"pushVisibleInstances()","text":"<p>Transfers visible instances from cache to GPU.</p> <p>Behavior: - Counts visible instances (rendered !== -1) - Pre-allocates exact-size arrays - Copies position, scale, and color data - Creates new <code>InstancedBufferGeometry</code> - Updates mesh with new geometry - Disposes old geometry</p>"},{"location":"components/visualization/thnpainter/#setmatrixcacheatlayer-setindex-index-binsizepos-rendered","title":"setMatrixCacheAt(layer, setIndex, index, binSizePos, rendered)","text":"<p>Updates matrix cache at specific position.</p> <p>Parameters: - layer (<code>number</code>): Layer index - setIndex (<code>number | null</code>): Set index or null for content - index (<code>number</code>): Instance index within layer - binSizePos (<code>Object</code>): Position and size for x, y, z axes - rendered (<code>number</code>): Color index (-1 for hidden)</p>"},{"location":"components/visualization/thnpainter/#creatematerial","title":"createMaterial()","text":"<p>Creates custom shader material with gradient support.</p> <p>Returns: <code>THREE.ShaderMaterial</code></p> <p>Features: - Vertex shader applies per-instance transformations - Fragment shader renders per-instance colors - Supports 32-color gradient array - Smooth interpolation between gradient stops</p> <p>Shader Code: </p><pre><code>// Vertex Shader\nattribute vec3 instancePosition;\nattribute vec3 instanceScale;\nattribute float instanceColorIndex;\nuniform vec3 colorArray[32];\nvarying vec3 vColor;\n\nvoid main() {\n vec3 transformed = position * instanceScale + instancePosition;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n\n float colorIndex = clamp(instanceColorIndex, 0.0, 31.9999);\n int idx0 = int(floor(colorIndex));\n int idx1 = int(ceil(colorIndex));\n float t = fract(colorIndex);\n vColor = mix(colorArray[idx0], colorArray[idx1], t);\n}\n\n// Fragment Shader\nvarying vec3 vColor;\nvoid main() {\n gl_FragColor = vec4(vColor, 1.0);\n}\n</code></pre><p></p>"},{"location":"components/visualization/thnpainter/#event-handlers","title":"Event Handlers","text":""},{"location":"components/visualization/thnpainter/#mouseclickdefaultevent","title":"mouseClickDefault(event)","text":"<p>Shows child histogram and publishes to canvas subject.</p>"},{"location":"components/visualization/thnpainter/#mousemovedefaultevent","title":"mousemoveDefault(event)","text":"<p>Updates bin information display via <code>binInfoSubjectGet()</code>.</p> <p>Behavior: - Checks if index changed from previous event - Merges parent path with current event - Publishes minimized event with coords, content, error, set</p>"},{"location":"components/visualization/thnpainter/#shiftmouseclickdefaultevent","title":"shiftMouseClickDefault(event)","text":"<p>Hides child histogram.</p>"},{"location":"components/visualization/thnpainter/#mousedbclickdefaultevent","title":"mouseDBClickDefault(event)","text":"<p>Navigates to child histogram (drill-down).</p>"},{"location":"components/visualization/thnpainter/#shiftmousedbclickdefaultevent","title":"shiftMouseDBClickDefault(event)","text":"<p>Navigates to parent histogram (drill-up).</p>"},{"location":"components/visualization/thnpainter/#navigation-methods","title":"Navigation Methods","text":""},{"location":"components/visualization/thnpainter/#setpointertochildposition-set","title":"setPointerToChild(position, set)","text":"<p>Drills down into nested histogram.</p> <p>Parameters: - position (<code>Array<Object></code>): Array of positions with x, y, z - set (<code>string</code>): Set name if applicable</p> <p>Behavior: - Clears matrix cache and BVH tree - Disposes old geometry and wireframe - Updates pointer to child - Reinitializes and renders new view</p>"},{"location":"components/visualization/thnpainter/#setpointertoparent","title":"setPointerToParent()","text":"<p>Navigates back to parent histogram.</p> <p>Behavior: - Clears matrix cache and BVH tree - Disposes old geometry and wireframe - Updates pointer to parent - Reinitializes and renders new view</p>"},{"location":"components/visualization/thnpainter/#showchildhistogramposition","title":"showChildHistogram(position)","text":"<p>Renders child histogram without navigation.</p> <p>Parameters: - position (<code>Array<Object></code>): Bin positions</p> <p>Behavior: - Calculates index and range - Calls <code>renderHistogram()</code> for next layer - Adds detailed histogram view while keeping parent</p>"},{"location":"components/visualization/thnpainter/#hidechildhistogramposition","title":"hideChildHistogram(position)","text":"<p>Hides child histogram and shows parent bin.</p> <p>Parameters: - position (<code>Array<Object></code>): Bin positions</p> <p>Behavior: - Clears matrix cache range for child - Re-renders parent layer - Restores bin representation</p>"},{"location":"components/visualization/thnpainter/#raycasting","title":"Raycasting","text":""},{"location":"components/visualization/thnpainter/#raycasthandlerraycaster","title":"raycastHandler(raycaster)","text":"<p>Custom raycast implementation using BVH tree.</p> <p>Parameters: - raycaster (<code>THREE.Raycaster</code>): Raycaster with <code>_triggerSource</code> property</p> <p>Behavior: - Uses <code>checkIntersectionBVH()</code> for optimized intersection - Calls <code>intersectionHandler()</code> with results - Handles trigger source identification</p>"},{"location":"components/visualization/thnpainter/#checkintersectionbvhray","title":"checkIntersectionBVH(ray)","text":"<p>Performs BVH-accelerated ray intersection.</p> <p>Parameters: - ray (<code>THREE.Ray</code>): Ray to test intersections</p> <p>Returns: <code>Array<Object></code> - Sorted intersections with metadata</p> <p>Algorithm: 1. Recursively traverses BVH tree 2. Tests ray against bounding boxes 3. Uses DFS for leaf node discovery 4. Sorts results by distance 5. Returns intersection with full context: - <code>index</code>: Multi-dimensional bin position - <code>instanceId</code>: Linear instance index - <code>jsrootInstance</code>: JSROOT bin indices - <code>range</code>: Axis ranges - <code>content</code>: Bin content value - <code>error</code>: Bin error - <code>set</code>: Dataset name (if applicable)</p>"},{"location":"components/visualization/thnpainter/#state-management_1","title":"State Management","text":""},{"location":"components/visualization/thnpainter/#handlestatechangestate","title":"handleStateChange(state)","text":"<p>Responds to state changes from <code>stateSubjectGet()</code>.</p> <p>Parameters: - state (<code>Object</code>): New state with sets, selectedSet, arrays, selectedArray</p> <p>Behavior: - Checks if sets/arrays changed - Updates selected set and array - Re-renders histogram if needed - Replays render history for consistency</p>"},{"location":"components/visualization/thnpainter/#setavailablesetsorigin","title":"setAvailableSets(origin)","text":"<p>Recursively finds available sets in histogram.</p> <p>Parameters: - origin (<code>Object</code>): ROOT histogram object</p> <p>Behavior: - Traverses histogram hierarchy - Identifies data sets in <code>children</code> property - Updates <code>stateSubjectGet()</code> with available sets</p>"},{"location":"components/visualization/thnpainter/#setavailablearraysorigin","title":"setAvailableArrays(origin)","text":"<p>Identifies custom arrays in histogram.</p> <p>Parameters: - origin (<code>Object</code>): ROOT histogram object</p> <p>Behavior: - Extracts keys from <code>fArrays</code> property - Always includes 'content' as first option - Updates <code>stateSubjectGet()</code> with available arrays</p>"},{"location":"components/visualization/thnpainter/#utility-methods","title":"Utility Methods","text":""},{"location":"components/visualization/thnpainter/#clearmatrixcacherangestartindex-multiplier-dimensions-baselayerindex","title":"clearMatrixCacheRange(startIndex, multiplier, dimensions, baseLayerIndex)","text":"<p>Clears matrix cache for a range of bins.</p> <p>Parameters: - startIndex (<code>number</code>): Starting linear index - multiplier (<code>number</code>): Range size - dimensions (<code>Array<number></code>): Instances per layer - baseLayerIndex (<code>number</code>): Starting layer</p>"},{"location":"components/visualization/thnpainter/#getbincontentobj-posx-posy-posz-selectedarray","title":"getBinContent(obj, posX, posY, posZ, selectedArray)","text":"<p>Gets bin content from histogram or custom array.</p> <p>Parameters: - obj (<code>Object</code>): ROOT histogram object - posX, posY, posZ (<code>number</code>): Bin positions (0-indexed) - selectedArray (<code>string</code>): Array name</p> <p>Returns: <code>number</code> - Bin content value</p>"},{"location":"components/visualization/thnpainter/#logrenderobj","title":"logRender(obj)","text":"<p>Logs render operations to history.</p> <p>Parameters: - obj (<code>Object</code>): Render operation details</p> <p>Purpose: Enables state reconstruction when selected set changes</p>"},{"location":"components/visualization/thnpainter/#keydownhandlerevent","title":"keyDownHandler(event)","text":"<p>Handles keyboard shortcuts.</p> <p>Supported Keys: - <code>Digit1-9</code> or <code>Numpad1-9</code>: Render specific layer - Configured <code>hideOutlines</code>: Toggle wireframe visibility - Configured <code>resetHistogram</code>: Reset to original view - Configured <code>goToPreviousLayer</code>: Navigate to parent</p>"},{"location":"components/visualization/thnpainter/#resethistogram","title":"resetHistogram()","text":"<p>Resets histogram to initial state.</p> <p>Behavior: - Calls <code>updateHistogram()</code> with original root object - Clears navigation history - Returns to top-level view</p>"},{"location":"components/visualization/thnpainter/#usage-example","title":"Usage Example","text":"<pre><code>import { THnPainter } from './THnPainter.js';\nimport * as THREE from 'three';\n\n// Create scene\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\n\n// Create histogram painter\nconst painter = new THnPainter(rootHistogramObject, 'histogram1', {\n padding: {\n default: { x: 0.1, y: 0.1, z: 0.1 },\n layer: [{ x: 0.05, y: 0.05, z: 0.05 }]\n },\n sc: {\n default: { min: 0.1, max: 1.0 }\n },\n color: {\n gradient: ['#0000ff', '#00ffff', '#00ff00', '#ffff00', '#ff0000']\n },\n wireframe: {\n visible: true,\n color: 0x888888\n }\n});\n\n// Add to scene\nscene.add(painter.mesh);\nscene.add(painter.wireframe.wireframe);\n\n// Custom event handler\npainter.addEvent('mouseclick', (intersection, painterInstance) => {\n console.log('Clicked bin:', intersection.index);\n console.log('Content:', intersection.content);\n console.log('Range:', intersection.range);\n});\n\n// Drill down to child\npainter.mouseDBClickDefault({\n index: [{ x: 5, y: 3, z: 2 }],\n set: 'dataSet1'\n});\n\n// Navigate back\npainter.shiftMouseDBClickDefault({});\n\n// Update with new data\npainter.updateHistogram({ obj: newRootHistogramObject });\n\n// Clean up\npainter.remove();\n</code></pre>"},{"location":"components/visualization/thnpainter/#state-integration","title":"State Integration","text":"<pre><code>import { stateSubjectGet } from '../rxjs/StateSubject.js';\n\n// Get current state\nconst currentState = stateSubjectGet().getValue();\nconsole.log('Available sets:', currentState.sets);\nconsole.log('Selected set:', currentState.selectedSet);\nconsole.log('Available arrays:', currentState.arrays);\n\n// Change selected set\nstateSubjectGet().next({\n ...currentState,\n selectedSet: ['dataSet2', 'dataSet3']\n});\n\n// Change selected array\nstateSubjectGet().next({\n ...currentState,\n selectedArray: 'customArray1'\n});\n</code></pre>"},{"location":"components/visualization/thnpainter/#performance-optimizations","title":"Performance Optimizations","text":""},{"location":"components/visualization/thnpainter/#instanced-rendering","title":"Instanced Rendering","text":"<ul> <li>Uses <code>InstancedBufferGeometry</code> for rendering thousands of bins</li> <li>Single draw call per histogram</li> <li>GPU-efficient attribute updates</li> </ul>"},{"location":"components/visualization/thnpainter/#bvh-tree","title":"BVH Tree","text":"<ul> <li>Hierarchical bounding volumes for raycasting</li> <li>O(log n) intersection tests instead of O(n)</li> <li>Recursive tree traversal with early exit</li> </ul>"},{"location":"components/visualization/thnpainter/#matrix-cache","title":"Matrix Cache","text":"<ul> <li>Pre-computed transformations stored in typed arrays</li> <li>Avoids recalculation during navigation</li> <li>Efficient memory layout for GPU transfer</li> </ul>"},{"location":"components/visualization/thnpainter/#render-history","title":"Render History","text":"<ul> <li>Records render operations for state replay</li> <li>Avoids full re-render on state changes</li> <li>Maintains visual consistency during navigation</li> </ul>"},{"location":"components/visualization/thnpainter/#configuration","title":"Configuration","text":"<p>The painter accepts extensive configuration through <code>opts</code>:</p> <pre><code>{\n padding: {\n default: { x: 0.1, y: 0.1, z: 0.1 },\n layer: [/* per-layer padding */],\n sets: { x: 0.05, y: 0, z: 0 } // padding between sets\n },\n scale: {\n default: { min: 0.1, max: 1.0 },\n layer: [/* per-layer scale */]\n },\n color: {\n gradient: ['#color1', '#color2', ...], // up to 32 colors\n mode: 'gradient' | 'solid'\n },\n sets: {\n scale: {\n maximum: 'absolute' | 'relative' // scale relative to layer or global\n }\n },\n wireframe: {\n visible: true,\n color: 0x888888,\n linewidth: 1\n },\n TH1ZScale: {\n default: 0.1,\n layer: [/* per-layer z-scale for TH1 */],\n set: 0.01 // z-scale for set rendering\n }\n}\n</code></pre>"},{"location":"components/visualization/thnpainter/#dependencies","title":"Dependencies","text":"<ul> <li>THREE.js: Core rendering, instanced geometry, custom shaders</li> <li>JSROOT: Histogram data structures</li> <li>RxJS: State management, bin info publishing</li> <li>Internal Classes:</li> <li><code>TPainter</code> (parent class) from <code>./TPainter.js</code></li> <li><code>HistogramPointerClass</code> from <code>../core/histogram-pointer-class.js</code></li> <li><code>HistogramWireframeClass</code> from <code>./histogram-wireframe-class.js</code></li> <li>RxJS Subjects:</li> <li><code>stateSubjectGet</code> from <code>../rxjs/StateSubject.js</code></li> <li><code>canvasSubjectGet</code> from <code>../rxjs/CanvasSubject.js</code></li> <li><code>binInfoSubjectGet</code> from <code>../rxjs/BinInfoSubject.js</code></li> <li>Utilities: Extensive histogram utilities from <code>../utils/histogramUtils.js</code></li> </ul>"},{"location":"components/visualization/thnpainter/#related-components","title":"Related Components","text":"<ul> <li>HistogramJsrootClass - JSROOT-based alternative renderer</li> <li>BinInfoVisualizer - Receives bin information</li> <li>NdmvrRaycaster - Scene-level raycasting infrastructure</li> </ul>"},{"location":"components/visualization/thnpainter/#source-code","title":"Source code:","text":"<p>View this component on Gitlab</p>"}]}
|