Browse Source

Improve profile fetching and person node coloring

- Only fetch profiles from event authors when follow list limit is 0
- Color person nodes differently based on their source:
  - Green (#10B981) for authors of displayed events
  - Kind 3 color for people from follow lists
- Track isFromFollowList flag through person extraction and display
- Update Legend to show colored diamonds matching the graph visualization

This helps distinguish between actual content authors and social graph connections.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
master
limina1 9 months ago
parent
commit
7b59907df7
  1. 2
      src/lib/navigator/EventNetwork/Legend.svelte
  2. 22
      src/lib/navigator/EventNetwork/index.svelte
  3. 1
      src/lib/navigator/EventNetwork/types.ts
  4. 34
      src/lib/navigator/EventNetwork/utils/personNetworkBuilder.ts
  5. 11
      src/routes/visualize/+page.svelte

2
src/lib/navigator/EventNetwork/Legend.svelte

@ -386,7 +386,7 @@
<div class="legend-icon"> <div class="legend-icon">
<span <span
class="legend-diamond" class="legend-diamond"
style="background-color: #10B981; opacity: {isDisabled ? 0.3 : 1};" style="background-color: {person.isFromFollowList ? getEventKindColor(3) : '#10B981'}; opacity: {isDisabled ? 0.3 : 1};"
/> />
</div> </div>
<span class="legend-text text-xs" style="opacity: {isDisabled ? 0.5 : 1};"> <span class="legend-text text-xs" style="opacity: {isDisabled ? 0.5 : 1};">

22
src/lib/navigator/EventNetwork/index.svelte

@ -336,8 +336,8 @@
if (showPersonNodes) { if (showPersonNodes) {
debug("Creating person anchor nodes"); debug("Creating person anchor nodes");
// Extract unique persons from events // Extract unique persons from events and follow lists
personMap = extractUniquePersons(events); personMap = extractUniquePersons(events, followListEvents);
// Create person anchor nodes based on filters // Create person anchor nodes based on filters
const personResult = createPersonAnchorNodes( const personResult = createPersonAnchorNodes(
@ -639,8 +639,13 @@
return baseClasses; return baseClasses;
}) })
.style("fill", (d: NetworkNode) => { .style("fill", (d: NetworkNode) => {
// Person anchors are green // Person anchors - color based on source
if (d.isPersonAnchor) { if (d.isPersonAnchor) {
// If from follow list, use kind 3 color
if (d.isFromFollowList) {
return getEventKindColor(3);
}
// Otherwise green for event authors
return "#10B981"; return "#10B981";
} }
// Tag anchors get their specific colors // Tag anchors get their specific colors
@ -870,8 +875,17 @@
if (svgGroup) { if (svgGroup) {
svgGroup svgGroup
.selectAll("g.node") .selectAll("g.node")
.select("circle.visual-circle") .select(".visual-shape")
.style("fill", (d: NetworkNode) => { .style("fill", (d: NetworkNode) => {
// Person anchors - color based on source
if (d.isPersonAnchor) {
// If from follow list, use kind 3 color
if (d.isFromFollowList) {
return getEventKindColor(3);
}
// Otherwise green for event authors
return "#10B981";
}
if (d.isTagAnchor) { if (d.isTagAnchor) {
return getTagAnchorColor(d.tagType || ""); return getTagAnchorColor(d.tagType || "");
} }

1
src/lib/navigator/EventNetwork/types.ts

@ -58,6 +58,7 @@ export interface NetworkNode extends SimulationNodeDatum {
isPersonAnchor?: boolean; // Whether this is a person anchor node isPersonAnchor?: boolean; // Whether this is a person anchor node
pubkey?: string; // The person's public key pubkey?: string; // The person's public key
displayName?: string; // The person's display name from kind 0 displayName?: string; // The person's display name from kind 0
isFromFollowList?: boolean; // Whether this person comes from follow lists
} }
/** /**

34
src/lib/navigator/EventNetwork/utils/personNetworkBuilder.ts

@ -44,6 +44,7 @@ function createSeed(str: string): number {
export interface PersonConnection { export interface PersonConnection {
signedByEventIds: Set<string>; signedByEventIds: Set<string>;
referencedInEventIds: Set<string>; referencedInEventIds: Set<string>;
isFromFollowList?: boolean; // Track if this person comes from follow lists
} }
/** /**
@ -51,13 +52,34 @@ export interface PersonConnection {
* Tracks both signed-by (event.pubkey) and referenced (["p", pubkey] tags) * Tracks both signed-by (event.pubkey) and referenced (["p", pubkey] tags)
*/ */
export function extractUniquePersons( export function extractUniquePersons(
events: NDKEvent[] events: NDKEvent[],
followListEvents?: NDKEvent[]
): Map<string, PersonConnection> { ): Map<string, PersonConnection> {
// Map of pubkey -> PersonConnection // Map of pubkey -> PersonConnection
const personMap = new Map<string, PersonConnection>(); const personMap = new Map<string, PersonConnection>();
console.log(`[PersonBuilder] Extracting persons from ${events.length} events`); console.log(`[PersonBuilder] Extracting persons from ${events.length} events`);
// First collect pubkeys from follow list events
const followListPubkeys = new Set<string>();
if (followListEvents && followListEvents.length > 0) {
console.log(`[PersonBuilder] Processing ${followListEvents.length} follow list events`);
followListEvents.forEach((event) => {
// Follow list author
if (event.pubkey) {
followListPubkeys.add(event.pubkey);
}
// People in follow lists (p tags)
if (event.tags) {
event.tags.forEach(tag => {
if (tag[0] === "p" && tag[1]) {
followListPubkeys.add(tag[1]);
}
});
}
});
}
events.forEach((event) => { events.forEach((event) => {
if (!event.id) return; if (!event.id) return;
@ -66,7 +88,8 @@ export function extractUniquePersons(
if (!personMap.has(event.pubkey)) { if (!personMap.has(event.pubkey)) {
personMap.set(event.pubkey, { personMap.set(event.pubkey, {
signedByEventIds: new Set(), signedByEventIds: new Set(),
referencedInEventIds: new Set() referencedInEventIds: new Set(),
isFromFollowList: followListPubkeys.has(event.pubkey)
}); });
} }
personMap.get(event.pubkey)!.signedByEventIds.add(event.id); personMap.get(event.pubkey)!.signedByEventIds.add(event.id);
@ -80,7 +103,8 @@ export function extractUniquePersons(
if (!personMap.has(referencedPubkey)) { if (!personMap.has(referencedPubkey)) {
personMap.set(referencedPubkey, { personMap.set(referencedPubkey, {
signedByEventIds: new Set(), signedByEventIds: new Set(),
referencedInEventIds: new Set() referencedInEventIds: new Set(),
isFromFollowList: followListPubkeys.has(referencedPubkey)
}); });
} }
personMap.get(referencedPubkey)!.referencedInEventIds.add(event.id); personMap.get(referencedPubkey)!.referencedInEventIds.add(event.id);
@ -90,6 +114,7 @@ export function extractUniquePersons(
}); });
console.log(`[PersonBuilder] Found ${personMap.size} unique persons`); console.log(`[PersonBuilder] Found ${personMap.size} unique persons`);
console.log(`[PersonBuilder] ${followListPubkeys.size} are from follow lists`);
return personMap; return personMap;
} }
@ -172,6 +197,7 @@ export function createPersonAnchorNodes(
pubkey, pubkey,
displayName, displayName,
connectedNodes: Array.from(connectedEventIds), connectedNodes: Array.from(connectedEventIds),
isFromFollowList: connection.isFromFollowList,
x, x,
y, y,
fx: x, // Fix position fx: x, // Fix position
@ -241,6 +267,7 @@ export interface PersonAnchorInfo {
displayName: string; displayName: string;
signedByCount: number; signedByCount: number;
referencedCount: number; referencedCount: number;
isFromFollowList: boolean;
} }
/** /**
@ -257,6 +284,7 @@ export function extractPersonAnchorInfo(
displayName: anchor.displayName || "", displayName: anchor.displayName || "",
signedByCount: connection?.signedByEventIds.size || 0, signedByCount: connection?.signedByEventIds.size || 0,
referencedCount: connection?.referencedInEventIds.size || 0, referencedCount: connection?.referencedInEventIds.size || 0,
isFromFollowList: connection?.isFromFollowList || false,
}; };
}); });
} }

11
src/routes/visualize/+page.svelte

@ -509,8 +509,13 @@
// Use the utility function to extract ALL pubkeys (authors + p tags + content) // Use the utility function to extract ALL pubkeys (authors + p tags + content)
const allPubkeys = extractPubkeysFromEvents(allEvents); const allPubkeys = extractPubkeysFromEvents(allEvents);
// Add pubkeys from follow lists if present // Check if follow list is configured with limit > 0
if (followListEvents.length > 0) { const followListConfig = allConfigs.find(c => c.kind === 3);
const shouldIncludeFollowPubkeys = followListConfig && followListConfig.limit > 0;
// Add pubkeys from follow lists only if follow list limit > 0
if (shouldIncludeFollowPubkeys && followListEvents.length > 0) {
debug("Including pubkeys from follow lists (limit > 0)");
followListEvents.forEach(event => { followListEvents.forEach(event => {
if (event.pubkey) allPubkeys.add(event.pubkey); if (event.pubkey) allPubkeys.add(event.pubkey);
event.tags.forEach(tag => { event.tags.forEach(tag => {
@ -519,6 +524,8 @@
} }
}); });
}); });
} else if (!shouldIncludeFollowPubkeys && followListEvents.length > 0) {
debug("Excluding follow list pubkeys (limit = 0, only fetching event authors)");
} }
debug("Profile extraction complete:", { debug("Profile extraction complete:", {

Loading…
Cancel
Save