UNPKG

@helioviewer/event-tree

Version:

A react component for grouping and listing and selecting solar events.

301 lines (235 loc) 7.16 kB
/** * Stores event information in an accessible way. * IDs are generated for each event and are accessible on this event tree via * EventTree[event_id] * Each event contains the ID of its parent and a list of child events. */ class EventTree { constructor(props) { Object.assign(this, props); } /** * @param {Object} events Event Data object * @param {string} source Top level event group (HEK, CCMC, RHESSI, etc) */ static make(events, source) { /** * Given a set of events like: * - top level * - groups: second level * - groups: third level * - data: [event 1, event 2, event 3] * This will return a structure like: * [{top level}, {second level}, {third level}, {event 1}, {event 2}, {event 3}]. * Each object in the list will have references to their children and an ID for their parent. */ const makeFlatTreeInner = (eventData, parentPath, level = 0) => { if (eventData.hasOwnProperty("groups")) { const root = { label: eventData.name, path: `${parentPath}>>${eventData.name}`, id: `${parentPath}>>${eventData.name}`, state: "unchecked", expand: level == 0, children: [] }; const childs = []; eventData.groups.forEach((ed) => { let innerFlatTree = makeFlatTreeInner(ed, root.path, level + 1); root.children.push(innerFlatTree[0].id); innerFlatTree[0].parent_id = root.id; childs.push(...innerFlatTree); }); return [root, ...childs]; } if (eventData.hasOwnProperty("data")) { const root = { label: eventData.name, path: `${parentPath}>>${eventData.name}`, id: `${parentPath}>>${eventData.name}`, state: "unchecked", expand: level == 0, children: [] }; const childs = []; eventData.data.forEach((e) => { let eventNode = { label: e.short_label ?? e.label, path: `${root.path}>>${e.short_label ?? e.label}`, id: `${root.path}>>${e.short_label ?? e.label}`, state: "unchecked", expand: level == 0, data: e }; root.children.push(eventNode.id); eventNode.parent_id = root.id; childs.push(eventNode); }); return [root, ...childs]; } }; const root = { label: source, path: source, id: source, state: "unchecked", parent_id: null, expand: true, children: [] }; const childs = []; events.forEach((ed) => { let innerFlatTree = makeFlatTreeInner(ed, source); root.children.push(innerFlatTree[0].id); innerFlatTree[0].parent_id = root.id; childs.push(...innerFlatTree); }); const res = {}; [root, ...childs].forEach((e) => { res[e.id] = e; }); return new EventTree(res); } toggleCheckbox(id) { const stateCycle = { checked: "unchecked", unchecked: "checked", undecided: "checked" }; const newState = stateCycle[this[id]["state"]]; const nodes = this.getNodes(id); for (const n of nodes) { this[n].state = newState; } let parentId = this[id].parent_id; while (parentId != null) { let siblingsStates = this[parentId].children.map((cid) => this[cid].state); this[parentId].state = siblingsStates.reduce( (finalState, eventState) => (finalState != eventState ? "undecided" : finalState), siblingsStates[0] ); parentId = this[parentId].parent_id; } return new EventTree({ ...this }); } toggleExpand(id) { if (this.hasOwnProperty(id)) { this[id].expand = !this[id].expand; } return new EventTree({ ...this }); } removeSelections() { for (const n in this) { this[n].state = "unchecked"; } return new EventTree({ ...this }); } applySelections(selections) { selections.forEach((id) => { if (this.hasOwnProperty(id)) { const nodes = this.getNodes(id); for (const n of nodes) { this[n].state = "checked"; } let parentId = this[id].parent_id; while (parentId != null) { let siblingsStates = this[parentId].children.map((cid) => this[cid].state); this[parentId].state = siblingsStates.reduce( (finalState, eventState) => (finalState != eventState ? "undecided" : finalState), siblingsStates[0] ); this[parentId].expand = this[parentId].state == "undecided" || this.isRoot(parentId) || this.isFirstLevel(parentId); parentId = this[parentId].parent_id; } } else { console.warn(id, " selection problem , it is not in tree!"); } }); return new EventTree({ ...this }); } selectedEvents() { const res = []; for (const n in this) { let node = this[n]; let isEvent = node.hasOwnProperty("data"); if (isEvent) { if (node.state == "checked") { res.push({ event_data: node.data, label: node.label, id: node.id }); } } } return res; } extractSelections() { const res = []; for (const n in this) { let node = this[n]; // We only care checked ones if (node.state == "checked") { // its parent could be null, then this means root node is selected // or if anynode is selected but its parent not selected if (node.parent_id == null || this[node.parent_id].state != "checked") { res.push(node.id); } } } return res; } getNodes(id) { const isEvent = this[id].hasOwnProperty("data"); if (isEvent) { return [id]; } const allNodes = [id]; this[id].children.forEach((cid) => { allNodes.push(...this.getNodes(cid)); }); return allNodes; } getEventsOfNode(id) { const isEvent = this[id].hasOwnProperty("data"); if (isEvent) { return [this[id]]; } const allEvents = []; this[id].children.forEach((cid) => { allEvents.push(...this.getEventsOfNode(cid)); }); return allEvents; } getEventCount(id) { return this.getEventsOfNode(id).length; } /** * An event branch a branch which contains an event data array. * Branches containing groups are not event branches. */ isEventBranch(id) { const isEvent = this[id].hasOwnProperty("data"); if (isEvent) { return false; } if (this[id].children.length <= 0) { return false; } const firstChildrenId = this[id].children[0]; return this[firstChildrenId].hasOwnProperty("data"); } getLevel(id) { // trigger exception , if it is not in tree, we handle all exceptions const nodeID = this[id].id; return nodeID.split(">>").length; } isRoot(id) { return this.getLevel(id) == 1; } isFirstLevel(id) { return this.getLevel(id) == 2; } } export default EventTree;