Skip to content

Commit

Permalink
Add support for synchronizing navigation across split panes.
Browse files Browse the repository at this point in the history
- Allow users to sync navigation by node id, or upload a data json file to specify
  node id mapping. When user selects one node in one side of the pane, the node
  with mapped id will be automatically selected in another pane.
- Allow visualizer component user to pass mapping data through visualizer config.

PiperOrigin-RevId: 682738216
  • Loading branch information
Google AI Edge authored and copybara-github committed Oct 6, 2024
1 parent cdcd675 commit 2d1a944
Show file tree
Hide file tree
Showing 22 changed files with 902 additions and 73 deletions.
3 changes: 2 additions & 1 deletion src/ui/src/components/home_page/home_page.ng.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@
(titleClicked)="handleClickTitle()"
(modelGraphProcessed)="handleModelGraphProcessed($event)"
(uiStateChanged)="handleUiStateChanged($event)"
(remoteNodeDataPathsChanged)="handleRemoteNodeDataPathsChanged($event)">
(remoteNodeDataPathsChanged)="handleRemoteNodeDataPathsChanged($event)"
(syncNavigationModeChanged)="handleSyncNavigationModeChanged($event)">
</model-graph-visualizer>

<div class="dragover-overlay">
Expand Down
19 changes: 18 additions & 1 deletion src/ui/src/components/home_page/home_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ import {ModelSourceInput} from '../model_source_input/model_source_input';
import {OpenInNewTabButton} from '../open_in_new_tab_button/open_in_new_tab_button';
import {OpenSourceLibsDialog} from '../open_source_libs_dialog/open_source_libs_dialog';
import {SettingsDialog} from '../settings_dialog/settings_dialog';
import {ModelGraphProcessedEvent} from '../visualizer/common/types';
import {
ModelGraphProcessedEvent,
SyncNavigationModeChangedEvent,
} from '../visualizer/common/types';
import {VisualizerConfig} from '../visualizer/common/visualizer_config';
import {VisualizerUiState} from '../visualizer/common/visualizer_ui_state';
import {Logo} from '../visualizer/logo';
Expand Down Expand Up @@ -111,6 +114,7 @@ export class HomePage implements AfterViewInit {
benchmark = false;
remoteNodeDataPaths: string[] = [];
remoteNodeDataTargetModels: string[] = [];
syncNavigation?: SyncNavigationModeChangedEvent;
hasUploadedModels = signal<boolean>(false);
shareButtonTooltip: Signal<string> = signal<string>('');

Expand Down Expand Up @@ -162,6 +166,9 @@ export class HomePage implements AfterViewInit {
// Remote node data paths encoded in the url.
this.remoteNodeDataPaths = this.urlService.getNodeDataSources();
this.remoteNodeDataTargetModels = this.urlService.getNodeDataTargets();

// Sync navigation.
this.syncNavigation = this.urlService.getSyncNavigation();
}

ngAfterViewInit() {
Expand Down Expand Up @@ -276,12 +283,22 @@ export class HomePage implements AfterViewInit {
);
this.remoteProcessedNodeDataTargetModels.add(modelName);
}

if (this.syncNavigation) {
this.modelGraphVisualizer?.syncNavigationService.loadSyncNavigationDataFromEvent(
this.syncNavigation,
);
}
}

handleRemoteNodeDataPathsChanged(paths: string[]) {
this.urlService.setNodeDataSources(paths);
}

handleSyncNavigationModeChanged(event: SyncNavigationModeChangedEvent) {
this.urlService.setSyncNavigation(event);
}

