THW Network Visualization

Hello @all!

Just a really quick update on our little network-view project.


What’s up so far

I wanted a nicer way for everyone to see who’s working on which projects. Also, it would be nice to have an easier way of finding the right person to talk to for any sort of topic. When I came across connectedpapers.com, I knew that a network visualisation would be really cool. So with massive help of my (not actually) patented workflow with v0 + Vercel + Codex, I spun up a quite nice website that does so. Here’s the gist:

  • We grab the “intro” RSS feed from this website to learn each member’s display and username.
  • Then we fire off one fetch per person (again on the respective RSS feed), pull in their posts, and stitch everything together.
  • Finally, we look at all the categories (tags) and show overlaps so you can spot shared interests at a glance.

It’s rough around the edges, but you can already see clusters of topics and who’s talking about them. Here is a screenshot of how the visualisation looks at the time of writing:

Each colour represents a different person. The topics the person published posts about are then connected to the person and shown in the same colour, but a bit more transparent. If multiple persons made a post about the same topic, the resulting colour is a mixture of all the authors’ colours.

When clicking on a person, all interests are displayed. When clicking on a topic bubble, all posts with that tag are displayed:

All this is purely through the data from the WordPress RSS feed. Below is a short summary of how this can be done. As most of the code is AI-generated, I am not super happy to share it publicly (Members have access to it through the club’s GitLab), but if anyone is interested in the full code, just write me and I will happily share it.


The RSS Feeds

An RSS (Really Simple Syndication) feed is an XML-formatted list of a site’s latest posts that your app can fetch, parse, and display automatically. WordPress per default usually generates them and you can acess them on each page by adding /feed to the url. In our case we use two feeds:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" …>
  <channel>
    <title>Introductions – Technologie Hub Wien</title>
    …
    <item>
      <title>Hi, it’s Eduard!</title>
      <dc:creator><![CDATA[EduardAbart]]></dc:creator>
      <pubDate>Sun, 09 Feb 2025 16:01:47 +0000</pubDate>
      <category><![CDATA[Introductions]]></category>
      <authorUsername>EduardAbart</authorUsername>
    </item>
    …
  </channel>
</rss>

We pull out <dc:creator>, <pubDate>, <category> and our custom <authorUsername>.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" …>
  <channel>
    <title>Geibinger – Technologie Hub Wien</title>
    …
    <item>
      <title>ESP-IDF Can Bus Communication</title>
      <dc:creator><![CDATA[Geibinger]]></dc:creator>
      <pubDate>Sun, 08 Jun 2025 15:10:02 +0000</pubDate>
      <category><![CDATA[Tutorial]]></category>
      <category domain="post_tag" nicename="can">CAN</category>
      <authorUsername>Jakob</authorUsername>
    </item>
    …
  </channel>
</rss>

Here we grab multiple <category> entries (both general and post_tag domains), plus the same creator, date, and username fields.

Serverless RSS Proxy

Browsers can’t hit arbitrary RSS feeds thanks to CORS, so we added a tiny Next.js API route on Vercel:

// app/api/fetchFeed/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const url = searchParams.get('url');
  if (!url) {
    return NextResponse.json({ error: 'Missing url parameter' }, { status: 400 });
  }

  try {
    const res = await fetch(url);
    if (!res.ok) {
      return NextResponse.json(
        { error: `Failed to fetch feed: ${res.statusText}` },
        { status: 500 }
      );
    }
    const xml = await res.text();
    return new NextResponse(xml, {
      headers: { 'Content-Type': 'application/xml' },
    });
  } catch {
    return NextResponse.json({ error: 'Failed to fetch feed' }, { status: 500 });
  }
}

That way the client just calls /api/fetchFeed?url=… and we sidestep CORS entirely.


Parsing & Aggregation on the Client

Once the XML lands, we normalize each item into a simple object:

return {
  title: text(item, "title"),
  link: text(item, "link"),
  pubDate: text(item, "pubDate"),
  contentSnippet,
  creator: getCreator(item) || "Unknown",
  authorUsername: text(item, "authorUsername"),
  categories: cats,
  thumbnailUrl: thumbnailUrl || undefined,
};

Then:

  1. Load the intro feed -> build Map<authorUsername, displayName>.
  2. Kick off fetchFeed for each username in parallel.
  3. Flatten, sort by pubDate, and group posts by creator.
  4. Pull out all categories per author -> that’s your interest network.

WordPress Tweaks

To get tags and usernames into the RSS, we slapped this code into functions.php (WordPress Dashboard -> Appearance -> Theme File Editor) :

// Add <category> for each post tag
add_action('rss2_item', function() {
  global $post;
  if ($tags = get_the_tags($post->ID)) {
    foreach ($tags as $tag) {
      printf(
        '<category domain="post_tag" nicename="%s">%s</category>'."\n",
        esc_attr($tag->slug),
        esc_html($tag->name)
      );
    }
  }
});

// Add <authorUsername> for mapping
add_action('rss2_item', function() {
  global $post;
  $author = get_userdata($post->post_author);
  if ($author) {
    printf('<authorUsername>%s</authorUsername>'."\n", esc_html($author->user_login));
  }
});

With those hooks, our feed items come packed with everything we need.


Next Steps

  • Integrate it into the public technologiehub domain (once we have it, I will update the post to include the actual link)
  • Add more posts from more different users

That’s it!

Goodbye and thanks for the fish!

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
Scroll to Top