first
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import {
|
||||
buildGraphRendererAdapterData,
|
||||
buildGraphRendererBehaviorContract,
|
||||
type GraphRendererBehaviorContract
|
||||
} from "../src/render";
|
||||
import type {
|
||||
GraphAggregationMarker,
|
||||
GraphData,
|
||||
GraphFocusInput,
|
||||
GraphTypeFilters,
|
||||
NodeId,
|
||||
PinMap,
|
||||
SelectionInput
|
||||
} from "../src/types";
|
||||
|
||||
export interface AggregationFallbackTrialOptions {
|
||||
pins?: PinMap;
|
||||
selection?: SelectionInput | null;
|
||||
searchResultIds?: NodeId[];
|
||||
aggregationMarkers?: GraphAggregationMarker[];
|
||||
focus?: GraphFocusInput;
|
||||
typeFilters?: GraphTypeFilters;
|
||||
}
|
||||
|
||||
export interface AggregationFallbackTrialModel {
|
||||
nodes: AggregationFallbackTrialNode[];
|
||||
edges: AggregationFallbackTrialEdge[];
|
||||
communities: AggregationFallbackTrialCommunity[];
|
||||
containers: AggregationFallbackTrialContainer[];
|
||||
budget: {
|
||||
visibleNodes: number;
|
||||
visibleEdges: number;
|
||||
visibleLabels: number;
|
||||
visibleCards: number;
|
||||
overflowNodes: number;
|
||||
overflowEdges: number;
|
||||
overflowLabels: number;
|
||||
overflowCards: number;
|
||||
maxInteractionUpdates: number;
|
||||
interactionUpdatedObjects: number;
|
||||
interactionHiddenObjects: number;
|
||||
preservedNodeIds: string[];
|
||||
};
|
||||
behavior: GraphRendererBehaviorContract;
|
||||
}
|
||||
|
||||
export interface AggregationFallbackTrialNode {
|
||||
id: string;
|
||||
label: string;
|
||||
x: number;
|
||||
y: number;
|
||||
color: string;
|
||||
radius: number;
|
||||
communityId: string | null;
|
||||
selected: boolean;
|
||||
searchHit: boolean;
|
||||
pinned: boolean;
|
||||
labelVisible: boolean;
|
||||
cardVisible: boolean;
|
||||
aggregationIds: string[];
|
||||
}
|
||||
|
||||
export interface AggregationFallbackTrialEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
strokeWidth: number;
|
||||
opacity: number;
|
||||
skeleton: boolean;
|
||||
}
|
||||
|
||||
export interface AggregationFallbackTrialCommunity {
|
||||
id: string;
|
||||
label: string;
|
||||
nodeIds: string[];
|
||||
selected: boolean;
|
||||
searchResultIds: string[];
|
||||
pinnedNodeIds: string[];
|
||||
}
|
||||
|
||||
export interface AggregationFallbackTrialContainer {
|
||||
id: string;
|
||||
label: string;
|
||||
communityId: string | null;
|
||||
x: number;
|
||||
y: number;
|
||||
radius: number;
|
||||
color: string;
|
||||
nodeIds: string[];
|
||||
selectedNodeIds: string[];
|
||||
searchResultIds: string[];
|
||||
pinnedNodeIds: string[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
const COMMUNITY_COLORS = [
|
||||
"#2563eb",
|
||||
"#059669",
|
||||
"#d97706",
|
||||
"#7c3aed",
|
||||
"#dc2626",
|
||||
"#0891b2",
|
||||
"#4f46e5",
|
||||
"#65a30d"
|
||||
];
|
||||
|
||||
export function buildAggregationFallbackTrialModel(
|
||||
data: GraphData,
|
||||
options: AggregationFallbackTrialOptions = {}
|
||||
): AggregationFallbackTrialModel {
|
||||
const adapter = buildGraphRendererAdapterData(data, {
|
||||
...options,
|
||||
focus: options.focus ?? { kind: "global" }
|
||||
});
|
||||
const renderable = adapter.renderable;
|
||||
const searchSet = new Set(options.searchResultIds ?? []);
|
||||
const nodeById = new Map(adapter.nodes.map((node) => [node.id, node]));
|
||||
|
||||
const nodes = renderable.nodes.map((node): AggregationFallbackTrialNode => {
|
||||
const adapterNode = nodeById.get(node.id);
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
x: node.point.x,
|
||||
y: node.point.y,
|
||||
color: colorForCommunity(adapterNode?.communityId ?? node.community),
|
||||
radius: node.displayMode === "card" ? 7 : node.labelVisible ? 5 : 3,
|
||||
communityId: adapterNode?.communityId ?? node.community ?? null,
|
||||
selected: adapterNode?.selected ?? node.selected,
|
||||
searchHit: adapterNode?.searchHit ?? searchSet.has(node.id),
|
||||
pinned: adapterNode?.pinHint.pinned ?? false,
|
||||
labelVisible: node.labelVisible,
|
||||
cardVisible: node.displayMode === "card" || node.displayMode === "compact-card",
|
||||
aggregationIds: adapterNode?.aggregationIds ?? []
|
||||
};
|
||||
});
|
||||
|
||||
const edges = renderable.edges.map((edge): AggregationFallbackTrialEdge => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
strokeWidth: edge.strokeWidth,
|
||||
opacity: edge.opacity,
|
||||
skeleton: edge.skeleton
|
||||
}));
|
||||
|
||||
const communities = adapter.communities.map((community): AggregationFallbackTrialCommunity => ({
|
||||
id: community.id,
|
||||
label: community.label,
|
||||
nodeIds: community.nodeIds,
|
||||
selected: community.selected,
|
||||
searchResultIds: community.searchResultIds,
|
||||
pinnedNodeIds: community.pinHints.map((hint) => hint.nodeId)
|
||||
}));
|
||||
|
||||
const containers = adapter.aggregations.map((aggregation): AggregationFallbackTrialContainer => {
|
||||
const points = aggregation.nodeIds
|
||||
.map((id) => nodeById.get(id)?.point)
|
||||
.filter((point): point is { x: number; y: number } => Boolean(point));
|
||||
const point = averagePoint(points);
|
||||
return {
|
||||
id: aggregation.id,
|
||||
label: aggregation.label,
|
||||
communityId: aggregation.communityId,
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
radius: aggregationRadius(aggregation.totalCount),
|
||||
color: colorForCommunity(aggregation.communityId),
|
||||
nodeIds: aggregation.nodeIds,
|
||||
selectedNodeIds: aggregation.selectedNodeIds,
|
||||
searchResultIds: aggregation.searchResultIds,
|
||||
pinnedNodeIds: aggregation.pinnedNodeIds,
|
||||
totalCount: aggregation.totalCount
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
nodes,
|
||||
edges,
|
||||
communities,
|
||||
containers,
|
||||
budget: {
|
||||
visibleNodes: renderable.budget.usage.maxVisibleNodes,
|
||||
visibleEdges: renderable.budget.usage.maxVisibleEdges,
|
||||
visibleLabels: renderable.budget.usage.maxLabels,
|
||||
visibleCards: renderable.budget.usage.maxCards,
|
||||
overflowNodes: renderable.overflow.nodes.hidden,
|
||||
overflowEdges: renderable.overflow.edges.hidden,
|
||||
overflowLabels: renderable.overflow.labels.hidden,
|
||||
overflowCards: renderable.overflow.cards.hidden,
|
||||
maxInteractionUpdates: renderable.interaction.maxUpdatedObjects,
|
||||
interactionUpdatedObjects: renderable.interaction.updatedObjects,
|
||||
interactionHiddenObjects: renderable.interaction.hiddenObjects,
|
||||
preservedNodeIds: renderable.interaction.preservedNodeIds
|
||||
},
|
||||
behavior: buildGraphRendererBehaviorContract(adapter, "aggregation-fallback")
|
||||
};
|
||||
}
|
||||
|
||||
function colorForCommunity(communityId: string | null): string {
|
||||
if (!communityId) return "#64748b";
|
||||
const index = Math.abs(hashString(communityId)) % COMMUNITY_COLORS.length;
|
||||
return COMMUNITY_COLORS[index];
|
||||
}
|
||||
|
||||
function hashString(value: string): number {
|
||||
let hash = 0;
|
||||
for (let index = 0; index < value.length; index += 1) {
|
||||
hash = ((hash << 5) - hash + value.charCodeAt(index)) | 0;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
function averagePoint(points: Array<{ x: number; y: number }>): { x: number; y: number } {
|
||||
if (!points.length) return { x: 0, y: 0 };
|
||||
const sum = points.reduce((memo, point) => ({ x: memo.x + point.x, y: memo.y + point.y }), { x: 0, y: 0 });
|
||||
return { x: sum.x / points.length, y: sum.y / points.length };
|
||||
}
|
||||
|
||||
function aggregationRadius(count: number): number {
|
||||
return Math.max(10, Math.min(42, 8 + Math.sqrt(Math.max(1, count)) * 1.4));
|
||||
}
|
||||
Reference in New Issue
Block a user