handleClickShowThirdPartyLibraries() {
this.dialog.open(OpenSourceLibsDialog, {});
}
Expand Down
2 changes: 1 addition & 1 deletion src/ui/src/components/visualizer/app_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export class AppService {

readonly doubleClickedNode = signal<NodeInfo | undefined>(undefined);

testMode: boolean = false;
testMode = false;

private groupNodeChildrenCountThresholdFromUrl: string | null = null;

Expand Down
57 changes: 57 additions & 0 deletions src/ui/src/components/visualizer/common/sync_navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @license
* Copyright 2024 The Model Explorer Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ==============================================================================
*/

import {TaskData, TaskType} from './task';

/** The data for navigation syncing. */
export interface SyncNavigationData extends TaskData {
type: TaskType.SYNC_NAVIGATION;

mapping: SyncNavigationMapping;
}

/**
* The mapping for navigation syncing, from node id from one side to node id
* from another side.
*/
export type SyncNavigationMapping = Record<string, string>;

/** The mode of navigation syncing. */
export enum SyncNavigationMode {
DISABLED = 'disabled',
MATCH_NODE_ID = 'match_node_id',
VISUALIZER_CONFIG = 'visualizer_config',
UPLOAD_MAPPING_FROM_COMPUTER = 'from_computer',
LOAD_MAPPING_FROM_CNS = 'from_cns',
}

/** The labels for sync navigation modes. */
export const SYNC_NAVIGATION_MODE_LABELS = {
[SyncNavigationMode.DISABLED]: 'Disabled',
[SyncNavigationMode.MATCH_NODE_ID]: 'Match node id',
[SyncNavigationMode.UPLOAD_MAPPING_FROM_COMPUTER]:
'Upload mapping from computer',
[SyncNavigationMode.LOAD_MAPPING_FROM_CNS]: 'Load mapping from CNS',
[SyncNavigationMode.VISUALIZER_CONFIG]: 'From Visualizer Config',
};

/** Information about the source of navigation. */
export interface NavigationSourceInfo {
paneIndex: number;
nodeId: string;
}
27 changes: 27 additions & 0 deletions src/ui/src/components/visualizer/common/task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @license
* Copyright 2024 The Model Explorer Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ==============================================================================
*/

/** The base data for a task. */
export declare interface TaskData {
type: TaskType;
}

/** The type of a task. */
export enum TaskType {
SYNC_NAVIGATION = 'sync_navigation',
}
14 changes: 14 additions & 0 deletions src/ui/src/components/visualizer/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import {GroupNode, ModelGraph, ModelNode} from './model_graph';
import {SyncNavigationMode} from './sync_navigation';

/** A type for key-value pairs. */
export type KeyValuePairs = Record<string, string>;
Expand Down Expand Up @@ -170,6 +171,7 @@ export interface SelectedNodeInfo {
rendererId: string;
isGroupNode: boolean;
noNodeShake?: boolean;
triggerNavigationSync?: boolean;
}

/** Info about a node to locate. */
Expand Down Expand Up @@ -658,3 +660,15 @@ export declare interface SerializedStyle {
id: NodeStyleId;
value: string;
}

/** Response from reading a file. */
export declare interface ReadFileResp {
content: string;
}

/** Event for sync navigation mode change. */
export declare interface SyncNavigationModeChangedEvent {
mode: SyncNavigationMode;
// Used when mode is LOAD_MAPPING_FROM_CNS.
cnsPath?: string;
}
4 changes: 4 additions & 0 deletions src/ui/src/components/visualizer/common/visualizer_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* ==============================================================================
*/

import {SyncNavigationData} from './sync_navigation';
import {NodeStylerRule, RendererType} from './types';

/** Configs for the visualizer. */
Expand Down Expand Up @@ -59,6 +60,9 @@ export declare interface VisualizerConfig {
/** The default node styler rules. */
nodeStylerRules?: NodeStylerRule[];

/** The data for navigation syncing. */
syncNavigationData?: SyncNavigationData;

/**
* Default graph renderer.
*
Expand Down
2 changes: 2 additions & 0 deletions src/ui/src/components/visualizer/common/worker_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export declare interface RelayoutGraphRequest extends WorkerEventBase {
clearAllExpandStates?: boolean;
forRestoringSnapshotAfterTogglingFlattenLayers?: boolean;
nodeStylerQueries?: NodeStylerRule[];
triggerNavigationSync?: boolean;
}

/** The response for re-laying out the whole graph. */
Expand All @@ -130,6 +131,7 @@ export declare interface RelayoutGraphResponse extends WorkerEventBase {
rectToZoomFit?: Rect;
forRestoringSnapshotAfterTogglingFlattenLayers?: boolean;
targetDeepestGroupNodeIdsToExpand?: string[];
triggerNavigationSync?: boolean;
}

/** The request for locating a node. */
Expand Down
14 changes: 14 additions & 0 deletions src/ui/src/components/visualizer/model_graph_visualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
NodeDataProviderData,
NodeDataProviderGraphData,
NodeInfo,
SyncNavigationModeChangedEvent,
} from './common/types';
import {genUid, inInputElement, isOpNode} from './common/utils';
import {type VisualizerConfig} from './common/visualizer_config';
Expand All @@ -53,6 +54,7 @@ import {ExtensionService} from './extension_service';
import {NodeDataProviderExtensionService} from './node_data_provider_extension_service';
import {NodeStylerService} from './node_styler_service';
import {SplitPanesContainer} from './split_panes_container';
import {SyncNavigationService} from './sync_navigation_service';
import {ThreejsService} from './threejs_service';
import {TitleBar} from './title_bar';
import {UiStateService} from './ui_state_service';
Expand All @@ -70,6 +72,7 @@ import {WorkerService} from './worker_service';
ExtensionService,
NodeDataProviderExtensionService,
NodeStylerService,
SyncNavigationService,
UiStateService,
WorkerService,
],
Expand Down Expand Up @@ -109,6 +112,10 @@ export class ModelGraphVisualizer implements OnInit, OnDestroy, OnChanges {
/** Triggered when a remote node data paths are updated. */
@Output() readonly remoteNodeDataPathsChanged = new EventEmitter<string[]>();

/** Triggered when the sync navigation mode is changed. */
@Output() readonly syncNavigationModeChanged =
new EventEmitter<SyncNavigationModeChangedEvent>();

/** Triggered when the selected node is changed. */
@Output() readonly selectedNodeChanged = new EventEmitter<NodeInfo>();

Expand Down Expand Up @@ -140,6 +147,7 @@ export class ModelGraphVisualizer implements OnInit, OnDestroy, OnChanges {
private readonly uiStateService: UiStateService,
private readonly nodeDataProviderExtensionService: NodeDataProviderExtensionService,
private readonly nodeStylerService: NodeStylerService,
readonly syncNavigationService: SyncNavigationService,
) {

effect(() => {
Expand Down Expand Up @@ -201,6 +209,12 @@ export class ModelGraphVisualizer implements OnInit, OnDestroy, OnChanges {
this.modelGraphProcessed.next(event);
});

this.syncNavigationService.syncNavigationModeChanged$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((event) => {
this.syncNavigationModeChanged.next(event);
});

this.initThreejs();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
NodeDataProviderData,
NodeDataProviderResultProcessedData,
NodeDataProviderRunData,
ReadFileResp,
ThresholdItem,
} from './common/types';
import {genUid, isOpNode} from './common/utils';
Expand All @@ -44,10 +45,6 @@ interface ProcessedGradientItem {
textColor?: Rgb;
}

declare interface ReadFileResp {
content: string;
}

declare interface ExternalReadFileResp {
content: string;
error?: string;
Expand Down
27 changes: 12 additions & 15 deletions src/ui/src/components/visualizer/node_styler_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,23 +110,20 @@ export class NodeStylerService {
private readonly appService: AppService,
private readonly localStorageService: LocalStorageService,
) {
effect(
() => {
const rules = this.rules();
effect(() => {
const rules = this.rules();

if (!this.appService.testMode) {
// Save rules to local storage on changes.
this.localStorageService.setItem(
LOCAL_STORAGE_KEY_NODE_STYLER_RULES,
JSON.stringify(rules),
);
}
if (!this.appService.testMode) {
// Save rules to local storage on changes.
this.localStorageService.setItem(
LOCAL_STORAGE_KEY_NODE_STYLER_RULES,
JSON.stringify(rules),
);
}

// Compute matched nodes.
this.computeMatchedNodes(rules);
},
{allowSignalWrites: true},
);
// Compute matched nodes.
this.computeMatchedNodes(rules);
});

// Load rules from local storage in non-test mode.
if (!this.appService.testMode) {
Expand Down
12 changes: 11 additions & 1 deletion src/ui/src/components/visualizer/split_panes_container.ng.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@
</div>
}

<div class="pane-title-container" *ngIf="hasSplitPane">
<div class="pane-title-container" *ngIf="hasSplitPane"
[class.extra-left-padding]="i === 1"
[class.extra-right-padding]="i === 0">
<div class="pane-title">
{{getPaneTitle(pane)}}
</div>
Expand Down Expand Up @@ -103,4 +105,12 @@
(mousedown)="handleMouseDownResizer($event, panesContainer)">
<div class="resizer-line"></div>
</div>

<!-- Sync navigation -->
@if (hasSplitPane && allPanesLoaded()) {
<div class="sync-navigation-container"
[style.left]="resizerLeft">
<sync-navigation-button></sync-navigation-button>
</div>
}
</div>
Loading

0 comments on commit 2d1a944

Please sign in to comment.