diff --git a/src/ui/src/components/home_page/home_page.ts b/src/ui/src/components/home_page/home_page.ts index 50f3ab28..61941ef2 100644 --- a/src/ui/src/components/home_page/home_page.ts +++ b/src/ui/src/components/home_page/home_page.ts @@ -46,6 +46,7 @@ import { SETTING_DISALLOW_VERTICAL_EDGE_LABELS, SETTING_EDGE_COLOR, SETTING_EDGE_LABEL_FONT_SIZE, + SETTING_HIDE_EMPTY_NODE_DATA_ENTRIES, SETTING_HIDE_OP_NODES_WITH_LABELS, SETTING_HIGHLIGHT_LAYER_NODE_INPUTS_OUTPUTS, SETTING_KEEP_LAYERS_WITH_A_SINGLE_CHILD, @@ -355,6 +356,9 @@ export class HomePage implements AfterViewInit { highlightLayerNodeInputsOutputs: this.settingsService.getBooleanValue( SETTING_HIGHLIGHT_LAYER_NODE_INPUTS_OUTPUTS, ), + hideEmptyNodeDataEntries: this.settingsService.getBooleanValue( + SETTING_HIDE_EMPTY_NODE_DATA_ENTRIES, + ), }; } diff --git a/src/ui/src/components/visualizer/common/utils.ts b/src/ui/src/components/visualizer/common/utils.ts index 7034088d..e29ce449 100644 --- a/src/ui/src/components/visualizer/common/utils.ts +++ b/src/ui/src/components/visualizer/common/utils.ts @@ -55,6 +55,7 @@ import { ShowOnNodeItemData, ShowOnNodeItemType, } from './types'; +import {VisualizerConfig} from './visualizer_config'; const CANVAS = new OffscreenCanvas(300, 300); @@ -530,6 +531,7 @@ export function getOpNodeDataProviderKeyValuePairsForAttrsTable( modelGraphId: string, showOnNodeItemTypes: Record<string, ShowOnNodeItemData>, curNodeDataProviderRuns: Record<string, NodeDataProviderRunData>, + config?: VisualizerConfig, ): KeyValueList { const keyValuePairs: KeyValueList = []; const runNames = Object.keys(showOnNodeItemTypes) @@ -544,7 +546,11 @@ export function getOpNodeDataProviderKeyValuePairsForAttrsTable( runNames.includes(getRunName(run, {id: modelGraphId})), ); for (const run of runs) { - const value = (run.results || {})?.[modelGraphId][node.id]?.strValue || '-'; + const result = (run.results || {})?.[modelGraphId]?.[node.id]; + if (config?.hideEmptyNodeDataEntries && !result) { + continue; + } + const value = result?.strValue || '-'; keyValuePairs.push({ key: getRunName(run, {id: modelGraphId}), value, diff --git a/src/ui/src/components/visualizer/common/visualizer_config.ts b/src/ui/src/components/visualizer/common/visualizer_config.ts index 83ccc290..b87b5f94 100644 --- a/src/ui/src/components/visualizer/common/visualizer_config.ts +++ b/src/ui/src/components/visualizer/common/visualizer_config.ts @@ -61,6 +61,9 @@ export declare interface VisualizerConfig { /** Whether to highlight layer node inputs and outputs. */ highlightLayerNodeInputsOutputs?: boolean; + /** Whether to hide empty node data entries. */ + hideEmptyNodeDataEntries?: boolean; + /** The default node styler rules. */ nodeStylerRules?: NodeStylerRule[]; diff --git a/src/ui/src/components/visualizer/common/worker_events.ts b/src/ui/src/components/visualizer/common/worker_events.ts index 2a6a390a..fea5340a 100644 --- a/src/ui/src/components/visualizer/common/worker_events.ts +++ b/src/ui/src/components/visualizer/common/worker_events.ts @@ -85,6 +85,7 @@ export declare interface ExpandOrCollapseGroupNodeRequest all?: boolean; // Timestamp of when the request is sent. ts?: number; + config?: VisualizerConfig; } /** The response for expanding/collapsing a group node. */ @@ -119,6 +120,7 @@ export declare interface RelayoutGraphRequest extends WorkerEventBase { forRestoringSnapshotAfterTogglingFlattenLayers?: boolean; nodeStylerQueries?: NodeStylerRule[]; triggerNavigationSync?: boolean; + config?: VisualizerConfig; } /** The response for re-laying out the whole graph. */ @@ -144,6 +146,7 @@ export declare interface LocateNodeRequest extends WorkerEventBase { rendererId: string; noNodeShake?: boolean; select?: boolean; + config?: VisualizerConfig; } /** The response for locating a node. */ diff --git a/src/ui/src/components/visualizer/info_panel.ng.html b/src/ui/src/components/visualizer/info_panel.ng.html index 3851712c..bab49db0 100644 --- a/src/ui/src/components/visualizer/info_panel.ng.html +++ b/src/ui/src/components/visualizer/info_panel.ng.html @@ -17,33 +17,36 @@ --> <div class="container" [class.graph-info]="showNodeDataProviderSummary"> - <div class="section" #sectionEle - [class.collapsed]="isSectionCollapsed(section.label)" - *ngFor="let section of sections; trackBy: trackBySectionLabel"> - <div class="header"> - <button mat-icon-button class="toggle" - (click)="handleToggleSection(section.label, sectionEle)"> - <mat-icon>{{getSectionToggleIcon(section.label)}}</mat-icon> - </button> - {{section.label}} - </div> - <div class="items-container"> - <table class="metadata-table info-attrs"> - @for (item of section.items; track item.id || item.label) { - <tr [class.search-match]="isSearchMatchedAttrId(item.label)"> - <td class="key"><hoverable-label [label]="item.label"></hoverable-label></td> - <td class="value"> - <expandable-info-text - [text]="item.value" [type]="item.label" - [bgColor]="item.bgColor || 'transparent'" - [textColor]="item.textColor || 'black'"> - </expandable-info-text> - </td> - </tr> - } - </table> - </div> - </div> + @for (section of sections; track section.label) { + @if (section.items.length > 0) { + <div class="section" #sectionEle + [class.collapsed]="isSectionCollapsed(section.label)"> + <div class="header"> + <button mat-icon-button class="toggle" + (click)="handleToggleSection(section.label, sectionEle)"> + <mat-icon>{{getSectionToggleIcon(section.label)}}</mat-icon> + </button> + {{section.label}} + </div> + <div class="items-container"> + <table class="metadata-table info-attrs"> + @for (item of section.items; track item.id || item.label) { + <tr [class.search-match]="isSearchMatchedAttrId(item.label)"> + <td class="key"><hoverable-label [label]="item.label"></hoverable-label></td> + <td class="value"> + <expandable-info-text + [text]="item.value" [type]="item.label" + [bgColor]="item.bgColor || 'transparent'" + [textColor]="item.textColor || 'black'"> + </expandable-info-text> + </td> + </tr> + } + </table> + </div> + </div> + } + } <!-- Summary for node data provider extensions --> <div class="section" *ngIf="showNodeDataProviderSummary" #ndpSectionEle diff --git a/src/ui/src/components/visualizer/info_panel.ts b/src/ui/src/components/visualizer/info_panel.ts index 9aacfff9..dd80d795 100644 --- a/src/ui/src/components/visualizer/info_panel.ts +++ b/src/ui/src/components/visualizer/info_panel.ts @@ -559,10 +559,6 @@ export class InfoPanel { return (connectedNodesMetadataItem?.connectedNodes || []).length > 0; } - trackBySectionLabel(index: number, section: InfoSection): string { - return section.label; - } - trackByItemIdOrLabel(index: number, item: InfoItem): string { return item.id || item.label; } @@ -798,6 +794,9 @@ export class InfoPanel { const nodeResult = ((run.results || {})[this.curModelGraph.id] || {})[ opNode.id ]; + if (this.appService.config()?.hideEmptyNodeDataEntries && !nodeResult) { + continue; + } const strValue = nodeResult?.strValue || '-'; const bgColor = nodeResult?.bgColor || 'transparent'; const textColor = nodeResult?.textColor || 'black'; diff --git a/src/ui/src/components/visualizer/webgl_renderer.ts b/src/ui/src/components/visualizer/webgl_renderer.ts index c87ea1e6..d8ab57c9 100644 --- a/src/ui/src/components/visualizer/webgl_renderer.ts +++ b/src/ui/src/components/visualizer/webgl_renderer.ts @@ -1393,6 +1393,7 @@ export class WebglRenderer implements OnInit, OnDestroy { rendererId, noNodeShake, select, + config: this.appService.config(), }; this.workerService.worker.postMessage(req); } @@ -1423,6 +1424,7 @@ export class WebglRenderer implements OnInit, OnDestroy { clearAllExpandStates, forRestoringSnapshotAfterTogglingFlattenLayers, triggerNavigationSync, + config: this.appService.config(), }; this.workerService.worker.postMessage(req); } @@ -1886,6 +1888,7 @@ export class WebglRenderer implements OnInit, OnDestroy { paneId: this.paneId, all, ts: Date.now(), + config: this.appService.config(), }; this.workerService.worker.postMessage(req); } diff --git a/src/ui/src/components/visualizer/webgl_renderer_attrs_table_service.ts b/src/ui/src/components/visualizer/webgl_renderer_attrs_table_service.ts index e04454fd..a772bad0 100644 --- a/src/ui/src/components/visualizer/webgl_renderer_attrs_table_service.ts +++ b/src/ui/src/components/visualizer/webgl_renderer_attrs_table_service.ts @@ -164,6 +164,7 @@ export class WebglRendererAttrsTableService { this.webglRenderer.curModelGraph.id, this.webglRenderer.curShowOnNodeItemTypes, this.webglRenderer.curNodeDataProviderRuns, + this.webglRenderer.appService.config(), ), ); } else if (isGroupNode(node)) { diff --git a/src/ui/src/components/visualizer/worker/graph_expander.ts b/src/ui/src/components/visualizer/worker/graph_expander.ts index 4ecd3fd2..db658ca0 100644 --- a/src/ui/src/components/visualizer/worker/graph_expander.ts +++ b/src/ui/src/components/visualizer/worker/graph_expander.ts @@ -29,6 +29,7 @@ import { isGroupNode, splitLabel, } from '../common/utils'; +import {VisualizerConfig} from '../common/visualizer_config'; import {Dagre, DagreGraphInstance} from './dagre_types'; import { @@ -55,6 +56,7 @@ export class GraphExpander { NodeDataProviderRunData >, private readonly testMode = false, + private readonly config?: VisualizerConfig, ) {} /** Expands the given group node to show its child nodes. */ @@ -85,6 +87,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); const rect = layout.layout(curGroupNodeId); if (this.testMode) { @@ -107,6 +111,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); layout.layout(); if (this.testMode) { @@ -161,6 +167,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); const rect = layout.layout(groupNodeId); if (this.testMode) { @@ -180,6 +188,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); layout.layout(); if (this.testMode) { @@ -246,6 +256,7 @@ export class GraphExpander { this.nodeDataProviderRuns, this.testMode, true, + this.config, ); // From the given group node's parent, layout, update size, and continue to @@ -265,6 +276,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); const rect = layout.layout(curGroupNodeId); if (this.testMode) { @@ -287,6 +300,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); layout.layout(); if (this.testMode) { @@ -346,6 +361,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); layout.layout(); } @@ -384,6 +401,8 @@ export class GraphExpander { this.dagre, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); layout.layout(); diff --git a/src/ui/src/components/visualizer/worker/graph_layout.ts b/src/ui/src/components/visualizer/worker/graph_layout.ts index dcc6f2ea..fc97a8f9 100644 --- a/src/ui/src/components/visualizer/worker/graph_layout.ts +++ b/src/ui/src/components/visualizer/worker/graph_layout.ts @@ -60,6 +60,7 @@ import { isOpNode, splitLabel, } from '../common/utils'; +import {VisualizerConfig} from '../common/visualizer_config'; import {Dagre, DagreGraphInstance} from './dagre_types'; @@ -114,6 +115,7 @@ export class GraphLayout { NodeDataProviderRunData >, private readonly testMode = false, + private readonly config?: VisualizerConfig, ) { this.dagreGraph = new this.dagre.graphlib.Graph(); } @@ -143,6 +145,8 @@ export class GraphLayout { this.showOnNodeItemTypes, this.nodeDataProviderRuns, this.testMode, + false, + this.config, ); // Set nodes/edges to dagre. @@ -259,6 +263,8 @@ export class GraphLayout { this.modelGraph, this.showOnNodeItemTypes, this.nodeDataProviderRuns, + this.testMode, + this.config, ); if (subgraphFullWidth < parentNodeWidth) { const extraOffsetX = (parentNodeWidth - subgraphFullWidth) / 2; @@ -318,6 +324,7 @@ export function getNodeWidth( showOnNodeItemTypes: Record<string, ShowOnNodeItemData>, nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, testMode = false, + config?: VisualizerConfig, ) { // Always return 32 in test mode. if (testMode) { @@ -404,6 +411,7 @@ export function getNodeWidth( modelGraph.id, showOnNodeItemTypes, nodeDataProviderRuns, + config, ); const nodeDataProviderWidths = getMaxAttrLabelAndValueWidth( nodeDataProviderKeyValuePairs, @@ -475,6 +483,7 @@ export function getNodeHeight( nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, testMode = false, forceRecalculate = false, + config?: VisualizerConfig, ) { if (testMode) { return DEFAULT_NODE_HEIGHT; @@ -495,6 +504,7 @@ export function getNodeHeight( node, nodeDataProviderRuns, modelGraph, + config, ); } else if (isGroupNode(node)) { attrsTableRowCount = getGroupNodeAttrsTableRowCount( @@ -521,6 +531,7 @@ export function getLayoutGraph( nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, testMode = false, useFakeNodeSize = false, + config?: VisualizerConfig, ): LayoutGraph { const layoutGraph: LayoutGraph = { nodes: {}, @@ -545,6 +556,7 @@ export function getLayoutGraph( showOnNodeItemTypes, nodeDataProviderRuns, testMode, + config, )), height: useFakeNodeSize ? 10 @@ -554,6 +566,8 @@ export function getLayoutGraph( showOnNodeItemTypes, nodeDataProviderRuns, testMode, + false, + config, ), config: isOpNode(node) ? node.config : undefined, }; @@ -586,6 +600,7 @@ function getOpNodeAttrsTableRowCount( node: OpNode, nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, modelGraph: ModelGraph, + config?: VisualizerConfig, ): number { // Basic info fields. const baiscFieldIds = @@ -624,14 +639,19 @@ function getOpNodeAttrsTableRowCount( showOnNodeItemType.startsWith( NODE_DATA_PROVIDER_SHOW_ON_NODE_TYPE_PREFIX, ) && - Object.values(nodeDataProviderRuns).some( - (run) => + Object.values(nodeDataProviderRuns).some((run) => { + const result = (run.results || {})?.[modelGraph.id]?.[node.id]; + if (config?.hideEmptyNodeDataEntries && !result) { + return false; + } + return ( getRunName(run, modelGraph) === showOnNodeItemType.replace( NODE_DATA_PROVIDER_SHOW_ON_NODE_TYPE_PREFIX, '', - ), - ), + ) + ); + }), ).length; return ( diff --git a/src/ui/src/components/visualizer/worker/graph_processor.ts b/src/ui/src/components/visualizer/worker/graph_processor.ts index 4691dff8..c418e7d7 100644 --- a/src/ui/src/components/visualizer/worker/graph_processor.ts +++ b/src/ui/src/components/visualizer/worker/graph_processor.ts @@ -484,6 +484,7 @@ export class GraphProcessor { this.testMode, // Use fake node size. true, + this.config, ); // Find root nodes of the layout graph. diff --git a/src/ui/src/components/visualizer/worker/worker.ts b/src/ui/src/components/visualizer/worker/worker.ts index 532e87db..dbf5dd58 100644 --- a/src/ui/src/components/visualizer/worker/worker.ts +++ b/src/ui/src/components/visualizer/worker/worker.ts @@ -113,6 +113,7 @@ self.addEventListener('message', (event: Event) => { workerEvent.showOnNodeItemTypes, workerEvent.nodeDataProviderRuns, workerEvent.all === true, + workerEvent.config, ); } else { deepestExpandedGroupNodeIds = handleCollapseGroupNode( @@ -121,6 +122,7 @@ self.addEventListener('message', (event: Event) => { workerEvent.showOnNodeItemTypes, workerEvent.nodeDataProviderRuns, workerEvent.all === true, + workerEvent.config, ); } cacheModelGraph(modelGraph, workerEvent.rendererId); @@ -146,6 +148,7 @@ self.addEventListener('message', (event: Event) => { workerEvent.nodeDataProviderRuns, workerEvent.targetDeepestGroupNodeIdsToExpand, workerEvent.clearAllExpandStates, + workerEvent.config, ); cacheModelGraph(modelGraph, workerEvent.rendererId); const resp: RelayoutGraphResponse = { @@ -174,6 +177,7 @@ self.addEventListener('message', (event: Event) => { workerEvent.showOnNodeItemTypes, workerEvent.nodeDataProviderRuns, workerEvent.nodeId, + workerEvent.config, ); cacheModelGraph(modelGraph, workerEvent.rendererId); const resp: LocateNodeResponse = { @@ -264,12 +268,15 @@ function handleExpandGroupNode( showOnNodeItemTypes: Record<string, ShowOnNodeItemData>, nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, all: boolean, + config?: VisualizerConfig, ): string[] { const expander = new GraphExpander( modelGraph, dagre, showOnNodeItemTypes, nodeDataProviderRuns, + false, + config, ); // Expane group node. @@ -337,12 +344,15 @@ function handleCollapseGroupNode( showOnNodeItemTypes: Record<string, ShowOnNodeItemData>, nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, all: boolean, + config?: VisualizerConfig, ): string[] { const expander = new GraphExpander( modelGraph, dagre, showOnNodeItemTypes, nodeDataProviderRuns, + false, + config, ); if (groupNodeId != null) { @@ -370,12 +380,15 @@ function handleReLayoutGraph( nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, targetDeepestGroupNodeIdsToExpand?: string[], clearAllExpandStates?: boolean, + config?: VisualizerConfig, ) { const expander = new GraphExpander( modelGraph, dagre, showOnNodeItemTypes, nodeDataProviderRuns, + false, + config, ); expander.reLayoutGraph( targetDeepestGroupNodeIdsToExpand, @@ -388,12 +401,15 @@ function handleLocateNode( showOnNodeItemTypes: Record<string, ShowOnNodeItemData>, nodeDataProviderRuns: Record<string, NodeDataProviderRunData>, nodeId: string, + config?: VisualizerConfig, ): string[] { const expander = new GraphExpander( modelGraph, dagre, showOnNodeItemTypes, nodeDataProviderRuns, + false, + config, ); return expander.expandToRevealNode(nodeId); } diff --git a/src/ui/src/services/settings_service.ts b/src/ui/src/services/settings_service.ts index 0206eb0f..d37c31ae 100644 --- a/src/ui/src/services/settings_service.ts +++ b/src/ui/src/services/settings_service.ts @@ -36,6 +36,7 @@ export enum SettingKey { KEEP_LAYERS_WITH_A_SINGLE_CHILD = 'keep_layers_with_a_single_child', SHOW_OP_NODE_OUT_OF_LAYER_EDGES_WITHOUT_SELECTING = 'show_op_node_out_of_layer_edges_without_selecting', HIGHLIGHT_LAYER_NODE_INPUTS_OUTPUTS = 'highlight_layer_node_inputs_outputs', + HIDE_EMPTY_NODE_DATA_ENTRIES = 'hide_empty_node_data_entries', } /** Setting types. */ @@ -170,6 +171,17 @@ export const SETTING_HIGHLIGHT_LAYER_NODE_INPUTS_OUTPUTS: Setting = { 'that layer.', }; +/** Settings for hiding empty noda data entries. */ +export const SETTING_HIDE_EMPTY_NODE_DATA_ENTRIES: Setting = { + label: 'Hide node data entries with empty values', + key: SettingKey.HIDE_EMPTY_NODE_DATA_ENTRIES, + type: SettingType.BOOLEAN, + defaultValue: false, + help: + 'Enable this setting to hide node data entries ' + + '(on node overlay and in side panel) with empty values.', +}; + const SETTINGS_LOCAL_STORAGE_KEY = 'model_explorer_settings'; /** All settings. */ @@ -184,6 +196,7 @@ export const ALL_SETTINGS = [ SETTING_DISALLOW_VERTICAL_EDGE_LABELS, SETTING_MAX_CONST_ELEMENT_COUNT_LIMIT, SETTING_HIGHLIGHT_LAYER_NODE_INPUTS_OUTPUTS, + SETTING_HIDE_EMPTY_NODE_DATA_ENTRIES, ]; /**