Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions packages/phoenix-event-display/src/event-display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import type {
PhoenixEventData,
PhoenixEventsData,
} from './lib/types/event-data';
import {
buildEventSummaries,
type EventSummary,
} from './helpers/event-summary';

declare global {
/**
Expand All @@ -35,6 +39,8 @@ export class EventDisplay {
public configuration: Configuration;
/** An object containing event data. */
private eventsData: PhoenixEventsData;
/** Currently displayed event key. */
private currentEventKey: string | null = null;
/** Array containing callbacks to be called when events change. */
private onEventsChange: ((events: any) => void)[] = [];
/** Array containing callbacks to be called when the displayed event changes. */
Expand All @@ -53,6 +59,8 @@ export class EventDisplay {
private urlOptionsManager: URLOptionsManager;
/** Flag to track if EventDisplay has been initialized. */
private isInitialized: boolean = false;
/** Stored keydown handler for event navigation shortcuts. */
private eventNavKeydownHandler: ((e: KeyboardEvent) => void) | null = null;

/**
* Create the Phoenix event display and intitialize all the elements.
Expand Down Expand Up @@ -114,6 +122,11 @@ export class EventDisplay {
if (this.ui) {
this.ui.cleanup();
}
// Clean up event navigation keyboard handler
if (this.eventNavKeydownHandler) {
document.removeEventListener('keydown', this.eventNavKeydownHandler);
this.eventNavKeydownHandler = null;
}
// Clear accumulated callbacks
this.onEventsChange = [];
this.onDisplayedEventChange = [];
Expand Down Expand Up @@ -194,10 +207,65 @@ export class EventDisplay {
const event = this.eventsData[eventKey];

if (event) {
this.currentEventKey = eventKey;
this.buildEventDataFromJSON(event);
}
}

/**
* Get the currently displayed event key.
*/
public getCurrentEventKey(): string | null {
return this.currentEventKey;
}

/**
* Load the next event in the event list.
* Wraps around to the first event after the last.
*/
public nextEvent() {
if (!this.eventsData) return;
const keys = Object.keys(this.eventsData);
if (keys.length === 0) return;
const currentIndex = this.currentEventKey
? keys.indexOf(this.currentEventKey)
: -1;
const nextIndex = (currentIndex + 1) % keys.length;
this.loadEvent(keys[nextIndex]);
}

/**
* Load the previous event in the event list.
* Wraps around to the last event before the first.
*/
public previousEvent() {
if (!this.eventsData) return;
const keys = Object.keys(this.eventsData);
if (keys.length === 0) return;
const currentIndex = this.currentEventKey
? keys.indexOf(this.currentEventKey)
: -1;
const prevIndex = currentIndex <= 0 ? keys.length - 1 : currentIndex - 1;
this.loadEvent(keys[prevIndex]);
}

/**
* Get all loaded events data.
* @returns The events data object, or undefined if no events loaded.
*/
public getEventsData(): PhoenixEventsData | undefined {
return this.eventsData;
}

/**
* Build summaries of all loaded events for the event browser.
* @returns Array of event summaries with collection counts and metadata.
*/
public getEventSummaries(): EventSummary[] {
if (!this.eventsData) return [];
return buildEventSummaries(this.eventsData);
}

/**
* Get the three manager responsible for three.js functions.
* @returns The three.js manager.
Expand Down Expand Up @@ -751,6 +819,30 @@ export class EventDisplay {
public enableKeyboardControls() {
this.ui.enableKeyboardControls();
this.graphicsLibrary.enableKeyboardControls();

// Remove previous event navigation listener if exists
if (this.eventNavKeydownHandler) {
document.removeEventListener('keydown', this.eventNavKeydownHandler);
}

// Shift+ArrowRight = next event, Shift+ArrowLeft = previous event
this.eventNavKeydownHandler = (e: KeyboardEvent) => {
const target = e.target as HTMLElement;
const isTyping = ['input', 'textarea', 'select'].includes(
target?.tagName.toLowerCase(),
);
const hasFocusableContent = target?.hasAttribute('tabindex');
if (isTyping || hasFocusableContent || !e.shiftKey) return;

if (e.code === 'ArrowRight') {
e.preventDefault();
this.nextEvent();
} else if (e.code === 'ArrowLeft') {
e.preventDefault();
this.previousEvent();
}
};
document.addEventListener('keydown', this.eventNavKeydownHandler);
}

/**
Expand Down
174 changes: 174 additions & 0 deletions packages/phoenix-event-display/src/helpers/event-summary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import type {
PhoenixEventsData,
PhoenixEventData,
MissingEnergyParams,
} from '../lib/types/event-data';

/** Summary of a single event for browsing/filtering. */
export interface EventSummary {
/** The key used to look up this event in PhoenixEventsData. */
eventKey: string;
/** Event number from metadata (if available). */
eventNumber: string | number | undefined;
/** Run number from metadata (if available). */
runNumber: string | number | undefined;
/** Total number of physics objects across all collections. */
totalObjects: number;
/** Count of objects per collection type (e.g. "Tracks": 42, "Jets": 5). */
collectionCounts: { [typeName: string]: number };
/** Missing transverse energy magnitude in MeV (NaN if no MET collection). */
met: number;
}

/** Known collection types that hold arrays of physics objects. */
const OBJECT_COLLECTION_TYPES = [
'Tracks',
'Jets',
'Hits',
'CaloClusters',
'CaloCells',
'Muons',
'Photons',
'Electrons',
'Vertices',
'MissingEnergy',
'PlanarCaloCells',
'IrregularCaloCells',
] as const;

/**
* Count all physics objects in a single event.
* Each collection type (Tracks, Jets, etc.) can have multiple named sub-collections.
*/
function countCollections(eventData: PhoenixEventData): {
counts: { [typeName: string]: number };
total: number;
} {
const counts: { [typeName: string]: number } = {};
let total = 0;

for (const typeName of OBJECT_COLLECTION_TYPES) {
const typeData = eventData[typeName];
if (!typeData || typeof typeData !== 'object') continue;

let typeCount = 0;

if (typeName === 'PlanarCaloCells') {
// PlanarCaloCells has a special structure: { collName: { plane: [...], cells: [...] } }
for (const collName of Object.keys(typeData)) {
const coll = (typeData as any)[collName];
if (coll?.cells && Array.isArray(coll.cells)) {
typeCount += coll.cells.length;
}
}
} else {
// Standard structure: { collName: objectArray }
for (const collName of Object.keys(typeData)) {
const coll = (typeData as any)[collName];
if (Array.isArray(coll)) {
typeCount += coll.length;
}
}
}

if (typeCount > 0) {
counts[typeName] = typeCount;
total += typeCount;
}
}

return { counts, total };
}

/**
* Compute missing transverse energy magnitude from MET collections.
* Returns NaN if no MET data is present.
*/
function computeMET(eventData: PhoenixEventData): number {
const metCollections = eventData.MissingEnergy;
if (!metCollections || typeof metCollections !== 'object') return NaN;

// Use the first MET collection found, take its first entry
for (const collName of Object.keys(metCollections)) {
const metArray = metCollections[collName];
if (Array.isArray(metArray) && metArray.length > 0) {
const met = metArray[0] as MissingEnergyParams;
if (met.etx !== undefined && met.ety !== undefined) {
return Math.sqrt(met.etx * met.etx + met.ety * met.ety);
}
}
}

return NaN;
}

/**
* Build a summary for a single event.
*/
export function summarizeEvent(
eventKey: string,
eventData: PhoenixEventData,
): EventSummary {
const { counts, total } = countCollections(eventData);
const met = computeMET(eventData);

return {
eventKey,
eventNumber:
eventData['event number'] ?? eventData.eventNumber ?? undefined,
runNumber: eventData['run number'] ?? eventData.runNumber ?? undefined,
totalObjects: total,
collectionCounts: counts,
met,
};
}

/**
* Pre-scan all events and build a summary index.
* This is the main entry point for the event browser.
*/
export function buildEventSummaries(
eventsData: PhoenixEventsData,
): EventSummary[] {
const summaries: EventSummary[] = [];

for (const eventKey of Object.keys(eventsData)) {
const eventData = eventsData[eventKey];
if (eventData && typeof eventData === 'object') {
summaries.push(summarizeEvent(eventKey, eventData));
}
}

return summaries;
}

/** Detector-level types that are fixed per detector, not per event. */
const DETECTOR_LEVEL_TYPES = new Set([
'CaloCells',
'Hits',
'PlanarCaloCells',
'IrregularCaloCells',
]);

/**
* Get all unique collection type names across all events.
* Reconstructed physics objects are listed first, detector-level types last.
*/
export function getAvailableColumns(summaries: EventSummary[]): string[] {
const columnSet = new Set<string>();
for (const summary of summaries) {
for (const typeName of Object.keys(summary.collectionCounts)) {
columnSet.add(typeName);
}
}
const reco: string[] = [];
const detector: string[] = [];
for (const col of Array.from(columnSet).sort()) {
if (DETECTOR_LEVEL_TYPES.has(col)) {
detector.push(col);
} else {
reco.push(col);
}
}
return [...reco, ...detector];
}
1 change: 1 addition & 0 deletions packages/phoenix-event-display/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export * from './helpers/runge-kutta';
export * from './helpers/pretty-symbols';
export * from './helpers/active-variable';
export * from './helpers/zip';
export * from './helpers/event-summary';

// Loaders
export * from './loaders/event-data-loader';
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import {
EventDataExplorerComponent,
EventDataExplorerDialogComponent,
CycleEventsComponent,
EventBrowserComponent,
EventBrowserOverlayComponent,
} from './ui-menu';

import { AttributePipe } from '../services/extras/attribute.pipe';
Expand Down Expand Up @@ -127,6 +129,8 @@ const PHOENIX_COMPONENTS: Type<any>[] = [
FileExplorerComponent,
RingLoaderComponent,
CycleEventsComponent,
EventBrowserComponent,
EventBrowserOverlayComponent,
];

@NgModule({
Expand Down
Loading
Loading